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

Compare commits

...

73 Commits
v6.2.1 ... v7.0

Author SHA1 Message Date
b26fc3bc4b 🚀 Version 7.0 2024-02-11 21:35:40 +01:00
f758c5d63a 👌 Cut off bottom of marketing screenshot 2024-02-10 16:18:58 +01:00
c7510d778d 🍱 Update marketing screenshot 2024-02-10 16:17:24 +01:00
70c5aadb7f 🔧 Bump build for PHP Monitor EAP 2024-01-25 13:48:39 +01:00
a731f15cf7 🐛 Prevent PHP dropdown from resetting 2024-01-21 14:42:06 +01:00
ab4c436202 🔧 Bump build for PHP Monitor EAP 2024-01-21 14:23:52 +01:00
c0231690d4 🐛 Fix alternative installation check 2024-01-21 14:22:51 +01:00
988e9d3351 👌 Cleanup and add comments 2024-01-13 13:24:06 +01:00
2f119d4332 Fix even more tests 2024-01-13 13:22:21 +01:00
d83c629a7b Fix some tests 2024-01-13 12:46:33 +01:00
e7d98dbeae 🔧 Bump build for PHP Monitor EAP 2024-01-09 21:24:50 +01:00
f3d5946743 🌐 Localize extension management (for domains) 2024-01-09 21:20:43 +01:00
7728a1125c 📝 Update README to reflect php = PHP 8.3 2024-01-09 21:00:06 +01:00
3612351df7 📝 Update README to reflect php = PHP 8.3 2024-01-09 20:59:52 +01:00
8e912151fb Allow toggling extensions via site list 2024-01-09 20:55:16 +01:00
3a2209e604 🌐 Add translations for language choice 2024-01-09 20:06:40 +01:00
1f0b56cab6 Language choice, prompt user to restart app 2024-01-09 20:01:08 +01:00
e08d970edd 🏗️ WIP: Load preferred language strings 2024-01-09 19:44:29 +01:00
32c757e711 🏗️ WIP: Language override option 2024-01-09 19:28:39 +01:00
480cdb94ae 🏗️ WIP: Language picker, fix SelectPreferenceView 2024-01-09 18:58:02 +01:00
7fbcac5dc2 👌 Check symlinks after PHP version modification 2023-12-27 14:47:21 +01:00
4edb5f5015 👌 Use generated asset catalog symbol extensions 2023-12-27 13:03:24 +01:00
294f84ccb2 👌 Further cleanup 2023-12-27 12:57:08 +01:00
155b57eb9e 🐛 Add incorrect PHP symlink purge (#270)
This commit introduces a new diagnostics feature which is executed
when the app boots. When the app boots, the integrity of the PHP
symlinks are checked to ensure that all symlinks correctly link to
a valid PHP version. If any links to an incorrect PHP version,
then those outdated or incorrect symlinks will be removed.

For example, if `php@8.2` links to `../Cellar/php/8.3.0` then that is
an obvious reason for that symlink to be purged because it links to
the incorrect version. (This example behaviour has been noted in #270.)

This occurs prior to the rest of the startup process, so this way
no incorrect PHP versions can pop up in the version switcher, and
no incorrect PHP version is reported as being installed when
managing installed PHP versions.
2023-12-27 12:40:35 +01:00
a459f015e1 🌐 Added translations for extension manager
(These were generated using OpenAI's API.)
2023-11-30 17:31:35 +01:00
27676f13f4 🔧 Fix build number 2023-11-30 17:14:36 +01:00
b4b2d7052f 🌐 Replace untranslated text w/ keys 2023-11-29 18:59:50 +01:00
6d25cf585e 🌐 Localize extension manager (English only) 2023-11-29 18:55:36 +01:00
ba04c94c05 👌 Cleanup UI code 2023-11-29 18:42:14 +01:00
13447ba533 Disallow installation if dependent exists 2023-11-29 18:35:31 +01:00
6f2e8f4b20 Check cutoff date for PHP version management 2023-11-27 21:39:41 +01:00
dc860074ef Parse extension dependencies 2023-11-27 21:38:38 +01:00
f586b8fcbe Allow choosing which PHP version to manage 2023-11-27 00:33:45 +01:00
94714c3e7a Extension manager responds to PHP change 2023-11-27 00:07:47 +01:00
904d05bdce 👌 Cleanup, fix loading issue 2023-11-26 23:51:39 +01:00
ec30bee72b Ask for confirmation before removing extensions 2023-11-26 22:39:44 +01:00
2fe3a4b7eb Perform cleanup when removing extensions 2023-11-26 22:03:25 +01:00
a7d5950aa0 Test synchronous shell output 2023-11-26 21:48:40 +01:00
e8306289ce Load extension info for all PHP versions
In order to make this possible, I've added a new `sync()` method to the
Shellable protocol, which now should allow us to run shell commands
synchronously. Back to basics, as this was how *all* commands were
run in legacy versions of PHP Monitor.

The advantage here is that there is now a choice. Previously, you'd
have to use the `system()` helper that I added.

Usage of that helper is now discouraged, as is the synchronous
shell method, but it may be useful for some methods where waiting
for the outcome of the output is required.

(Important: the `system()` helper is still required when determining
what the preferred terminal is during the initialization of the
`Paths` class.)
2023-11-26 21:26:48 +01:00
ff61d8c52e 🚀 Version 6.2.2 2023-11-24 23:30:12 +01:00
da41673855 Fix broken tests 2023-11-24 23:29:25 +01:00
5bda727981 🔧 Bump version 2023-11-24 22:58:04 +01:00
23cf575026 ⬆️ Upstream PHP 8.3 upgrade changes to v7.0 2023-11-24 22:57:33 +01:00
d3053b8fe3 Allow for seamless upgrade to PHP 8.3
The older version (8.2 in most cases) that becomes obsolete will also
be reinstalled and the app will attempt to switch to the last active
version as well. It is likely that PHP Monitor will have to repair
your older PHP installations after upgrading the `php` formula, but
this too should be a seamless process.
2023-11-24 22:26:33 +01:00
7159ca8612 🐛 Correctly handle mismatches when upgrading PHP 2023-11-24 20:23:12 +01:00
141c06d14b 🐛 Update PHP alias 2023-11-24 18:35:36 +01:00
94c84aaab3 👌 Tweak text 2023-11-22 21:37:11 +01:00
9ca16e72d5 Show external extensions 2023-11-22 21:29:51 +01:00
67a00f979a 📝 TODO items as warnings 2023-11-21 22:37:28 +01:00
1e4c45dcbd 🍱 Tweak UI of extension list 2023-11-21 22:36:05 +01:00
87c44f3ae3 Allow installation and removal of extensions (#266) 2023-11-21 22:18:28 +01:00
f39732a0e6 🏗 WIP: UI for searchable extensions 2023-11-21 20:03:50 +01:00
3b78ac43d7 👌 Fixed various lint issues 2023-11-21 18:19:17 +01:00
1f19b81530 🔀 Merge branch 'dev/6.x' into dev/7.x 2023-11-21 18:12:10 +01:00
d714d7ad4c 👌 Install additional taps
The following taps are now automatically installed:

- `shivammathur/php`
- `shivammathur/extensions`
2023-11-21 18:11:18 +01:00
4dce6c033e 🌐 French localization fixes, onboarding size fix 2023-11-21 17:41:47 +01:00
72a8a1e382 🔧 Tweak which formulae to install for PHP 8.3 and PHP 8.0
- Since PHP 8.0 is now EOL, it will be installed via the tap.
- Since PHP 8.3 is now stable, it will be installed without the tap.
2023-11-21 17:41:41 +01:00
07b17f3f84 🌐 French localization fixes, onboarding size fix 2023-11-21 17:40:53 +01:00
7f0f7ff3e9 🔧 Tweak which formulae to install for PHP 8.3 and PHP 8.0
- Since PHP 8.0 is now EOL, it will be installed via the tap.
- Since PHP 8.3 is now stable, it will be installed without the tap.
2023-11-21 17:15:36 +01:00
c7c143c760 🌐 Added French translation
With contributions from @tplesnar and @nhedger
2023-11-21 17:12:44 +01:00
ee050af364 🌐 Added French translation
With contributions from @tplesnar and @nhedger
2023-11-21 17:12:29 +01:00
f7e2551587 🏗 WIP: Adjust extension manager view 2023-11-21 17:11:28 +01:00
cc0cc21e5f 🏗️ WIP: Cleanup 2023-11-13 17:44:11 +01:00
883ea05bd1 🏗️ WIP: Extension manager UI (rough) 2023-11-13 13:14:27 +01:00
641bddfce7 Add UI test for PHP config editor 2023-11-07 18:21:14 +01:00
2f7223fba5 🔥 Remove unused ProgressWindowView 2023-11-07 18:08:12 +01:00
3b23ce7805 👌 Even more cleanup 2023-11-07 18:04:13 +01:00
a634d083a6 ⬆️ Adopt #Preview, cleanup PHP Version Manager 2023-11-07 17:56:38 +01:00
9a3dd2fa22 🐛 Fix extensions toggle (#265) 2023-11-02 17:26:46 +01:00
6fd6241567 🔧 Start of v7.x branch, updated version number 2023-11-01 12:35:24 +01:00
c8ab2e67f6 ♻️ Various refactoring 2023-11-01 12:33:34 +01:00
f82ab913c6 Detect which extensions are available 2023-10-31 20:40:11 +01:00
58943148fa 🏗️ WIP: Detect which extensions are available 2023-10-30 20:21:50 +01:00
100 changed files with 3328 additions and 939 deletions

View File

@ -7,6 +7,20 @@
objects = {
/* Begin PBXBuildFile section */
0309E6672B0D4B2F002AC007 /* BrewExtensionsObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0309E6662B0D4B2F002AC007 /* BrewExtensionsObservable.swift */; };
031E2B692B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031E2B682B1525A7007C29E1 /* BrewPhpExtension.swift */; };
031E2B6A2B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031E2B682B1525A7007C29E1 /* BrewPhpExtension.swift */; };
031E2B6B2B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031E2B682B1525A7007C29E1 /* BrewPhpExtension.swift */; };
031E2B6C2B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031E2B682B1525A7007C29E1 /* BrewPhpExtension.swift */; };
033D45982B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */; };
033D45992B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */; };
033D459A2B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */; };
033D459B2B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */; };
033D459E2B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */; };
033D459F2B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */; };
033D45A02B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */; };
033D45A12B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */; };
033D45A32B0D531D00070080 /* PhpExtensionManagerView+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45A22B0D531D00070080 /* PhpExtensionManagerView+Actions.swift */; };
03E36FE728D9219000636F7F /* ActiveShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* ActiveShell.swift */; };
03E36FE828D9219000636F7F /* ActiveShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* ActiveShell.swift */; };
5420395926135DC100FB00FA /* PreferencesVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PreferencesVC.swift */; };
@ -78,10 +92,10 @@
C40C7F2927721FF600DDDCDC /* Valet+Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* Valet+Alerts.swift */; };
C40C7F3027722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; };
C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; };
C40D725A2A018ACC0054A067 /* PhpFormulaeStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40D72592A018ACC0054A067 /* PhpFormulaeStatus.swift */; };
C40D725B2A018ACC0054A067 /* PhpFormulaeStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40D72592A018ACC0054A067 /* PhpFormulaeStatus.swift */; };
C40D725C2A018ACC0054A067 /* PhpFormulaeStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40D72592A018ACC0054A067 /* PhpFormulaeStatus.swift */; };
C40D725D2A018ACC0054A067 /* PhpFormulaeStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40D72592A018ACC0054A067 /* PhpFormulaeStatus.swift */; };
C40D725A2A018ACC0054A067 /* BusyStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40D72592A018ACC0054A067 /* BusyStatus.swift */; };
C40D725B2A018ACC0054A067 /* BusyStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40D72592A018ACC0054A067 /* BusyStatus.swift */; };
C40D725C2A018ACC0054A067 /* BusyStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40D72592A018ACC0054A067 /* BusyStatus.swift */; };
C40D725D2A018ACC0054A067 /* BusyStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40D72592A018ACC0054A067 /* BusyStatus.swift */; };
C40D725F2A018AE30054A067 /* BrewFormula+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40D725E2A018AE30054A067 /* BrewFormula+UI.swift */; };
C40D72602A018AE30054A067 /* BrewFormula+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40D725E2A018AE30054A067 /* BrewFormula+UI.swift */; };
C40D72612A018AE30054A067 /* BrewFormula+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40D725E2A018AE30054A067 /* BrewFormula+UI.swift */; };
@ -122,11 +136,20 @@
C41F3D08298AED0D0042ACBF /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3660A29113F20006BD146 /* System.swift */; };
C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4205A7D27F4D21800191A39 /* ValetProxy.swift */; };
C4205A7F27F4D21800191A39 /* ValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4205A7D27F4D21800191A39 /* ValetProxy.swift */; };
C42106662AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42106652AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift */; };
C42106672AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42106652AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift */; };
C42106682AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42106652AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift */; };
C42106692AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42106652AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift */; };
C422DDAA28A2C49900CEAC97 /* PhpDoctorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDA928A2C49900CEAC97 /* PhpDoctorView.swift */; };
C4232EE52612526500158FC6 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = C4232EE42612526500158FC6 /* Credits.html */; };
C42337A3281F19F000459A48 /* Xdebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42337A2281F19F000459A48 /* Xdebug.swift */; };
C42759672627662800093CAE /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42759662627662800093CAE /* NSMenuExtension.swift */; };
C42759682627662800093CAE /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42759662627662800093CAE /* NSMenuExtension.swift */; };
C428311E2B52AD6F0009F9F1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3A22B0098000E7CF16 /* Assets.xcassets */; };
C428311F2B52AD6F0009F9F1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3A22B0098000E7CF16 /* Assets.xcassets */; };
C42831202B52AD700009F9F1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3A22B0098000E7CF16 /* Assets.xcassets */; };
C4292D542B023F61004F0D2A /* PhpExtensionManagerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4292D532B023F61004F0D2A /* PhpExtensionManagerWindowController.swift */; };
C4292D562B024006004F0D2A /* PhpExtensionManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4292D552B024006004F0D2A /* PhpExtensionManagerView.swift */; };
C4297F7A28970D59004C4630 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.swift */; };
C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */; };
C42CFB1627DFDE7900862737 /* nginx-site.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1527DFDE7900862737 /* nginx-site.test */; };
@ -153,15 +176,19 @@
C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A1925D9CD1000591B77 /* Utility.swift */; };
C43A8A2025D9D1D700591B77 /* brew-formula.json in Resources */ = {isa = PBXBuildFile; fileRef = C43A8A1F25D9D1D700591B77 /* brew-formula.json */; };
C43A8A2425D9D20D00591B77 /* HomebrewPackageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A2325D9D20D00591B77 /* HomebrewPackageTest.swift */; };
C43BCD4429FBEF40001547BC /* InstallAndUpgradeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43BCD4329FBEF40001547BC /* InstallAndUpgradeCommand.swift */; };
C43BCD4529FBEF40001547BC /* InstallAndUpgradeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43BCD4329FBEF40001547BC /* InstallAndUpgradeCommand.swift */; };
C43BCD4629FBEF40001547BC /* InstallAndUpgradeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43BCD4329FBEF40001547BC /* InstallAndUpgradeCommand.swift */; };
C43BCD4729FBEF40001547BC /* InstallAndUpgradeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43BCD4329FBEF40001547BC /* InstallAndUpgradeCommand.swift */; };
C43BCD4429FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43BCD4329FBEF40001547BC /* ModifyPhpVersionCommand.swift */; };
C43BCD4529FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43BCD4329FBEF40001547BC /* ModifyPhpVersionCommand.swift */; };
C43BCD4629FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43BCD4329FBEF40001547BC /* ModifyPhpVersionCommand.swift */; };
C43BCD4729FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43BCD4329FBEF40001547BC /* ModifyPhpVersionCommand.swift */; };
C43FDBE929A932B0003D85EC /* PhpConfigChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43FDBE829A932B0003D85EC /* PhpConfigChecker.swift */; };
C44067F527E2582B0045BD4E /* DomainListNameCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F427E2582B0045BD4E /* DomainListNameCell.swift */; };
C44067F727E258410045BD4E /* DomainListPhpCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F627E258410045BD4E /* DomainListPhpCell.swift */; };
C44067F927E2585E0045BD4E /* DomainListTypeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F827E2585E0045BD4E /* DomainListTypeCell.swift */; };
C44067FB27E25FD70045BD4E /* DomainListTLSCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067FA27E25FD70045BD4E /* DomainListTLSCell.swift */; };
C4415E8D2B0287E90035F520 /* 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 */; };
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 */; };
@ -195,6 +222,18 @@
C44F868E2835BD8D005C353A /* phpmon-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C44F868D2835BD8D005C353A /* phpmon-config.json */; };
C450C8C628C919EC002A2B4B /* PreferenceName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C450C8C528C919EC002A2B4B /* PreferenceName.swift */; };
C450C8C728C919EC002A2B4B /* PreferenceName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C450C8C528C919EC002A2B4B /* PreferenceName.swift */; };
C4513F8E2B13E2E5001AD760 /* PhpExtensionManagerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4292D532B023F61004F0D2A /* PhpExtensionManagerWindowController.swift */; };
C4513F8F2B13E2E5001AD760 /* PhpExtensionManagerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4292D532B023F61004F0D2A /* PhpExtensionManagerWindowController.swift */; };
C4513F902B13E2E6001AD760 /* PhpExtensionManagerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4292D532B023F61004F0D2A /* PhpExtensionManagerWindowController.swift */; };
C4513F912B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4292D552B024006004F0D2A /* PhpExtensionManagerView.swift */; };
C4513F922B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4292D552B024006004F0D2A /* PhpExtensionManagerView.swift */; };
C4513F932B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4292D552B024006004F0D2A /* PhpExtensionManagerView.swift */; };
C4513F942B13E30B001AD760 /* BrewExtensionsObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0309E6662B0D4B2F002AC007 /* BrewExtensionsObservable.swift */; };
C4513F952B13E30C001AD760 /* BrewExtensionsObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0309E6662B0D4B2F002AC007 /* BrewExtensionsObservable.swift */; };
C4513F962B13E30C001AD760 /* BrewExtensionsObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0309E6662B0D4B2F002AC007 /* BrewExtensionsObservable.swift */; };
C4513F972B13E338001AD760 /* PhpExtensionManagerView+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45A22B0D531D00070080 /* PhpExtensionManagerView+Actions.swift */; };
C4513F982B13E338001AD760 /* PhpExtensionManagerView+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45A22B0D531D00070080 /* PhpExtensionManagerView+Actions.swift */; };
C4513F992B13E338001AD760 /* PhpExtensionManagerView+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45A22B0D531D00070080 /* PhpExtensionManagerView+Actions.swift */; };
C451AFF62969E40F0078E617 /* HelpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C451AFF52969E40F0078E617 /* HelpButton.swift */; };
C451AFF72969E40F0078E617 /* HelpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C451AFF52969E40F0078E617 /* HelpButton.swift */; };
C451AFF82969E40F0078E617 /* HelpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C451AFF52969E40F0078E617 /* HelpButton.swift */; };
@ -586,14 +625,6 @@
C48DDD0E29C75C9E00D032D9 /* BlockingOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48DDD0C29C75C9E00D032D9 /* BlockingOverlayView.swift */; };
C48DDD0F29C75C9E00D032D9 /* BlockingOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48DDD0C29C75C9E00D032D9 /* BlockingOverlayView.swift */; };
C48DDD1029C75C9E00D032D9 /* BlockingOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48DDD0C29C75C9E00D032D9 /* BlockingOverlayView.swift */; };
C490E3A729BC940D006D2DE6 /* ProgressWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C490E3A629BC940D006D2DE6 /* ProgressWindowView.swift */; };
C490E3AA29BC9B3E006D2DE6 /* ProgressViewSubject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C490E3A929BC9B3E006D2DE6 /* ProgressViewSubject.swift */; };
C490E3B029BC9FE8006D2DE6 /* ProgressViewSubject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C490E3A929BC9B3E006D2DE6 /* ProgressViewSubject.swift */; };
C490E3B129BC9FE8006D2DE6 /* ProgressViewSubject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C490E3A929BC9B3E006D2DE6 /* ProgressViewSubject.swift */; };
C490E3B229BC9FE8006D2DE6 /* ProgressViewSubject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C490E3A929BC9B3E006D2DE6 /* ProgressViewSubject.swift */; };
C490E3B329BC9FEA006D2DE6 /* ProgressWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C490E3A629BC940D006D2DE6 /* ProgressWindowView.swift */; };
C490E3B429BC9FEA006D2DE6 /* ProgressWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C490E3A629BC940D006D2DE6 /* ProgressWindowView.swift */; };
C490E3B529BC9FEA006D2DE6 /* ProgressWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C490E3A629BC940D006D2DE6 /* ProgressWindowView.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 */; };
@ -626,10 +657,10 @@
C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F792754499000D44ED0 /* Valet.swift */; };
C4AF9F7B2754499000D44ED0 /* Valet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F792754499000D44ED0 /* Valet.swift */; };
C4AF9F7D275454A900D44ED0 /* ValetVersionExtractorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F7C275454A900D44ED0 /* ValetVersionExtractorTest.swift */; };
C4AFC4AE29C4F32F00BF4E0D /* BrewFormula.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AFC4AD29C4F32F00BF4E0D /* BrewFormula.swift */; };
C4AFC4AF29C4F32F00BF4E0D /* BrewFormula.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AFC4AD29C4F32F00BF4E0D /* BrewFormula.swift */; };
C4AFC4B029C4F32F00BF4E0D /* BrewFormula.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AFC4AD29C4F32F00BF4E0D /* BrewFormula.swift */; };
C4AFC4B129C4F32F00BF4E0D /* BrewFormula.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AFC4AD29C4F32F00BF4E0D /* BrewFormula.swift */; };
C4AFC4AE29C4F32F00BF4E0D /* BrewPhpFormula.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AFC4AD29C4F32F00BF4E0D /* BrewPhpFormula.swift */; };
C4AFC4AF29C4F32F00BF4E0D /* BrewPhpFormula.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AFC4AD29C4F32F00BF4E0D /* BrewPhpFormula.swift */; };
C4AFC4B029C4F32F00BF4E0D /* BrewPhpFormula.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AFC4AD29C4F32F00BF4E0D /* BrewPhpFormula.swift */; };
C4AFC4B129C4F32F00BF4E0D /* BrewPhpFormula.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AFC4AD29C4F32F00BF4E0D /* BrewPhpFormula.swift */; };
C4AFC4B429C4F43300BF4E0D /* HomebrewUpgradableTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AFC4B229C4F43300BF4E0D /* HomebrewUpgradableTest.swift */; };
C4AFC4B829C4F6DC00BF4E0D /* brew-outdated.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AFC4B729C4F57B00BF4E0D /* brew-outdated.json */; };
C4B5635E276AB09000F12CCB /* VersionExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5635D276AB09000F12CCB /* VersionExtractor.swift */; };
@ -641,10 +672,10 @@
C4B585452770FE3900DA4FBE /* RealCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* RealCommand.swift */; };
C4B6091A2853AAD300C95265 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B609192853AAD300C95265 /* SectionHeaderView.swift */; };
C4B6091D2853AB9700C95265 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B6091C2853AB9700C95265 /* ServicesView.swift */; };
C4B79EB629CA387F00A483EE /* BrewFormulaeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B79EB529CA387F00A483EE /* BrewFormulaeHandler.swift */; };
C4B79EB729CA387F00A483EE /* BrewFormulaeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B79EB529CA387F00A483EE /* BrewFormulaeHandler.swift */; };
C4B79EB829CA387F00A483EE /* BrewFormulaeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B79EB529CA387F00A483EE /* BrewFormulaeHandler.swift */; };
C4B79EB929CA387F00A483EE /* BrewFormulaeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B79EB529CA387F00A483EE /* BrewFormulaeHandler.swift */; };
C4B79EB629CA387F00A483EE /* BrewPhpFormulaeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B79EB529CA387F00A483EE /* BrewPhpFormulaeHandler.swift */; };
C4B79EB729CA387F00A483EE /* BrewPhpFormulaeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B79EB529CA387F00A483EE /* BrewPhpFormulaeHandler.swift */; };
C4B79EB829CA387F00A483EE /* BrewPhpFormulaeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B79EB529CA387F00A483EE /* BrewPhpFormulaeHandler.swift */; };
C4B79EB929CA387F00A483EE /* BrewPhpFormulaeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B79EB529CA387F00A483EE /* BrewPhpFormulaeHandler.swift */; };
C4B79EBC29CA38DB00A483EE /* BrewCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B79EBB29CA38DB00A483EE /* BrewCommand.swift */; };
C4B79EBD29CA38DB00A483EE /* BrewCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B79EBB29CA38DB00A483EE /* BrewCommand.swift */; };
C4B79EBE29CA38DB00A483EE /* BrewCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B79EBB29CA38DB00A483EE /* BrewCommand.swift */; };
@ -784,6 +815,10 @@
C4E49DEB28F7643D0026AC4E /* CommandProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */; };
C4E49DED28F764A00026AC4E /* TestableCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DEC28F764A00026AC4E /* TestableCommand.swift */; };
C4E49DEE28F764A00026AC4E /* TestableCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DEC28F764A00026AC4E /* TestableCommand.swift */; };
C4E684092AF26B830023ED25 /* BrewTapFormulae.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E684082AF26B830023ED25 /* BrewTapFormulae.swift */; };
C4E6840A2AF26B830023ED25 /* BrewTapFormulae.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E684082AF26B830023ED25 /* BrewTapFormulae.swift */; };
C4E6840B2AF26B830023ED25 /* BrewTapFormulae.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E684082AF26B830023ED25 /* BrewTapFormulae.swift */; };
C4E6840C2AF26B830023ED25 /* BrewTapFormulae.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E684082AF26B830023ED25 /* BrewTapFormulae.swift */; };
C4E9D2C02878B336008FFDAD /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */; };
C4E9D90129CBA09E00BD28D4 /* PHP Monitor Self-Updater.app in Resources */ = {isa = PBXBuildFile; fileRef = C4E9D90029CBA09E00BD28D4 /* PHP Monitor Self-Updater.app */; };
C4EB53E528551F9B006F9937 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E428551F9B006F9937 /* HeaderView.swift */; };
@ -804,6 +839,7 @@
C4F30B0B278E203C00755FCE /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; };
C4F319C927B034A500AFF46F /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DEB7D327A5D60B00834718 /* Stats.swift */; };
C4F361612836BFD9003598CC /* MainMenu+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F361602836BFD9003598CC /* MainMenu+Actions.swift */; };
C4F520672AF03791006787F2 /* ExtensionEnumeratorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F520662AF03791006787F2 /* ExtensionEnumeratorTest.swift */; };
C4F5FBCD28218CB8001065C5 /* Xdebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42337A2281F19F000459A48 /* Xdebug.swift */; };
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F7809B25D80344000DBC97 /* CommandTest.swift */; };
C4F780A825D80AE8000DBC97 /* php.ini in Resources */ = {isa = PBXBuildFile; fileRef = C4F780A725D80AE8000DBC97 /* php.ini */; };
@ -864,6 +900,12 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
0309E6662B0D4B2F002AC007 /* BrewExtensionsObservable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewExtensionsObservable.swift; sourceTree = "<group>"; };
031E2B682B1525A7007C29E1 /* BrewPhpExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewPhpExtension.swift; sourceTree = "<group>"; };
0336CAAF2B0D0CDA009A1034 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallPhpExtensionCommand.swift; sourceTree = "<group>"; };
033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemovePhpExtensionCommand.swift; sourceTree = "<group>"; };
033D45A22B0D531D00070080 /* PhpExtensionManagerView+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PhpExtensionManagerView+Actions.swift"; sourceTree = "<group>"; };
03E36FE628D9219000636F7F /* ActiveShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveShell.swift; sourceTree = "<group>"; };
5420395826135DC100FB00FA /* PreferencesVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesVC.swift; sourceTree = "<group>"; };
5420395E2613607600FB00FA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
@ -904,7 +946,7 @@
C40C7F1D2772136000DDDCDC /* PhpEnvironments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpEnvironments.swift; sourceTree = "<group>"; };
C40C7F2727721FF600DDDCDC /* Valet+Alerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Valet+Alerts.swift"; sourceTree = "<group>"; };
C40C7F2F27722E8D00DDDCDC /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
C40D72592A018ACC0054A067 /* PhpFormulaeStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpFormulaeStatus.swift; sourceTree = "<group>"; };
C40D72592A018ACC0054A067 /* BusyStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BusyStatus.swift; sourceTree = "<group>"; };
C40D725E2A018AE30054A067 /* BrewFormula+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BrewFormula+UI.swift"; sourceTree = "<group>"; };
C40F505428ECA64E004AD45B /* TestableConfigurations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableConfigurations.swift; sourceTree = "<group>"; };
C40FE736282ABA4F00A302C2 /* AppVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersion.swift; sourceTree = "<group>"; };
@ -933,11 +975,14 @@
C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalKeybindPreference.swift; sourceTree = "<group>"; };
C41E87192763D42300161EE0 /* DomainListVC+ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DomainListVC+ContextMenu.swift"; sourceTree = "<group>"; };
C4205A7D27F4D21800191A39 /* ValetProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetProxy.swift; sourceTree = "<group>"; };
C42106652AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PhpVersionManagerView+Actions.swift"; sourceTree = "<group>"; };
C422DDA928A2C49900CEAC97 /* PhpDoctorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpDoctorView.swift; sourceTree = "<group>"; };
C422DDAC28A2DAC600CEAC97 /* PhpDoctorWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpDoctorWindowController.swift; sourceTree = "<group>"; };
C4232EE42612526500158FC6 /* Credits.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = Credits.html; sourceTree = "<group>"; };
C42337A2281F19F000459A48 /* Xdebug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Xdebug.swift; sourceTree = "<group>"; };
C42759662627662800093CAE /* NSMenuExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSMenuExtension.swift; sourceTree = "<group>"; };
C4292D532B023F61004F0D2A /* PhpExtensionManagerWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtensionManagerWindowController.swift; sourceTree = "<group>"; };
C4292D552B024006004F0D2A /* PhpExtensionManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtensionManagerView.swift; sourceTree = "<group>"; };
C4297F7928970D59004C4630 /* WarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningView.swift; sourceTree = "<group>"; };
C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+FixMyValet.swift"; sourceTree = "<group>"; };
C42CFB1527DFDE7900862737 /* nginx-site.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site.test"; sourceTree = "<group>"; };
@ -953,12 +998,13 @@
C43A8A1925D9CD1000591B77 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = "<group>"; };
C43A8A1F25D9D1D700591B77 /* brew-formula.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "brew-formula.json"; sourceTree = "<group>"; };
C43A8A2325D9D20D00591B77 /* HomebrewPackageTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackageTest.swift; sourceTree = "<group>"; };
C43BCD4329FBEF40001547BC /* InstallAndUpgradeCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallAndUpgradeCommand.swift; sourceTree = "<group>"; };
C43BCD4329FBEF40001547BC /* ModifyPhpVersionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModifyPhpVersionCommand.swift; sourceTree = "<group>"; };
C43FDBE829A932B0003D85EC /* PhpConfigChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigChecker.swift; sourceTree = "<group>"; };
C44067F427E2582B0045BD4E /* DomainListNameCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListNameCell.swift; sourceTree = "<group>"; };
C44067F627E258410045BD4E /* DomainListPhpCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListPhpCell.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>"; };
C4415E8C2B0287E90035F520 /* BrewFormulaeObservable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewFormulaeObservable.swift; sourceTree = "<group>"; };
C441CC552AE8249400DDFACD /* ConfigFSNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigFSNotifier.swift; sourceTree = "<group>"; };
C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIHelper.swift; sourceTree = "<group>"; };
C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionPopoverView.swift; sourceTree = "<group>"; };
@ -1020,8 +1066,6 @@
C48D6C6F279CD2AC00F26D7E /* VersionNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionNumber.swift; sourceTree = "<group>"; };
C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpVersionNumberTest.swift; sourceTree = "<group>"; };
C48DDD0C29C75C9E00D032D9 /* BlockingOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockingOverlayView.swift; sourceTree = "<group>"; };
C490E3A629BC940D006D2DE6 /* ProgressWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressWindowView.swift; sourceTree = "<group>"; };
C490E3A929BC9B3E006D2DE6 /* ProgressViewSubject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressViewSubject.swift; sourceTree = "<group>"; };
C4927F0A27B2DFC200C55AFD /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = "<group>"; };
C4930849279F331F009C240B /* AddSiteVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSiteVC.swift; sourceTree = "<group>"; };
C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentCheck.swift; sourceTree = "<group>"; };
@ -1037,7 +1081,7 @@
C4AF9F76275447F100D44ED0 /* ValetConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetConfigurationTest.swift; sourceTree = "<group>"; };
C4AF9F792754499000D44ED0 /* Valet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Valet.swift; sourceTree = "<group>"; };
C4AF9F7C275454A900D44ED0 /* ValetVersionExtractorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetVersionExtractorTest.swift; sourceTree = "<group>"; };
C4AFC4AD29C4F32F00BF4E0D /* BrewFormula.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewFormula.swift; sourceTree = "<group>"; };
C4AFC4AD29C4F32F00BF4E0D /* BrewPhpFormula.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewPhpFormula.swift; sourceTree = "<group>"; };
C4AFC4B229C4F43300BF4E0D /* HomebrewUpgradableTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewUpgradableTest.swift; sourceTree = "<group>"; };
C4AFC4B729C4F57B00BF4E0D /* brew-outdated.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "brew-outdated.json"; sourceTree = "<group>"; };
C4B5635D276AB09000F12CCB /* VersionExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionExtractor.swift; sourceTree = "<group>"; };
@ -1046,7 +1090,7 @@
C4B5853D2770FE3900DA4FBE /* RealCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealCommand.swift; sourceTree = "<group>"; };
C4B609192853AAD300C95265 /* SectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderView.swift; sourceTree = "<group>"; };
C4B6091C2853AB9700C95265 /* ServicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesView.swift; sourceTree = "<group>"; };
C4B79EB529CA387F00A483EE /* BrewFormulaeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewFormulaeHandler.swift; sourceTree = "<group>"; };
C4B79EB529CA387F00A483EE /* BrewPhpFormulaeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewPhpFormulaeHandler.swift; sourceTree = "<group>"; };
C4B79EBB29CA38DB00A483EE /* BrewCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewCommand.swift; sourceTree = "<group>"; };
C4B79EC529CA474200A483EE /* FakeCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeCommand.swift; sourceTree = "<group>"; };
C4B79ECA29CA475900A483EE /* RemovePhpVersionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemovePhpVersionCommand.swift; sourceTree = "<group>"; };
@ -1104,6 +1148,7 @@
C4E49DE628F764050026AC4E /* ActiveCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveCommand.swift; sourceTree = "<group>"; };
C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandProtocol.swift; sourceTree = "<group>"; };
C4E49DEC28F764A00026AC4E /* TestableCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableCommand.swift; sourceTree = "<group>"; };
C4E684082AF26B830023ED25 /* BrewTapFormulae.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewTapFormulae.swift; sourceTree = "<group>"; };
C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = "<group>"; };
C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = "<group>"; };
C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
@ -1118,6 +1163,7 @@
C4F30B02278E16BA00755FCE /* HomebrewService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewService.swift; sourceTree = "<group>"; };
C4F30B06278E195800755FCE /* brew-services.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "brew-services.json"; sourceTree = "<group>"; };
C4F361602836BFD9003598CC /* MainMenu+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Actions.swift"; sourceTree = "<group>"; };
C4F520662AF03791006787F2 /* ExtensionEnumeratorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionEnumeratorTest.swift; sourceTree = "<group>"; };
C4F5FBCC28218C93001065C5 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = "<group>"; };
C4F7807925D7F84B000DBC97 /* Unit Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Unit Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
C4F7809B25D80344000DBC97 /* CommandTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandTest.swift; sourceTree = "<group>"; };
@ -1173,6 +1219,23 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0309E6652B0D4B1D002AC007 /* Data */ = {
isa = PBXGroup;
children = (
0309E6662B0D4B2F002AC007 /* BrewExtensionsObservable.swift */,
);
path = Data;
sourceTree = "<group>";
};
033D459C2B0D506B00070080 /* PHP Versions */ = {
isa = PBXGroup;
children = (
C43BCD4329FBEF40001547BC /* ModifyPhpVersionCommand.swift */,
C4B79ECA29CA475900A483EE /* RemovePhpVersionCommand.swift */,
);
path = "PHP Versions";
sourceTree = "<group>";
};
5420395726135DB800FB00FA /* Preferences */ = {
isa = PBXGroup;
children = (
@ -1415,6 +1478,25 @@
path = Extensions;
sourceTree = "<group>";
};
C4292D512B023F37004F0D2A /* PHP Extension Manager */ = {
isa = PBXGroup;
children = (
0309E6652B0D4B1D002AC007 /* Data */,
C4292D522B023F52004F0D2A /* UI */,
);
path = "PHP Extension Manager";
sourceTree = "<group>";
};
C4292D522B023F52004F0D2A /* UI */ = {
isa = PBXGroup;
children = (
C4292D532B023F61004F0D2A /* PhpExtensionManagerWindowController.swift */,
C4292D552B024006004F0D2A /* PhpExtensionManagerView.swift */,
033D45A22B0D531D00070080 /* PhpExtensionManagerView+Actions.swift */,
);
path = UI;
sourceTree = "<group>";
};
C4297F7828970D4E004C4630 /* PHP Doctor */ = {
isa = PBXGroup;
children = (
@ -1446,6 +1528,14 @@
path = Cells;
sourceTree = "<group>";
};
C4415E8B2B02877A0035F520 /* State */ = {
isa = PBXGroup;
children = (
C40D72592A018ACC0054A067 /* BusyStatus.swift */,
);
path = State;
sourceTree = "<group>";
};
C4463FD029804C13007B93D5 /* Common */ = {
isa = PBXGroup;
children = (
@ -1487,6 +1577,7 @@
children = (
C4D5576329C77CC5001A44CD /* PhpVersionManagerWindowController.swift */,
C43931C429C4BD610069165B /* PhpVersionManagerView.swift */,
C42106652AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift */,
C48DDD0C29C75C9E00D032D9 /* BlockingOverlayView.swift */,
);
path = UI;
@ -1503,7 +1594,7 @@
C44DFA7F2A6705A100B98ED5 /* Data */ = {
isa = PBXGroup;
children = (
C40D72592A018ACC0054A067 /* PhpFormulaeStatus.swift */,
C4415E8C2B0287E90035F520 /* BrewFormulaeObservable.swift */,
C40D725E2A018AE30054A067 /* BrewFormula+UI.swift */,
C44DFA7E2A67059700B98ED5 /* Fake */,
);
@ -1549,6 +1640,7 @@
C44DFA7A2A6703FD00B98ED5 /* PHP Config Editor */,
C4297F7828970D4E004C4630 /* PHP Doctor */,
C43931C329C4BD510069165B /* PHP Version Manager */,
C4292D512B023F37004F0D2A /* PHP Extension Manager */,
);
path = Modules;
sourceTree = "<group>";
@ -1594,6 +1686,15 @@
path = phpmon;
sourceTree = "<group>";
};
C4513F8D2B13CD08001AD760 /* PHP Extensions */ = {
isa = PBXGroup;
children = (
033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */,
033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */,
);
path = "PHP Extensions";
sourceTree = "<group>";
};
C456A0D02AA6175D0080144F /* Config */ = {
isa = PBXGroup;
children = (
@ -1776,15 +1877,6 @@
path = "PHP Version";
sourceTree = "<group>";
};
C490E3A329BC92E6006D2DE6 /* Progress */ = {
isa = PBXGroup;
children = (
C490E3A629BC940D006D2DE6 /* ProgressWindowView.swift */,
C490E3A929BC9B3E006D2DE6 /* ProgressViewSubject.swift */,
);
path = Progress;
sourceTree = "<group>";
};
C4AF9F6A275445C900D44ED0 /* Valet */ = {
isa = PBXGroup;
children = (
@ -1817,10 +1909,12 @@
C4B79EBA29CA38D100A483EE /* Commands */,
C45B42C329C7C67400366A14 /* Fake */,
C43931C929C4C03F0069165B /* Brew.swift */,
C4AFC4AD29C4F32F00BF4E0D /* BrewFormula.swift */,
C4B79EB529CA387F00A483EE /* BrewFormulaeHandler.swift */,
C4AFC4AD29C4F32F00BF4E0D /* BrewPhpFormula.swift */,
C4B79EB529CA387F00A483EE /* BrewPhpFormulaeHandler.swift */,
C4F2E4362752F0870020E974 /* BrewDiagnostics.swift */,
C40934A1298EEB2C00D25014 /* CaskFile.swift */,
C4E684082AF26B830023ED25 /* BrewTapFormulae.swift */,
031E2B682B1525A7007C29E1 /* BrewPhpExtension.swift */,
);
path = Homebrew;
sourceTree = "<group>";
@ -1858,6 +1952,7 @@
C44CCD4327AFE93300CE40E5 /* Errors */,
C4F8C0A222D4F100002EFE61 /* Extensions */,
C4811D2822D70D9C00B5F6B3 /* Helpers */,
C4415E8B2B02877A0035F520 /* State */,
5489625628312F95004F647A /* Protocols */,
);
path = Common;
@ -1895,9 +1990,9 @@
C4B79EBA29CA38D100A483EE /* Commands */ = {
isa = PBXGroup;
children = (
C4513F8D2B13CD08001AD760 /* PHP Extensions */,
033D459C2B0D506B00070080 /* PHP Versions */,
C4B79EBB29CA38DB00A483EE /* BrewCommand.swift */,
C43BCD4329FBEF40001547BC /* InstallAndUpgradeCommand.swift */,
C4B79ECA29CA475900A483EE /* RemovePhpVersionCommand.swift */,
);
path = Commands;
sourceTree = "<group>";
@ -1940,6 +2035,7 @@
C4551656297AED18009B8466 /* ValetRcTest.swift */,
C40934AA298EEDA900D25014 /* CaskFileParserTest.swift */,
C4AFC4B229C4F43300BF4E0D /* HomebrewUpgradableTest.swift */,
C4F520662AF03791006787F2 /* ExtensionEnumeratorTest.swift */,
);
path = Parsers;
sourceTree = "<group>";
@ -2055,7 +2151,6 @@
children = (
C4B609182853AAA700C95265 /* Domains */,
C4B609172853AA9E00C95265 /* Menu */,
C490E3A329BC92E6006D2DE6 /* Progress */,
C4B609162853AA9A00C95265 /* Common */,
);
path = SwiftUI;
@ -2235,7 +2330,7 @@
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1420;
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1500;
ORGANIZATIONNAME = "Nico Verbruggen";
TargetAttributes = {
C406A5EF298AD2CE00B5B85A = {
@ -2267,6 +2362,7 @@
de,
"pt-PT",
Base,
fr,
);
mainGroup = C41C1B2A22B0097F00E7CF16;
packageReferences = (
@ -2319,6 +2415,7 @@
C4E2E85528FC256B003B070C /* brew-services-sudo.json in Resources */,
C4E2E85928FC256B003B070C /* brew-services-normal.json in Resources */,
C40934A8298EEB8700D25014 /* phpmon-dev.rb in Resources */,
C428311F2B52AD6F0009F9F1 /* Assets.xcassets in Resources */,
C4E2E84F28FC22E4003B070C /* brew-formula.json in Resources */,
C4E2E86228FC28A6003B070C /* brew-services.json in Resources */,
);
@ -2332,6 +2429,7 @@
C4E2E85628FC256B003B070C /* brew-services-sudo.json in Resources */,
C4E2E85A28FC256B003B070C /* brew-services-normal.json in Resources */,
C40934A9298EEB8700D25014 /* phpmon-dev.rb in Resources */,
C42831202B52AD700009F9F1 /* Assets.xcassets in Resources */,
C4E2E85028FC22E4003B070C /* brew-formula.json in Resources */,
C4E2E86128FC28A6003B070C /* brew-services.json in Resources */,
);
@ -2343,6 +2441,7 @@
files = (
C4551659297AED7D009B8466 /* valetrc.valid in Resources */,
C4FC8D3E2A49816300FBBD16 /* Localizable.strings in Resources */,
C428311E2B52AD6F0009F9F1 /* Assets.xcassets in Resources */,
54FCFD27276C883F004CE748 /* SelectPreferenceView.xib in Resources */,
54FCFD2E276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */,
C42CFB1827DFDFDC00862737 /* nginx-site-isolated.test in Resources */,
@ -2411,16 +2510,18 @@
C41ADCE82970CCC700120423 /* FSNotifier.swift in Sources */,
C47699EF28A2F2A30060FEB8 /* WarningManager.swift in Sources */,
C490E3BB29BCA375006D2DE6 /* Measurements.swift in Sources */,
C4E684092AF26B830023ED25 /* BrewTapFormulae.swift in Sources */,
C4B79EC629CA474200A483EE /* FakeCommand.swift in Sources */,
C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */,
C47DF1AF299D5A3B0007055D /* LoginItemManager.swift in Sources */,
C490E3AA29BC9B3E006D2DE6 /* ProgressViewSubject.swift in Sources */,
C4292D542B023F61004F0D2A /* PhpExtensionManagerWindowController.swift in Sources */,
C4D3661A291173EA006BD146 /* DictionaryExtension.swift in Sources */,
C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */,
C409349D298EE8E900D25014 /* AppUpdater.swift in Sources */,
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */,
C43931CA29C4C03F0069165B /* Brew.swift in Sources */,
C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */,
C4292D562B024006004F0D2A /* PhpExtensionManagerView.swift in Sources */,
C48D6C70279CD2AC00F26D7E /* VersionNumber.swift in Sources */,
C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */,
C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */,
@ -2437,6 +2538,7 @@
C45B91532956123A00F4EC78 /* FakeServicesManager.swift in Sources */,
C41C708D28AA7F7900E8D498 /* NoWarningsView.swift in Sources */,
C4080FF627BD8C6400BF2C6B /* BetterAlert.swift in Sources */,
0309E6672B0D4B2F002AC007 /* BrewExtensionsObservable.swift in Sources */,
C4E0F7ED27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */,
C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */,
C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
@ -2460,6 +2562,7 @@
C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */,
C4E9D2C02878B336008FFDAD /* OnboardingView.swift in Sources */,
C4F2E4372752F0870020E974 /* BrewDiagnostics.swift in Sources */,
031E2B692B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */,
C4AD38B228ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */,
C4EB53E528551F9B006F9937 /* HeaderView.swift in Sources */,
C40FE737282ABA4F00A302C2 /* AppVersion.swift in Sources */,
@ -2508,6 +2611,7 @@
5420395F2613607600FB00FA /* Preferences.swift in Sources */,
C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */,
54FCFD2A276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */,
033D45982B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */,
C47699F128A2F3150060FEB8 /* Warning.swift in Sources */,
54D9E0B227E4F51E003B9AD9 /* HotKeysController.swift in Sources */,
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */,
@ -2543,6 +2647,7 @@
C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */,
C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */,
C4FE011128084FC200D1DE6D /* SelectionVC.swift in Sources */,
033D459E2B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */,
C4709CA228524B3400088BB8 /* StatsView.swift in Sources */,
C44CCD4027AFE2FC00CE40E5 /* AlertableError.swift in Sources */,
C4B6091D2853AB9700C95265 /* ServicesView.swift in Sources */,
@ -2553,34 +2658,36 @@
C4D36601291132B7006BD146 /* ValetScanners.swift in Sources */,
C4EED88927A48778006D7272 /* InterAppHandler.swift in Sources */,
C40C7F1E2772136000DDDCDC /* PhpEnvironments.swift in Sources */,
C4B79EB629CA387F00A483EE /* BrewFormulaeHandler.swift in Sources */,
C4B79EB629CA387F00A483EE /* BrewPhpFormulaeHandler.swift in Sources */,
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
033D45A32B0D531D00070080 /* PhpExtensionManagerView+Actions.swift in Sources */,
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
C4D5CFCA27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */,
C4D36615291160A1006BD146 /* WIP.swift in Sources */,
C485707028BF452300539B36 /* PhpDoctorWindowController.swift in Sources */,
C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */,
C40D725A2A018ACC0054A067 /* PhpFormulaeStatus.swift in Sources */,
C40D725A2A018ACC0054A067 /* BusyStatus.swift in Sources */,
C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */,
C4FACE83288F1F9700FC478F /* OnboardingWindowController.swift in Sources */,
C4415E8D2B0287E90035F520 /* BrewFormulaeObservable.swift in Sources */,
C4080FFA27BD956700BF2C6B /* BetterAlertVC.swift in Sources */,
C43FDBE929A932B0003D85EC /* PhpConfigChecker.swift in Sources */,
C490E3A729BC940D006D2DE6 /* ProgressWindowView.swift in Sources */,
C4BF56AB2949381100379603 /* FakeValetInteractor.swift in Sources */,
C4B5635E276AB09000F12CCB /* VersionExtractor.swift in Sources */,
C451AFF62969E40F0078E617 /* HelpButton.swift in Sources */,
54D9E0B627E4F51E003B9AD9 /* HotKey.swift in Sources */,
C4AFC4AE29C4F32F00BF4E0D /* BrewFormula.swift in Sources */,
C4AFC4AE29C4F32F00BF4E0D /* BrewPhpFormula.swift in Sources */,
C4D936C927E3EB6100BD69FE /* PhpHelper.swift in Sources */,
C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */,
C44067F927E2585E0045BD4E /* DomainListTypeCell.swift in Sources */,
54D9E0BA27E4F51E003B9AD9 /* ModifierFlagsExtension.swift in Sources */,
C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */,
C42106662AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */,
C40508AF28ADA23D008FAC1F /* NoDomainResultsView.swift in Sources */,
C4B79ECB29CA475900A483EE /* RemovePhpVersionCommand.swift in Sources */,
C40D725F2A018AE30054A067 /* BrewFormula+UI.swift in Sources */,
C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */,
C43BCD4429FBEF40001547BC /* InstallAndUpgradeCommand.swift in Sources */,
C43BCD4429FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */,
C4E2E84A28FC1E70003B070C /* DataExtension.swift in Sources */,
C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */,
C42337A3281F19F000459A48 /* Xdebug.swift in Sources */,
@ -2606,7 +2713,7 @@
C4FD87A929AB9ABD0002D701 /* PhpConfigChecker.swift in Sources */,
C471E83028F9BB650021E251 /* Application.swift in Sources */,
C471E83128F9BB650021E251 /* LocalNotification.swift in Sources */,
C4B79EB829CA387F00A483EE /* BrewFormulaeHandler.swift in Sources */,
C4B79EB829CA387F00A483EE /* BrewPhpFormulaeHandler.swift in Sources */,
C471E83228F9BB650021E251 /* MenuBarImageGenerator.swift in Sources */,
C4BB393B2981AFC700F8E797 /* PhpVersionSource.swift in Sources */,
C471E83328F9BB650021E251 /* PMWindowController.swift in Sources */,
@ -2618,6 +2725,7 @@
C471E83928F9BB650021E251 /* ValetSite.swift in Sources */,
C471E83A28F9BB650021E251 /* FakeValetSite.swift in Sources */,
C471E83C28F9BB650021E251 /* ValetDomainScanner.swift in Sources */,
033D459A2B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */,
C4E2E86928FC3002003B070C /* Utility.swift in Sources */,
C471E83D28F9BB650021E251 /* FakeDomainScanner.swift in Sources */,
C471E83F28F9BB650021E251 /* AppDelegate.swift in Sources */,
@ -2630,6 +2738,7 @@
C45E2A7529199248005C7CFD /* InternalSwitcherTest.swift in Sources */,
C471E84428F9BB650021E251 /* App+ActivationPolicy.swift in Sources */,
C471E84528F9BB650021E251 /* App+GlobalHotkey.swift in Sources */,
C4513F922B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */,
C471E84628F9BB650021E251 /* InterAppHandler.swift in Sources */,
C471E84728F9BB650021E251 /* Startup.swift in Sources */,
C471E84828F9BB650021E251 /* EnvironmentCheck.swift in Sources */,
@ -2663,7 +2772,6 @@
C471E85E28F9BB650021E251 /* DomainListVC+ContextMenu.swift in Sources */,
C4E2E86628FC2F1B003B070C /* XCPMApplication.swift in Sources */,
C471E85F28F9BB650021E251 /* DomainListVC+Actions.swift in Sources */,
C490E3B429BC9FEA006D2DE6 /* ProgressWindowView.swift in Sources */,
C4D5576629C77CC5001A44CD /* PhpVersionManagerWindowController.swift in Sources */,
C4ACE9E329F84EDD00110766 /* PhpGuard.swift in Sources */,
C471E86028F9BB650021E251 /* SelectionVC.swift in Sources */,
@ -2681,11 +2789,12 @@
C471E86828F9BB650021E251 /* PreferencesWindowController.swift in Sources */,
C471E86928F9BB650021E251 /* PreferencesWindowController+Hotkey.swift in Sources */,
C48DDD0F29C75C9E00D032D9 /* BlockingOverlayView.swift in Sources */,
C4AFC4B029C4F32F00BF4E0D /* BrewFormula.swift in Sources */,
C4AFC4B029C4F32F00BF4E0D /* BrewPhpFormula.swift in Sources */,
C471E86A28F9BB650021E251 /* PreferencesVC.swift in Sources */,
C471E86B28F9BB650021E251 /* PreferenceName.swift in Sources */,
C471E86C28F9BB650021E251 /* Preferences.swift in Sources */,
C4D3660D29113F20006BD146 /* System.swift in Sources */,
033D45A02B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */,
C471E86D28F9BB650021E251 /* CustomPrefs.swift in Sources */,
C45D654E29F52F74004C28F9 /* BrewPermissionFixer.swift in Sources */,
C4E2E84C28FC1E70003B070C /* DataExtension.swift in Sources */,
@ -2695,7 +2804,6 @@
C471E87028F9BB650021E251 /* GlobalKeybindPreference.swift in Sources */,
C471E87228F9BB650021E251 /* CheckboxPreferenceView.swift in Sources */,
C471E87428F9BB650021E251 /* SelectPreferenceView.swift in Sources */,
C490E3B029BC9FE8006D2DE6 /* ProgressViewSubject.swift in Sources */,
C471E87628F9BB650021E251 /* HotkeyPreferenceView.swift in Sources */,
C471E87728F9BB650021E251 /* Keys.swift in Sources */,
C471E87828F9BB650021E251 /* TerminalProgressWindowController.swift in Sources */,
@ -2710,6 +2818,7 @@
C471E88228F9BB650021E251 /* OnboardingView.swift in Sources */,
C471E88328F9BB650021E251 /* VersionPopoverView.swift in Sources */,
C471E88428F9BB650021E251 /* NoDomainResultsView.swift in Sources */,
C4513F952B13E30C001AD760 /* BrewExtensionsObservable.swift in Sources */,
C471E88528F9BB650021E251 /* ServicesView.swift in Sources */,
C471E88628F9BB650021E251 /* StatsView.swift in Sources */,
C451AFF82969E40F0078E617 /* HelpButton.swift in Sources */,
@ -2727,6 +2836,7 @@
C471E7E928F9BAC20021E251 /* Paths.swift in Sources */,
C45B91552956123A00F4EC78 /* FakeServicesManager.swift in Sources */,
C471E7FE28F9BACE0021E251 /* HomebrewDecodable.swift in Sources */,
C4415E8F2B0287E90035F520 /* BrewFormulaeObservable.swift in Sources */,
C471E7D828F9BA8F0021E251 /* FileSystemProtocol.swift in Sources */,
C471E7F328F9BAC70021E251 /* PhpHelper.swift in Sources */,
C471E7E728F9BAC20021E251 /* Constants.swift in Sources */,
@ -2740,6 +2850,7 @@
C471E80528F9BAD40021E251 /* ActivePhpInstallation.swift in Sources */,
C471E80628F9BAD40021E251 /* PhpInstallation.swift in Sources */,
C471E81828F9BAE80021E251 /* StringExtension.swift in Sources */,
C4E6840B2AF26B830023ED25 /* BrewTapFormulae.swift in Sources */,
C471E7FA28F9BACB0021E251 /* InternalSwitcher.swift in Sources */,
C471E82628F9BB2E0021E251 /* ComposerJson.swift in Sources */,
C471E82428F9BB2E0021E251 /* ProjectTypeDetection.swift in Sources */,
@ -2750,6 +2861,7 @@
C471E80828F9BAD40021E251 /* PhpExtension.swift in Sources */,
C471E7F928F9BACB0021E251 /* PhpSwitcher.swift in Sources */,
C471E82A28F9BB330021E251 /* ValetListable.swift in Sources */,
031E2B6B2B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */,
C471E82728F9BB310021E251 /* BrewDiagnostics.swift in Sources */,
C471E81C28F9BB250021E251 /* BetterAlert.swift in Sources */,
C471E7DB28F9BA8F0021E251 /* RealShell.swift in Sources */,
@ -2759,11 +2871,13 @@
C471E7F228F9BAC70021E251 /* PhpEnvironments.swift in Sources */,
C471E7E628F9BAC20021E251 /* Process.swift in Sources */,
C471E81928F9BAE80021E251 /* NSMenuItemExtension.swift in Sources */,
C4513F982B13E338001AD760 /* PhpExtensionManagerView+Actions.swift in Sources */,
C45B914B295607F400F4EC78 /* Service.swift in Sources */,
C471E7D928F9BA8F0021E251 /* TestableShell.swift in Sources */,
C471E81428F9BAE80021E251 /* NSWindowExtension.swift in Sources */,
C43BCD4629FBEF40001547BC /* InstallAndUpgradeCommand.swift in Sources */,
C43BCD4629FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */,
C471E7D328F9BA8F0021E251 /* ActiveShell.swift in Sources */,
C42106682AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */,
C4B79EC829CA474200A483EE /* FakeCommand.swift in Sources */,
C471E7DE28F9BAA30021E251 /* CommandProtocol.swift in Sources */,
C471E81B28F9BB250021E251 /* BetterAlertVC.swift in Sources */,
@ -2780,12 +2894,13 @@
C45E2A77291992DA005C7CFD /* FeatureTestCase.swift in Sources */,
C471E82028F9BB290021E251 /* NginxConfigurationFile.swift in Sources */,
C471E7D428F9BA8F0021E251 /* ActiveFileSystem.swift in Sources */,
C4513F902B13E2E6001AD760 /* PhpExtensionManagerWindowController.swift in Sources */,
C471E81528F9BAE80021E251 /* ArrayExtension.swift in Sources */,
C471E7DA28F9BA8F0021E251 /* TestableCommand.swift in Sources */,
C471E7E528F9BAC20021E251 /* Events.swift in Sources */,
C471E7D628F9BA8F0021E251 /* RealFileSystem.swift in Sources */,
C471E81728F9BAE80021E251 /* NSMenuExtension.swift in Sources */,
C40D725C2A018ACC0054A067 /* PhpFormulaeStatus.swift in Sources */,
C40D725C2A018ACC0054A067 /* BusyStatus.swift in Sources */,
C471E81328F9BAE80021E251 /* XibLoadable.swift in Sources */,
C4D3661C291173EA006BD146 /* DictionaryExtension.swift in Sources */,
C4B79ECD29CA475900A483EE /* RemovePhpVersionCommand.swift in Sources */,
@ -2803,12 +2918,13 @@
C471E89128F9BB8F0021E251 /* Errors.swift in Sources */,
C4B79EC929CA474200A483EE /* FakeCommand.swift in Sources */,
C471E89228F9BB8F0021E251 /* Alert.swift in Sources */,
033D45A12B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */,
C471E89328F9BB8F0021E251 /* Application.swift in Sources */,
C471E89428F9BB8F0021E251 /* LocalNotification.swift in Sources */,
C441CC592AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */,
C40934A5298EEB2C00D25014 /* CaskFile.swift in Sources */,
C471E89528F9BB8F0021E251 /* MenuBarImageGenerator.swift in Sources */,
C40D725D2A018ACC0054A067 /* PhpFormulaeStatus.swift in Sources */,
C40D725D2A018ACC0054A067 /* BusyStatus.swift in Sources */,
C471E89628F9BB8F0021E251 /* PMWindowController.swift in Sources */,
C471E89728F9BB8F0021E251 /* VersionExtractor.swift in Sources */,
C47DF1B2299D5A3B0007055D /* LoginItemManager.swift in Sources */,
@ -2820,17 +2936,17 @@
C471E89F28F9BB8F0021E251 /* ValetDomainScanner.swift in Sources */,
C471E8A028F9BB8F0021E251 /* FakeDomainScanner.swift in Sources */,
C471E8A228F9BB8F0021E251 /* AppDelegate.swift in Sources */,
C42106692AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */,
C43931CD29C4C03F0069165B /* Brew.swift in Sources */,
C451AFF92969E40F0078E617 /* HelpButton.swift in Sources */,
C4ACE9E429F84EDD00110766 /* PhpGuard.swift in Sources */,
C471E8A328F9BB8F0021E251 /* AppDelegate+MenuOutlets.swift in Sources */,
C4B79EB929CA387F00A483EE /* BrewFormulaeHandler.swift in Sources */,
C4B79EB929CA387F00A483EE /* BrewPhpFormulaeHandler.swift in Sources */,
C471E8A428F9BB8F0021E251 /* AppDelegate+Notifications.swift in Sources */,
C490E3B329BC9FEA006D2DE6 /* ProgressWindowView.swift in Sources */,
C489E0BE2A220A4200323F5E /* FakeBrewFormulaeHandler.swift in Sources */,
C490E3B229BC9FE8006D2DE6 /* ProgressViewSubject.swift in Sources */,
C471E8A528F9BB8F0021E251 /* AppDelegate+InterApp.swift in Sources */,
C471E8A628F9BB8F0021E251 /* App.swift in Sources */,
C4513F912B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */,
C471E8A728F9BB8F0021E251 /* App+ActivationPolicy.swift in Sources */,
C45B914C295607F400F4EC78 /* Service.swift in Sources */,
C471E8A828F9BB8F0021E251 /* App+GlobalHotkey.swift in Sources */,
@ -2855,6 +2971,7 @@
C471E8BD28F9BB8F0021E251 /* DomainListTypeCell.swift in Sources */,
C471E8BE28F9BB8F0021E251 /* DomainListKindCell.swift in Sources */,
C4E2E86A28FC3002003B070C /* Utility.swift in Sources */,
031E2B6C2B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */,
C471E8BF28F9BB8F0021E251 /* DomainListWindowController.swift in Sources */,
C471E8C028F9BB8F0021E251 /* DomainListVC.swift in Sources */,
C4D5576729C77CC5001A44CD /* PhpVersionManagerWindowController.swift in Sources */,
@ -2879,6 +2996,7 @@
C471E8CD28F9BB8F0021E251 /* PreferencesVC.swift in Sources */,
C471E8CE28F9BB8F0021E251 /* PreferenceName.swift in Sources */,
C471E8CF28F9BB8F0021E251 /* Preferences.swift in Sources */,
C4415E902B0287E90035F520 /* BrewFormulaeObservable.swift in Sources */,
C4E2E84D28FC1E70003B070C /* DataExtension.swift in Sources */,
C471E8D028F9BB8F0021E251 /* CustomPrefs.swift in Sources */,
C471E8D128F9BB8F0021E251 /* MenuBarIcons.swift in Sources */,
@ -2957,31 +3075,36 @@
C471E82C28F9BB340021E251 /* ValetListable.swift in Sources */,
C471E82828F9BB310021E251 /* BrewDiagnostics.swift in Sources */,
C471E81E28F9BB260021E251 /* BetterAlert.swift in Sources */,
C43BCD4729FBEF40001547BC /* InstallAndUpgradeCommand.swift in Sources */,
C43BCD4729FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */,
C44E985F29B23EBF0059F773 /* UpdateCheckTest.swift in Sources */,
C4513F8E2B13E2E5001AD760 /* PhpExtensionManagerWindowController.swift in Sources */,
C471E7D228F9BA630021E251 /* ActiveFileSystem.swift in Sources */,
C471E80028F9BAD10021E251 /* Xdebug.swift in Sources */,
C471E7F528F9BAC80021E251 /* PhpEnvironments.swift in Sources */,
C471E7ED28F9BAC30021E251 /* Process.swift in Sources */,
C471E81128F9BAE80021E251 /* NSMenuItemExtension.swift in Sources */,
C471E7CC28F9BA5B0021E251 /* TestableShell.swift in Sources */,
C4E6840C2AF26B830023ED25 /* BrewTapFormulae.swift in Sources */,
C471E80C28F9BAE80021E251 /* NSWindowExtension.swift in Sources */,
C471E7CA28F9BA480021E251 /* TestableFileSystem.swift in Sources */,
C471E7DD28F9BAA30021E251 /* CommandProtocol.swift in Sources */,
C471E7D128F9BA630021E251 /* RealFileSystem.swift in Sources */,
033D459B2B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */,
C471E81D28F9BB260021E251 /* BetterAlertVC.swift in Sources */,
C471E82B28F9BB340021E251 /* Valet.swift in Sources */,
C471E80328F9BAD40021E251 /* PhpConfigurationFile.swift in Sources */,
C471E7C928F9BA2F0021E251 /* TestableConfigurations.swift in Sources */,
C471E7EA28F9BAC30021E251 /* Logger.swift in Sources */,
C471E7FB28F9BACE0021E251 /* HomebrewService.swift in Sources */,
C4513F942B13E30B001AD760 /* BrewExtensionsObservable.swift in Sources */,
C471E7EB28F9BAC30021E251 /* Helpers.swift in Sources */,
C4CB6E68292C362C002E9027 /* Homebrew.swift in Sources */,
C4181F1128FAF9330042EA28 /* UITestCase.swift in Sources */,
C4611E622AEAD3110010BE24 /* ByteLimitView.swift in Sources */,
C4AFC4B129C4F32F00BF4E0D /* BrewFormula.swift in Sources */,
C4AFC4B129C4F32F00BF4E0D /* BrewPhpFormula.swift in Sources */,
C471E81F28F9BB290021E251 /* NginxConfigurationFile.swift in Sources */,
C471E7BF28F9B90F0021E251 /* StartupTest.swift in Sources */,
C4513F972B13E338001AD760 /* PhpExtensionManagerView+Actions.swift in Sources */,
C4D3661D291173EA006BD146 /* DictionaryExtension.swift in Sources */,
C471E80D28F9BAE80021E251 /* ArrayExtension.swift in Sources */,
C471E7CD28F9BA600021E251 /* ShellProtocol.swift in Sources */,
@ -3031,6 +3154,7 @@
C485707728BF455300539B36 /* HeaderView.swift in Sources */,
C4F780B125D80B4D000DBC97 /* PhpExtension.swift in Sources */,
C4D5CFCB27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */,
C4E6840A2AF26B830023ED25 /* BrewTapFormulae.swift in Sources */,
C4068CA827B07A1300544CD5 /* SelectPreferenceView.swift in Sources */,
C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */,
C41ADCE92970CCC700120423 /* FSNotifier.swift in Sources */,
@ -3049,7 +3173,6 @@
C4AF9F7B2754499000D44ED0 /* Valet.swift in Sources */,
C4C1019C27C65C6F001FACC2 /* Process.swift in Sources */,
C451AFF72969E40F0078E617 /* HelpButton.swift in Sources */,
C490E3B129BC9FE8006D2DE6 /* ProgressViewSubject.swift in Sources */,
C47DF1B0299D5A3B0007055D /* LoginItemManager.swift in Sources */,
C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */,
C45B914A295607F400F4EC78 /* Service.swift in Sources */,
@ -3059,6 +3182,7 @@
C4B5635F276AB09000F12CCB /* VersionExtractor.swift in Sources */,
C463E381284930EE00422731 /* PresetHelper.swift in Sources */,
C441CC572AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */,
C4F520672AF03791006787F2 /* ExtensionEnumeratorTest.swift in Sources */,
C46FA98C2822F08F00D78807 /* PhpConfigurationFileTest.swift in Sources */,
C4D5576529C77CC5001A44CD /* PhpVersionManagerWindowController.swift in Sources */,
C4BF90C127C57C220054E78C /* MainMenu+FixMyValet.swift in Sources */,
@ -3069,13 +3193,14 @@
C4611E5F2AEAD2FB0010BE24 /* ConfigManagerView.swift in Sources */,
C4F780AE25D80B37000DBC97 /* PhpExtensionTest.swift in Sources */,
C456A0C72AA614BD0080144F /* PhpPreference.swift in Sources */,
C42106672AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */,
C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
C4FC21B128391F8E00D368BB /* MainMenu+Actions.swift in Sources */,
54D9E0B927E4F51E003B9AD9 /* KeyCombo.swift in Sources */,
C4EED88A27A48778006D7272 /* InterAppHandler.swift in Sources */,
C4159AF728E4D40400545349 /* RealShellTest.swift in Sources */,
C450C8C728C919EC002A2B4B /* PreferenceName.swift in Sources */,
C40D725B2A018ACC0054A067 /* PhpFormulaeStatus.swift in Sources */,
C40D725B2A018ACC0054A067 /* BusyStatus.swift in Sources */,
C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */,
C40D72602A018AE30054A067 /* BrewFormula+UI.swift in Sources */,
C485707B28BF458900539B36 /* VersionPopoverView.swift in Sources */,
@ -3089,9 +3214,11 @@
C4AFC4B429C4F43300BF4E0D /* HomebrewUpgradableTest.swift in Sources */,
C4E2E84828FC1D93003B070C /* TestableConfigurationTest.swift in Sources */,
C4D936CB27E3EE4A00BD69FE /* DomainListCellProtocol.swift in Sources */,
C4513F962B13E30C001AD760 /* BrewExtensionsObservable.swift in Sources */,
C4B97B76275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */,
C4AFC4AF29C4F32F00BF4E0D /* BrewFormula.swift in Sources */,
C4AFC4AF29C4F32F00BF4E0D /* BrewPhpFormula.swift in Sources */,
C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */,
C4415E8E2B0287E90035F520 /* BrewFormulaeObservable.swift in Sources */,
C485706D28BF450900539B36 /* NSMenuItemExtension.swift in Sources */,
C481F79726164A78004FBCFF /* PreferencesVC.swift in Sources */,
C495F5B028A42E080087F70A /* EnvironmentCheck.swift in Sources */,
@ -3106,7 +3233,6 @@
C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */,
C4E49DEE28F764A00026AC4E /* TestableCommand.swift in Sources */,
C4AF9F78275447F100D44ED0 /* ValetConfigurationTest.swift in Sources */,
C490E3B529BC9FEA006D2DE6 /* ProgressWindowView.swift in Sources */,
C4611E612AEAD3110010BE24 /* ByteLimitView.swift in Sources */,
C40175B92903108900763A68 /* ValetInteractor.swift in Sources */,
C4CE3BBC27B324250086CA49 /* ComposerWindow.swift in Sources */,
@ -3116,6 +3242,7 @@
C4080FF727BD8C6400BF2C6B /* BetterAlert.swift in Sources */,
C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */,
5489625928313231004F647A /* CreatedFromFile.swift in Sources */,
C4513F932B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */,
C471E79328F9B21F0021E251 /* ActiveFileSystem.swift in Sources */,
54D9E0B327E4F51E003B9AD9 /* HotKeysController.swift in Sources */,
C4D36616291160A1006BD146 /* WIP.swift in Sources */,
@ -3142,6 +3269,7 @@
C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */,
C456A0D22AA6179D0080144F /* BytePhpPreferenceTest.swift in Sources */,
54FCFD31276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */,
031E2B6A2B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */,
C4998F0B2617633900B2526E /* PreferencesWindowController.swift in Sources */,
C485707228BF453800539B36 /* SwiftUIHelper.swift in Sources */,
C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */,
@ -3149,6 +3277,8 @@
C44C198E276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */,
C4B79EBD29CA38DB00A483EE /* BrewCommand.swift in Sources */,
C485707828BF456300539B36 /* Warning.swift in Sources */,
033D459F2B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */,
C4513F8F2B13E2E5001AD760 /* PhpExtensionManagerWindowController.swift in Sources */,
C415938027A1B54F00D2E1B7 /* ProjectTypeDetection.swift in Sources */,
C40F505628ECA64E004AD45B /* TestableConfigurations.swift in Sources */,
C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */,
@ -3158,6 +3288,7 @@
C471E79428F9B23B0021E251 /* FileSystemProtocol.swift in Sources */,
C4F30B0B278E203C00755FCE /* MainMenu+Startup.swift in Sources */,
C485707C28BF459500539B36 /* NoWarningsView.swift in Sources */,
033D45992B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */,
C4F5FBCD28218CB8001065C5 /* Xdebug.swift in Sources */,
C40B24F227A310770018C7D2 /* Events.swift in Sources */,
C490E3B829BCA367006D2DE6 /* App+BrewWatch.swift in Sources */,
@ -3188,16 +3319,17 @@
C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */,
C4D9F24C280B69E100DCD39A /* AddProxyVC.swift in Sources */,
C4D4CB3829C109CF00DB9F93 /* InternalSwitcher+Valet.swift in Sources */,
C4B79EB729CA387F00A483EE /* BrewFormulaeHandler.swift in Sources */,
C4B79EB729CA387F00A483EE /* BrewPhpFormulaeHandler.swift in Sources */,
C4B5853F2770FE3900DA4FBE /* Paths.swift in Sources */,
C481F79A26164A7C004FBCFF /* Preferences.swift in Sources */,
C4513F992B13E338001AD760 /* PhpExtensionManagerView+Actions.swift in Sources */,
C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */,
C4A81CA528C67101008DD9D1 /* PMTableView.swift in Sources */,
C45E76152854A65300B4FE0C /* ServicesManager.swift in Sources */,
C4D36602291132B7006BD146 /* ValetScanners.swift in Sources */,
C40934AB298EEDA900D25014 /* CaskFileParserTest.swift in Sources */,
C436B39E29F3C42500B6A64E /* PreferencesTabs.swift in Sources */,
C43BCD4529FBEF40001547BC /* InstallAndUpgradeCommand.swift in Sources */,
C43BCD4529FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */,
C4551657297AED18009B8466 /* ValetRcTest.swift in Sources */,
C464ADAD275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */,
C40C7F1F2772136000DDDCDC /* PhpEnvironments.swift in Sources */,
@ -3247,6 +3379,7 @@
C4FC8D442A4981BC00FBBD16 /* nl */,
C4DD662A2A4A1B4E00D6A731 /* de */,
C4622F572A7593CB0016F8FB /* pt-PT */,
0336CAAF2B0D0CDA009A1034 /* fr */,
);
name = Localizable.strings;
sourceTree = "<group>";
@ -3394,6 +3527,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@ -3458,6 +3592,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@ -3520,7 +3655,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1346;
CURRENT_PROJECT_VERSION = 1422;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -3533,7 +3668,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 6.2.1;
MARKETING_VERSION = 7.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -3551,7 +3686,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1346;
CURRENT_PROJECT_VERSION = 1422;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -3564,7 +3699,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 6.2.1;
MARKETING_VERSION = 7.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -3729,6 +3864,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@ -3791,7 +3927,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1346;
CURRENT_PROJECT_VERSION = 1422;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -3804,7 +3940,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 6.2.1;
MARKETING_VERSION = 7.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) DEV";
@ -3838,6 +3974,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@ -3907,7 +4044,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1346;
CURRENT_PROJECT_VERSION = 1422;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -3920,7 +4057,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 6.2.1;
MARKETING_VERSION = 7.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) DEV";
@ -3954,6 +4091,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@ -4023,7 +4161,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1346;
CURRENT_PROJECT_VERSION = 1422;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -4036,7 +4174,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 6.2.1;
MARKETING_VERSION = 7.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.eap;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) EAP";
@ -4142,6 +4280,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@ -4204,7 +4343,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1346;
CURRENT_PROJECT_VERSION = 1422;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -4217,7 +4356,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 6.2.1;
MARKETING_VERSION = 7.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.eap;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) EAP";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -112,13 +112,15 @@ All stable and supported PHP versions are also supported by PHP Monitor. However
Backports that are installable via PHP Monitor's **PHP Version Manager** functionality are subject to availability via [this tap](https://github.com/shivammathur/homebrew-php).
PHP extensions that are installable via PHP Monitor's **PHP Extension Manager** functionality are subject to availability via [this tap](https://github.com/shivammathur/homebrew-extensions).
For maximum compatibility with older PHP versions, you may wish to keep using Valet 2 or 3. For more information, please see [SECURITY.md](./SECURITY.md) to find out which versions of PHP are supported with different versions of Valet.
</details>
<details>
<summary><strong>How do I install additional versions of PHP, including legacy versions?</strong></summary>
Assuming you have installed the `php` formula, the latest stable version of PHP is installed. At the time of writing, this is PHP 8.2.
Assuming you have installed the `php` formula, the latest stable version of PHP is installed. At the time of writing, this is PHP 8.3.
You can install other supported versions of PHP via PHP Monitor's **PHP Version Manager**. (You can manually install or upgrade PHP versions too, but this is not recommended.)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 KiB

After

Width:  |  Height:  |  Size: 723 KiB

View File

@ -14,8 +14,6 @@ class Actions {
public static func linkPhp() async {
await brew("link php --overwrite --force")
// TODO: Verify that this worked, if not, notify the user
}
public static func restartPhpFpm() async {

View File

@ -18,6 +18,20 @@ struct Constants {
*/
static let MinimumRecommendedValetVersion = "2.16.2"
/**
PHP Monitor supplies a hardcoded list of PHP packages in its own
PHP Version Manager.
This hardcoded list will expire and will need to be modified when
the cutoff date occurs, which is when the `php` formula will
become PHP 8.4, and a new build will need to be made.
If users launch an older version of the app, then a warning
will be displayed to let them know that certain operations
will not work correctly and that they need to update their app.
*/
static let PhpFormulaeCutoffDate = "2024-11-01"
/**
* The PHP versions that are considered pre-release versions.
* Past a certain date, an experimental version "graduates"
@ -25,8 +39,7 @@ struct Constants {
*/
static var ExperimentalPhpVersions: Set<String> {
let releaseDates = [
"8.4": Date.fromString("2024-12-01"), // PLACEHOLDER DATE
"8.3": Date.fromString("2023-11-23") // OFFICIAL RELEASE
"8.4": Date.fromString("2024-12-01") // PLACEHOLDER DATE
]
return Set(releaseDates
@ -48,8 +61,7 @@ struct Constants {
static let DetectedPhpVersions: Set = [
"5.6",
"7.0", "7.1", "7.2", "7.3", "7.4",
"8.0", "8.1", "8.2",
"8.3",
"8.0", "8.1", "8.2", "8.3",
"8.4"
]

View File

@ -102,6 +102,10 @@ public class Paths {
return "\(shared.baseDir.rawValue)/etc"
}
public static var tapPath: String {
return "\(shared.baseDir.rawValue)/Library/Taps"
}
public static var caskroomPath: String {
return "\(shared.baseDir.rawValue)/Caskroom/"
+ (App.identifier.contains(".dev") ? "phpmon-dev" : "phpmon")

View File

@ -8,6 +8,18 @@ import Foundation
import SwiftUI
struct Localization {
static var preferredLanguage: String? {
guard let language = Preferences.preferences[.languageOverride] as? String else {
return nil
}
if language.isEmpty {
return nil
}
return language
}
static var bundle: Bundle = {
if !isRunningTests {
return Bundle.main
@ -32,7 +44,15 @@ struct Localization {
extension String {
var localized: String {
let string = NSLocalizedString(self, tableName: nil, bundle: Localization.bundle, value: "", comment: "")
var preferredBundle: Bundle = Localization.bundle
if let preferred = Localization.preferredLanguage,
let path = Localization.bundle.path(forResource: preferred, ofType: "lproj"),
let bundle = Bundle(path: path) {
preferredBundle = bundle
}
let string = NSLocalizedString(self, tableName: nil, bundle: preferredBundle, value: "", comment: "")
// Fallback to English translation if the localized value is equal to the key (should not happen)
if string == self {

View File

@ -64,7 +64,7 @@ class RealFileSystem: FileSystemProtocol {
// MARK: FS Attributes
func makeExecutable(_ path: String) throws {
_ = system("chmod +x \(path.replacingTildeWithHomeDirectory)")
_ = ActiveShell.shared.sync("chmod +x \(path.replacingTildeWithHomeDirectory)")
}
// MARK: - Checks

View File

@ -75,7 +75,7 @@ class MenuBarImageGenerator {
// Then we'll fetch the image we want on the left
var iconType = Preferences.preferences[.iconTypeToDisplay] as? String
if iconType == nil {
if iconType == nil || !MenuBarIcon.allCases.map({ $0.rawValue }).contains(iconType) {
Log.warn("Invalid icon type found, using the default")
iconType = MenuBarIcon.iconPhp.rawValue
}

View File

@ -10,7 +10,6 @@ import Foundation
/**
Run a simple blocking Shell command on the user's own system.
Avoid using this method in favor of the fakeable Shell class unless needed for express system operations.
*/
public func system(_ command: String) -> String {
let task = Process()

View File

@ -62,13 +62,6 @@ class ActivePhpInstallation {
return
}
// Load extension information
let mainConfigurationFileUrl = URL(fileURLWithPath: "\(Paths.etcPath)/php/\(version.short)/php.ini")
if let file = PhpConfigurationFile.from(filePath: mainConfigurationFileUrl.path) {
iniFiles.append(file)
}
// Get configuration values
limits = Limits(
memory_limit: getByteCount(key: "memory_limit"),
@ -76,15 +69,10 @@ class ActivePhpInstallation {
post_max_size: getByteCount(key: "post_max_size")
)
// Return a list of .ini files parsed after php.ini
let paths = Command.execute(
path: Paths.php,
arguments: ["-r", "echo php_ini_scanned_files();"],
trimNewlines: false
)
.replacingOccurrences(of: "\n", with: "")
.split(separator: ",")
.map { String($0) }
let paths = ActiveShell.shared
.sync("\(Paths.php) --ini | grep -E -o '(/[^ ]+\\.ini)'").out
.split(separator: "\n")
.map { String($0) }
// See if any extensions are present in said .ini files
paths.forEach { (iniFilePath) in

View File

@ -29,7 +29,7 @@ class PhpEnvironments {
/**
Determine which PHP version the `php` formula is aliased to.
*/
func determinePhpAlias() async {
@MainActor func determinePhpAlias() async {
let brewPhpAlias = await Shell.pipe("\(Paths.brew) info php --json").out
self.homebrewPackage = try! JSONDecoder().decode(
@ -37,7 +37,28 @@ class PhpEnvironments {
from: brewPhpAlias.data(using: .utf8)!
).first!
Log.info("[BREW] On your system, the `php` formula means version \(homebrewPackage.version)!")
PhpEnvironments.brewPhpAlias = self.homebrewPackage.version
Log.info("[BREW] On your system, the `php` formula means version \(homebrewPackage.version).")
// Check if that version actually corresponds to an older version
let phpConfigExecutablePath = "\(Paths.optPath)/php/bin/php-config"
if FileSystem.fileExists(phpConfigExecutablePath) {
let longVersionString = Command.execute(
path: phpConfigExecutablePath,
arguments: ["--version"],
trimNewlines: false
).trimmingCharacters(in: .whitespacesAndNewlines)
if let version = try? VersionNumber.parse(longVersionString) {
PhpEnvironments.brewPhpAlias = version.short
if version.short != homebrewPackage.version {
Log.info("[BREW] An older version of `php` is actually installed (\(version.short)).")
}
} else {
Log.warn("Could not determine the actual version of the php binary; assuming Homebrew is correct.")
PhpEnvironments.brewPhpAlias = homebrewPackage.version
}
}
}
// MARK: - Properties
@ -66,7 +87,14 @@ class PhpEnvironments {
var cachedPhpInstallations: [String: PhpInstallation] = [:]
/** Information about the currently linked PHP installation. */
var currentInstall: ActivePhpInstallation?
var currentInstall: ActivePhpInstallation? {
didSet {
// Let the PHP extension manager, if it exists, know the version changed
if let version = currentInstall?.version.short {
App.shared.phpExtensionManagerWindowController?.view?.manager.phpVersion = version
}
}
}
/**
The version that the `php` formula via Brew is aliased to on the current system.
@ -77,7 +105,12 @@ class PhpEnvironments {
As such, we take that information from Homebrew.
*/
static var brewPhpAlias: String {
static var brewPhpAlias: String = ""
/**
It's possible for the alias to be newer than the actual installed version of PHP.
*/
static var homebrewBrewPhpAlias: String {
if PhpEnvironments.shared.homebrewPackage == nil { return "8.2" }
return PhpEnvironments.shared.homebrewPackage.version
@ -144,7 +177,12 @@ class PhpEnvironments {
// Avoid inserting a duplicate
if !supportedVersions.contains(phpAlias) && FileSystem.fileExists("\(Paths.optPath)/php/bin/php") {
supportedVersions.insert(phpAlias)
let phpAliasInstall = PhpInstallation(phpAlias)
// Before inserting, ensure that the actual output matches the alias
// if that isn't the case, our formula remains out-of-date
if !phpAliasInstall.isMissingBinary {
supportedVersions.insert(phpAlias)
}
}
availablePhpVersions = Array(supportedVersions)

View File

@ -12,19 +12,36 @@ class PhpInstallation {
var versionNumber: VersionNumber
var iniFiles: [PhpConfigurationFile] = []
var isMissingBinary: Bool = false
var isHealthy: Bool = true
var extensions: [PhpExtension] {
return self.iniFiles.flatMap({ $0.extensions })
}
/**
In order to determine details about a PHP installation,
well simply run `php-config --version` in the relevant directory.
*/
init(_ version: String) {
let phpConfigExecutablePath = "\(Paths.optPath)/php@\(version)/bin/php-config"
let phpConfigExecutablePath = "\(Paths.optPath)/php@\(version)/bin/php-config",
phpExecutablePath = "\(Paths.optPath)/php@\(version)/bin/php"
let phpExecutablePath = "\(Paths.optPath)/php@\(version)/bin/php"
versionNumber = VersionNumber.make(from: version)!
self.versionNumber = VersionNumber.make(from: version)!
determineVersion(phpConfigExecutablePath, phpExecutablePath)
determineHealth(phpExecutablePath)
determineIniFiles(phpExecutablePath)
// Find all enabled extensions
let enabled = self.extensions.filter({ $0.enabled }).map({ $0.name })
Log.info("PHP \(versionNumber.short) has the following extensions enabled: \(enabled)")
}
private func determineVersion(_ phpConfigExecutablePath: String, _ phpExecutablePath: String) {
if FileSystem.fileExists(phpConfigExecutablePath) {
let longVersionString = Command.execute(
path: phpConfigExecutablePath,
@ -34,9 +51,15 @@ class PhpInstallation {
// The parser should always work, or the string has to be very unusual.
// If so, the app SHOULD crash, so that the users report what's up.
self.versionNumber = try! VersionNumber.parse(longVersionString)
versionNumber = try! VersionNumber.parse(longVersionString)
} else {
// Keep track that the `php-config` binary is missing; this often means there's a mismatch between
// the `php` version alias and the actual installed version (e.g. you haven't upgraded `php`)
isMissingBinary = true
}
}
private func determineHealth(_ phpExecutablePath: String) {
if FileSystem.fileExists(phpExecutablePath) {
let testCommand = Command.execute(
path: phpExecutablePath,
@ -53,4 +76,18 @@ class PhpInstallation {
}
}
}
private func determineIniFiles(_ phpExecutablePath: String) {
let paths = ActiveShell.shared
.sync("\(phpExecutablePath) --ini | grep -E -o '(/[^ ]+\\.ini)'").out
.split(separator: "\n")
.map { String($0) }
// See if any extensions are present in said .ini files
paths.forEach { (iniFilePath) in
if let file = PhpConfigurationFile.from(filePath: iniFilePath) {
iniFiles.append(file)
}
}
}
}

View File

@ -27,7 +27,7 @@ extension InternalSwitcher {
return corrections.contains(true)
}
// MARK: - PHP FPM pool
// MARK: - Corrections
public func disableDefaultPhpFpmPool(_ version: String) async -> FixApplied {
let pool = "\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf"
@ -54,37 +54,7 @@ extension InternalSwitcher {
return false
}
func getExpectedConfigurationFiles(for version: String) -> [ExpectedConfigurationFile] {
return [
ExpectedConfigurationFile(
destination: "/php-fpm.d/valet-fpm.conf",
source: "/cli/stubs/etc-phpfpm-valet.conf",
replacements: [
"VALET_USER": Paths.whoami,
"VALET_HOME_PATH": "~/.config/valet".replacingTildeWithHomeDirectory,
"valet.sock": "valet\(version.replacingOccurrences(of: ".", with: "")).sock"
],
applies: { Valet.shared.version!.major > 2 }
),
ExpectedConfigurationFile(
destination: "/conf.d/error_log.ini",
source: "/cli/stubs/etc-phpfpm-error_log.ini",
replacements: [
"VALET_USER": Paths.whoami,
"VALET_HOME_PATH": "~/.config/valet".replacingTildeWithHomeDirectory
],
applies: { return true }
),
ExpectedConfigurationFile(
destination: "/conf.d/php-memory-limits.ini",
source: "/cli/stubs/php-memory-limits.ini",
replacements: [:],
applies: { return true }
)
]
}
func ensureConfigurationFilesExist(_ version: String) async -> FixApplied {
public func ensureConfigurationFilesExist(_ version: String) async -> FixApplied {
let files = self.getExpectedConfigurationFiles(for: version)
// For each of the files, attempt to fix anything that is wrong
@ -124,6 +94,38 @@ extension InternalSwitcher {
return outcomes.contains(true)
}
// MARK: - Internals
private func getExpectedConfigurationFiles(for version: String) -> [ExpectedConfigurationFile] {
return [
ExpectedConfigurationFile(
destination: "/php-fpm.d/valet-fpm.conf",
source: "/cli/stubs/etc-phpfpm-valet.conf",
replacements: [
"VALET_USER": Paths.whoami,
"VALET_HOME_PATH": "~/.config/valet".replacingTildeWithHomeDirectory,
"valet.sock": "valet\(version.replacingOccurrences(of: ".", with: "")).sock"
],
applies: { Valet.shared.version!.major > 2 }
),
ExpectedConfigurationFile(
destination: "/conf.d/error_log.ini",
source: "/cli/stubs/etc-phpfpm-error_log.ini",
replacements: [
"VALET_USER": Paths.whoami,
"VALET_HOME_PATH": "~/.config/valet".replacingTildeWithHomeDirectory
],
applies: { return true }
),
ExpectedConfigurationFile(
destination: "/conf.d/php-memory-limits.ini",
source: "/cli/stubs/php-memory-limits.ini",
replacements: [:],
applies: { return true }
)
]
}
}
public struct ExpectedConfigurationFile {

View File

@ -86,14 +86,37 @@ class RealShell: ShellProtocol {
// MARK: - Shellable Protocol
func sync(_ command: String) -> ShellOutput {
let task = getShellProcess(for: command)
let outputPipe = Pipe()
let errorPipe = Pipe()
if ProcessInfo.processInfo.environment["SLOW_SHELL_MODE"] != nil {
sleep(3)
}
task.standardOutput = outputPipe
task.standardError = errorPipe
task.launch()
task.waitUntilExit()
let stdOut = String(data: outputPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)!
let stdErr = String(data: errorPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)!
if Log.shared.verbosity == .cli {
log(task: task, stdOut: stdOut, stdErr: stdErr)
}
return .out(stdOut, stdErr)
}
func pipe(_ command: String) async -> ShellOutput {
let task = getShellProcess(for: command)
let outputPipe = Pipe()
let errorPipe = Pipe()
// Seriously slow down how long it takes for the shell to return output
// (in order to debug or identify async issues)
if ProcessInfo.processInfo.environment["SLOW_SHELL_MODE"] != nil {
Log.info("[SLOW SHELL] \(command)")
await delay(seconds: 3.0)
@ -104,20 +127,20 @@ class RealShell: ShellProtocol {
task.launch()
task.waitUntilExit()
let stdOut = String(
data: outputPipe.fileHandleForReading.readDataToEndOfFile(),
encoding: .utf8
)!
let stdErr = String(
data: errorPipe.fileHandleForReading.readDataToEndOfFile(),
encoding: .utf8
)!
let stdOut = String(data: outputPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)!
let stdErr = String(data: errorPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)!
if Log.shared.verbosity == .cli {
var args = task.arguments ?? []
let last = "\"" + (args.popLast() ?? "") + "\""
var log = """
log(task: task, stdOut: stdOut, stdErr: stdErr)
}
return .out(stdOut, stdErr)
}
private func log(task: Process, stdOut: String, stdErr: String) {
var args = task.arguments ?? []
let last = "\"" + (args.popLast() ?? "") + "\""
var log = """
<~~~~~~~~~~~~~~~~~~~~~~~
$ \(([self.launchPath] + args + [last]).joined(separator: " "))
@ -126,22 +149,19 @@ class RealShell: ShellProtocol {
\(stdOut)
"""
if !stdErr.isEmpty {
log.append("""
if !stdErr.isEmpty {
log.append("""
[ERR]:
\(stdErr)
""")
}
}
log.append("""
log.append("""
~~~~~~~~~~~~~~~~~~~~~~~~>
""")
Log.info(log)
}
return .out(stdOut, stdErr)
Log.info(log)
}
func quiet(_ command: String) async {

View File

@ -14,6 +14,16 @@ protocol ShellProtocol {
*/
var PATH: String { get }
/**
Run a command synchronously. Use with caution.
Common usage:
```
let output = Shell.sync("php -v")
```
*/
func sync(_ command: String) -> ShellOutput
/**
Run a command asynchronously.
Returns the most relevant output (prefers error output if it exists).

View File

@ -1,5 +1,5 @@
//
// PhpFormulaeStatus.swift
// BusyStatus.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 02/05/2023.
@ -8,7 +8,7 @@
import Foundation
class PhpFormulaeStatus: ObservableObject {
class BusyStatus: ObservableObject {
@Published var busy: Bool
@Published var title: String
@Published var description: String
@ -18,4 +18,12 @@ class PhpFormulaeStatus: ObservableObject {
self.title = title
self.description = description
}
public static func notBusy() -> BusyStatus {
return BusyStatus(busy: false, title: "", description: "")
}
public static func busy() -> BusyStatus {
return BusyStatus(busy: false, title: "", description: "")
}
}

View File

@ -73,12 +73,26 @@ public struct TestableConfiguration: Codable {
: .fake(.text)
]) { (_, new) in new }
self.commandOutput["/opt/homebrew/opt/php@\(version.short)/bin/php-config --version"]
= version.long
// PHP configuration files
self.shellOutput["/opt/homebrew/opt/php@\(version.short)/bin/php --ini | grep -E -o '(/[^ ]+\\.ini)'"] =
.instant("/opt/homebrew/etc/php/\(version.short)/conf.d/php-memory-limits.ini")
// PHP Homebrew operations
self.shellOutput["/opt/homebrew/bin/brew unlink php@\(version.short)"] = .delayed(0.2, "OK")
self.shellOutput["sudo /opt/homebrew/bin/brew services stop php@\(version.short)"] = .delayed(0.2, "OK")
self.shellOutput["sudo /opt/homebrew/bin/brew services start php@\(version.short)"] = .delayed(0.2, "OK")
self.shellOutput["/opt/homebrew/bin/brew link php@\(version.short) --overwrite --force"] = .delayed(0.2, "OK")
// PHP version output
self.commandOutput["/opt/homebrew/opt/php@\(version.short)/bin/php-config --version"] = version.long
self.commandOutput["/opt/homebrew/opt/php@\(version.short)/bin/php -v"] = "OK"
if primary {
self.shellOutput["ls /opt/homebrew/opt | grep php"]
= .instant("php")
// Files expected to be present for currently linked PHP version
self.shellOutput["ls /opt/homebrew/opt | grep php"] =
.instant("php")
self.shellOutput["/opt/homebrew/bin/php --ini | grep -E -o '(/[^ ]+\\.ini)'"] =
.instant("/opt/homebrew/etc/php/\(version.short)/conf.d/php-memory-limits.ini")
self.filesystem["/opt/homebrew/opt/php"]
= .fake(.symlink, "/opt/homebrew/Cellar/php/\(version.long)")
self.filesystem["/opt/homebrew/opt/php/bin/php"]
@ -89,12 +103,8 @@ public struct TestableConfiguration: Codable {
= .fake(.symlink, "/opt/homebrew/Cellar/php/\(version.short)/bin/php-config")
self.commandOutput["/opt/homebrew/bin/php-config --version"]
= version.long
self.commandOutput["/opt/homebrew/bin/php -r echo php_ini_scanned_files();"] =
"""
/opt/homebrew/etc/php/\(version.short)/conf.d/php-memory-limits.ini,
"""
} else {
// Output expected to be present for non-linked PHP versions
self.shellOutput["ls /opt/homebrew/opt | grep php@"] =
BatchFakeShellOutput.instant(
self.secondaryPhpVersions
@ -103,6 +113,7 @@ public struct TestableConfiguration: Codable {
)
}
}
// swiftlint:enable function_body_length
// MARK: Interactions

View File

@ -19,6 +19,17 @@ public class TestableShell: ShellProtocol {
var expectations: [String: BatchFakeShellOutput] = [:]
func sync(_ command: String) -> ShellOutput {
// This assertion will only fire during test builds
assert(expectations.keys.contains(command), "No response declared for command: \(command)")
guard let expectation = expectations[command] else {
return .err("No Expected Output")
}
return expectation.syncOutput()
}
func quiet(_ command: String) async {
_ = try! await self.attach(command, didReceiveOutput: { _, _ in }, withTimeout: 60)
}
@ -112,6 +123,29 @@ struct BatchFakeShellOutput: Codable {
return output
}
/**
Outputs the fake shell output as expected, but does this synchronously.
*/
public func syncOutput(
ignoreDelay: Bool = false
) -> ShellOutput {
let output = ShellOutput.empty()
for item in items {
if !ignoreDelay {
Thread.sleep(forTimeInterval: item.delay)
}
if item.stream == .stdErr {
output.err += item.output
} else if item.stream == .stdOut {
output.out += item.output
}
}
return output
}
/**
For testing purposes (and speed) we may omit the delay, regardless of its timespan.
*/

View File

@ -83,6 +83,9 @@ class App {
/** The window controller of the PHP version manager window. */
var phpVersionManagerWindowController: PhpVersionManagerWindowController?
/** The window controller of the PHP extension manager window. */
var phpExtensionManagerWindowController: PhpExtensionManagerWindowController?
/** List of detected (installed) applications that PHP Monitor can work with. */
var detectedApplications: [Application] = []

View File

@ -109,6 +109,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
static func initializeTestingProfile(_ path: String) {
Log.info("The configuration with path `\(path)` is being requested...")
// Clear for PHP Guard
Stats.clearCurrentGlobalPhpVersion()
// Load the configuration file
TestableConfiguration.loadFrom(path: path).apply()
}

View File

@ -142,7 +142,7 @@ class Startup {
return await Shell.pipe("\(Paths.binPath)/php -v").err
.contains("Library not loaded")
},
name: "`no dyld issue detected",
name: "no `dyld` issue (`Library not loaded`) detected",
titleText: "startup.errors.dyld_library.title".localized,
subtitleText: "startup.errors.dyld_library.subtitle".localized(
Paths.optPath

View File

@ -8,16 +8,6 @@
import Foundation
class BrewFormulaeObservable: ObservableObject {
@Published var phpVersions: [BrewFormula] = []
var upgradeable: [BrewFormula] {
return phpVersions.filter { formula in
formula.hasUpgrade
}
}
}
class Brew {
static let shared = Brew()
@ -46,10 +36,10 @@ class Brew {
/// Each formula for each PHP version that can be installed.
public static let phpVersionFormulae = [
"8.4": "shivammathur/php/php@8.4",
"8.3": "shivammathur/php/php@8.3", // TODO: when php@8.3 lands in stable, update this
"8.3": "php@8.3",
"8.2": "php@8.2",
"8.1": "php@8.1",
"8.0": "php@8.0",
"8.0": "shivammathur/php/php@8.0",
"7.4": "shivammathur/php/php@7.4",
"7.3": "shivammathur/php/php@7.3",
"7.2": "shivammathur/php/php@7.2",

View File

@ -27,6 +27,21 @@ class BrewDiagnostics {
}
}
/**
Logs a bunch of useful information during startup.
*/
public static func logBootInformation() {
Log.info(BrewDiagnostics.customCaskInstalled
? "[BREW] The app has been installed via Homebrew Cask."
: "[BREW] The app has been installed directly (optimal)."
)
Log.info(BrewDiagnostics.usesNginxFullFormula
? "[BREW] The app will be using the `nginx-full` formula."
: "[BREW] The app will be using the `nginx` formula."
)
}
/**
Determines whether the PHP Monitor Cask is installed.
*/
@ -46,6 +61,43 @@ class BrewDiagnostics {
return destination.contains("/nginx-full/")
}()
/**
It is possible to have outdated symlinks for PHP installations. This can mean that certain PHP installations
are going to be reported incorrectly (e.g. `php@8.2` links to an installation in a `8.3` folder after an upgrade).
To ensure this does not cause issues, PHP Monitor will automatically remove all incorrect PHP symlinks.
*/
public static func checkForOutdatedPhpInstallationSymlinks() async {
// Set up a regular expression
let regex = try! NSRegularExpression(pattern: "^php@[0-9]+\\.[0-9]+$", options: .caseInsensitive)
// Check for incorrect versions
if let contents = try? FileSystem.getShallowContentsOfDirectory("\(Paths.optPath)")
.filter({
let range = NSRange($0.startIndex..., in: $0)
return regex.firstMatch(in: $0, options: [], range: range) != nil
}) {
for symlink in contents {
let version = symlink.replacingOccurrences(of: "php@", with: "")
if let destination = try? FileSystem.getDestinationOfSymlink("\(Paths.optPath)/\(symlink)") {
if !destination.contains("Cellar/php/\(version)")
&& !destination.contains("Cellar/php@\(version)") {
Log.err("Symlink for \(symlink) is incorrect. Removing...")
do {
try FileSystem.remove("\(Paths.optPath)/\(symlink)")
Log.info("Incorrect symlink for \(symlink) has been successfully removed.")
} catch {
Log.err("Symlink for \(symlink) was incorrect but could not be removed!")
}
}
} else {
Log.warn("Could not read symlink at: \(Paths.optPath)/\(symlink)! Symlink check skipped.")
}
}
}
}
/**
It is possible to have the `shivammathur/php` tap installed, and for the core homebrew information to be outdated.
This will then result in two different aliases claiming to point to the same formula (`php`).

View File

@ -0,0 +1,98 @@
//
// BrewPhpExtension.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 27/11/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
struct BrewPhpExtension: Hashable, Comparable {
let name: String
let phpVersion: String
let isInstalled: Bool
let path: String
let dependencies: [String]
var extensionDependencies: [String] {
return dependencies
.filter {
$0.contains("shivammathur/extensions/") && $0.contains("@\(phpVersion)")
}
.map {
$0.replacingOccurrences(of: "shivammathur/extensions/", with: "")
.replacingOccurrences(of: "@\(phpVersion)", with: "")
}
}
var formulaName: String {
return "\(name)@\(phpVersion)"
}
init(path: String, name: String, phpVersion: String) {
self.path = path
self.name = name
self.phpVersion = phpVersion
self.isInstalled = BrewPhpExtension.hasInstallationReceipt(
for: "\(name)@\(phpVersion)"
)
self.dependencies = BrewPhpExtension.extractDependencies(from: path)
}
var hasAlternativeInstall: Bool {
guard let php = PhpEnvironments.shared.cachedPhpInstallations[self.phpVersion] else {
return false
}
let alreadyDiscovered = php.extensions.contains(where: { $0.name == self.name })
return alreadyDiscovered && !isInstalled
}
internal func firstDependent(in exts: [BrewPhpExtension]) -> BrewPhpExtension? {
return exts
.filter({ $0.isInstalled })
.first { $0.dependencies.contains("shivammathur/extensions/\(self.formulaName)") }
}
static func hasInstallationReceipt(for formulaName: String) -> Bool {
return FileSystem.fileExists("\(Paths.optPath)/\(formulaName)/INSTALL_RECEIPT.json")
}
static func < (lhs: BrewPhpExtension, rhs: BrewPhpExtension) -> Bool {
return lhs.name < rhs.name
}
static func == (lhs: BrewPhpExtension, rhs: BrewPhpExtension) -> Bool {
return lhs.name == rhs.name
}
private static func extractDependencies(from path: String) -> [String] {
let regexPattern = #"depends_on "(.*)""#
var dependencies: [String] = []
guard let content = try? FileSystem.getStringFromFile(path) else {
return []
}
do {
let regex = try NSRegularExpression(pattern: regexPattern, options: [])
let range = NSRange(content.startIndex..<content.endIndex, in: content)
let matches = regex.matches(in: content, options: [], range: range)
for match in matches {
if let range = Range(match.range(at: 1), in: content) {
let dependencyName = String(content[range])
dependencies.append(dependencyName)
}
}
} catch {
return []
}
return dependencies
}
}

View File

@ -1,5 +1,5 @@
//
// BrewFormula.swift
// BrewPhpFormula.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 17/03/2023.
@ -8,7 +8,7 @@
import Foundation
struct BrewFormula {
struct BrewPhpFormula: Equatable {
/// Name of the formula.
let name: String
@ -21,6 +21,8 @@ struct BrewFormula {
/// The upgrade that is currently available, if it exists.
let upgradeVersion: String?
// TODO: A rebuild attribute could be checked, to check if a Tap update exists for a pre-release version
/// Whether this formula is a stable version of PHP.
let prerelease: Bool
@ -48,6 +50,25 @@ struct BrewFormula {
return upgradeVersion != nil
}
/// Whether this formula alias is different.
var hasUpgradedFormulaAlias: Bool {
return self.shortVersion == PhpEnvironments.homebrewBrewPhpAlias
&& PhpEnvironments.homebrewBrewPhpAlias != PhpEnvironments.brewPhpAlias
}
var unavailableAfterUpgrade: Bool {
if installedVersion == nil || upgradeVersion == nil {
return false
}
if let installed = try? VersionNumber.parse(self.installedVersion!),
let upgrade = try? VersionNumber.parse(self.upgradeVersion!) {
return upgrade.short != installed.short
}
return false
}
/// The associated Homebrew folder with this PHP formula.
var homebrewFolder: String {
let resolved = name
@ -60,7 +81,7 @@ struct BrewFormula {
/// The short version associated with this formula, if installed.
var shortVersion: String? {
guard let version = self.installedVersion else {
return nil
return self.displayName.replacingOccurrences(of: "PHP ", with: "")
}
return VersionNumber.make(from: version)?.short ?? nil
@ -81,6 +102,7 @@ struct BrewFormula {
return nil
}
return PhpEnvironments.shared.cachedPhpInstallations[shortVersion]?.isHealthy ?? nil
return PhpEnvironments.shared.cachedPhpInstallations[shortVersion]?
.isHealthy ?? nil
}
}

View File

@ -8,22 +8,23 @@
import Foundation
protocol HandlesBrewFormulae {
func loadPhpVersions(loadOutdated: Bool) async -> [BrewFormula]
protocol HandlesBrewPhpFormulae {
func loadPhpVersions(loadOutdated: Bool) async -> [BrewPhpFormula]
func refreshPhpVersions(loadOutdated: Bool) async
}
extension HandlesBrewFormulae {
extension HandlesBrewPhpFormulae {
public func refreshPhpVersions(loadOutdated: Bool) async {
let items = await loadPhpVersions(loadOutdated: loadOutdated)
Task { @MainActor in
await PhpEnvironments.shared.determinePhpAlias()
Brew.shared.formulae.phpVersions = items
}
}
}
class BrewFormulaeHandler: HandlesBrewFormulae {
public func loadPhpVersions(loadOutdated: Bool) async -> [BrewFormula] {
class BrewPhpFormulaeHandler: HandlesBrewPhpFormulae {
public func loadPhpVersions(loadOutdated: Bool) async -> [BrewPhpFormula] {
var outdated: [OutdatedFormula]?
if loadOutdated {
@ -43,7 +44,8 @@ class BrewFormulaeHandler: HandlesBrewFormulae {
}
return Brew.phpVersionFormulae.map { (version, formula) in
let fullVersion = PhpEnvironments.shared.cachedPhpInstallations[version]?.versionNumber.text
let fullVersion = PhpEnvironments.shared.cachedPhpInstallations[version]?
.versionNumber.text
var upgradeVersion: String?
@ -53,7 +55,7 @@ class BrewFormulaeHandler: HandlesBrewFormulae {
})?.current_version
}
return BrewFormula(
return BrewPhpFormula(
name: formula,
displayName: "PHP \(version)",
installedVersion: fullVersion,

View File

@ -0,0 +1,53 @@
//
// BrewTapFormulae.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 01/11/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
class BrewTapFormulae {
public static func from(tap: String) -> [String: [BrewPhpExtension]] {
let directory = "\(Paths.tapPath)/\(tap)/Formula"
let files = try? FileSystem.getShallowContentsOfDirectory(directory)
var availableExtensions = [String: [BrewPhpExtension]]()
guard let files = files else {
return availableExtensions
}
let regex = try! NSRegularExpression(pattern: "(\\w+)@(\\d+\\.\\d+)\\.rb")
for file in files {
let matches = regex.matches(in: file, range: NSRange(file.startIndex..., in: file))
if let match = matches.first {
if let phpExtensionRange = Range(match.range(at: 1), in: file),
let versionRange = Range(match.range(at: 2), in: file) {
// Determine what the extension's name is
let phpExtensionName = String(file[phpExtensionRange])
// Determine what PHP version this is for
let phpVersion = String(file[versionRange])
// Create a new BrewPhpExtension object (determines if installed)
let phpExtension = BrewPhpExtension(
path: "\(Paths.tapPath)/\(tap)/Formula/\(file)",
name: phpExtensionName,
phpVersion: phpVersion
)
// Append the extension to the list
var extensions = availableExtensions[phpVersion, default: []]
extensions.append(phpExtension)
availableExtensions[phpVersion] = extensions.sorted()
}
}
}
return availableExtensions
}
}

View File

@ -10,6 +10,8 @@ import Foundation
protocol BrewCommand {
func execute(onProgress: @escaping (BrewCommandProgress) -> Void) async throws
func getCommandTitle() -> String
}
extension BrewCommand {
@ -31,6 +33,44 @@ extension BrewCommand {
}
return nil
}
internal func run(_ command: String, _ onProgress: @escaping (BrewCommandProgress) -> Void) async throws {
var loggedMessages: [String] = []
let (process, _) = try! await Shell.attach(
command,
didReceiveOutput: { text, _ in
if !text.isEmpty {
Log.perf(text)
loggedMessages.append(text)
}
if let (number, text) = self.reportInstallationProgress(text) {
onProgress(.create(value: number, title: getCommandTitle(), description: text))
}
},
withTimeout: .minutes(15)
)
if process.terminationStatus <= 0 {
loggedMessages = []
return
} else {
throw BrewCommandError(error: "The command failed to run correctly.", log: loggedMessages)
}
}
internal func checkPhpTap(_ onProgress: @escaping (BrewCommandProgress) -> Void) async throws {
if !BrewDiagnostics.installedTaps.contains("shivammathur/php") {
let command = "brew tap shivammathur/php"
try await run(command, onProgress)
}
if !BrewDiagnostics.installedTaps.contains("shivammathur/extensions") {
let command = "brew tap shivammathur/extensions"
try await run(command, onProgress)
}
}
}
struct BrewCommandProgress {

View File

@ -0,0 +1,81 @@
//
// InstallPhpExtensionCommand.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 21/11/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
class InstallPhpExtensionCommand: BrewCommand {
let installing: [BrewPhpExtension]
func getExtensionNames() -> String {
return installing.map { $0.name }.joined(separator: ", ")
}
func getCommandTitle() -> String {
return "phpman.steps.installing".localized(getExtensionNames())
}
public init(install extensions: [BrewPhpExtension]) {
self.installing = extensions
}
func execute(onProgress: @escaping (BrewCommandProgress) -> Void) async throws {
let progressTitle = "phpman.steps.wait".localized
onProgress(.create(
value: 0.2,
title: progressTitle,
description: "phpman.steps.preparing".localized
))
// Make sure the tap is installed
try await self.checkPhpTap(onProgress)
// Make sure that the extension(s) are installed
try await self.installPackages(onProgress)
// Finally, complete all operations
await self.completedOperations(onProgress)
}
private func installPackages(_ onProgress: @escaping (BrewCommandProgress) -> Void) async throws {
// If no installations are needed, early exit
if self.installing.isEmpty {
return
}
let command = """
export HOMEBREW_NO_INSTALL_UPGRADE=true; \
export HOMEBREW_NO_INSTALL_CLEANUP=true; \
\(Paths.brew) install \(self.installing.map { $0.formulaName }.joined(separator: " ")) --force
"""
try await run(command, onProgress)
}
private func completedOperations(_ onProgress: @escaping (BrewCommandProgress) -> Void) async {
// Reload and restart PHP versions
onProgress(.create(value: 0.95, title: self.getCommandTitle(), description: "phpman.steps.reloading".localized))
// Check which version of PHP are now installed
await PhpEnvironments.detectPhpVersions()
// Keep track of the currently installed version
await MainMenu.shared.refreshActiveInstallation()
// Also rebuild the content of the main menu
await MainMenu.shared.rebuild()
// Let the UI know that the installation has been completed
onProgress(.create(
value: 1,
title: "phpman.steps.completed".localized,
description: "phpman.steps.success".localized
))
}
}

View File

@ -0,0 +1,89 @@
//
// RemovePhpExtensionCommand.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 21/11/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
class RemovePhpExtensionCommand: BrewCommand {
public let phpExtension: BrewPhpExtension
public init(remove formula: BrewPhpExtension) {
self.phpExtension = formula
}
func getCommandTitle() -> String {
return "phpman.steps.removing".localized(phpExtension.name)
}
func execute(onProgress: @escaping (BrewCommandProgress) -> Void) async throws {
onProgress(.create(
value: 0.2,
title: getCommandTitle(),
description: "phpman.steps.removing".localized("`\(phpExtension.name)`...")
))
// Keep track of the file that contains the information about the extension
let existing = PhpEnvironments.shared
.cachedPhpInstallations[phpExtension.phpVersion]?
.extensions.first(where: { ext in
ext.name == phpExtension.name
})
let command = """
export HOMEBREW_NO_INSTALL_UPGRADE=true; \
export HOMEBREW_NO_INSTALL_CLEANUP=true; \
\(Paths.brew) remove \(phpExtension.formulaName) --force --ignore-dependencies
"""
var loggedMessages: [String] = []
let (process, _) = try! await Shell.attach(
command,
didReceiveOutput: { text, _ in
if !text.isEmpty {
Log.perf(text)
loggedMessages.append(text)
}
},
withTimeout: .minutes(5)
)
if process.terminationStatus <= 0 {
onProgress(.create(value: 0.95, title: getCommandTitle(), description: "phpman.steps.reloading".localized))
if let ext = existing {
await performExtensionCleanup(for: ext)
}
await PhpEnvironments.detectPhpVersions()
await MainMenu.shared.refreshActiveInstallation()
onProgress(.create(value: 1, title: getCommandTitle(), description: "phpman.steps.success".localized))
} else {
throw BrewCommandError(error: "phpman.steps.failure".localized, log: loggedMessages)
}
}
private func performExtensionCleanup(for ext: PhpExtension) async {
if ext.file.hasSuffix("20-\(ext.name).ini") {
// The extension's default configuration file can be removed
Log.info("The extension was found in a default extension .ini location. Purging that .ini file.")
do {
try FileSystem.remove(ext.file)
} catch {
Log.err("The file `\(ext.file)` could not be removed.")
}
} else {
// The extension's default configuration file cannot be removed, it should be disabled instead
Log.info("The extension was not found in a default location. Disabling the extension only.")
if ext.enabled {
await ext.toggle()
}
}
}
}

View File

@ -8,23 +8,33 @@
import Foundation
class InstallAndUpgradeCommand: BrewCommand {
class ModifyPhpVersionCommand: BrewCommand {
let title: String
let installing: [BrewFormula]
let upgrading: [BrewFormula]
let installing: [BrewPhpFormula]
let upgrading: [BrewPhpFormula]
let phpGuard: PhpGuard
func getCommandTitle() -> String {
return title
}
/**
You can pass in which PHP versions need to be upgraded and which ones need to be installed.
The process will be executed in two steps: first upgrades, then installations.
Upgrades come first because... well, otherwise installations may very well break.
Each version that is installed will need to be checked afterwards (if it is OK).
Each version that is installed will need to be checked afterwards. Installing a
newer formula may break other PHP installations, which in turn need to be fixed.
- Important: If any PHP formula is a major upgrade that causes a PHP "version" to be
uninstalled, this is remedied by running `upgradeMainPhpFormula()`. This process
will ensure that the upgrade is applied, but the also that old version is
re-installed and linked again.
*/
public init(
title: String,
upgrading: [BrewFormula],
installing: [BrewFormula]
upgrading: [BrewPhpFormula],
installing: [BrewPhpFormula]
) {
self.title = title
self.installing = installing
@ -33,17 +43,32 @@ class InstallAndUpgradeCommand: BrewCommand {
}
func execute(onProgress: @escaping (BrewCommandProgress) -> Void) async throws {
let progressTitle = "Please wait..."
let progressTitle = "phpman.steps.wait".localized
onProgress(.create(
value: 0.2,
title: progressTitle,
description: "PHP Monitor is preparing Homebrew..."
description: "phpman.steps.preparing".localized
))
// Try to run all upgrade and installation operations
try await self.upgradePackages(onProgress)
try await self.installPackages(onProgress)
// Determine if a formula will become unavailable
// This is the case when `php` will be bumped to a new version
let unavailable = upgrading.first(where: { formula in
formula.unavailableAfterUpgrade
})
// Make sure the tap is installed
try await self.checkPhpTap(onProgress)
if unavailable == nil {
// Try to run all upgrade and installation operations
try await self.upgradePackages(onProgress)
try await self.installPackages(onProgress)
} else {
// Simply upgrade `php` to the latest version
try await self.upgradeMainPhpFormula(unavailable!, onProgress)
await PhpEnvironments.shared.determinePhpAlias()
}
// Re-check the installed versions
await PhpEnvironments.detectPhpVersions()
@ -55,6 +80,27 @@ class InstallAndUpgradeCommand: BrewCommand {
await self.completedOperations(onProgress)
}
private func upgradeMainPhpFormula(
_ unavailable: BrewPhpFormula,
_ onProgress: @escaping (BrewCommandProgress) -> Void
) async throws {
// Determine which version was previously available (that will become unavailable)
guard let short = try? VersionNumber
.parse(unavailable.installedVersion!).short else {
return
}
// Upgrade the main formula
let command = """
export HOMEBREW_NO_INSTALL_CLEANUP=true; \
\(Paths.brew) upgrade php;
\(Paths.brew) install php@\(short);
"""
// Run the upgrade command
try await run(command, onProgress)
}
private func upgradePackages(_ onProgress: @escaping (BrewCommandProgress) -> Void) async throws {
// If no upgrades are needed, early exit
if self.upgrading.isEmpty {
@ -117,35 +163,12 @@ class InstallAndUpgradeCommand: BrewCommand {
try await run(command, onProgress)
}
private func run(_ command: String, _ onProgress: @escaping (BrewCommandProgress) -> Void) async throws {
var loggedMessages: [String] = []
let (process, _) = try! await Shell.attach(
command,
didReceiveOutput: { text, _ in
if !text.isEmpty {
Log.perf(text)
loggedMessages.append(text)
}
if let (number, text) = self.reportInstallationProgress(text) {
onProgress(.create(value: number, title: self.title, description: text))
}
},
withTimeout: .minutes(15)
)
if process.terminationStatus <= 0 {
loggedMessages = []
return
} else {
throw BrewCommandError(error: "The command failed to run correctly.", log: loggedMessages)
}
}
private func completedOperations(_ onProgress: @escaping (BrewCommandProgress) -> Void) async {
// Reload and restart PHP versions
onProgress(.create(value: 0.95, title: self.title, description: "Reloading PHP versions..."))
onProgress(.create(value: 0.95, title: self.title, description: "phpman.steps.reloading".localized))
// Ensure all symlinks are correctly linked
await BrewDiagnostics.checkForOutdatedPhpInstallationSymlinks()
// Check which version of PHP are now installed
await PhpEnvironments.detectPhpVersions()
@ -164,9 +187,8 @@ class InstallAndUpgradeCommand: BrewCommand {
// Let the UI know that the installation has been completed
onProgress(.create(
value: 1,
title: "Operation completed!",
description: "The installation has succeeded."
title: "phpman.steps.completed".localized,
description: "phpman.steps.success".localized
))
}
}

View File

@ -21,13 +21,15 @@ class RemovePhpVersionCommand: BrewCommand {
self.phpGuard = PhpGuard()
}
func execute(onProgress: @escaping (BrewCommandProgress) -> Void) async throws {
let progressTitle = "Removing PHP \(version)..."
func getCommandTitle() -> String {
return "phpman.steps.removing".localized("PHP \(version)...")
}
func execute(onProgress: @escaping (BrewCommandProgress) -> Void) async throws {
onProgress(.create(
value: 0.2,
title: progressTitle,
description: "Please wait while Homebrew removes PHP \(version)..."
title: getCommandTitle(),
description: "phpman.steps.wait".localized
))
let command = """
@ -56,7 +58,7 @@ class RemovePhpVersionCommand: BrewCommand {
)
if process.terminationStatus <= 0 {
onProgress(.create(value: 0.95, title: progressTitle, description: "Reloading PHP versions..."))
onProgress(.create(value: 0.95, title: getCommandTitle(), description: "phpman.steps.reloading".localized))
await PhpEnvironments.detectPhpVersions()
@ -66,7 +68,7 @@ class RemovePhpVersionCommand: BrewCommand {
await MainMenu.shared.switchToPhpVersionAndWait(version, silently: true)
}
onProgress(.create(value: 1, title: progressTitle, description: "The operation has succeeded."))
onProgress(.create(value: 1, title: getCommandTitle(), description: "phpman.steps.success".localized))
} else {
throw BrewCommandError(error: "The command failed to run correctly.", log: loggedMessages)
}

View File

@ -9,6 +9,10 @@
import Foundation
class FakeCommand: BrewCommand {
func getCommandTitle() -> String {
return "Hello"
}
let version: String
init(version: String) {

View File

@ -287,7 +287,7 @@ extension MainMenu {
PhpEnvironments.shared.delegate = self
PhpEnvironments.shared.delegate?.switcherDidStartSwitching(to: version)
updatePhpVersionInStatusBar()
refreshIcon()
rebuild()
await PhpEnvironments.switcher.performSwitch(to: version)
@ -302,7 +302,7 @@ extension MainMenu {
PhpEnvironments.shared.delegate?.switcherDidStartSwitching(to: version)
Task(priority: .userInitiated) { [unowned self] in
updatePhpVersionInStatusBar()
refreshIcon()
rebuild()
await PhpEnvironments.switcher.performSwitch(to: version)
@ -328,7 +328,7 @@ extension MainMenu {
PhpEnvironments.shared.delegate?.switcherDidStartSwitching(to: version)
}
updatePhpVersionInStatusBar()
refreshIcon()
rebuild()
await PhpEnvironments.switcher.performSwitch(to: version)

View File

@ -63,7 +63,8 @@ extension MainMenu {
}
if behaviours.contains(.updatesMenuBarContents) {
updatePhpVersionInStatusBar()
refreshIcon()
rebuild()
}
if behaviours.contains(.broadcastServicesUpdate) {

View File

@ -15,7 +15,7 @@ extension MainMenu {
func startup() async {
// Start with the icon
Task { @MainActor in
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
self.setStatusBar(image: NSImage.statusBarIcon)
}
if await Startup().checkEnvironment() {
@ -32,19 +32,14 @@ extension MainMenu {
// Determine what the `php` formula is aliased to
await PhpEnvironments.shared.determinePhpAlias()
// Make sure that broken symlinks are removed ASAP
await BrewDiagnostics.checkForOutdatedPhpInstallationSymlinks()
// Initialize preferences
_ = Preferences.shared
// Determine install method
Log.info(BrewDiagnostics.customCaskInstalled
? "[BREW] The app has been installed via Homebrew Cask."
: "[BREW] The app has been installed directly (optimal)."
)
Log.info(BrewDiagnostics.usesNginxFullFormula
? "[BREW] The app will be using the `nginx-full` formula."
: "[BREW] The app will be using the `nginx` formula."
)
// Put some useful diagnostics information in log
BrewDiagnostics.logBootInformation()
// Attempt to find out more info about Valet
if Valet.shared.version != nil {
@ -63,9 +58,6 @@ extension MainMenu {
// Check for an alias conflict
await BrewDiagnostics.checkForCaskConflict()
// Update the icon
updatePhpVersionInStatusBar()
// Attempt to find out if PHP-FPM is broken
PhpEnvironments.prepare()
@ -76,7 +68,6 @@ extension MainMenu {
WarningManager.shared.evaluateWarnings()
// Set up the config watchers on launch (updated automatically when switching)
Log.info("Setting up watchers...")
App.shared.handlePhpConfigWatcher()
// Detect built-in and custom applications
@ -106,11 +97,32 @@ extension MainMenu {
}
// Keep track of which PHP versions are currently about to release
Log.info("Experimental PHP versions: \(Constants.ExperimentalPhpVersions)")
Log.info("Experimental PHP versions are: \(Constants.ExperimentalPhpVersions)")
// Find out which services are active
Log.info("The services manager knows about \(ServicesManager.shared.services.count) services.")
// Post-launch stats and update check, but only if not running tests
await performPostLaunchActions()
// Check if the linked version has changed between launches of phpmon
PhpGuard().compareToLastGlobalVersion()
// We are ready!
PhpEnvironments.shared.isBusy = false
// Finally!
Log.info("PHP Monitor is ready to serve!")
// Check if we upgraded from a previous version
AppUpdater.checkIfUpdateWasPerformed()
}
/**
Performs a set of post-launch actions, like incrementing stats and checking for updates.
(This code is skipped when running SwiftUI previews.)
*/
private func performPostLaunchActions() async {
if !isRunningSwiftUIPreview {
Stats.incrementSuccessfulLaunchCount()
Stats.evaluateSponsorMessageShouldBeDisplayed()
@ -124,15 +136,6 @@ extension MainMenu {
await AppUpdater().checkForUpdates(userInitiated: false)
}
}
// Check if the linked version has changed between launches of phpmon
PhpGuard().compareToLastGlobalVersion()
// We are ready!
Log.info("PHP Monitor is ready to serve!")
// Check if we upgraded just now
AppUpdater.checkIfUpdateWasPerformed()
}
/**

View File

@ -27,7 +27,7 @@ extension MainMenu {
// Perform UI updates on main thread
Task { @MainActor [self] in
updatePhpVersionInStatusBar()
refreshIcon()
rebuild()
if Valet.installed && !PhpEnvironments.shared.validate(version) {

View File

@ -37,8 +37,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
// MARK: - UI related
/**
Rebuilds the menu (either asynchronously or synchronously).
Defaults to rebuilding the menu asynchronously.
Rebuilds the menu on the main thread.
*/
func rebuild() {
Task { @MainActor [self] in
@ -80,7 +79,8 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
@objc func refreshActiveInstallation() {
if !PhpEnvironments.shared.isBusy {
PhpEnvironments.shared.currentInstall = ActivePhpInstallation.load()
updatePhpVersionInStatusBar()
refreshIcon()
rebuild()
} else {
Log.perf("Skipping version refresh due to busy status!")
}
@ -155,12 +155,12 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
Task { @MainActor [self] in
if PhpEnvironments.shared.isBusy {
Log.perf("Refreshing icon: currently busy")
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
setStatusBar(image: NSImage.statusBarIcon)
} else {
Log.perf("Refreshing icon: no longer busy")
if Preferences.preferences[.shouldDisplayDynamicIcon] as! Bool == false {
// Static icon has been requested
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIconStatic"))!)
setStatusBar(image: NSImage.statusBarIconStatic)
} else {
// The dynamic icon has been requested
let long = Preferences.preferences[.fullPhpVersionDynamicIcon] as! Bool
@ -215,6 +215,10 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
PhpVersionManagerWindowController.show()
}
@objc func openPhpExtensionManager() {
PhpExtensionManagerWindowController.show()
}
@objc func openDonate() {
NSWorkspace.shared.open(Constants.Urls.DonationPage)
}

View File

@ -152,6 +152,9 @@ extension StatusMenu {
NSMenuItem(title: "mi_php_version_manager".localized,
action: #selector(MainMenu.openPhpVersionManager),
keyEquivalent: "m"),
NSMenuItem(title: "mi_php_ext_manager".localized,
action: #selector(MainMenu.openPhpExtensionManager),
keyEquivalent: "e"),
NSMenuItem(title: "mi_php_config".localized,
action: #selector(MainMenu.openActiveConfigFolder),
keyEquivalent: "c"),
@ -200,16 +203,6 @@ extension StatusMenu {
post: stats.post_max_size,
upload: stats.upload_max_filesize)
)
// TODO: As soon as this does more than just edit memory limits, move this
/*
addItem(NSMenuItem.separator())
addItem(NSMenuItem(
title: "mi_manage_limits".localized,
action: #selector(MainMenu.openConfigGUI),
keyEquivalent: "l")
)
*/
}
// MARK: - Extensions

View File

@ -91,7 +91,7 @@ class BetterAlert {
}
NSApp.activate(ignoringOtherApps: true)
windowController.window?.makeKeyAndOrderFront(nil)
windowController.window?.setCenterPosition(offsetY: 70)
return NSApplication.shared.runModal(for: windowController.window!)

View File

@ -20,6 +20,7 @@ enum PreferenceName: String, Codable {
case globalHotkey = "global_hotkey"
case automaticBackgroundUpdateCheck = "backgroundUpdateCheck"
case showPhpDoctorSuggestions = "show_php_doctor_suggestions"
case languageOverride = "language_override"
// APPEARANCE
case shouldDisplayDynamicIcon = "use_dynamic_icon"
@ -84,7 +85,8 @@ enum PreferenceName: String, Codable {
],
.string: [
.globalHotkey,
.iconTypeToDisplay
.iconTypeToDisplay,
.languageOverride
]
]
}

View File

@ -51,6 +51,7 @@ class Preferences {
PreferenceName.allowProtocolForIntegrations.rawValue: true,
PreferenceName.automaticBackgroundUpdateCheck.rawValue: true,
PreferenceName.showPhpDoctorSuggestions.rawValue: true,
PreferenceName.languageOverride.rawValue: "",
/// Preferences: Appearance
PreferenceName.shouldDisplayDynamicIcon.rawValue: true,

View File

@ -17,7 +17,9 @@ class GeneralPreferencesVC: GenericPreferenceVC {
let vc = NSStoryboard(name: "Main", bundle: nil)
.instantiateController(withIdentifier: "preferencesTemplateVC") as! GenericPreferenceVC
_ = vc.addView(when: true, vc.getShowPhpDoctorSuggestionsPV())
_ = vc
.addView(when: true, vc.getLanguageOptionsPV())
.addView(when: true, vc.getShowPhpDoctorSuggestionsPV())
.addView(when: true, vc.getAutoRestartServicesPV())
.addView(when: true, vc.getAutomaticComposerUpdatePV())
.addView(when: true, vc.getShortcutPV())

View File

@ -48,11 +48,44 @@ class GenericPreferenceVC: NSViewController {
)
}
func getLanguageOptionsPV() -> NSView {
var options = Bundle.main.localizations
.filter({ $0 != "Base"})
.map({ lang in
return PreferenceDropdownOption(
label: Locale.current.localizedString(forLanguageCode: lang)!,
value: lang
)
})
options.insert(PreferenceDropdownOption(label: "System Default", value: ""), at: 0)
return SelectPreferenceView.make(
sectionText: "prefs.language".localized,
descriptionText: "prefs.language_options_desc".localized,
options: options,
preference: .languageOverride,
action: {
MainMenu.shared.refreshIcon()
MainMenu.shared.rebuild()
if let window = App.shared.preferencesWindowController?.window {
let alert = NSAlert()
alert.messageText = "alert.language_changed.title".localized
alert.informativeText = "alert.language_changed.subtitle".localized
alert.alertStyle = .warning
alert.addButton(withTitle: "generic.ok".localized)
alert.beginSheetModal(for: window)
}
}
)
}
func getIconOptionsPV() -> NSView {
return SelectPreferenceView.make(
sectionText: "",
descriptionText: "prefs.icon_options_desc".localized,
options: MenuBarIcon.allCases.map({ return $0.rawValue }),
options: MenuBarIcon.allCases
.map({ return PreferenceDropdownOption(label: $0.rawValue, value: $0.rawValue) }),
localizationPrefix: "prefs.icon_options",
preference: .iconTypeToDisplay,
action: {

View File

@ -68,6 +68,8 @@ class PreferencesWindowController: PMWindowController {
App.shared.preferencesWindowController?.positionWindowInTopRightCorner()
}
App.shared.preferencesWindowController?.window?.orderFrontRegardless()
NSApp.activate(ignoringOtherApps: true)
}

View File

@ -84,6 +84,10 @@ class Stats {
)
}
public static func clearCurrentGlobalPhpVersion() {
UserDefaults.standard.removeObject(forKey: InternalStats.lastGlobalPhpVersion.rawValue)
}
/**
Determine if the sponsor message should be displayed.

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22505"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -23,7 +23,7 @@
<action selector="toggled:" target="c22-O7-iKe" id="c9y-JM-TdE"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Bcg-X1-qca">
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Bcg-X1-qca">
<rect key="frame" x="168" y="5" width="410" height="14"/>
<textFieldCell key="cell" title="DESCRIPTION" id="9fH-up-Sob">
<font key="font" metaFont="smallSystem"/>
@ -31,7 +31,7 @@
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="B8f-nb-Y0A">
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="B8f-nb-Y0A">
<rect key="frame" x="-2" y="27" width="154" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="150" id="euj-t0-xv4"/>

View File

@ -9,30 +9,34 @@
import Foundation
import Cocoa
class SelectPreferenceView: NSView, XibLoadable {
struct PreferenceDropdownOption {
let label: String
let value: String
}
class SelectPreferenceView: NSView, XibLoadable {
@IBOutlet weak var labelSection: NSTextField!
@IBOutlet weak var labelDescription: NSTextField!
@IBOutlet weak var popupButton: NSPopUpButton!
var localizationPrefix: String = ""
var localizationPrefix: String?
var imagePrefix: String?
var options: [String] = [] {
var options: [PreferenceDropdownOption] = [] {
didSet {
self.popupButton.removeAllItems()
self.options.forEach { value in
self.popupButton.addItem(
withTitle: "\(localizationPrefix).\(value)".localized
)
self.options.forEach { option in
if let prefix = localizationPrefix {
self.popupButton.addItem(withTitle: "\(prefix).\(option.label)".localized)
} else {
self.popupButton.addItem(withTitle: option.label)
}
}
if imagePrefix == nil {
return
}
self.popupButton.itemArray.enumerated().forEach { item in
item.element.image = NSImage(named: "\(imagePrefix!)_\(self.options[item.offset])")
if let prefix = imagePrefix {
self.popupButton.itemArray.enumerated().forEach { item in
item.element.image = NSImage(named: "\(prefix)_\(self.options[item.offset].value)")
}
}
}
}
@ -43,19 +47,18 @@ class SelectPreferenceView: NSView, XibLoadable {
didSet {
let value = Preferences.preferences[preference] as! String
self.options.enumerated().forEach { option in
if option.element == value {
if option.element.value == value {
self.popupButton.selectItem(at: option.offset)
}
}
}
}
// swiftlint:disable function_parameter_count
static func make(
sectionText: String,
descriptionText: String,
options: [String],
localizationPrefix: String,
options: [PreferenceDropdownOption],
localizationPrefix: String? = nil,
imagePrefix: String? = nil,
preference: PreferenceName,
action: @escaping () -> Void) -> NSView {
@ -72,11 +75,10 @@ class SelectPreferenceView: NSView, XibLoadable {
return view
}
// swiftlint:enable function_parameter_count
@IBAction func valueChanged(_ sender: Any) {
let index = self.popupButton.indexOfSelectedItem
Preferences.update(.iconTypeToDisplay, value: self.options[index])
Preferences.update(self.preference, value: self.options[index].value)
self.action()
}

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21701"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22505"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -13,16 +13,16 @@
<rect key="frame" x="0.0" y="0.0" width="596" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Bcg-X1-qca">
<rect key="frame" x="183" y="5" width="395" height="14"/>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Bcg-X1-qca">
<rect key="frame" x="168" y="5" width="410" height="14"/>
<textFieldCell key="cell" title="DESCRIPTION" id="9fH-up-Sob">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="B8f-nb-Y0A">
<rect key="frame" x="13" y="29" width="154" height="16"/>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="B8f-nb-Y0A">
<rect key="frame" x="-2" y="29" width="154" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="150" id="euj-t0-xv4"/>
</constraints>
@ -33,7 +33,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YaB-Tg-Ir3">
<rect key="frame" x="182" y="23" width="110" height="25"/>
<rect key="frame" x="167" y="23" width="110" height="25"/>
<popUpButtonCell key="cell" type="push" title="Icon Option" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="SaA-mm-HBo" id="Su6-C4-aGo">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -58,7 +58,7 @@
<constraint firstItem="Bcg-X1-qca" firstAttribute="top" secondItem="YaB-Tg-Ir3" secondAttribute="bottom" constant="8" symbolic="YES" id="Mji-pe-CNl"/>
<constraint firstAttribute="trailing" secondItem="Bcg-X1-qca" secondAttribute="trailing" constant="20" symbolic="YES" id="UPo-Il-l81"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="YaB-Tg-Ir3" secondAttribute="trailing" constant="20" symbolic="YES" id="Zlg-jj-uKY"/>
<constraint firstItem="B8f-nb-Y0A" firstAttribute="leading" secondItem="c22-O7-iKe" secondAttribute="leading" constant="15" id="Ztd-uk-4aw"/>
<constraint firstItem="B8f-nb-Y0A" firstAttribute="leading" secondItem="c22-O7-iKe" secondAttribute="leading" id="aBU-J8-gRK"/>
<constraint firstAttribute="bottom" secondItem="Bcg-X1-qca" secondAttribute="bottom" constant="5" id="hNE-mU-jcu"/>
</constraints>
<connections>

View File

@ -27,15 +27,15 @@ struct HelpButton: View {
.buttonStyle(BorderlessButtonStyle())
.focusable(false)
}
struct HelpButton_Previews: PreviewProvider {
static var previews: some View {
Group {
HelpButton(action: {}).padding()
.previewDisplayName("Light Mode")
HelpButton(action: {}).padding().preferredColorScheme(.dark)
.previewDisplayName("Dark Mode")
}
}
}
}
#Preview("Light Mode") {
HelpButton(action: {})
.padding(100)
}
#Preview("Dark Mode") {
HelpButton(action: {})
.padding(100)
.preferredColorScheme(.dark)
}

View File

@ -27,7 +27,9 @@ var isRunningSwiftUIPreview: Bool {
extension Color {
public static var appPrimary: Color = Color("AppColor")
public static var appSecondary: Color = Color("AppSecondary")
// This next one is generated automatically via asset catalogs now
// public static var appSecondary: Color = Color("AppSecondary")
public static var debug: Color = {
if ProcessInfo.processInfo.environment["PAINT_PHPMON_SWIFTUI_VIEWS"] != nil {

View File

@ -30,8 +30,6 @@ struct NoDomainResults: View {
}
}
struct NoDomainResults_Previews: PreviewProvider {
static var previews: some View {
NoDomainResults()
}
#Preview {
NoDomainResults()
}

View File

@ -126,78 +126,82 @@ struct DisclaimerView: View {
}
}
struct VersionPopoverView_Previews: PreviewProvider {
static var previews: some View {
VersionPopoverView(
site: FakeValetSite(
fakeWithName: "amazingwebsite",
tld: "test",
secure: true,
path: "/path/to/site",
linked: true,
constraint: ""
),
validPhpVersions: [],
parent: nil
)
.previewDisplayName("Unknown Requirement")
VersionPopoverView(
site: FakeValetSite(
fakeWithName: "amazingwebsite",
tld: "test",
secure: true,
path: "/path/to/site",
linked: true,
constraint: "^8.1"
),
validPhpVersions: [],
parent: nil
)
.previewDisplayName("Requirement Matches")
VersionPopoverView(
site: FakeValetSite(
fakeWithName: "anothersite",
tld: "test",
secure: true,
path: "/path/to/site",
linked: true,
constraint: "^8.0",
isolated: "8.0"
),
validPhpVersions: [],
parent: nil
)
.previewDisplayName("Isolated")
VersionPopoverView(
site: FakeValetSite(
fakeWithName: "anothersite",
tld: "test",
secure: true,
path: "/path/to/site",
linked: true,
constraint: "^8.0",
isolated: "7.4"
),
validPhpVersions: [],
parent: nil
)
.previewDisplayName("Isolated Mismatch")
VersionPopoverView(
site: FakeValetSite(
fakeWithName: "anothersite",
tld: "test",
secure: true,
path: "/path/to/site",
linked: true,
constraint: "^8.0"
),
validPhpVersions: [
VersionNumber(major: 8, minor: 0, patch: 0),
VersionNumber(major: 8, minor: 1, patch: 0)
],
parent: nil
)
.previewDisplayName("Recommend Alternatives")
}
#Preview("Unknown Requirement") {
VersionPopoverView(
site: FakeValetSite(
fakeWithName: "amazingwebsite",
tld: "test",
secure: true,
path: "/path/to/site",
linked: true,
constraint: ""
),
validPhpVersions: [],
parent: nil
)
}
#Preview("Requirement Matches") {
VersionPopoverView(
site: FakeValetSite(
fakeWithName: "amazingwebsite",
tld: "test",
secure: true,
path: "/path/to/site",
linked: true,
constraint: "^8.1"
),
validPhpVersions: [],
parent: nil
)
}
#Preview("Isolated") {
VersionPopoverView(
site: FakeValetSite(
fakeWithName: "anothersite",
tld: "test",
secure: true,
path: "/path/to/site",
linked: true,
constraint: "^8.0",
isolated: "8.0"
),
validPhpVersions: [],
parent: nil
)
}
#Preview("Isolated Mismatch") {
VersionPopoverView(
site: FakeValetSite(
fakeWithName: "anothersite",
tld: "test",
secure: true,
path: "/path/to/site",
linked: true,
constraint: "^8.0",
isolated: "7.4"
),
validPhpVersions: [],
parent: nil
)
}
#Preview("Recommend Alternatives") {
VersionPopoverView(
site: FakeValetSite(
fakeWithName: "anothersite",
tld: "test",
secure: true,
path: "/path/to/site",
linked: true,
constraint: "^8.0"
),
validPhpVersions: [
VersionNumber(major: 8, minor: 0, patch: 0),
VersionNumber(major: 8, minor: 1, patch: 0)
],
parent: nil
)
}

View File

@ -45,9 +45,7 @@ struct HeaderView: View {
}
}
struct HeaderView_Previews: PreviewProvider {
static var previews: some View {
HeaderView(text: "Hello world")
.frame(width: 330.0)
}
#Preview {
HeaderView(text: "Hello world")
.frame(width: 330.0)
}

View File

@ -172,23 +172,21 @@ struct ServiceView: View {
}
}
struct ServicesView_Previews: PreviewProvider {
static var previews: some View {
ServicesView(manager: FakeServicesManager(
formulae: ["php", "nginx", "dnsmasq"],
status: .active
), perRow: 4)
.frame(width: 330.0)
.previewDisplayName("Active 1")
ServicesView(manager: FakeServicesManager(
formulae: [
"php", "nginx", "dnsmasq", "thing1",
"thing2", "thing3", "thing4", "thing5"
],
status: .inactive
), perRow: 4)
.frame(width: 330.0)
.previewDisplayName("Active 2")
}
#Preview("Active 1") {
ServicesView(manager: FakeServicesManager(
formulae: ["php", "nginx", "dnsmasq"],
status: .active
), perRow: 4)
.frame(width: 330.0)
}
#Preview("Active 2") {
ServicesView(manager: FakeServicesManager(
formulae: [
"php", "nginx", "dnsmasq", "thing1",
"thing2", "thing3", "thing4", "thing5"
],
status: .inactive
), perRow: 4)
.frame(width: 330.0)
}

View File

@ -89,6 +89,7 @@ struct StatsView: View {
} label: {
Image(systemName: "gearshape.fill")
}
.accessibility(identifier: "phpConfigButton")
.focusable(false)
.frame(minWidth: 30, alignment: .center)
}
@ -98,12 +99,10 @@ struct StatsView: View {
}
}
struct StatsView_Previews: PreviewProvider {
static var previews: some View {
StatsView(
memoryLimit: "1024 MB",
maxPostSize: "1024 MB",
maxUploadSize: "1024 MB"
).frame(height: 100)
}
#Preview {
StatsView(
memoryLimit: "1024 MB",
maxPostSize: "1024 MB",
maxUploadSize: "1024 MB"
).frame(height: 100)
}

View File

@ -1,22 +0,0 @@
//
// ProgressViewSubject.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 11/03/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
import SwiftUI
class ProgressViewSubject: ObservableObject {
@Published var title: String
@Published var description: String?
@Published var progress: Double
init(title: String, description: String) {
self.title = title
self.description = description
self.progress = 0
}
}

View File

@ -1,65 +0,0 @@
//
// ProgressWindowView.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 11/03/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import SwiftUI
struct ProgressWindowView: View {
@ObservedObject var subject: ProgressViewSubject
var body: some View {
VStack(alignment: .leading) {
VStack(alignment: .leading) {
Text(subject.title)
.font(.system(size: 14))
.bold()
if subject.description != nil {
Text(subject.description!)
.font(.system(size: 13))
}
}
.padding(.leading, 20)
.padding(.top, 12)
ProgressView(value: subject.progress)
.padding(.top, 0)
.padding(.bottom, 12)
.padding(.horizontal, 20)
}
}
@MainActor static func display(_ subject: ProgressViewSubject) async -> NSWindowController {
let view = ProgressWindowView(subject: subject)
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 420, height: 240),
styleMask: [.titled, .closable, .utilityWindow],
backing: .buffered,
defer: false
)
window.title = ""
window.titlebarAppearsTransparent = true
window.contentView = NSHostingView(rootView: view)
let controller = NSWindowController(window: window)
controller.showWindow(nil)
controller.positionWindowInTopRightCorner()
controller.window?.makeKeyAndOrderFront(self)
// NSApp.activate(ignoringOtherApps: true)
return controller
}
}
struct ProgressWindowView_Previews: PreviewProvider {
static var previews: some View {
ProgressWindowView(
subject: ProgressViewSubject(
title: "Long running task",
description: "Please be patient"
)
)
}
}

View File

@ -56,7 +56,6 @@ class ConfigFSNotifier {
return App.shared.handlePhpConfigWatcher(forceReload: true)
}
self?.parent.didChange?(self!.url)
}

View File

@ -16,21 +16,19 @@ class DomainListKindCell: NSTableCellView, DomainListCellProtocol {
func populateCell(with site: ValetSite) {
// If the `aliasPath` is nil, we're dealing with a parked site (otherwise: linked).
imageViewType.image = NSImage(
named: site.aliasPath == nil
? "IconParked"
: "IconLinked"
)
imageViewType.image = site.aliasPath == nil
? NSImage.iconParked
: NSImage.iconLinked
// Unless, of course, this is a default site
if site.absolutePath == Valet.shared.config.defaultSite {
imageViewType.image = NSImage(named: "IconDefault")
imageViewType.image = NSImage.iconDefault
}
imageViewType.contentTintColor = NSColor.tertiaryLabelColor
}
func populateCell(with proxy: ValetProxy) {
imageViewType.image = NSImage(named: "IconProxy")
imageViewType.image = NSImage.iconProxy
}
}

View File

@ -34,14 +34,13 @@ class DomainListPhpCell: NSTableCellView, DomainListCellProtocol {
if site.isolatedPhpVersion != nil {
imageViewPhpVersionOK.isHidden = false
imageViewPhpVersionOK.image = NSImage(named: "Isolated")
imageViewPhpVersionOK.image = NSImage.isolated
imageViewPhpVersionOK.toolTip = "domain_list.tooltips.isolated".localized(site.servingPhpVersion)
} else {
imageViewPhpVersionOK.isHidden = (site.preferredPhpVersion == "???"
|| !site.isCompatibleWithPreferredPhpVersion)
imageViewPhpVersionOK.image = NSImage(named: "Checkmark")
imageViewPhpVersionOK.image = NSImage.checkmark
imageViewPhpVersionOK.toolTip = "domain_list.tooltips.checkmark".localized(site.preferredPhpVersion)
}
}

View File

@ -110,6 +110,22 @@ extension DomainListVC {
}
}
@objc func toggleExtension(sender: ExtensionMenuItem) {
Task {
self.setUIBusy()
await sender.phpExtension?.toggle()
if Preferences.isEnabled(.autoServiceRestartAfterExtensionToggle) {
await Actions.restartPhpFpm()
}
reloadContextMenu()
self.setUINotBusy()
}
}
@objc func isolateSite(sender: PhpMenuItem) {
guard let site = selectedSite else {
return

View File

@ -42,7 +42,20 @@ extension DomainListVC {
addDisabledIsolation(to: menu)
}
addSeparator(to: menu)
if let extensions = site.isolatedPhpVersion?.extensions ?? PhpEnvironments.phpInstall?.extensions,
let version = site.isolatedPhpVersion?.versionNumber.short ?? PhpEnvironments.phpInstall?.version.short {
menu.addItem(HeaderView.asMenuItem(text: "mi_detected_extensions".localized))
addMenuItemsForExtensions(
to: menu,
for: extensions,
version: version
)
}
menu.addItem(HeaderView.asMenuItem(text: "domain_list.actions".localized))
addToggleSecure(to: menu, secured: site.secured)
addUnlink(to: menu, with: site)
@ -150,6 +163,28 @@ extension DomainListVC {
)
}
private func addMenuItemsForExtensions(to menu: NSMenu, for extensions: [PhpExtension], version: String) {
var items: [NSMenuItem] = [
NSMenuItem(title: "domain_list.applies_to".localized(version))
]
for phpExtension in extensions {
let item = ExtensionMenuItem(
title: "\(phpExtension.name) (\(phpExtension.fileNameOnly))",
action: #selector(self.toggleExtension),
keyEquivalent: ""
)
item.state = phpExtension.enabled ? .on : .off
item.phpExtension = phpExtension
items.append(item)
}
menu.addItem(NSMenuItem(title: "domain_list.extensions".localized, submenu: items))
menu.addItem(NSMenuItem.separator())
}
// MARK: - Menu Items for Proxy
private func addMenuItemsForProxy(_ proxy: ValetProxy) {

View File

@ -31,7 +31,7 @@ struct OnboardingTextItem: View {
.opacity(self.unavailable ? 0.5 : 1)
Text(description.localizedForSwiftUI)
.foregroundColor(Color.secondary)
.font(.system(size: 13))
.font(.system(size: 12))
.lineLimit(4)
.fixedSize(horizontal: false, vertical: true)
.frame(minWidth: 0, maxWidth: 800, alignment: .leading)
@ -51,7 +51,7 @@ struct OnboardingView: View {
HStack {
Image(nsImage: NSApp.applicationIconImage)
.resizable()
.frame(width: 80, height: 80)
.frame(width: 100, height: 100)
.padding(.bottom, 5)
.padding(.trailing, 25)
VStack(alignment: .leading, spacing: 0) {
@ -126,20 +126,19 @@ struct OnboardingView: View {
Button("onboarding.tour.close".localized) {
App.shared.onboardingWindowController?.close()
}
.padding(.bottom, 5)
.padding(.bottom, 15)
.padding(.top, 10)
}
}
.padding(.leading)
.padding(.trailing)
.padding(.bottom, 0)
}
.frame(width: 600)
.fixedSize(horizontal: true, vertical: false)
}
}
struct OnboardingView_Previews: PreviewProvider {
static var previews: some View {
Group {
OnboardingView()
}
}
#Preview {
OnboardingView()
}

View File

@ -27,7 +27,7 @@ class OnboardingWindowController: PMWindowController {
window.titlebarAppearsTransparent = true
window.delegate = delegate ?? windowController
window.contentView = NSHostingView(rootView: OnboardingView())
window.setContentSize(NSSize(width: 600, height: 600))
window.setContentSize(window.contentView!.fittingSize)
App.shared.onboardingWindowController = windowController
}

View File

@ -98,19 +98,19 @@ struct ByteLimitView: View {
}
}
struct ByteLimitView_Previews: PreviewProvider {
static var previews: some View {
PreferenceContainer(
name: "Max Size",
description:
"Here's an extensive description that is obviously way too long but it should wrap." +
"The point of the wrapping text is that is allows us to see what's going on with the layout here."
) {
ByteLimitView(preference: BytePhpPreference(key: "max_memory"))
}.frame(width: 600, height: 200)
ConfigManagerView()
.frame(width: 600, height: .infinity)
.previewDisplayName("Config Manager")
}
#Preview("Byte Limit View") {
PreferenceContainer(
name: "Max Size",
description:
"Here's an extensive description that is obviously way too long but it should wrap." +
"The point of the wrapping text is that is allows us to see what's going on with the layout here."
) {
ByteLimitView(preference: BytePhpPreference(key: "max_memory"))
}.frame(width: 600, height: 200)
}
#Preview("Config Manager") {
ConfigManagerView()
.frame(width: 600, height: .infinity)
.previewDisplayName("Config Manager")
}

View File

@ -84,10 +84,6 @@ struct ConfigManagerView: View {
}
}
struct ConfigManagerView_Previews: PreviewProvider {
static var previews: some View {
ConfigManagerView()
.frame(width: 600)
.previewDisplayName("Live Preview")
}
#Preview {
ConfigManagerView().frame(width: 600)
}

View File

@ -34,8 +34,8 @@ class WarningManager: ObservableObject {
.trimmingCharacters(in: .whitespacesAndNewlines) == "1"
},
name: "Running PHP Monitor with Rosetta on M1",
title: "warnings.arm_compatibility.title".localized,
paragraphs: { return ["warnings.arm_compatibility.description".localized] },
title: "warnings.arm_compatibility.title",
paragraphs: { return ["warnings.arm_compatibility.description"] },
url: "https://github.com/nicoverbruggen/phpmon/wiki/PHP-Monitor-and-Apple-Silicon"
),
Warning(
@ -44,11 +44,11 @@ class WarningManager: ObservableObject {
!FileSystem.isWriteableFile("/usr/local/bin/")
},
name: "Helpers cannot be symlinked and not in PATH",
title: "warnings.helper_permissions.title".localized,
title: "warnings.helper_permissions.title",
paragraphs: { return [
"warnings.helper_permissions.description".localized,
"warnings.helper_permissions.unavailable".localized,
"warnings.helper_permissions.symlink".localized
"warnings.helper_permissions.description",
"warnings.helper_permissions.unavailable",
"warnings.helper_permissions.symlink"
] },
url: "https://github.com/nicoverbruggen/phpmon/wiki/PHP-Monitor-helper-binaries"
),
@ -58,7 +58,7 @@ class WarningManager: ObservableObject {
return !PhpConfigChecker.shared.missing.isEmpty
},
name: "Your PHP installation is missing configuration files",
title: "warnings.files_missing.title".localized,
title: "warnings.files_missing.title",
paragraphs: { return [
"warnings.files_missing.description".localized(
PhpConfigChecker.shared.missing.joined(separator: "\n")

View File

@ -25,8 +25,6 @@ struct NoWarningsView: View {
}
}
struct NoWarningsView_Previews: PreviewProvider {
static var previews: some View {
NoWarningsView()
}
#Preview {
NoWarningsView().padding()
}

View File

@ -89,19 +89,19 @@ struct PhpDoctorView: View {
}
.listRowInsets(EdgeInsets())
.listStyle(.plain)
.frame(maxHeight: .infinity, alignment: .top)
.frame(minHeight: 350, maxHeight: .infinity, alignment: .top)
}
.frame(width: 600)
.fixedSize(horizontal: true, vertical: false)
}
}
struct WarningListView_Previews: PreviewProvider {
static var previews: some View {
PhpDoctorView(empty: true, fake: true, manager: WarningManager())
.frame(width: 600, height: 480)
.previewDisplayName("Empty List")
PhpDoctorView(empty: false, fake: true, manager: WarningManager())
.frame(width: 600, height: 480)
.previewDisplayName("List With All Warnings")
}
#Preview("Empty List") {
PhpDoctorView(empty: true, fake: true, manager: WarningManager())
.frame(width: 600, height: 480)
}
#Preview("List With All Warnings") {
PhpDoctorView(empty: false, fake: true, manager: WarningManager())
.frame(width: 600, height: 480)
}

View File

@ -27,7 +27,7 @@ class PhpDoctorWindowController: PMWindowController {
window.titlebarAppearsTransparent = true
window.delegate = delegate ?? windowController
window.contentView = NSHostingView(rootView: PhpDoctorView())
window.setContentSize(NSSize(width: 600, height: 480))
window.setContentSize(window.contentView!.fittingSize)
App.shared.phpDoctorWindowController = windowController
}

View File

@ -26,7 +26,7 @@ struct WarningView: View {
Text(title.localizedForSwiftUI)
.fontWeight(.bold)
ForEach(paragraphs, id: \.self) { paragraph in
Text(paragraph)
Text(paragraph.localizedForSwiftUI)
.font(.system(size: 13))
}
}
@ -47,23 +47,23 @@ struct WarningView: View {
}
}
struct WarningView_Previews: PreviewProvider {
static var previews: some View {
WarningView(
title: "warnings.helper_permissions.title",
paragraphs: ["warnings.helper_permissions.description"],
documentationUrl: "https://nicoverbruggen.be"
)
.frame(width: 600, height: 105)
WarningView(
title: "warnings.helper_permissions.title",
paragraphs: ["warnings.helper_permissions.description"],
documentationUrl: "https://nicoverbruggen.be"
)
.preferredColorScheme(.dark)
.frame(width: 600, height: 105)
// WarningListView().frame(width: 600, height: 580)
}
#Preview("Light Mode") {
WarningView(
title: "warnings.helper_permissions.title",
paragraphs: ["warnings.helper_permissions.description"],
documentationUrl: "https://nicoverbruggen.be"
)
.frame(width: 600, height: 105)
.padding(25)
}
#Preview("Dark Mode") {
WarningView(
title: "warnings.helper_permissions.title",
paragraphs: ["warnings.helper_permissions.description"],
documentationUrl: "https://nicoverbruggen.be"
)
.preferredColorScheme(.dark)
.frame(width: 600, height: 105)
.padding(25)
}

View File

@ -0,0 +1,32 @@
//
// BrewExtensionsObservable.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 21/11/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
class BrewExtensionsObservable: ObservableObject {
@Published var phpVersion: String {
didSet {
self.loadExtensionData(for: phpVersion)
}
}
@Published var extensions: [BrewPhpExtension] = []
init(phpVersion: String) {
self.phpVersion = phpVersion
self.loadExtensionData(for: phpVersion)
}
public func loadExtensionData(for version: String) {
let tapFormulae = BrewTapFormulae.from(tap: "shivammathur/homebrew-extensions")
if let filteredTapFormulae = tapFormulae[version] {
self.extensions = filteredTapFormulae
}
}
}

View File

@ -0,0 +1,90 @@
//
// PhpExtensionManagerView+Actions.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 21/11/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
extension PhpExtensionManagerView {
public func presentErrorAlert(
title: String,
description: String,
button: String,
style: NSAlert.Style = .critical
) {
Alert.confirm(
onWindow: App.shared.phpExtensionManagerWindowController!.window!,
messageText: title,
informativeText: description,
buttonTitle: button,
secondButtonTitle: "",
style: style,
onFirstButtonPressed: {}
)
}
public func install(_ ext: BrewPhpExtension) {
Task {
await self.runCommand(InstallPhpExtensionCommand(install: [ext]))
}
}
public func confirmUninstall(_ ext: BrewPhpExtension) {
Alert.confirm(
onWindow: App.shared.phpExtensionManagerWindowController!.window!,
messageText: "phpextman.warnings.removal.title".localized(ext.name),
informativeText: "phpextman.warnings.removal.desc".localized(ext.name),
buttonTitle: "phpextman.warnings.removal.button".localized,
buttonIsDestructive: true,
secondButtonTitle: "generic.cancel".localized,
style: .warning,
onFirstButtonPressed: {
Task {
await self.runCommand(RemovePhpExtensionCommand(remove: ext))
}
}
)
}
public func runCommand(_ command: BrewCommand) async {
if PhpEnvironments.shared.isBusy {
self.presentErrorAlert(
title: "phpman.action_prevented_busy.title".localized,
description: "phpman.action_prevented_busy.desc".localized,
button: "generic.ok".localized
)
return
}
let phpVersionManaged = self.manager.phpVersion
do {
self.status.busy = true
try await command.execute { progress in
Task { @MainActor in
self.status.title = progress.title
self.status.description = progress.description
self.status.busy = progress.value != 1
}
}
self.manager.phpVersion = phpVersionManaged
self.status.busy = false
} catch let error {
let error = error as! BrewCommandError
let messages = error.log.suffix(2).joined(separator: "\n")
self.status.busy = false
self.presentErrorAlert(
title: "phpman.failures.install.title".localized,
description: "phpman.failures.install.desc".localized(messages),
button: "generic.ok".localized
)
}
}
}

View File

@ -0,0 +1,190 @@
//
// PhpExtensionManagerView.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 13/11/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
import SwiftUI
struct PhpExtensionManagerView: View {
@ObservedObject var manager: BrewExtensionsObservable
@ObservedObject var status: BusyStatus
@State var searchText: String
init() {
self.searchText = ""
self.status = BusyStatus.busy()
let version = PhpEnvironments.shared.currentInstall!.version.short
self.manager = BrewExtensionsObservable(phpVersion: version)
self.status.busy = false
}
var filteredExtensions: [BrewPhpExtension] {
guard !searchText.isEmpty else {
return manager.extensions
}
return manager.extensions.filter { $0.name.contains(searchText) }
}
var body: some View {
VStack(spacing: 0) {
header.padding(20)
HStack(spacing: 0) {
Text("phpextman.list.showing_count".localized("\(filteredExtensions.count)"))
.padding(10)
.font(.system(size: 12))
phpVersionPicker.disabled(self.status.busy)
}
.frame(maxWidth: .infinity, maxHeight: 35)
.background(Color.blue.opacity(0.3))
.padding(.bottom, 0)
BlockingOverlayView(
busy: self.status.busy,
title: self.status.title,
text: self.status.description
) {
List(Array(self.filteredExtensions.enumerated()), id: \.1.name) { (_, ext) in
listContent(for: ext)
.padding(.vertical, 8)
.padding(.horizontal, 8)
}
.edgesIgnoringSafeArea(.top)
.listStyle(PlainListStyle())
.searchable(text: $searchText)
}
}
.frame(width: 600, height: 600)
}
// MARK: View Variables
private var phpVersionPicker: some View {
Picker("",
selection: $manager.phpVersion) {
ForEach(PhpEnvironments.shared.availablePhpVersions, id: \.self) {
Text("PHP \($0)")
.tag($0)
.font(.system(size: 12))
}
}
.focusable(false)
.labelsHidden()
.pickerStyle(MenuPickerStyle())
.font(.system(size: 12))
.frame(width: 100)
}
private var header: some View {
HStack(alignment: .center, spacing: 15) {
Image(systemName: "puzzlepiece.extension.fill")
.resizable()
.frame(width: 40, height: 40)
.foregroundColor(Color.blue)
.padding(12)
VStack(alignment: .leading, spacing: 5) {
Text("phpextman.description".localizedForSwiftUI)
.font(.system(size: 12))
.frame(maxWidth: .infinity, alignment: .leading)
Text("phpextman.disclaimer".localizedForSwiftUI)
.font(.system(size: 12))
.foregroundColor(.gray)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
private func dependency(named name: String) -> some View {
return Text(name)
.font(.system(size: 9))
.padding(.horizontal, 5)
.padding(.vertical, 1)
.background(Color.appPrimary)
.foregroundColor(Color.white)
.clipShape(Capsule())
.fixedSize(horizontal: true, vertical: true)
}
private func extensionLabel(for ext: BrewPhpExtension) -> some View {
return Group {
if ext.isInstalled {
if let dependent = ext.firstDependent(in: self.manager.extensions) {
Text("phpextman.list.status.dependent".localized(dependent.name))
.font(.system(size: 11))
.foregroundStyle(.secondary)
} else {
Text("phpextman.list.status.can_manage".localizedForSwiftUI)
.font(.system(size: 11))
.foregroundStyle(.secondary)
}
} else {
if ext.hasAlternativeInstall {
Text("phpextman.list.status.external".localizedForSwiftUI)
.font(.system(size: 11))
.foregroundStyle(.orange)
} else {
Text("phpextman.list.status.installable".localizedForSwiftUI)
.font(.system(size: 11))
.foregroundStyle(.secondary)
}
}
}
}
private func listContent(for ext: BrewPhpExtension) -> some View {
HStack(alignment: .center, spacing: 7.0) {
VStack(alignment: .center, spacing: 0) {
HStack {
HStack {
Image(systemName: ext.isInstalled || ext.hasAlternativeInstall
? "puzzlepiece.extension.fill"
: "puzzlepiece.extension")
.resizable()
.frame(width: 24, height: 20)
.foregroundColor(ext.hasAlternativeInstall ? Color.gray : Color.blue)
}.frame(width: 36, height: 24)
VStack(alignment: .leading, spacing: 5) {
HStack {
Text(ext.name).bold()
}
if !ext.extensionDependencies.isEmpty {
HStack(spacing: 3) {
Text("phpextman.list.depends_on".localizedForSwiftUI)
.font(.system(size: 10))
ForEach(ext.extensionDependencies, id: \.self) {
dependency(named: $0)
}
}
}
extensionLabel(for: ext)
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
HStack {
if ext.isInstalled {
Button("phpman.buttons.uninstall".localizedForSwiftUI, role: .destructive) {
self.confirmUninstall(ext)
}.disabled(ext.firstDependent(in: self.manager.extensions) != nil)
} else {
Button("phpman.buttons.install".localizedForSwiftUI) {
self.install(ext)
}.disabled(ext.hasAlternativeInstall)
}
}
}
}
}
#Preview {
PhpExtensionManagerView()
.frame(width: 600, height: 600)
}

View File

@ -0,0 +1,52 @@
//
// PhpVersionManagerWindowController.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 13/11/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
import SwiftUI
class PhpExtensionManagerWindowController: PMWindowController {
// MARK: - Window Identifier
var view: PhpExtensionManagerView!
override var windowName: String {
return "PhpExtensionManager"
}
public static func create(delegate: NSWindowDelegate?) {
let windowController = Self()
windowController.window = NSWindow()
windowController.view = PhpExtensionManagerView()
guard let window = windowController.window else { return }
window.title = "phpextman.window.title".localized
window.styleMask = [.titled, .closable, .miniaturizable]
window.titlebarAppearsTransparent = false
window.delegate = delegate ?? windowController
window.contentView = NSHostingView(rootView: windowController.view)
window.setContentSize(NSSize(width: 600, height: 800))
App.shared.phpExtensionManagerWindowController = windowController
}
public static func show(delegate: NSWindowDelegate? = nil) {
if App.shared.phpExtensionManagerWindowController == nil {
Self.create(delegate: delegate)
}
App.shared.phpExtensionManagerWindowController?.showWindow(self)
App.shared.phpExtensionManagerWindowController?.positionWindowInTopRightCorner()
NSApp.activate(ignoringOtherApps: true)
App.shared.phpExtensionManagerWindowController?.window?.orderFrontRegardless()
}
}

View File

@ -1,5 +1,5 @@
//
// BrewFormula+UI.swift
// BrewPhpFormula+UI.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 02/05/2023.
@ -9,7 +9,7 @@
import Foundation
import SwiftUI
extension BrewFormula {
extension BrewPhpFormula {
var icon: String {
if self.hasUpgrade {
return "arrow.up.square.fill"

View File

@ -0,0 +1,19 @@
//
// BrewPhpFormula.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 13/11/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
class BrewFormulaeObservable: ObservableObject {
@Published var phpVersions: [BrewPhpFormula] = []
var upgradeable: [BrewPhpFormula] {
return phpVersions.filter { formula in
formula.hasUpgrade
}
}
}

View File

@ -8,61 +8,68 @@
import Foundation
class FakeBrewFormulaeHandler: HandlesBrewFormulae {
// swiftlint:disable function_body_length
public func loadPhpVersions(loadOutdated: Bool) async -> [BrewFormula] {
// swiftlint:disable function_body_length
class FakeBrewFormulaeHandler: HandlesBrewPhpFormulae {
public func loadPhpVersions(loadOutdated: Bool) async -> [BrewPhpFormula] {
return [
BrewFormula(
BrewPhpFormula(
name: "php@9.9",
displayName: "PHP 9.9",
installedVersion: nil,
upgradeVersion: "9.9.0",
prerelease: true
),
BrewFormula(
name: "php@8.3",
BrewPhpFormula(
name: "php@8.4",
displayName: "PHP 8.4",
installedVersion: nil,
upgradeVersion: "8.4.0",
prerelease: true
),
BrewPhpFormula(
name: "php",
displayName: "PHP 8.3",
installedVersion: nil,
upgradeVersion: "8.3.0",
prerelease: true
),
BrewFormula(
name: "php",
BrewPhpFormula(
name: "php@8.2",
displayName: "PHP 8.2",
installedVersion: "8.2.3",
upgradeVersion: "8.2.4"
),
BrewFormula(
BrewPhpFormula(
name: "php@8.1",
displayName: "PHP 8.1",
installedVersion: "8.1.17",
upgradeVersion: nil
),
BrewFormula(
BrewPhpFormula(
name: "php@8.0",
displayName: "PHP 8.0",
installedVersion: nil,
upgradeVersion: nil
),
BrewFormula(
BrewPhpFormula(
name: "php@7.4",
displayName: "PHP 7.4",
installedVersion: nil,
upgradeVersion: nil
),
BrewFormula(
BrewPhpFormula(
name: "php@7.3",
displayName: "PHP 7.3",
installedVersion: nil,
upgradeVersion: nil
),
BrewFormula(
BrewPhpFormula(
name: "php@7.2",
displayName: "PHP 7.2",
installedVersion: nil,
upgradeVersion: nil
),
BrewFormula(
BrewPhpFormula(
name: "php@7.1",
displayName: "PHP 7.1",
installedVersion: nil,
@ -71,3 +78,4 @@ class FakeBrewFormulaeHandler: HandlesBrewFormulae {
]
}
}
// swiftlint:enable function_body_length

View File

@ -0,0 +1,163 @@
//
// PhpVersionManagerView+Interactivity.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 07/11/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
import SwiftUI
extension PhpVersionManagerView {
public func runCommand(_ command: ModifyPhpVersionCommand) async {
if PhpEnvironments.shared.isBusy {
self.presentErrorAlert(
title: "phpman.action_prevented_busy.title".localized,
description: "phpman.action_prevented_busy.desc".localized,
button: "generic.ok".localized
)
return
}
do {
self.setBusyStatus(true)
try await command.execute { progress in
Task { @MainActor in
self.status.title = progress.title
self.status.description = progress.description
self.status.busy = progress.value != 1
// Whenever a key step is finished, refresh the PHP versions
if progress.value == 1 {
await self.handler.refreshPhpVersions(loadOutdated: false)
}
}
}
// Finally, after completing the command, also refresh PHP versions
await self.handler.refreshPhpVersions(loadOutdated: false)
// and mark the app as no longer busy
self.setBusyStatus(false)
} catch let error {
let error = error as! BrewCommandError
let messages = error.log.suffix(2).joined(separator: "\n")
self.setBusyStatus(false)
await self.handler.refreshPhpVersions(loadOutdated: false)
self.presentErrorAlert(
title: "phpman.failures.install.title".localized,
description: "phpman.failures.install.desc".localized(messages),
button: "generic.ok".localized
)
}
}
public func repairAll() async {
await self.runCommand(ModifyPhpVersionCommand(
title: "phpman.operations.repairing".localized,
upgrading: [],
installing: []
))
}
public func upgradeAll(_ formulae: [BrewPhpFormula]) async {
await self.runCommand(ModifyPhpVersionCommand(
title: "phpman.operations.updating".localized,
upgrading: formulae,
installing: []
))
}
public func install(_ formula: BrewPhpFormula) async {
await self.runCommand(ModifyPhpVersionCommand(
title: "phpman.operations.installing".localized(formula.displayName),
upgrading: [],
installing: [formula]
))
}
public func confirmUninstall(_ formula: BrewPhpFormula) async {
// Disallow removal of the currently active versipn
if formula.installedVersion == PhpEnvironments.shared.currentInstall?.version.text {
self.presentErrorAlert(
title: "phpman.uninstall_prevented.title".localized,
description: "phpman.uninstall_prevented.desc".localized,
button: "generic.ok".localized
)
return
}
Alert.confirm(
onWindow: App.shared.phpVersionManagerWindowController!.window!,
messageText: "phpman.warnings.removal.title".localized(formula.displayName),
informativeText: "phpman.warnings.removal.desc".localized(formula.displayName),
buttonTitle: "phpman.warnings.removal.button".localized,
buttonIsDestructive: true,
secondButtonTitle: "generic.cancel".localized,
style: .warning,
onFirstButtonPressed: {
Task { await self.uninstall(formula) }
}
)
}
public func uninstall(_ formula: BrewPhpFormula) async {
let command = RemovePhpVersionCommand(formula: formula.name)
do {
self.setBusyStatus(true)
try await command.execute { progress in
Task { @MainActor in
self.status.title = progress.title
self.status.description = progress.description
self.status.busy = progress.value != 1
if progress.value == 1 {
await self.handler.refreshPhpVersions(loadOutdated: false)
self.setBusyStatus(false)
}
}
}
} catch {
self.setBusyStatus(false)
self.presentErrorAlert(
title: "phpman.failures.uninstall.title".localized,
description: "phpman.failures.uninstall.desc".localized(
"brew uninstall \(formula.name) --force"
),
button: "generic.ok".localized
)
}
}
public func setBusyStatus(_ busy: Bool) {
Task { @MainActor in
PhpEnvironments.shared.isBusy = busy
self.status.busy = busy
}
}
public func presentErrorAlert(
title: String,
description: String,
button: String,
style: NSAlert.Style = .critical
) {
Alert.confirm(
onWindow: App.shared.phpVersionManagerWindowController!.window!,
messageText: title,
informativeText: description,
buttonTitle: button,
secondButtonTitle: "",
style: style,
onFirstButtonPressed: {}
)
}
var hasUpdates: Bool {
return self.formulae.phpVersions.contains { formula in
return formula.hasUpgrade
}
}
}

View File

@ -9,37 +9,38 @@
import Foundation
import SwiftUI
// swiftlint:disable type_body_length
struct PhpVersionManagerView: View {
@ObservedObject var formulae: BrewFormulaeObservable
@ObservedObject var status: PhpFormulaeStatus
var handler: HandlesBrewFormulae
@ObservedObject var status: BusyStatus
var handler: HandlesBrewPhpFormulae
init(
formulae: BrewFormulaeObservable,
handler: HandlesBrewFormulae
handler: HandlesBrewPhpFormulae
) {
self.formulae = formulae
self.handler = handler
self.status = PhpFormulaeStatus(
self.status = BusyStatus(
busy: true,
title: "phpman.busy.title".localized,
description: "phpman.busy.description.outdated".localized
)
if handler is FakeBrewFormulaeHandler {
Task { [self] in
await self.handler.refreshPhpVersions(loadOutdated: false)
self.status.busy = false
}
} else {
Task { [self] in
Task { [self] in
if handler is FakeBrewFormulaeHandler {
await self.fakeInitialLoad()
} else {
await self.initialLoad()
}
}
}
private func fakeInitialLoad() async {
await self.handler.refreshPhpVersions(loadOutdated: false)
self.status.busy = false
}
private func initialLoad() async {
guard let version = Brew.shared.version else {
return
@ -47,6 +48,7 @@ struct PhpVersionManagerView: View {
await delay(seconds: 1)
// PHP formulae may not be installable with older Homebrew version
if version.major != 4 {
Task { @MainActor in
self.presentErrorAlert(
@ -58,6 +60,16 @@ struct PhpVersionManagerView: View {
}
}
// PHP formulae may be out of date past the cutoff date
if Date.fromString(Constants.PhpFormulaeCutoffDate)! < Date.now {
self.presentErrorAlert(
title: "phpman.warnings.outdated.title".localized,
description: "phpman.warnings.outdated.desc".localized(version.text),
button: "generic.ok".localized,
style: .warning
)
}
await PhpEnvironments.detectPhpVersions()
await self.handler.refreshPhpVersions(loadOutdated: false)
await self.handler.refreshPhpVersions(loadOutdated: true)
@ -70,7 +82,9 @@ struct PhpVersionManagerView: View {
self.status.title = "phpman.busy.title".localized
self.status.description = "phpman.busy.description.outdated".localized
}
await self.handler.refreshPhpVersions(loadOutdated: true)
Task { @MainActor in
self.status.busy = false
}
@ -78,294 +92,208 @@ struct PhpVersionManagerView: View {
var body: some View {
VStack {
HStack(alignment: .center, spacing: 15) {
Image(systemName: "arrow.down.to.line.circle.fill")
.resizable()
.frame(width: 40, height: 40)
.foregroundColor(Color.blue)
.padding(12)
VStack(alignment: .leading, spacing: 5) {
Text("phpman.description".localizedForSwiftUI)
.font(.system(size: 12))
.frame(maxWidth: .infinity, alignment: .leading)
Text("phpman.disclaimer".localizedForSwiftUI)
.font(.system(size: 12))
.foregroundColor(.gray)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.padding(10)
header.padding(10)
if self.hasUpdates {
Divider()
HStack(alignment: .center, spacing: 15) {
Text("phpman.has_updates.description".localizedForSwiftUI)
.foregroundColor(.gray)
.font(.system(size: 11))
Button("phpman.has_updates.button".localizedForSwiftUI, action: {
Task { await self.upgradeAll(self.formulae.upgradeable) }
})
.focusable(false)
.disabled(self.status.busy)
}
.padding(10)
hasUpdatesView
} else {
Divider()
HStack(alignment: .center, spacing: 15) {
Button {
Task { await self.reload() }
} label: {
Image(systemName: "arrow.clockwise")
.buttonStyle(.automatic)
.controlSize(.large)
}
.focusable(false)
.disabled(self.status.busy)
Text("phpman.refresh.button.description".localizedForSwiftUI)
.foregroundColor(.gray)
.font(.system(size: 11))
}
.padding(10)
noUpdatesView
}
BlockingOverlayView(busy: self.status.busy, title: self.status.title, text: self.status.description) {
List(Array(formulae.phpVersions.enumerated()), id: \.1.name) { (index, formula) in
HStack(alignment: .center, spacing: 7.0) {
Image(systemName: formula.icon)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 16, height: 16)
.foregroundColor(formula.iconColor)
.padding(.horizontal, 5)
VStack(alignment: .leading, spacing: 2) {
HStack {
Text(formula.displayName).bold()
if formula.prerelease {
Text("phpman.version.prerelease".localized.uppercased())
.font(.system(size: 9))
.padding(.horizontal, 5)
.padding(.vertical, 1)
.background(Color.appPrimary)
.foregroundColor(Color.white)
.clipShape(Capsule())
.fixedSize(horizontal: true, vertical: true)
}
}
if formula.isInstalled && formula.hasUpgrade {
Text("phpman.version.has_update".localized(
formula.installedVersion!,
formula.upgradeVersion!
))
.font(.system(size: 11))
.foregroundColor(.gray)
} else if formula.isInstalled && formula.installedVersion != nil {
Text("phpman.version.installed".localized(formula.installedVersion!))
.font(.system(size: 11))
.foregroundColor(.gray)
} else {
Text("phpman.version.available_for_installation".localizedForSwiftUI)
.font(.system(size: 11))
.foregroundColor(.gray)
}
if !formula.healthy {
Text("phpman.version.broken".localizedForSwiftUI)
.font(.system(size: 11))
.foregroundColor(.red)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
if !formula.healthy {
Button("phpman.buttons.repair".localizedForSwiftUI, role: .destructive) {
Task { await self.repairAll() }
}
}
if formula.isInstalled {
Button("phpman.buttons.uninstall".localizedForSwiftUI, role: .destructive) {
Task { await self.confirmUninstall(formula) }
}
} else {
Button("phpman.buttons.install".localizedForSwiftUI) {
Task { await self.install(formula) }
}
}
BlockingOverlayView(
busy: self.status.busy,
title: self.status.title,
text: self.status.description
) {
if #available(macOS 13, *) {
List(Array(formulae.phpVersions.enumerated()), id: \.1.name) { (index, formula) in
listContent(for: formula)
.listRowBackground(
index % 2 == 0
? Color.gray.opacity(0)
: Color.gray.opacity(0.08)
)
.padding(.vertical, 8)
.padding(.horizontal, 8)
.listRowSeparator(.hidden)
}
.listRowBackground(index % 2 == 0 ? Color.gray.opacity(0): Color.gray.opacity(0.08))
.padding(.vertical, 8)
.padding(.horizontal, 8)
.edgesIgnoringSafeArea(.top)
.listStyle(PlainListStyle())
} else {
List(Array(formulae.phpVersions.enumerated()), id: \.1.name) { (index, formula) in
listContent(for: formula)
.listRowBackground(
index % 2 == 0
? Color.gray.opacity(0)
: Color.gray.opacity(0.08)
)
.padding(.vertical, 8)
.padding(.horizontal, 8)
}
.edgesIgnoringSafeArea(.top)
.listStyle(PlainListStyle())
}
.edgesIgnoringSafeArea(.top)
.listStyle(PlainListStyle())
}
}.frame(width: 600, height: 600)
}
public func runCommand(_ command: InstallAndUpgradeCommand) async {
if PhpEnvironments.shared.isBusy {
self.presentErrorAlert(
title: "phpman.action_prevented_busy.title".localized,
description: "phpman.action_prevented_busy.desc".localized,
button: "generic.ok".localized
)
return
// MARK: View Variables
private var header: some View {
HStack(alignment: .center, spacing: 15) {
Image(systemName: "arrow.down.to.line.circle.fill")
.resizable()
.frame(width: 40, height: 40)
.foregroundColor(Color.blue)
.padding(12)
VStack(alignment: .leading, spacing: 5) {
Text("phpman.description".localizedForSwiftUI)
.font(.system(size: 12))
.frame(maxWidth: .infinity, alignment: .leading)
Text("phpman.disclaimer".localizedForSwiftUI)
.font(.system(size: 12))
.foregroundColor(.gray)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
do {
self.setBusyStatus(true)
try await command.execute { progress in
Task { @MainActor in
self.status.title = progress.title
self.status.description = progress.description
self.status.busy = progress.value != 1
private var hasUpdatesView: some View {
Group {
Divider()
HStack(alignment: .center, spacing: 15) {
Text("phpman.has_updates.description".localizedForSwiftUI)
.foregroundColor(.gray)
.font(.system(size: 11))
// Whenever a key step is finished, refresh the PHP versions
if progress.value == 1 {
await self.handler.refreshPhpVersions(loadOutdated: false)
}
Button("phpman.has_updates.button".localizedForSwiftUI, action: {
Task { await self.upgradeAll(self.formulae.upgradeable) }
})
.focusable(false)
.disabled(self.status.busy)
}
.padding(10)
}
}
private var noUpdatesView: some View {
Group {
Divider()
HStack(alignment: .center, spacing: 15) {
Button {
Task { await self.reload() }
} label: {
Image(systemName: "arrow.clockwise")
.buttonStyle(.automatic)
.controlSize(.large)
}
.focusable(false)
.disabled(self.status.busy)
Text("phpman.refresh.button.description".localizedForSwiftUI)
.foregroundColor(.gray)
.font(.system(size: 11))
}
.padding(10)
}
}
private var prereleaseBadge: some View {
Text("phpman.version.prerelease".localized.uppercased())
.font(.system(size: 9))
.padding(.horizontal, 5)
.padding(.vertical, 1)
.background(Color.appPrimary)
.foregroundColor(Color.white)
.clipShape(Capsule())
.fixedSize(horizontal: true, vertical: true)
}
// MARK: View Builders
private func listContent(for formula: BrewPhpFormula) -> some View {
HStack(alignment: .center, spacing: 7.0) {
formulaIcon(for: formula)
formulaDescription(for: formula)
formulaButtons(for: formula)
}
}
private func formulaButtons(for formula: BrewPhpFormula) -> some View {
HStack {
if !formula.healthy {
Button("phpman.buttons.repair".localizedForSwiftUI, role: .destructive) {
Task { await self.repairAll() }
}
}
// Finally, after completing the command, also refresh PHP versions
await self.handler.refreshPhpVersions(loadOutdated: false)
// and mark the app as no longer busy
self.setBusyStatus(false)
} catch let error {
let error = error as! BrewCommandError
let messages = error.log.suffix(2).joined(separator: "\n")
self.setBusyStatus(false)
await self.handler.refreshPhpVersions(loadOutdated: false)
self.presentErrorAlert(
title: "phpman.failures.install.title".localized,
description: "phpman.failures.install.desc".localized(messages),
button: "generic.ok".localized
)
}
}
public func repairAll() async {
await self.runCommand(InstallAndUpgradeCommand(
title: "phpman.operations.repairing".localized,
upgrading: [],
installing: []
))
}
public func upgradeAll(_ formulae: [BrewFormula]) async {
await self.runCommand(InstallAndUpgradeCommand(
title: "phpman.operations.updating".localized,
upgrading: formulae,
installing: []
))
}
public func install(_ formula: BrewFormula) async {
await self.runCommand(InstallAndUpgradeCommand(
title: "phpman.operations.installing".localized(formula.displayName),
upgrading: [],
installing: [formula]
))
}
public func confirmUninstall(_ formula: BrewFormula) async {
// Disallow removal of the currently active versipn
if formula.installedVersion == PhpEnvironments.shared.currentInstall?.version.text {
self.presentErrorAlert(
title: "phpman.uninstall_prevented.title".localized,
description: "phpman.uninstall_prevented.desc".localized,
button: "generic.ok".localized
)
return
}
Alert.confirm(
onWindow: App.shared.phpVersionManagerWindowController!.window!,
messageText: "phpman.warnings.removal.title".localized(formula.displayName),
informativeText: "phpman.warnings.removal.desc".localized(formula.displayName),
buttonTitle: "phpman.warnings.removal.button".localized,
buttonIsDestructive: true,
secondButtonTitle: "generic.cancel".localized,
style: .warning,
onFirstButtonPressed: {
Task { await self.uninstall(formula) }
if formula.isInstalled {
Button("phpman.buttons.uninstall".localizedForSwiftUI, role: .destructive) {
Task { await self.confirmUninstall(formula) }
}
} else {
Button("phpman.buttons.install".localizedForSwiftUI) {
Task { await self.install(formula) }
}.disabled(formula.hasUpgradedFormulaAlias)
}
)
}
}
public func uninstall(_ formula: BrewFormula) async {
let command = RemovePhpVersionCommand(formula: formula.name)
private func formulaDescription(for formula: BrewPhpFormula) -> some View {
VStack(alignment: .leading, spacing: 2) {
HStack {
Text(formula.displayName).bold()
do {
self.setBusyStatus(true)
try await command.execute { progress in
Task { @MainActor in
self.status.title = progress.title
self.status.description = progress.description
self.status.busy = progress.value != 1
if progress.value == 1 {
await self.handler.refreshPhpVersions(loadOutdated: false)
self.setBusyStatus(false)
}
if formula.prerelease {
prereleaseBadge
}
}
} catch {
self.setBusyStatus(false)
self.presentErrorAlert(
title: "phpman.failures.uninstall.title".localized,
description: "phpman.failures.uninstall.desc".localized(
"brew uninstall \(formula.name) --force"
),
button: "generic.ok".localized
)
if formula.hasUpgradedFormulaAlias {
Text("phpman.version.automatic_upgrade".localized(formula.shortVersion!))
.font(.system(size: 11))
.foregroundColor(.gray)
} else if formula.isInstalled && formula.hasUpgrade {
Text("phpman.version.has_update".localized(
formula.installedVersion!,
formula.upgradeVersion!
))
.font(.system(size: 11))
.foregroundColor(.gray)
} else if formula.isInstalled && formula.installedVersion != nil {
Text("phpman.version.installed".localized(formula.installedVersion!))
.font(.system(size: 11))
.foregroundColor(.gray)
} else {
Text("phpman.version.available_for_installation".localizedForSwiftUI)
.font(.system(size: 11))
.foregroundColor(.gray)
}
if !formula.healthy {
Text("phpman.version.broken".localizedForSwiftUI)
.font(.system(size: 11))
.foregroundColor(.red)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}
public func setBusyStatus(_ busy: Bool) {
Task { @MainActor in
PhpEnvironments.shared.isBusy = busy
self.status.busy = busy
}
}
public func presentErrorAlert(
title: String,
description: String,
button: String,
style: NSAlert.Style = .critical
) {
Alert.confirm(
onWindow: App.shared.phpVersionManagerWindowController!.window!,
messageText: title,
informativeText: description,
buttonTitle: button,
secondButtonTitle: "",
style: style,
onFirstButtonPressed: {}
)
}
var hasUpdates: Bool {
return self.formulae.phpVersions.contains { formula in
return formula.hasUpgrade
}
private func formulaIcon(for formula: BrewPhpFormula) -> some View {
Image(systemName: formula.icon)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 16, height: 16)
.foregroundColor(formula.iconColor)
.padding(.horizontal, 5)
}
}
// swiftlint:enable type_body_length
struct PhpVersionManagerView_Previews: PreviewProvider {
static var previews: some View {
PhpVersionManagerView(
formulae: Brew.shared.formulae,
handler: FakeBrewFormulaeHandler()
).frame(width: 600, height: 600)
}
#Preview {
PhpVersionManagerView(
formulae: Brew.shared.formulae,
handler: FakeBrewFormulaeHandler()
).frame(width: 600, height: 600)
}

View File

@ -25,7 +25,7 @@ class PhpVersionManagerWindowController: PMWindowController {
windowController.window = NSWindow()
windowController.view = PhpVersionManagerView(
formulae: Brew.shared.formulae,
handler: BrewFormulaeHandler()
handler: BrewPhpFormulaeHandler()
)
guard let window = windowController.window else { return }

View File

@ -12,7 +12,11 @@
"mi_no_php_linked" = "Keine PHP-Version verknüpft!";
"mi_fix_php_link" = "Automatisch beheben...";
"mi_no_php_linked_explain" = "Was ist das?";
"mi_php_version_manager" = "PHP Versionen Manager...";
"mi_php_version_manager" = "PHP-Installationen verwalten...";
"mi_php_ext_manager" = "PHP-Erweiterungen verwalten...";
"mi_php_config_manager" = "PHP-Konfigurationseditor...";
"mi_manage_limits" = "Limits verwalten...";
"mi_diagnostics" = "Diagnosen";
"mi_active_services" = "Aktive Dienste";
@ -81,6 +85,27 @@
"mi_xdebug_actions" = "Aktionen";
"mi_xdebug_disable_all" = "Alle Modi deaktivieren";
// PHPEXTMAN
"phpextman.window.title" = "Erweiterungen";
"phpextman.description" = "**PHP Extension Manager** ermöglicht Ihnen die Verwaltung verschiedener PHP-Erweiterungen mit einem einfachen Klick auf die Schaltfläche. Da Homebrew verwendet wird, müssen Erweiterungen nicht on-the-fly mit `pecl` kompiliert werden.";
"phpextman.disclaimer" = "Bestimmte Erweiterungen erfordern möglicherweise die Installation anderer Abhängigkeiten, im Allgemeinen sollte jedoch die Installation von Erweiterungen viel schneller sein als die Installation von PHP-Versionen.";
"phpextman.warnings.removal.title" = "Die Erweiterung `%@` deinstallieren?";
"phpextman.warnings.removal.desc" = "Die Erweiterung und ihre einzigartige Konfigurationsdatei werden entfernt. Die Funktionalität der Erweiterung wird für diese PHP-Installation nicht mehr verfügbar sein. Sind Sie sicher?
(Wenn die Erweiterung mit einem nicht standardmäßigen Dateinamen aktiviert ist, wird sie nicht entfernt. Wenn Sie also nicht möchten, dass die .ini-Datei entfernt wird, benennen Sie sie am besten einfach um. In diesem Fall wird die Erweiterung nur als Teil des Bereinigungsprozesses deaktiviert.)";
"phpextman.warnings.removal.button" = "Deinstallieren";
"phpextman.list.showing_count" = "Aktuell werden %@ Erweiterungen für folgendes angezeigt:";
"phpextman.list.depends_on" = "Hängt ab von:";
"phpextman.list.status.external" = "Diese Erweiterung ist bereits über eine andere Quelle installiert und kann nicht verwaltet werden.";
"phpextman.list.status.installable" = "Diese Erweiterung kann installiert werden.";
"phpextman.list.status.dependent" = "Sie können diese nicht deinstallieren, bevor Sie %@ deinstallieren.";
"phpextman.list.status.can_manage" = "Diese Erweiterung ist installiert und kann von PHP Monitor verwaltet werden.";
// PHPMAN
"phpman.busy.title" = "Suche nach Aktualisierungen...";
@ -90,6 +115,7 @@
"phpman.version.has_update" = "Version %@ installiert, %@ verfügbar.";
"phpman.version.installed" = "Version %@ ist derzeit installiert.";
"phpman.version.available_for_installation" = "Diese Version kann installiert werden.";
"phpman.version.automatic_upgrade" = "Diese Version wird automatisch installiert, indem eine ältere Version aktualisiert wird.";
"phpman.buttons.uninstall" = "Deinstallieren";
"phpman.buttons.install" = "Installieren";
"phpman.buttons.update" = "Aktualisieren";
@ -212,6 +238,9 @@ Möglicherweise werden Sie während des Deinstallationsvorgangs nach Ihrem Passw
"domain_list.columns.kind" = "Art";
"domain_list.columns.project_type" = "Projekttyp";
"domain_list.extensions" = "Erweiterungen umschalten";
"domain_list.applies_to" = "Gilt für PHP %@";
// CHOOSE WHAT TO ADD
"selection.title" = "Welche Art von Domain möchten Sie einrichten?";
@ -788,3 +817,11 @@ Bitte beachten Sie, dass einige Funktionen (unten ausgegraut) derzeit nicht verf
"onboarding.tour.feature_unavailable" = "Diese Funktion ist derzeit nicht verfügbar und erfordert die Installation von Laravel Valet.";
"onboarding.tour.once" = "Sie sehen die Willkommenstour nur einmal. Sie können die Willkommenstour später über das Symbol in der Menüleiste (im Menü unter Erste Hilfe & Dienste) erneut öffnen.";
"onboarding.tour.close" = "Tour beenden";
// LANGUAGE CHOICE
"prefs.language" = "Sprache:";
"prefs.language_options_desc" = "Wählen Sie eine andere Sprache für die Verwendung mit PHP Monitor. Um diese Änderung vollständig anzuwenden, müssen Sie die App neu starten.";
"alert.language_changed.title" = "Sie sollten PHP Monitor neu starten!";
"alert.language_changed.subtitle" = "Sie haben soeben die Anzeigesprache von PHP Monitor geändert. Das Menü wird sofort die korrekte Sprache verwenden, aber Sie müssen die App möglicherweise neu starten, damit alle Texte in der App Ihre neue Sprachwahl widerspiegeln.";

View File

@ -12,7 +12,9 @@
"mi_no_php_linked" = "No PHP version linked!";
"mi_fix_php_link" = "Fix Automatically...";
"mi_no_php_linked_explain" = "What's This?";
"mi_php_version_manager" = "PHP Version Manager...";
"mi_php_version_manager" = "Manage PHP Installations...";
"mi_php_ext_manager" = "Manage PHP Extensions...";
"mi_php_config_manager" = "PHP Configuration Editor...";
"mi_manage_limits" = "Manage Limits...";
@ -98,6 +100,26 @@
"php_ini.upload_max_filesize.title" = "Upload Max Size";
"php_ini.upload_max_filesize.description" = "The maximum size of an uploaded file. POST Max Size must be larger than this value.";
// PHPEXTMAN
"phpextman.window.title" = "Extensions";
"phpextman.description" = "**PHP Extension Manager** lets you manage different PHP extensions with a simple click of the button. Because Homebrew is used, extensions won't need to be compiled on the fly using `pecl`.";
"phpextman.disclaimer" = "Certain extensions may require other dependencies to be installed, but generally speaking installing extensions should be much faster than installing PHP versions.";
"phpextman.warnings.removal.title" = "Uninstall the extension `%@`?";
"phpextman.warnings.removal.desc" = "The extension and its unique configuration file will be removed. The extension's functionality will no longer be available for this PHP installation. Are you sure?
(If the extension is enabled using a non-standard filename, it will not be removed. So if you don't want the .ini file to be removed, it's best to simply rename it to something else. In that case, the extension will only be disabled as part of the clean-up process.)";
"phpextman.warnings.removal.button" = "Uninstall";
"phpextman.list.showing_count" = "Currently showing %@ extensions for:";
"phpextman.list.depends_on" = "Depends on:";
"phpextman.list.status.external" = "This extension is already installed via another source, and cannot be managed.";
"phpextman.list.status.installable" = "This extension can be installed.";
"phpextman.list.status.dependent" = "You cannot uninstall this before uninstalling %@.";
"phpextman.list.status.can_manage" = "This extension is installed and can be managed by PHP Monitor.";
// PHPMAN
"phpman.busy.title" = "Checking for updates!";
@ -107,12 +129,22 @@
"phpman.version.has_update" = "Version %@ installed, %@ available.";
"phpman.version.installed" = "Version %@ is currently installed.";
"phpman.version.available_for_installation" = "This version can be installed.";
"phpman.version.automatic_upgrade" = "This version will be automatically installed by upgrading an older version.";
"phpman.buttons.uninstall" = "Uninstall";
"phpman.buttons.install" = "Install";
"phpman.buttons.update" = "Update";
"phpman.buttons.repair" = "Repair";
"phpman.version.prerelease" = "Pre-release";
"phpman.steps.installing" = "Installing %@";
"phpman.steps.removing" = "Removing %@";
"phpman.steps.reloading" = "Reloading PHP versions...";
"phpman.steps.preparing" = "PHP Monitor is preparing Homebrew...";
"phpman.steps.wait" = "Please wait...";
"phpman.steps.completed" = "Operation completed!";
"phpman.steps.success" = "The operation has succeeded.";
"phpman.steps.failure" = "The command failed to run correctly.";
"phpman.title" = "PHP Version Manager";
"phpman.description" = "**PHP Version Manager** lets you install, upgrade and delete different PHP versions via Homebrew without needing to run the commands in the terminal yourself.";
"phpman.disclaimer" = "Please note that installing or upgrading PHP versions may cause other Homebrew packages to be upgraded as well. Most installation steps usually take some time, so please be patient while Homebrew does its job.";
@ -122,6 +154,9 @@
"phpman.has_updates.description" = "One or more updates are available. (Please note that PHP Monitor will always install or update PHP versions in bulk, so you will always upgrade all installations at once.)";
"phpman.has_updates.button" = "Upgrade All";
"phpman.warnings.outdated.title" = "This version of PHP Monitor is outdated";
"phpman.warnings.outdated.desc" = "It is highly likely that the Homebrew formulae have changed since this version of PHP Monitor was created. I highly recommend updating the application before using the version manager to install, remove or upgrade PHP versions.";
"phpman.warnings.unsupported.title" = "Your version of Homebrew may cause issues";
"phpman.warnings.unsupported.desc" = "No functionality is disabled, but some commands may not work as expected. You are currently running Homebrew %@.
@ -229,6 +264,9 @@ You may be asked for your password during the uninstallation process if file per
"domain_list.columns.kind" = "Kind";
"domain_list.columns.project_type" = "Project Type";
"domain_list.extensions" = "Toggle Extensions";
"domain_list.applies_to" = "Applies to PHP %@";
// CHOOSE WHAT TO ADD
"selection.title" = "What kind of domain would you like to set up?";
@ -805,3 +843,11 @@ Please note that some features (greyed out below) are currently unavailable beca
"onboarding.tour.feature_unavailable" = "This feature is currently unavailable and requires Laravel Valet to be installed.";
"onboarding.tour.once" = "You will only see the Welcome Tour once. You can re-open the Welcome Tour later via the menu bar icon (available in the menu, under First Aid & Services).";
"onboarding.tour.close" = "Close Tour";
// LANGUAGE CHOICE
"prefs.language" = "Language:";
"prefs.language_options_desc" = "Choose a different language to use with PHP Monitor. To fully apply this change, you must restart the app.";
"alert.language_changed.title" = "You must restart PHP Monitor!";
"alert.language_changed.subtitle" = "You just changed the display language of PHP Monitor. The menu will immediately use the correct language, but you may need to restart the app for all text throughout the app to reflect your new language choice.";

View File

@ -0,0 +1,838 @@
// MENU ITEMS (MI)
"mi_busy" = "PHP Monitor est occupé...";
"mi_unsure" = "Nous ne sommes pas sûrs de quelle version de PHP vous utilisez.";
"mi_php_version" = "Version globale: PHP";
"mi_php_switch" = "Passer à PHP";
"mi_php_unsupported" = "Certaines versions de PHP installées ne sont pas affichées.";
"mi_php_broken_1" = "Oups ! Il semblerait que votre installation de PHP soit endommagée...";
"mi_php_broken_2" = "Essayez d'exécuter `php -v` dans votre terminal.";
"mi_php_broken_3" = "Vous pouvez également essayer de passer à une autre version.";
"mi_php_broken_4" = "Exécuter `brew reinstall php` (ou la commande équivalente pour votre version désirée) pourrait aider.";
"mi_no_php_linked" = "Aucune version de PHP n'est liée !";
"mi_fix_php_link" = "Réparer Automatiquement...";
"mi_no_php_linked_explain" = "Qu'est-ce que c'est ?";
"mi_php_version_manager" = "Gérer les Installations PHP...";
"mi_php_ext_manager" = "Gérer les Extensions PHP...";
"mi_php_config_manager" = "Éditeur de Configuration PHP...";
"mi_manage_limits" = "Gérer les Limites...";
"mi_diagnostics" = "Diagnostiques";
"mi_active_services" = "Services actifs";
"mi_restart_php_fpm" = "Redémarrer le service: php";
"mi_restart_nginx" = "Redémarrer le service: nginx";
"mi_restart_dnsmasq" = "Redémarrer le service: dnsmasq";
"mi_manage_services" = "Gérer les services";
"mi_restart_valet_services" = "Redémarrer les services Valet";
"mi_stop_valet_services" = "Arrêter les services Valet";
"mi_fix_my_valet" = "Réparer mon Valet...";
"mi_fix_my_valet_tooltip" = "Un problème avec votre installation de Valet ? Essayez les correctifs automatiques de PHP Monitor qui vous permettront d'être à nouveau opérationnel en un rien de temps !";
"mi_fix_brew_permissions" = "Restaurer les permissions Homebrew";
"mi_fix_brew_permissions_tooltip" = "Des problèmes de permission lors de l'exécution de `brew upgrade` ? PHP Monitor vient à votre rescousse !";
"mi_php_refresh" = "Actualiser les informations";
"mi_configuration" = "Configuration PHP";
"mi_limits" = "Configuration des limites";
"mi_memory_limit" = "Limite de mémoire";
"mi_post_max_size" = "Taille maximale POST";
"mi_upload_max_filesize" = "Taille maximal d'upload";
"mi_manual_actions" = "Actions manuelles";
"mi_services" = "Services";
"mi_other" = "Dépannage & services";
"mi_first_aid" = "Dépannage";
"mi_xdebug_mode" = "Gérer Xdebug";
"mi_composer" = "Composer";
"mi_valet_config" = "Localiser le dossier Valet (.config/valet)";
"mi_php_config" = "Localiser le fichier de configuration de PHP (php.ini)";
"mi_phpmon_config" = "Localiser le dossier de PHP Monitor (.config/phpmon)";
"mi_global_composer" = "Localiser le fichier Composer global (.composer)";
"mi_phpinfo" = "Afficher la configuration actuelle (phpinfo)";
"mi_update_global_composer" = "Mettre à jour les dépendances globales de Composer...";
"mi_detected_extensions" = "Extensions détectées";
"mi_no_extensions_detected" = "Aucune extension supplémentaire détectée.";
"mi_php_doctor" = "PHP Doctor";
"mi_fa_php_doctor" = "Ouvrir PHP Doctor...";
"mi_recommendations_count" = "%i problèmes(s) détecté(s)!";
"mi_view_recommendations" = "Afficher les recommandations...";
"mi_valet" = "Laravel Valet";
"mi_domain_list" = "Afficher la liste des domaines...";
"mi_preferences" = "Préférences...";
"mi_donate" = "Faire un don...";
"mi_check_for_updates" = "Vérifier les mises à jour...";
"mi_lite_mode" = "À propos du mode Standalone...";
"mi_quit" = "Quitter PHP Monitor";
"mi_about" = "À propos de PHP Monitor";
"mi_presets_title" = "Paramètres de configuration";
"mi_apply_presets_title" = "Appliquer les paramètres de configuration";
"mi_revert_to_prev_config" = "Revenir à la configuration précédente...";
"mi_profiles_loaded" = "%i profils chargés à partir du fichier de configuration";
"mi_no_presets" = "Aucun paramètre disponible.";
"mi_set_up_presets" = "En savoir plus sur les paramètres...";
"mi_view_onboarding" = "Ouvrir la visite guidée de bienvenue...";
"mi_xdebug_available_modes" = "Modes disponibles";
"mi_xdebug_actions" = "Actions";
"mi_xdebug_disable_all" = "Désactiver tous les modes";
// CONFMAN
"confman.title" = "Éditeur de configuration PHP";
"confman.description" = "Cette fonction vous permet de personnaliser facilement votre installation PHP. Les modifications sont appliquées automatiquement.";
"confman.byte_limit.unlimited" = "Autoriser l'utilisation sans limite";
"php_ini.memory_limit.title" = "Limite de mémoire";
"php_ini.memory_limit.description" = "Ce paramètre définit la quantité maximale de mémoire (en octets) qu'un script est autorisé à allouer. Cela permet d'éviter que des scripts mal écrits ne consomment toute la mémoire disponible sur un serveur.";
"php_ini.post_max_size.title" = "POST Taille maximale";
"php_ini.post_max_size.description" = "Définit la taille maximale des message POST autorisées. Ce paramètre affecte également le téléchargement montant de fichiers. Pour envoyer des fichiers volumineux, cette valeur doit être supérieure à la taille maximale de téléchargement montant. D'une manière générale, la limite de mémoire doit être supérieure à la taille maximale des messages POST.";
"php_ini.file_uploads.title" = "Téléchargement montant de fichiers";
"php_ini.file_uploads.description" = "Activer ou désactiver complètement les téléchargements montant de fichiers. Il est recommandé de laisser cette option activée.";
"php_ini.upload_max_filesize.title" = "Taille maximale de téléchargement montant";
"php_ini.upload_max_filesize.description" = "Taille maximale d'un téléchargement montant. La taille maximale POST doit être supérieure à cette valeur.";
// PHPEXTMAN
"phpextman.window.title" = "Extensions";
"phpextman.description" = "**PHP Extension Manager** vous permet de gérer différentes extensions PHP d'un simple clic sur le bouton. Comme Homebrew est utilisé, les extensions n'ont pas besoin d'être compilées à la volée avec `pecl`.";
"phpextman.disclaimer" = "Certaines extensions peuvent nécessiter l'installation d'autres dépendances, mais en général, l'installation des extensions devrait être beaucoup plus rapide que l'installation des versions de PHP.";
"phpextman.warnings.removal.title" = "Désinstaller l'extension `%@`?";
"phpextman.warnings.removal.desc" = "L'extension et son fichier de configuration unique seront supprimés. La fonctionnalité de l'extension ne sera plus disponible pour cette installation PHP. Êtes-vous sûr?
(Si l'extension est activée à l'aide d'un nom de fichier non standard, elle ne sera pas supprimée. Donc, si vous ne voulez pas que le fichier .ini soit supprimé, il est préférable de simplement le renommer. Dans ce cas, l'extension ne sera désactivée que dans le cadre du processus de nettoyage.)";
"phpextman.warnings.removal.button" = "Désinstaller";
"phpextman.list.showing_count" = "Affiche actuellement %@ extensions pour:";
"phpextman.list.depends_on" = "Dépend de:";
"phpextman.list.status.external" = "Cette extension est déjà installée via une autre source et ne peut pas être gérée.";
"phpextman.list.status.installable" = "Cette extension peut être installée.";
"phpextman.list.status.dependent" = "Vous ne pouvez pas désinstaller ceci avant de désinstaller %@.";
"phpextman.list.status.can_manage" = "Cette extension est installée et peut être gérée par PHP Monitor.";
// PHPMAN
"phpman.busy.title" = "Vérification des mises à jour en cours !";
"phpman.busy.description.outdated" = "Vérification des versions PHP obsolètes en cours...";
"phpman.version.broken" = "Cette version semble être endommagée. Vous pouvez tenter de la réparer.";
"phpman.version.has_update" = "Version %@ installée, %@ disponible.";
"phpman.version.installed" = "La version %@ est actuellement installée.";
"phpman.version.available_for_installation" = "Cette version peut être installée.";
"phpman.version.automatic_upgrade" = "Cette version sera installée automatiquement en mettant à jour une version plus ancienne.";
"phpman.buttons.uninstall" = "Désinstaller";
"phpman.buttons.install" = "Installer";
"phpman.buttons.update" = "Mettre à jour";
"phpman.buttons.repair" = "Réparer";
"phpman.version.prerelease" = "Pré-version";
"phpman.title" = "Gestionnaire de versions PHP";
"phpman.description" = "Le **gestionnaire de versions PHP** vous permet d'installer, de mettre à niveau et de supprimer différentes versions de PHP via Homebrew sans avoir à exécuter les commandes vous-même dans le terminal.";
"phpman.disclaimer" = "Veuillez noter que l'installation ou la mise à niveau des versions de PHP peut entraîner la mise à niveau d'autres packages Homebrew. La plupart des étapes d'installation prennent généralement un certain temps, donc veuillez patienter pendant que Homebrew fait son travail.";
"phpman.refresh.button" = "Rechercher des mises à jour";
"phpman.refresh.button.description" = "Vous pouvez appuyer sur le bouton de rafraîchissement pour vérifier si des mises à jour sont disponibles pour les versions de PHP installées.";
"phpman.has_updates.description" = "Une ou plusieurs mises à jour sont disponibles. (Veuillez noter que PHP Monitor installera ou mettra toujours à jour les versions de PHP en bloc, vous mettrez donc à jour toutes les installations en une seule fois.)";
"phpman.has_updates.button" = "Tout mettre à jour";
"phpman.warnings.unsupported.title" = "Votre version de Homebrew peut causer des problèmes";
"phpman.warnings.unsupported.desc" = "Aucune fonctionnalité n'est désactivée, mais certaines commandes peuvent ne pas fonctionner comme prévu. Vous utilisez actuellement Homebrew %@.
Actuellement, Homebrew 4 est la seule version prise en charge par le gestionnaire de versions PHP. Si vous utilisez une version plus récente de Homebrew, vous pouvez vérifier si une version plus récente de PHP Monitor est disponible.";
"phpman.warnings.removal.title" = "Êtes-vous sûr de vouloir désinstaller %@ ?";
"phpman.warnings.removal.desc" = "Veuillez noter que les fichiers de configuration ne seront pas supprimés, il sera donc facile de réinstaller ultérieurement si nécessaire.
Il se peut que vous deviez saisir votre mot de passe pendant le processus de désinstallation si les autorisations des fichiers ne permettent pas une suppression simple.";
"phpman.warnings.removal.button" = "Désinstaller";
"phpman.failures.install.title" = "L'installation a échoué !";
"phpman.failures.install.desc" = "Malheureusement, l'opération a renvoyé un code d'erreur pour une raison inconnue. Il se peut que les formules aient été correctement installées ou mises à niveau. Malheureusement, je ne peux pas faire grand-chose à ce sujet. Veuillez consulter les derniers messages de Homebrew ici pour plus d'informations sur ce qui s'est passé :
%@";
"phpman.action_prevented_busy.title" = "PHP Monitor est actuellement occupé.";
"phpman.action_prevented_busy.desc" = "PHP Monitor est actuellement occupé à effectuer une opération telle que le changement de versions de PHP. Pour garantir que votre système ne se casse pas, vous devez attendre que PHP Monitor soit prêt avant de réessayer.";
"phpman.uninstall_prevented.title" = "Vous ne pouvez pas désinstaller la version de PHP actuellement active via PHP Monitor.";
"phpman.uninstall_prevented.desc" = "Afin de prévenir les problèmes avec PHP Monitor et d'autres plantages, il n'est pas possible de désinstaller la version actuellement liée de PHP via cette interface utilisateur. Vous pouvez changer de version et réessayer, ou désinstaller cette version manuellement via le terminal.\n\nVeuillez noter que PHP Monitor peut planter si vous désinstallez la version de PHP actuellement liée.";
"phpman.failures.uninstall.title" = "La désinstallation a échoué !";
"phpman.failures.uninstall.desc" = "Malheureusement, la désinstallation automatique a échoué. Vous pouvez essayer de lancer manuellement cette commande : `%@` et découvrir ce qui ne fonctionne pas. N'oubliez pas de redémarrer PHP Monitor (ou d'appuyer sur le bouton de rafraîchissement) une fois terminé.";
"phpman.unlinked.title" = "Aucune des versions de PHP installées sur votre système n'est actuellement liée.";
"phpman.unlinked.desc" = "Il est probable que vous ayez toujours une version de PHP installée, mais actuellement aucune version de PHP n'est liée.";
"phpman.unlinked.detail" = "Sans aucune version de PHP liée, le binaire php n'est pas accessible sur votre système et vous ne pouvez pas exécuter de scripts PHP sans être explicitement inclus dans le PATH. PHP Monitor peut résoudre automatiquement ce problème (choisissez Réparer automatiquement dans le menu principal) ou vous pouvez le résoudre vous-même en exécutant la commande `brew link php --force`.";
"phpman.operations.repairing" = "Réparation des installations en cours...";
"phpman.operations.updating" = "Installation des mises à jour en cours...";
"phpman.operations.installing" = "Installation de %@ en cours...";
"phpman.steps.fetching" = "Récupération en cours...";
"phpman.steps.downloading" = "Téléchargement des données du package en cours...";
"phpman.steps.installing" = "Installation de certaines données du package en cours...";
"phpman.steps.pouring" = "Versement en cours... Cela peut prendre un certain temps...";
"phpman.steps.summary" = "L'installation d'un package est terminée...";
"phpman.services.loading" = "Chargement...";
"phpman.services.not_installed" = "Un service nécessaire n'est pas installé.";
"phpman.services.error" = "Un service nécessaire rapporte une érreur.";
"phpman.services.inactive" = "Un service nécessaire n'est pas lancé.";
"phpman.services.all_ok" = "Tout les services Valet sont OK.";
// LITE MODE
"lite_mode_explanation.title" = "Vous utilisez actuellement PHP Monitor en mode Standalone.";
"lite_mode_explanation.subtitle" = "PHP Monitor dispose de fonctionnalités supplémentaires qui sont disponibles si vous utilisez Laravel Valet. Actuellement, PHP Monitor n'a pas détecté d'installation active de Valet sur votre système, ces fonctionnalités ne sont donc pas disponibles.";
"lite_mode_explanation.description" = "Pour plus d'informations, je vous recommande de consulter le README (accessible sur GitHub) qui expliquera les étapes à suivre pour installer Valet et faire fonctionner correctement PHP Monitor avec cette installation. Vous devrez redémarrer PHP Monitor après avoir installé Laravel Valet avant de quitter le mode Standalone.";
// GENERIC
"generic.ok" = "OK";
"generic.cancel" = "Annuler";
"generic.retry" = "Réessayer";
"generic.notice" = "Notice";
// PRESET LOADING
"preset_help_title" = "Utilisation des paramètres de configuration";
"preset_help_info" = "Vous pouvez configurer des paramètres de configuration dans le fichier config.json, situé dans ~/.config/phpmon/config.json. Ces paramètres peuvent appliquer une sélection de valeurs de configuration en une seule fois. Il s'agit d'une fonctionnalité puissante, mais qui doit actuellement être configurée manuellement.";
"preset_help_desc" = "Après avoir redémarré PHP Monitor, tous les paramètres présents dans le fichier seront chargés. Si aucun paramètre n'apparaît, il est probable que le fichier n'ait pas pu être analysé correctement.\n\nVous pouvez cliquer sur le point d'interrogation dans cette alerte pour accéder à la FAQ sur GitHub, où vous trouverez plus d'informations sur cette fonctionnalité, y compris un exemple de fichier.";
// MENU ITEMS (if window is open)
"mm_add_folder_as_link" = "Ajouter un dossier en tant que lien...";
"mm_reload_domain_list" = "Recharger la liste des domaines";
"mm_find_in_domain_list" = "Rechercher dans la liste des domaines";
// SITE LIST
"domain_list.title" = "Domaines";
"domain_list.subtitle" = "";
"domain_list.no_domains" = "Vous n'avez pas encore configuré de domaines ou de proxies.";
"domain_list.no_domains_for_search_query" = "Votre recherche ne retourne aucun résultat.";
"domain_list.tooltips.isolated" = "Ce domaine est isolé et utilise PHP %@ au lieu de la version de PHP liée globalement.";
"domain_list.tooltips.checkmark" = "Ce domaine est servi avec une version de PHP compatible avec cette exigence (PHP %@). Cliquez sur la version de PHP à côté de cette coche pour obtenir plus d'informations sur la manière dont cette exigence a été déterminée.";
"domain_list.alerts_isolation_failed.title" = "Oups ! L'isolation du site n'a pas été appliquée.";
"domain_list.alerts_isolation_failed.subtitle" = "Quelque chose s'est mal passé lors de la tentative de modification du statut d'isolation pour ce site. Si c'est votre site par défaut mais qu'il n'est pas lié, je vous recommande de le lier manuellement avant de configurer l'isolation.";
"domain_list.alerts_isolation_failed.desc" = "Pour déterminer ce qui ne fonctionne pas, vous pouvez essayer d'exécuter la commande manuellement dans votre terminal : %@";
"domain_list.alerts_status_not_changed.title" = "Oups ! Le statut SSL n'a pas été modifié.";
"domain_list.alerts_status_not_changed.desc" = "Quelque chose s'est mal passé. Essayez d'exécuter la commande manuellement dans votre terminal : %@";
"domain_list.alerts_status_changed.title" = "Statut SSL modifié";
"domain_list.alerts_status_changed.desc" = "Le domaine '%@' est maintenant %@.";
"domain_list.alerts_status_secure" = "sécurisé";
"domain_list.alerts_status_unsecure" = "non sécurisé";
"domain_list.confirm_unlink" = "Êtes-vous sûr de vouloir dissocier '%@' ?";
"domain_list.confirm_unlink_desc" = "Aucun fichier ne sera supprimé. Vous pouvez toujours lier à nouveau le dossier en cliquant sur le bouton + et en sélectionnant le dossier d'origine.";
"site_link.close" = "Fermer";
"site_link.switch_to_php" = "Basculer vers PHP %@";
"domain_list.confirm_unproxy" = "Êtes-vous sûr de vouloir supprimer le proxy '%@' ?";
"domain_list.confirm_unproxy_desc" = "Vous pouvez toujours recréer le proxy en cliquant à nouveau sur le bouton +.";
"domain_list.columns.secure" = "TLS";
"domain_list.columns.domain" = "Domaine";
"domain_list.columns.active" = "Actif";
"domain_list.columns.kind" = "Sorte";
"domain_list.columns.project_type" = "Type de Projet";
"domain_list.extensions" = "Activer/désactiver les extensions";
"domain_list.applies_to" = "S'applique à PHP %@";
// CHOOSE WHAT TO ADD
"selection.title" = "Quel type de domaine souhaitez-vous configurer ?";
"selection.description" = "Les liens sont utilisés pour servir directement les projets. Si vous avez un dossier Laravel, Symfony, WordPress, etc. contenant du code, vous voudrez créer un lien et choisir le dossier où se trouve votre code.\n\nSi vous avez besoin d'un proxy, vous pouvez proxyer par exemple un conteneur vers un nom de domaine particulier. Cela peut être utile en combinaison avec Docker, par exemple.";
"selection.create_link" = "Créer un lien";
"selection.create_proxy" = "Créer un Proxy";
"selection.cancel" = "Annuler";
// ADD PROXY TO DOMAINS LIST
"domain_list.add.set_up_proxy" = "Configurer un Proxy";
"domain_list.add.proxy_subject" = "Sujet du proxy (doit inclure le protocole et le port)";
"domain_list.add.domain_name" = "Nom de domaine";
"domain_list.add.create_proxy" = "Créer le Proxy";
"domain_list.add.proxy_available" = "%@ sera proxy et sera disponible via : %@://%@.%@";
"domain_list.add.proxy_https_warning" = "%@ sera proxy et sera disponible via : %@://%@.%@.
( !) IMPORTANT : Ce proxy peut ne pas fonctionner jusqu'à ce que vous ajoutiez manuellement `proxy_ssl_verify off;` au fichier de configuration de nginx pour ce domaine. Il est recommandé d'utiliser un domaine non sécurisé comme sujet du proxy.";
// ADD SITE TO DOMAINS LIST
"domain_list.add.link_folder" = "Lier un dossier";
"domain_list.add.domain_name_placeholder" = "Entrez un nom de domaine ici";
"domain_list.add.secure_after_creation" = "Sécuriser %@.%@ après la création";
"domain_list.add.secure_description" = "La sécurisation d'un domaine nécessite des privilèges administrateurs.\nIl se peut que l'on vous demande votre mot de passe ou votre Touch ID.";
"domain_list.add.create_link" = "Créer Lien";
"domain_list.add.cancel" = "Annuler";
"domain_list.add.folder_available" = "Ce site sera accessible via l'URL suivante : %@://%@.%@";
"domain_list.add.empty_fields" = "Un ou plusieurs champs sont vides. Veuillez remplir tous les champs obligatoires.";
"domain_list.add.errors.empty" = "Vous devez saisir un nom de domaine.";
"domain_list.add.errors.empty_proxy" = "Vous devez indiquer ce qui sera proxy.";
"domain_list.add.errors.subject_invalid" = "Le sujet que vous avez saisi n'est pas valide.\nVous devez inclure le protocole et le port.";
"domain_list.add.errors.already_exists" = "Un lien portant ce nom existe déjà.";
// ADD SITE ERROR: FOLDER MISSING SINCE SELECTION
"domain_list.alert.folder_missing.desc" = "Le dossier que vous avez choisi ne semble plus exister. Voulez-vous annuler l'ajout de ce dossier ? Si vous avez déplacé le dossier, vous pouvez toujours le remettre en place et réessayer.";
"domain_list.alert.folder_missing.title" = "Dossier manquant !";
"domain_list.alert.folder_missing.cancel" = "Annuler le Lien";
"domain_list.alert.folder_missing.return" = "OK";
"domain_list.add.modal_description" = "Sélectionnez d'abord le dossier que vous souhaitez lier.";
// SITE LIST ACTIONS
"domain_list.isolate" = "Changer la Version de PHP";
"domain_list.site_isolation" = "Isolation du site";
"domain_list.remove_isolation" = "Supprimer l'isolation";
"domain_list.always_use_php" = "Tojours utiliser PHP %@";
"domain_list.isolation_unavailable" = "Isolement non pris en charge (dans Valet 2)";
"domain_list.actions" = "Actions";
"domain_list.unlink" = "Supprimer le Lien vers le Dossier";
"domain_list.secure" = "Sécuriser le Domaine";
"domain_list.unsecure" = "Supprimer la Sécurisation du Domaine";
"domain_list.open_in" = "Ouvrir avec %@";
"domain_list.open_in_finder" = "Ouvrir dans le Finder";
"domain_list.open_in_browser" = "Ouvrir dans le Navigateur";
"domain_list.open_in_terminal" = "Ouvrir dans le Terminal";
"domain_list.detected_apps" = "Applications Détectées";
"domain_list.system_apps" = "Applications Système";
"domain_list.unproxy" = "Supprimer le Proxy";
"domain_list.use_in_terminal" = "Ouvrir PHP %@ dans le Terminal";
"domain_list.alerts_isolated_php_terminal.title" = "Vous pouvez utiliser PHP %@ dans un terminal spécifique!";
"domain_list.alerts_isolated_php_terminal.subtitle" = "Malheureusement, PHP Monitor ne peut pas ouvrir un terminal pour vous (et taper les commandes appropriées). Vous devrez donc manuellement créer le script d'aide afin d'utiliser cette version spécifique de PHP. Pour ce faire, vous pouvez taper ce qui suit dans le terminal de votre choix :
. pm%@
Ceci va générer le script d'aide tel qu'il est généré par PHP Monitor et permettre l'utilisation de PHP %@ pour ce terminal spécifique.
Cela n'a pas d'effet sur les autres terminaux, mais uniquement sur la session de terminal particulière sur laquelle vous l'utilisez. (par exemple, si vous avez plusieurs onglets dans votre application de terminal, les autres onglets et fenêtres ne sont pas affectés).";
"domain_list.alerts_isolated_php_terminal.desc" = "Si cela ne fonctionne pas, vous pouvez vérifier PHP Doctor via le menu Dépannage ici dans PHP Monitor. Plus d'informations sur cette fonctionnalité peuvent être trouvées sur GitHub (sur le wiki du dépôt de PHP Monitor). Cette alerte est incluse pour améliorer la visibilité de cette fonctionnalité.";
"domain_list.warning.spaces" = "Attention ! Ce site a un espace dans son dossier.\nLe site ne sera pas accessible via le navigateur.";
"domain_list.alert.invalid_folder_name" = "Nom de dossier non valide";
"domain_list.alert.invalid_folder_name_desc" = "Ce dossier n'a pas pu être résolu en une URL valide. Cela est généralement dû à la présence d'un espace dans le nom du dossier. Veuillez renommer le dossier, recharger la liste des sites et réessayer.";
"domain_list.columns.tls" = "TLS";
"domain_list.columns.php" = "PHP";
"domain_list.columns.type" = "Type";
// DRIVERS
"driver.not_detected" = "Autres";
// PRESET
"preset.extension" = "%i extension";
"preset.extensions" = "%i extensions";
"preset.preference" = "%i préférence";
"preset.preferences" = "%i préférences";
// EDITORS
"editors.alert.try_again" = "Essayez à nouveau";
"editors.alert.cancel" = "Annuler";
// PREFERENCES
"prefs.title" = "PHP Monitor";
"prefs.subtitle" = "Préférences";
"prefs.close" = "Fermer";
"prefs.tabs.general" = "Général";
"prefs.tabs.appearance" = "Apparence";
"prefs.tabs.visibility" = "Visibilité";
"prefs.tabs.notifications" = "Notifications";
"prefs.global_shortcut" = "Raccourci Clavier :";
"prefs.dynamic_icon" = "Type d'Icône :";
"prefs.info_density" = "Densité d'Informations :";
"prefs.services" = "Services :";
"prefs.switcher" = "Sélecteur :";
"prefs.php_doctor" = "PHP Doctor :";
"prefs.integrations" = "Intégrations :";
"prefs.updates" = "Mises-à-jour :";
"prefs.notifications" = "Notifications :";
"prefs.warnings" = "Avertissements :";
"prefs.menu_contents" = "Le menu contient :";
"prefs.startup" = "Démarrage :";
"prefs.auto_start_desc" = "Lancer automatiquement PHP Monitor lorsque vous vous connectez à votre Mac.";
"prefs.auto_start_title" = "Démarrer PHP Monitor à la connexion";
"prefs.icon_options.php" = "Afficher l'icône PHP";
"prefs.icon_options.elephant" = "Afficher l'icône Éléphant";
"prefs.icon_options.none" = "Ne Pas Afficher d'icône";
"prefs.icon_options_desc" = "Cette option détermine l'icône qui sera affichée à côté du numéro de version de la version de PHP actuellement liée. Si l'option Icône dynamique a été désactivée, cette option n'aura aucun effet.";
"prefs.auto_restart_services_title" = "Redémarrer PHP-FPM automatiquement";
"prefs.auto_restart_services_desc" = "Lorsque cette option est cochée, PHP-FPM redémarre automatiquement lorsque vous cochez ou décochez une extension. Légèrement plus lent lorsqu'il est activé, mais il applique immédiatement le changement d'extension pour tous les sites que vous servez, sans avoir besoin de redémarrer PHP-FPM manuellement.";
"prefs.dynamic_icon_title" = "Afficher une icône dynamique dans la barre de menu";
"prefs.dynamic_icon_desc" = "Si vous décochez cette case, l'icône du camion sera toujours visible. Si elle est cochée, elle affichera le numéro de la version majeure de la version de PHP actuellement liée.";
"prefs.display_full_php_version" = "Afficher la version complète de PHP partout";
"prefs.display_full_php_version_desc" = "Afficher la version complète au lieu de la version majeure affichée dans la barre de menu et le menu déroulant. (Cela peut s'avérer indésirable sur les petits écrans, c'est pourquoi cette option est désactivée par défaut).";
"prefs.auto_composer_update_title" = "Mise à jour automatique des dépendances globales";
"prefs.auto_composer_update_desc" = "Si cette option est cochée, elle demandera automatiquement à Composer d'exécuter `composer global update` à chaque fois que vous passerez d'une version de PHP à une autre. Vous serez en mesure de voir quels changements sont effectués, ou si cela échoue.";
"prefs.open_protocol_title" = "Autoriser les intégrations tierces";
"prefs.open_protocol_desc" = "Lorsque cette option est cochée, elle permet à l'interaction avec des utilitaires tiers de fonctionner (par exemple Alfred, Raycast). Si vous désactivez cette option, le PHP Monitor recevra toujours les commandes, mais n'agira pas en conséquence.";
"prefs.automatic_update_check_title" = "Vérifier automatiquement les mises à jour";
"prefs.automatic_update_check_desc" = "Lorsque cette option est cochée, PHP Monitor vérifie automatiquement si une version plus récente est disponible, et vous en informe si c'est le cas.";
"prefs.php_doctor_suggestions_title" = "Toujours afficher les suggestions";
"prefs.php_doctor_suggestions_desc" = "Si vous décochez cette option, aucune suggestion de PHP Doctor n'apparaîtra dans le menu de PHP Monitor. Gardez à l'esprit que PHP Doctor n'apparaîtra pas s'il n'y a pas de recommandations.";
"prefs.shortcut_set" = "Définir un raccourci global";
"prefs.shortcut_listening" = "<écoute de l'appui sur le clavier>";
"prefs.shortcut_clear" = "Éffacer";
"prefs.shortcut_desc" = "Si une combinaison de raccourcis est configurée, vous pouvez basculer le moniteur PHP où que vous soyez en appuyant sur la combinaison de touches que vous avez choisie. (Annulez le choix d'un raccourci en appuyant sur la barre d'espacement).";
"prefs.notify_about_version_change_desc" = "Affiche une notification lorsque la version active de PHP change.";
"prefs.notify_about_version_change" = "Notification du changement de version de PHP";
"prefs.notify_about_php_fpm_change_desc" = "Affiche une notification lorsque le processus PHP-FPM actif a redémarré suite à un changement de configuration.";
"prefs.notify_about_php_fpm_change" = "Notifier le redémarrage de PHP-FPM";
"prefs.notify_about_services_desc" = "Affiche une notification lorsque l'un des services Homebrew (installés et configurés par Valet) a été redémarré ou arrêté.";
"prefs.notify_about_services" = "Notifier l'état des services";
"prefs.notify_about_presets_desc" = "Affiche une notification lorsqu'un paramètre a été appliqué ou annulé avec succès.";
"prefs.notify_about_presets" = "Notification de l'application des paramètres";
"prefs.notify_about_secure_status_desc" = "Affiche une notification lorsqu'un domaine a été sécurisé ou non sécurisé" ;
"prefs.notify_about_secure_status" = "Notification de l'état sécurisé/non sécurisé";
"prefs.notify_about_composer_success_desc" = "Affiche une notification lorsque la configuration globale de Composer a été mise à jour avec succès" ;
"prefs.notify_about_composer_success" = "Notifier sur les mises à jour globales composer" ;
"prefs.warn_about_non_standard_tld_desc" = "Si vous utilisez un TLD non standard, vous ne souhaiterez peut-être pas recevoir de notifications répétées à ce sujet" ;
"prefs.warn_about_non_standard_tld" = "Avertir à propos d'un TLD non standard" ;
"prefs.display_global_version_switcher_desc" = "Si cette option est désactivée, vous ne pourrez pas modifier la version de PHP liée globalement via le menu principal.";
"prefs.display_global_version_switcher" = "PHP Switcher";
"prefs.display_services_manager_desc" = "S'il est désactivé, vous ne pourrez pas voir, démarrer ou arrêter les différents services. (Si des services sont désactivés, vous ne pourrez pas facilement voir que c'est le cas).";
"prefs.display_services_manager" = "Gestionnaire des services";
"prefs.display_valet_integration_desc" = "Si elle est désactivée, vous ne pourrez pas localiser le dossier principal de Valet ni ouvrir la liste des domaines.";
"prefs.display_valet_integration" = "Intégration Valet";
"prefs.display_php_config_finder_desc" = "Si cette option est désactivée, vous ne pourrez pas localiser facilement vos fichiers de configuration PHP et/ou générer un dump phpinfo().";
"prefs.display_php_config_finder" = "Chercheur de configuration PHP";
"prefs.display_composer_toolkit_desc" = "Si cette option est désactivée, vous ne pourrez pas invoquer Composer via le menu principal. La mise à jour automatique de Composer après avoir changé de préférence n'est pas affectée par ce changement.";
"prefs.display_composer_toolkit" = "Boîte à Outils Composer";
"prefs.display_limits_widget_desc" = "Si cette option est désactivée, vous ne pourrez pas voir le widget des limites (mémoire, POST, téléchargement) dans le menu principal.";
"prefs.display_limits_widget" = "Widget des Limites";
"prefs.display_extensions_desc" = "Si cette option est désactivée, vous ne pourrez pas contrôler facilement les extensions via le menu principal.";
"prefs.display_extensions" = "Extensions";
"prefs.display_presets_desc" = "Si cette option est désactivée, il ne sera pas possible d'appliquer ou de revenir sur les paramètres de configuration de PHP.";
"prefs.display_presets" = "Paramètres";
"prefs.display_misc_desc" = "Si vous êtes désactivé, vous ne pourrez pas accéder au menu Premiers Secours et Services.";
"prefs.display_misc" = "Menu Premiers Secours et Services";
// NOTIFICATIONS
"notification.version_changed_title" = "PHP %@ maintenant actif";
"notification.version_changed_desc" = "PHP Monitor est passé à PHP %@.";
"notification.php_fpm_restarted" = "PHP-FPM a redémarrer automatiquement";
"notification.php_fpm_restarted_desc" = "Vous avez basculé une extension, et PHP-FPM a donc été redémarré automatiquement.";
"notification.services_stopped" = "Les Services Valet sont arrêtés";
"notification.services_stopped_desc" = "Tous les services ont été arrêtés avec succès.";
"notification.services_restarted" = "Redémarrage des services Valet";
"notification.services_restarted_desc" = "Tous les services ont été redémarrés avec succès.";
"notification.preset_applied_title" = "Paramètre appliqué";
"notification.preset_applied_desc" = "Le paramètre '%@' a été appliqué avec succès.";
"notification.preset_reverted_title" = "Paramètre annulé";
"notification.preset_reverted_desc" = "Le dernier paramètre que vous avez appliqué a été annulé. Votre configuration précédente est maintenant active.";
"notification.phpmon_updated.title" = "PHP Monitor a été mis à jour !";
"notification.phpmon_updated.desc" = "Vous utilisez maintenant PHP Monitor v%@. L'équipe vous remercie !";
"notification.phpmon_updated_dev.desc" = "PHP Monitor v%@ (build %@) est maintenant installé et actif.";
// Composer Update
"alert.composer_missing.title" = "Composer introuvable!";
"alert.composer_missing.subtitle" = "PHP Monitor n'a pas pu trouver Composer. Assurez-vous que Composer est installé et réessayez.";
"alert.composer_missing.desc" = "PHP Monitor suppose que Composer est situé dans:
• `/usr/local/bin/composer`
• `/opt/homebrew/bin/composer`
Assurez-vous qu'il est installé à l'un de ces endroits, ou créez un lien symbolique si Composer est installé ailleurs.";
"alert.composer_progress.title" = "Mise à jour des dépendances globales...";
"alert.composer_progress.info" = "Vous pouvez voir la progression dans le terminal ci-dessous.";
"alert.composer_failure.title" = "Quelque chose n'a pas fonctionné !";
"alert.composer_failure.info" = "Vos dépendances globales de Composer n'ont pas pu être mises à jour.
Vous trouverez plus d'informations dans le message ci-dessous. Vous devrez corriger ce
problème manuellement, en utilisant votre propre application Terminal (ceci vous montre simplement un message).";
"alert.composer_success.title" = "Composer a fini sa mise à jour!";
"alert.composer_success.info" = "Les dépendances globales de votre Composer ont été mises à jour avec succès.";
// Composer Version
"alert.composer_php_isolated.desc" = "Ce site a été isolé, ce qui signifie que Valet sert PHP %@ pour ce site spécifiquement. La version globale est actuellement PHP %@.";
"alert.composer_php_requirement.title" = "'%@' a besoin de PHP %@.";
"alert.composer_php_requirement.unable_to_determine" = "Impossible de déterminer les versions requises PHP";
"alert.composer_php_requirement.type.unknown" = "PHP Monitor n'a pas pu déterminer quelle version de PHP est requise pour ce domaine. La contrainte peut être déterminée si vous avez un fichier `composer.json` ou un fichier `.valetphprc` dans le dossier de votre projet.";
"alert.composer_php_requirement.type.require" = "La version PHP requise a été déterminée en vérifiant le champ `require` dans le fichier `composer.json` lors de la dernière mise à jour de la liste des sites.";
"alert.composer_php_requirement.type.platform" = "La version PHP requise a été déterminée en vérifiant le champ `platform` dans le fichier `composer.json` lors de la dernière mise à jour de la liste des sites.";
"alert.composer_php_requirement.type.valetphprc" = "La version de PHP requise a été déterminée en vérifiant le fichier .valetphprc dans le dossier de votre projet.";
"alert.composer_php_requirement.type.valetrc" = "La version de PHP requise a été déterminée en vérifiant le fichier .valetrc dans le répertoire de votre projet.";
"alert.unable_to_determine_is_fine" = "Si vous avez un projet simple, il se peut qu'aucune version de PHP ne soit exigée. Dans ce cas, vous pouvez ignorer cet avertissement.";
"alert.php_version_ideal" = "La version PHP actuellement active est idéale pour ce site.";
"alert.php_version_incorrect" = "La version PHP actuellement active ne respecte pas les contraintes définies pour ce site.";
"alert.php_suggestions" = "Il se peut qu'il existe une version différente de PHP qui soit plus proche de la contrainte.";
// Suggest Fix My Valet
"alert.php_switch_failed.title" = "Le basculement vers PHP %@ semble avoir échoué.";
"alert.php_switch_failed.info" = "PHP Monitor a détecté que PHP %@ n'est pas actif après avoir terminé sa procédure de remplacement. Vous pouvez essayer d'exécuter « Fix My Valet » et recommencer à basculer après cela. Voulez-vous essayer cette solution ?";
"alert.php_switch_failed.desc" = "Tout d'abord, vous devriez essayer « Fix My Valet » si vous ne l'avez pas déjà fait. Si PHP Monitor reste incapable de changer la version active de PHP, il se peut que vous deviez mettre à jour Valet et les paquets Homebrew. Vous pouvez le faire en lançant `brew update && brew upgrade` ainsi qu'en mettant à jour Valet en lançant `composer global update && valet install`.";
"alert.php_switch_failed.confirm" = "Oui, éxécute \"Fix My Valet\"";
"alert.php_switch_failed.cancel" = "Ne Pas Éxécuter";
// PHP Formula Missing
"alert.php_formula_missing.title" = "Oups ! La formule `php` doit être installée pour que Fix My Valet fonctionne...";
"alert.php_formula_missing.info" = "Il semble que vous n'ayez pas installé la formule `php`, ce qui empêche PHP Monitor d'exécuter Fix My Valet. Veuillez l'installer en utilisant `brew install php`, redémarrez PHP Monitor et réessayez.";
// Fix My Valet Started
"alert.fix_my_valet.title" = "Vous avez des problèmes ? Fix My Valet est prêt à vous aider !";
"alert.fix_my_valet.info" = "Cela peut prendre un certain temps. Veuillez être patient.\n\nLorsque cela sera fait, tous les autres services seront interrompus et PHP %@ sera lié. Vous pourrez passer à la version de PHP de votre choix une fois cette opération terminée.\n\n(Vous recevrez une autre notification lorsque Fix My Valet sera terminé.)";
"alert.fix_my_valet.ok" = "Continuer";
"alert.fix_my_valet.cancel" = "Annuler";
// Fix My Valet Done
"alert.fix_my_valet_done.title" = "Fix My Valet a terminé ses opérations.";
"alert.fix_my_valet_done.subtitle" = "Tous les services appropriés ont été arrêtés et les sélectionnés redémarrés, et la dernière version de PHP devrait maintenant être active. Vous pouvez maintenant essayer de passer à une autre version de PHP.";
"alert.fix_my_valet_done.stay" = "Rester avec PHP %@";
"alert.fix_my_valet_done.switch_back" = "Revenir à PHP %@";
"alert.fix_my_valet_done.desc" = "Si le chargement des sites ne fonctionne toujours pas, vous pouvez essayer d'exécuter `valet install` à nouveau, cela peut corriger un problème 502 (Bad Gateway).\n\nSi Valet est cassé et que vous ne pouvez pas exécuter `valet install`, vous pouvez avoir besoin d'exécuter `composer global update`. Veuillez consulter la FAQ sur GitHub si vous avez d'autres problèmes.";
// Restore Homebrew Permissions
"alert.fix_homebrew_permissions.title" = "À propos de \"Rétablir les permissions Homebrew\"";
"alert.fix_homebrew_permissions.subtitle" = "Cette fonctionnalité a été créée pour que vous puissiez exécuter `brew upgrade` ou `brew cleanup` sans problème de permission. (Vous serez notifié lorsque ce correctif aura été appliqué.)";
"alert.fix_homebrew_permissions.desc" = "Cela nécessite des privilèges administrateurs, car PHP Monitor va restaurer votre accès aux fichiers et dossiers qui sont actuellement la propriété de l'utilisateur `root`, du fait que les services Valet s'exécutent en tant qu'utilisateur root.";
"alert.fix_homebrew_permissions.ok" = "Rétablir les Permissions";
"alert.fix_homebrew_permissions.cancel" = "Annuler";
"alert.fix_homebrew_permissions_done.title" = "Toutes les autorisations de fichiers et de dossiers pour les dépendances de Valet ont été rétablies.";
"alert.fix_homebrew_permissions_done.subtitle" = "Pour cette raison, tous les services de Valet ne sont plus actifs. Vous pouvez maintenant interagir avec Homebrew, mais vos sites Valet seront indisponibles car tous les services sont désactivés.";
"alert.fix_homebrew_permissions_done.desc" = "Lorsque vous avez terminé avec Homebrew (après avoir lancé `brew upgrade`, par exemple), vous devriez redémarrer PHP Monitor et sélectionner « Redémarrer les services Valet » Lorsque vous avez terminé avec Homebrew (après avoir lancé `brew upgrade`, par exemple), vous devriez redémarrer PHP Monitor et sélectionner ``Restart Valet Services`` si vous voulez que Valet fonctionne à nouveau. Il est toujours recommandé de redémarrer PHP Monitor à chaque fois que vous mettez à jour une version de PHP avec `brew upgrade`, sans quoi des problèmes pourraient survenir.";
// PHP FPM Broken
"alert.php_fpm_broken.title" = "Votre configuration PHP-FPM ne pointe pas vers le socket Valet !";
"alert.php_fpm_broken.info" = "PHP Monitor a déterminé qu'il y a des problèmes avec votre configuration PHP-FPM. Cela se traduira par des réponses '502 Bad Gateway' si vous visitez des sites web liés via Valet.";
"alert.php_fpm_broken.description" = "Si cela fait longtemps, vous pouvez habituellement corriger cela en lançant `valet install`, qui met à jour votre configuration PHP-FPM.\n\nSi vous voyez ce message et que vous essayez d'exécuter une pré-version de PHP, il est possible que Valet ne supporte pas encore celle-ci.\n\nVous devrez peut-être mettre à jour votre installation de Laravel Valet vers au moins la version 3.1.11, après quoi vous devrez exécuter `valet install`. Plus d'informations ici : https://phpmon.app/prerelease-php";
// PHP Monitor Cannot Start
"alert.cannot_start.title" = "PHP Monitor ne peut pas démarrer en raison d'un problème de configuration de votre système";
"alert.cannot_start.subtitle" = "Le problème dont vous venez d'être informé empêche le PHP Monitor de fonctionner correctement.";
"alert.cannot_start.description" = "Il se peut que vous n'ayez pas besoin de quitter PHP Monitor et de le redémarrer. Si vous avez résolu le problème (ou si vous ne vous souvenez pas du problème exact), vous pouvez cliquer sur Réessayer, ce qui permettra à PHP Monitor de refaire les vérifications de démarrage.";
"alert.cannot_start.close" = "Quitter";
"alert.cannot_start.retry" = "Essayer à nouveau";
// PHP alias issue
"alert.php_alias_conflict.title" = "Conflit d'alias de la formule Homebrew `php` détecté";
"alert.php_alias_conflict.info" = "PHP Monitor a détecté des alias `php` conflictuels dans votre configuration Homebrew, qui ont tous deux été détectés comme étant installés.\n\nCeci entraînera probablement l'échec de la liaison lors du changement de version de PHP, et brisera la fonctionnalité de PHP Monitor.\n\nPour plus d'informations, veuillez visiter : https://github.com/nicoverbruggen/phpmon/issues/54";
"alert.min_valet_version.title" = "La version installée de Valet ne correspond pas à la version minimale requise. Le PHP Monitor peut ne pas fonctionner comme prévu !";
"alert.min_valet_version.info" = "Vous êtes en train d'exécuter Valet %@.
Pour une prise en charge optimale des dernières versions de PHP et un changement de version correct, il est recommandé de mettre à jour vers la version %@, qui est la configuration minimale requise pour cette version de PHP Monitor.
Vous pouvez mettre à jour en executant `composer global update` dans votre terminal. Une fois fait, lancez `valet install` à nouveau. Pour de meilleurs résultats, redémarrez PHP Monitor. Tant que ceci n'est pas réglé, PHP Monitor ne fonctionnera pas comme prévu.";
// Preset text description
"alert.preset_description.switcher_version" = "Basculer vers PHP %@.\n\n";
"alert.preset_description.applying_extensions" = "Les extensions suivantes seront appliquées :\n";
"alert.preset_description.applying_config" = "Applique les valeurs de configuration suivantes :\n";
"alert.preset_description.enabled" = "activée";
"alert.preset_description.disabled" = "désactivée";
"alert.preset_description.empty" = "(vide)";
// PHP version unavailable
"alert.php_switch_unavailable.title" = "Version de PHP non supportée";
"alert.php_switch_unavailable.subtitle" = "PHP Monitor ne peut pas passer à PHP %@, car il n'est peut-être pas installé ou disponible. L'application de ce paramètre a été annulée.";
"alert.php_switch_unavailable.info" = "Assurez-vous que PHP %@ est installé et vous pouvez le sélectionner dans le menu déroulant. Les versions actuellement supportées sont PHP : %@.";
"alert.php_switch_unavailable.ok" = "OK";
// Service error
"alert.service_error.title" = "Le service '%@' signale une erreur!";
"alert.service_error.subtitle.error_log" = "Cela signifie que le service '%@' n'est pas lancé. Cela peut empêcher Valet de fonctionner correctement. Ce service est associé à un fichier log que vous pouvez consulter.";
"alert.service_error.subtitle.no_error_log" = "Cela signifie que le service '%@' n'est pas lancé. Cela peut empêcher Valet de fonctionner correctement. Malheureusement, il n'y a pas de fichier log associé à ce service.";
"alert.service_error.extra" = "Vous pouvez également suivre les étapes de résolution des problèmes les plus communes. Pour en savoir plus, cliquez sur le bouton '?' dans la section services de PHP Monitor.";
"alert.service_error.button.show_log" = "Voir le log d'erreur";
"alert.service_error.button.close" = "Fermer";
// Composer issues
"alert.global_composer_platform_issues.title" = "Composer a détecté des problèmes dans votre plate-forme";
"alert.global_composer_platform_issues.subtitle" = "La version de PHP que vous avez choisie est trop ancienne pour les dépendances globales de Composer que vous avez installées. Ces dépendances devront être mises à jour.";
"alert.global_composer_platform_issues.desc" = "La façon la plus simple d'éviter ce problème à l'avenir est de basculer vers la plus ancienne version de PHP que vous avez installée et de lancer à nouveau `composer global update`. \n\nVous pouvez également choisir l'option \"Mettre à jour automatiquement les dépendances globales\" dans les préférences pour éviter ce problème.\n\nSi vous continuez à voir ce message même après avoir essayé de mettre à jour ces dépendances globales, vous pouvez consulter votre fichier de configuration globale de composer, situé à `~/.composer/composer.json`.";
"alert.global_composer_platform_issues.buttons.update" = "Mettre à jour les dépendances globales";
"alert.global_composer_platform_issues.buttons.quit" = "Quitter PHP Monitor";
// Revert
"alert.revert_description.title" = "Revenir à la configuration précédente ?";
"alert.revert_description.subtitle" = "PHP Monitor peut revenir à la configuration précédente active. Voici celle qui sera appliqué : \n\n%@";
"alert.revert_description.ok" = "Revenir";
"alert.revert_description.cancel" = "Annuler";
// STARTUP
/// 0. Architecture mismatch
"alert.homebrew_missing.title" = "Le PHP Monitor ne peut pas démarrer !";
"alert.homebrew_missing.subtitle" = "Un binaire Homebrew fonctionnel n'a pas pu être trouvé à l'emplacement standard. Veuillez redémarrer l'application après avoir résolu ce problème.";
"alert.homebrew_missing.info" = "Vous utilisez PHP Monitor avec l'architecture suivante : %@. En conséquence, un binaire Homebrew fonctionnel est attendu dans `%@`, mais n'a pas été trouvé. C'est pourquoi PHP Monitor ne peut pas fonctionner. \n\nSi vous n'avez pas encore installé Homebrew, faites-le. Si vous êtes sur Apple Silicon, assurez-vous que Homebrew et PHP Monitor utilisent la même architecture, en activant ou désactivant Rosetta si nécessaire.";
"alert.homebrew_missing.quit" = "Quitter";
/// PHP binary not found
"startup.errors.php_binary.title" = "PHP n'est pas correctement installé";
"startup.errors.php_binary.subtitle" = "Vous devez installer PHP via Homebrew. L'application ne fonctionnera pas correctement tant que vous n'aurez pas résolu ce problème.";
"startup.errors.php_binary.desc" = "Généralement, l'exécution de `brew link php` dans votre Terminal résoudra ce problème.\n\nPour diagnostiquer ce qui ne va pas, vous pouvez essayer de lancer `which php` dans votre Terminal, il devrait retourner `%@`.";
/// PHP not found in /usr/local/opt or /opt/homebrew/opt
"startup.errors.php_opt.title" = "PHP n'est pas correctement installé";
"startup.errors.php_opt.subtitle" = "L'alias PHP n'a pas été trouvé dans `%@`. L'application ne fonctionnera pas correctement tant que vous n'aurez pas résolu ce problème.";
"startup.errors.php_opt.desc" = "Si vous avez déjà installé la formule `php`, vous pouvez avoir besoin de lancer `brew install php` pour que PHP Monitor détecte cette installation.";
/// PHP binary is broken
"startup.errors.dyld_library.title" = "PHP est installé, mais ne fonctionne manifestement pas.";
"startup.errors.dyld_library.subtitle" = "Lorsque le PHP Monitor tente d'exécuter des commandes, il ne parvient pas à le faire correctement. C'est généralement un indicateur d'une installation PHP défectueuse.";
"startup.errors.dyld_library.desc" = "Lancer `brew reinstall php && brew link php` dans votre Terminal peut résoudre ce problème, veuillez donc essayer.";
/// Valet is not installed
"startup.errors.valet_executable.title" = "Laravel Valet n'est pas correctement installé";
"startup.errors.valet_executable.subtitle" = "Vous devez installer Valet avec Composer. L'application ne fonctionnera pas correctement tant que vous n'aurez pas résolu ce problème.";
"startup.errors.valet_executable.desc" = "Si vous n'avez pas encore installé Laravel Valet, faites-le d'abord. Si vous l'avez installé mais que vous voyez quand même ce message, essayez de lancer `which valet` dans Terminal, il devrait retourner : `%@`.";
/// Valet configuration file missing or broken
"startup.errors.valet_json_invalid.title" = "Fichier de configuration de Laravel Valet invalide ou manquant";
"startup.errors.valet_json_invalid.subtitle" = "PHP Monitor doit pouvoir lire le fichier de configuration. Il semble que le fichier soit malformé ou manquant. Veuillez vérifier qu'il existe et qu'il est correctement formaté.";
"startup.errors.valet_json_invalid.desc" = "Vous pouvez trouver le fichier à `~/.config/valet/config.json`. Si Laravel Valet ne peut pas analyser le fichier de configuration, l'exécution de n'importe quelle commande `valet` corrigera automatiquement le fichier JSON. Essayez d'exécuter `valet --version` pour corriger automatiquement le fichier.";
/// Valet version not readable
"startup.errors.valet_version_unknown.title" = "La version de votre Valet n'a pu être lue";
"startup.errors.valet_version_unknown.subtitle" = "L'analyse de la réponse de `valet --version` a échoué. Assurez-vous que votre installation de Valet fonctionne et qu'elle est à jour.";
"startup.errors.valet_version_unknown.desc" = "Essayez de lancer `valet --version` dans un terminal pour savoir ce qui se passe.";
"startup.errors.valet_not_installed.title" = "Le dossier de configuration de Valet est absent";
"startup.errors.valet_not_installed.subtitle" = "Le répertoire requis `~/.config/valet` est manquant. Cela signifie généralement que vous avez oublié d'exécuter `valet install`.";
"startup.errors.valet_not_installed.desc" = "En supposant que vous avez déjà installé Valet via Composer, lancez `valet install` pour terminer la configuration de Laravel Valet.
Si vous voyez ce message mais que vous ne savez pas pourquoi ce dossier a disparu, vous pouvez chercher à savoir pourquoi il a disparu - il ne devrait pas disparaître comme ça et cela signifie que l'installation de Valet est défectueuse.";
// Valet version too new or old
"startup.errors.valet_version_not_supported.title" = "Cette version de Valet n'est pas compatible";
"startup.errors.valet_version_not_supported.subtitle" = "Vous utilisez une version de Valet qui n'est pas supportée actuellement. PHP Monitor fonctionne actuellement avec Valet v2, v3 et v4. Afin d'éviter de causer des problèmes sur votre système, PHP Monitor ne peut pas démarrer.";
"startup.errors.valet_version_not_supported.desc" = "Vous devez installer une version de Valet qui est compatible avec PHP Monitor, ou vous pouvez avoir besoin de mettre à jour vers une version plus récente de PHP Monitor qui peut inclure la compatibilité avec cette version de Valet (consultez les dernières notes de version pour plus d'informations).";
/// Brew & sudoers
"startup.errors.sudoers_brew.title" = "Brew n'a pas été ajouté à sudoers.d";
"startup.errors.sudoers_brew.subtitle" = "Vous devez exécuter `sudo valet trust` pour vous assurer que Valet peut démarrer et arrêter les services sans avoir à utiliser sudo à chaque fois. L'application ne fonctionnera pas correctement tant que vous n'aurez pas résolu ce problème.";
"startup.errors.sudoers_brew.desc" = "Si vous continuez à voir cette erreur, il est possible qu'il y ait un problème de permission où PHP Monitor ne peut pas valider le fichier, ce qui peut généralement être résolu en exécutant : `sudo chmod +r /private/etc/sudoers.d/brew`";
/// Valet & sudoers
"startup.errors.sudoers_valet.title" = "Valet n'a pas été ajouté à sudoers.d";
"startup.errors.sudoers_valet.subtitle" = "Vous devez exécuter `sudo valet trust` pour vous assurer que Valet peut démarrer et arrêter les services sans avoir à utiliser sudo à chaque fois. L'application ne fonctionnera pas correctement tant que vous n'aurez pas résolu ce problème. Si vous l'avez déjà fait, exécutez à nouveau `sudo valet trust`.";
"startup.errors.sudoers_valet.desc" = "Si vous continuez à voir cette erreur, il est possible qu'il y ait un problème de permission où PHP Monitor ne peut pas valider le fichier, ce qui peut généralement être résolu en exécutant : `sudo chmod +r /private/etc/sudoers.d/valet`";
/// Platform issue detected
"startup.errors.global_composer_platform_issues.title" = "PHP Monitor et Valet ne peuvent pas fonctionner correctement : Composer signale un problème avec votre plateforme";
"startup.errors.global_composer_platform_issues.subtitle" = "Veuillez suivre les étapes suivantes pour éviter ce problème à l'avenir :\n\n1. Exécuter `composer global update`.\n2. Redémarrer PHP Monitor. (Il devrait fonctionner à nouveau.)\n3. Passez à la version la plus ancienne de PHP que vous avez installée.\n4. Exécutez à nouveau `composer global update`.";
"startup.errors.global_composer_platform_issues.desc" = "Vous pouvez aller dans Préférences et cocher l'option 'Mettre à jour automatiquement les dépendances globales'. Cela mettra à jour les dépendances globales de Composer à chaque fois que vous changerez de version de PHP, ce qui n'est pas idéal si vous n'avez pas un accès constant à internet.\n\nPour savoir exactement ce qui ne va pas, essayez de lancer `valet --version`. Valet n'est actuellement pas fonctionnel avec les dépendances installées. Habituellement, cela est dû à une incompatibilité de version : c'est-à-dire que les dépendances installées correspondent à une version de PHP plus récente que la version actuellement active.";
/// Cannot retrieve services
"startup.errors.services_json_error.title" = "Impossible de déterminer l'état des services";
"startup.errors.services_json_error.subtitle" = "PHP Monitor interroge habituellement `brew` en utilisant la commande suivante pour tester si les services peuvent être récupérés : `sudo brew services info nginx --json`.\n\n Le moniteur PHP n'a pas pu interpréter cette réponse.";
"startup.errors.services_json_error.desc" = "Cela peut arriver si votre installation Homebrew n'est pas à jour, auquel cas Homebrew ne renverra pas encore de JSON. Vous pouvez généralement corriger cela en lançant `brew update` ou `brew tap homebrew/services`. Vous pouvez aussi essayer de lancer `sudo brew services info nginx --json` dans le terminal de votre choix.";
/// Issue with `which` alias
"startup.errors.which_alias_issue.title" = "Un problème de configuration a été détecté";
"startup.errors.which_alias_issue.subtitle" = "Il semble qu'il y ait un fichier dans `/usr/local/bin/which`. Ceci est généralement mis en place par NodeJS, mais `node` n'est pas dans le PATH de `/usr/local/bin`. Pour corriger cela, continuez à lire.";
"startup.errors.which_alias_issue.desc" = "Vous devrez faire un lien symbolique entre `node` et le répertoire `/usr/local/bin` pour vous assurer que PHP Monitor peut démarrer avec succès. Pour plus d'informations, voir : https://github.com/nicoverbruggen/phpmon/issues/174";
/// Laravel Herd conflicts
"startup.errors.herd_running.title" = "Laravel Herd semble être lancé";
"startup.errors.herd_running.subtitle" = "Il semble que Laravel Herd soit en cours d'exécution. La configuration de Valet intégrée à Herd peut entrer en conflit avec votre installation habituelle de Valet, veuillez donc quitter Herd avant de continuer. (Vous pouvez parfaitement mélanger l'utilisation de Herd et de Valet mais vous ne devez pas utiliser les deux en même temps).";
"startup.errors.herd_running.desc" = "Vous pouvez également constater que l'alias `php` ajouté par Herd à votre $PATH peut empêcher l'alias `php` de PHP Monitor de fonctionner, alors gardez cela à l'esprit. Vous pouvez vérifier `~/.zshrc` et voir ce que Herd a ajouté à votre $PATH.";
// Warning about a different PHP version linked than last time
"startup.version_mismatch.title" = "Votre version active de PHP a changé.";
"startup.version_mismatch.subtitle" = "Depuis la dernière activation du moniteur PHP, la version PHP liée a été modifiée pour PHP %@. Souhaitez-vous revenir à PHP %@, ou souhaitez-vous continuer à utiliser la version actuelle ?";
"startup.version_mismatch.desc" = "PHP Monitor surveille la version de PHP qui est liée globalement. La version globale peut avoir été changée à cause d'un autre programme ou Homebrew peut avoir lié une formule différente après les mises à jour.";
"startup.version_mismatch.button_switch_back" = "Revenir à PHP %@";
"startup.version_mismatch.button_stay" = "Continuer à utiliser PHP %@";
// Warning about unsupported PHP versions
"startup.unsupported_versions_explanation.title" = "Installation(s) PHP non supportée(s) par Valet détectée(s) !";
"startup.unsupported_versions_explanation.subtitle" = "Les versions suivantes de PHP sont installées sur votre système mais ne sont pas supportées par cette version de Valet.
%@
Valet risque de ne pas fonctionner si vous liez ces versions de PHP, c'est pourquoi PHP Monitor ne vous laissera pas passer à ces versions.";
"startup.unsupported_versions_explanation.desc" = "Si vous avez besoin de la prise en charge de versions plus anciennes de PHP, vous devrez peut-être passer à une version plus ancienne de Valet. Sinon, il peut être judicieux de désinstaller toutes les versions obsolètes qui ne sont pas utilisées. Il se peut également que cette version de Valet soit trop ancienne. Ce message ne disparaîtra qu'après le redémarrage de PHP Monitor.";
// Sponsor encouragement
"startup.sponsor_encouragement.title" = "Si PHP Monitor vous a été utile, à vous ou à votre entreprise, n'hésitez pas à laisser un \"tip\".";
"startup.sponsor_encouragement.subtitle" = "Pour être 100% transparent : je prévois de garder PHP Monitor open source et gratuit. Votre soutien rend cette décision très facile.\n\n - (Vous ne verrez cette annonce qu'une seule fois.)";
"startup.sponsor_encouragement.desc" = "Si vous avez déjà fait un don, c'est grâce à VOUS que l'application a pu bénéficier de toutes ces mises à jour. Dans ce cas, ceci est un message de remerciement pour vous. J'apprécie votre soutien.";
"startup.sponsor_encouragement.accept" = "Sponsoriser Maintenant";
"startup.sponsor_encouragement.learn_more" = "En Apprendre Plus";
"startup.sponsor_encouragement.skip" = "Non Merci";
// ERROR MESSAGES (based on AlertableError)
"alert.errors.homebrew_permissions.applescript_returned_nil.title" = "La restauration des permissions Homebrew a été annulée.";
"alert.errors.homebrew_permissions.applescript_returned_nil.description" = "Le résultat du script exécuté pour ajuster les permissions est `nil`, ce qui signifie habituellement que vous n'avez pas accordé les permissions administratives à PHP Monitor.\n\nSi vous avez cliqué sur Annuler lors de l'invite d'authentification, c'est normal. Si vous vous êtes authentifié et que vous voyez toujours ce message, quelque chose a probablement échoué.";
"alert.key_service_not_running.title" = "En raison de problèmes avec les services Homebrew nécessaires, Valet ne fonctionne pas correctement pour le moment";
"alert.key_service_not_running.subtitle" = "Pour que Valet fonctionne correctement, il faut qu'au moins trois services clés fonctionnent correctement.
PHP Monitor signale que ce n'est pas le cas. Vous pouvez essayer de résoudre ce problème en appuyant sur le bouton avec le 'X' dans le menu sous le service concerné pour (re)démarrer le service qui est actuellement inactif.";
"alert.key_service_not_running.desc" = "Si le fait de cliquer sur le bouton situé sous le service ne fonctionne pas (c.-à-d. que la roulette apparaît mais affiche toujours un \"X\" au bout d'un certain temps), il se peut que vous deviez lancer Fix My Valet. Vous pouvez le faire via le menu First Aid > Fix My Valet.
Vous pouvez également utiliser `valet stop` et `valet start` dans le terminal, ce qui peut également résoudre le problème (comme alternative à Fix My Valet).
Pour un débogage plus approfondi, vous pouvez consulter le GitHub issue tracker, où d'autres personnes peuvent avoir eu des problèmes similaires. En tant que développeur, j'essaie de m'assurer que toutes les questions reçoivent une réponse :)";
"alert.key_service_has_error.title" = "En raison de problèmes avec les services Homebrew nécessaires, Valet ne fonctionne pas correctement pour le moment.";
"alert.key_service_has_error.subtitle" = "Pour que Valet fonctionne correctement, il faut qu'au moins trois services clés fonctionnent correctement.
PHP Monitor signale que ce n'est pas le cas. Il semble que l'un des services concernés signale un état d'erreur, et je recommande donc de cliquer sur le 'E'.
PHP Monitor tentera de redémarrer le service, et s'il échoue (ce qui est probable), il proposera de localiser le fichier journal s'il existe, ce qui peut contenir des informations supplémentaires qui peuvent vous aider à déboguer le problème.";
"alert.key_service_has_error.desc" = "Malheureusement, si un service signale une erreur, cela est souvent dû à une configuration invalide, qui peut être difficile à déboguer.
CONSEILS DE DÉPANNAGE
• Essayez de redémarrer le service et de vérifier le fichier log (s'il existe) en premier lieu. Vous pouvez demander à PHP Monitor de le faire en cliquant sur le bouton 'E'.
 PHP : Si vous voyez un statut d'erreur, il se peut qu'il y ait un problème avec la configuration de PHP. Assurez-vous que les fichiers .ini de l'installation Homebrew sont corrects et qu'il n'y a pas de conflit concernant les sockets.
 nginx : Si vous obtenez un statut d'erreur, il est probable qu'un site soit mal configuré (ce qui est généralement indiqué dans le log d'erreur). Vous voudrez probablement vérifier le dossier nginx de Valet.
• dnsmasq: Si vous obtenez une erreur, le fichier de configuration de dnsmasq est probablement endommagé (il se trouve généralement dans ~/.config/valet/dnsmasq.d).";
// CHECK FOR UPDATES
"updater.alerts.newer_version_available.title" = "PHP Monitor v%@ est maintenant disponible !";
"updater.alerts.newer_version_available.subtitle" = "Il est fortement recommandé de maintenir PHP Monitor à jour, car les nouvelles versions corrigent généralement les bogues et incluent des correctifs pour prendre en charge les dernières versions de Valet et de PHP.";
"updater.installation_source.brew" = "La méthode recommandée pour installer les mises à jour de PHP Monitor est d'appuyer simplement sur “Install Update” (Installer la mise à jour). \n\nComme vous avez utilisé Homebrew pour installer l'application, vous pouvez également effectuer la mise à jour via le terminal en exécutant `%@`, mais cela n'est pas recommandé. \n\n(Veuillez noter que l'installation via cette mise à jour intégrée supprimera PHP Monitor du répertoire Caskroom de Homebrew, afin d'éviter que des mises à jour en double ne soient téléchargées et ne causent des problèmes potentiels plus tard).";
"updater.installation_source.direct" = "La méthode recommandée pour installer les mises à jour de PHP Monitor est de cliquer simplement sur 'Installer la mise à jour'.";
"updater.alerts.buttons.release_notes" = "Voir les notes de mise à jour";
"updater.alerts.is_latest_version.title" = "PHP Monitor est à jour !";
"updater.alerts.is_latest_version.subtitle" = "La version actuellement installée (v%@) est à jour. Il n'y a pas de version plus récente disponible.";
"updater.alerts.cannot_check_for_update.title" = "PHP Monitor n'a pas pu déterminer si une version plus récente est disponible.";
"updater.alerts.cannot_check_for_update.subtitle" = "Il se peut que vous ne soyez pas connecté à l'internet, que vous bloquiez le trafic ou que GitHub soit en panne et ne vous permette pas de vérifier les mises à jour. Si vous continuez à voir ce message, vous pouvez vérifier manuellement la page des versions.";
"updater.alerts.cannot_check_for_update.description" = "La version actuellement installée est : %@. Vous pouvez accéder à la liste des dernières versions (sur GitHub) en cliquant sur le bouton à gauche.";
"updater.alerts.buttons.releases_on_github" = "Voir les versions";
"updater.alerts.buttons.install" = "Installer la mise à jour";
"updater.alerts.buttons.dismiss" = "Annuler";
// WARNINGS ABOUT NON-DEFAULT TLD
"alert.warnings.tld_issue.title" = "Vous n'utilisez pas `.test` comme TLD pour Valet.";
"alert.warnings.tld_issue.subtitle" = "L'utilisation d'un TLD autre que celui par défaut peut ne pas fonctionner correctement et n'est pas officiellement pris en charge.";
"alert.warnings.tld_issue.description" = "PHP Monitor restera fonctionnel, mais il pourrait y avoir des problèmes : l'application pourrait ne pas montrer correctement quels domaines ont été sécurisés. Pour des résultats optimaux, allez dans votre fichier de configuration Valet (config.json dans le répertoire Valet) et changez le TLD en `test`.";
"alert.do_not_tell_again" = "Ne m'en Parlez Plus";
// WARNINGS
"warnings.limits_error.title" = "PHP Monitor n'a pas pu obtenir les limites.";
"warnings.limits_error.steps" = "Essayez de lancer 'php -v' dans votre terminal.";
"warnings.title" = "PHP Doctor";
"warnings.description" = "**PHP Doctor** vous proposera des améliorations de la configuration de votre système.";
"warnings.disclaimer" = "Vous pouvez choisir de masquer toutes les recommandations dans le menu PHP Monitor des préférences, mais il est recommandé de traiter tous les points pouvant être améliorés.";
"warnings.refresh.button" = "Scanner à nouveau";
"warnings.refresh.button.description" = "Appuyez sur ce bouton une fois que vous avez corrigé un problème. PHP Monitor va alors réévaluer votre environnement. Si le problème est vraiment corrigé, la recommandation devrait disparaître.";
"warnings.helper_permissions.title" = "Les programmes d'aide du PHP Monitor ne sont pas disponibles pour le moment.";
"warnings.helper_permissions.description" = "Le PHP Monitor est livré avec plusieurs scripts auxiliaires. L'utilisation de ces scripts vous permet d'invoquer facilement une version spécifique de PHP sans changer la version de PHP liée.";
"warnings.helper_permissions.unavailable" = "Cependant, ces aides sont potentiellement *indisponibles* parce que PHP Monitor ne peut pas créer ou mettre à jour les liens symboliques requis.";
"warnings.helper_permissions.symlink" = "Si vous ne souhaitez pas rendre `/usr/local/bin` accessible en écriture, vous pouvez ajouter le répertoire d'aide de PHP Monitor à votre variable `PATH` pour faire disparaître cet avertissement. (Cliquez sur “En savoir plus” pour savoir comment résoudre ce problème).";
"warnings.arm_compatibility.title" = "Vous exécutez PHP Monitor en utilisant Rosetta sur Apple Silicon, ce qui signifie que votre environnement PHP est également exécuté via Rosetta.";
"warnings.arm_compatibility.description" = "Vous semblez utiliser une version compatible ARM de macOS, mais vous exécutez PHP Monitor en utilisant Rosetta. Bien que cela fonctionne correctement, il est recommandé d'utiliser la version native de Homebrew.";
"warnings.files_missing.title" = "Des fichiers de configuration importants sont absents de votre installation PHP.";
"warnings.files_missing.description" = "Les fichiers de configuration clés suivants doivent exister à la suite de l'installation de PHP :
• %@
Lorsque des fichiers de ce type sont manquants, vous devriez passer à la version de PHP associée à ces fichiers : cela peut résoudre le problème. Si cela ne résout pas le problème, il est recommandé de réinstaller la (les) version(s) PHP appropriée(s) via Homebrew, ce qui devrait restaurer les fichiers de configuration manquants. Les fichiers de configuration manquants peuvent être la raison pour laquelle vous obtenez des erreurs '502 Bad Gateway', même après avoir exécuté Fix My Valet (si vous utilisez Valet).";
"warnings.none" = "Il n'y a pas de recommandations disponibles pour vous en ce moment. Tout est parfait !";
// ONBOARDING
"onboarding.title" = "Visite d'Accueil";
"onboarding.welcome" = "Bienvenue sur PHP Monitor !";
"onboarding.explore" = "Vous avez maintenant accès à l'ensemble des fonctionnalités de PHP Monitor. Vous pouvez en apprendre davantage sur certaines des fonctionnalités offertes par PHP Monitor en consultant cet écran.";
"onboarding.explore.lite" = "Vous avez maintenant accès aux fonctionnalités les plus importantes de PHP Monitor.
Veuillez noter que certaines fonctionnalités (grisées ci-dessous) sont actuellement indisponibles car Laravel Valet n'est pas actif.";
"onboarding.tour.menu_bar.title" = "Le Pouvoir dans votre Barre de Menu";
"onboarding.tour.menu_bar" = "PHP Monitor se trouve dans votre barre de menu. À partir de ce menu, vous pouvez accéder à la plupart des fonctionnalités clés de PHP Monitor, y compris le changement de la version globale de PHP, la localisation des fichiers de configuration, l'installation de différentes versions de PHP, et plus encore.";
"onboarding.tour.faq_hint" = "**Questions?** Je vous recommande de lire le [README](https://github.com/nicoverbruggen/phpmon/blob/main/README.md) sur GitHub: il contient une FAQ complète avec divers conseils et des questions et réponses courantes.";
"onboarding.tour.services.title" = "Gérer les services Homebrew";
"onboarding.tour.services" = "Une fois que vous avez cliqué sur l'icône de la barre de menu, vous pouvez voir d'un coup d'œil si tous les services Homebrew sont opérationnels, en vous basant sur les coches ou les croix. Vous pouvez également cliquer sur un service pour le basculer rapidement.";
"onboarding.tour.domains.title" = "Gérer les Domaines";
"onboarding.tour.domains" = "En ouvrant la fenêtre Domaines via la barre de menu, vous pouvez voir quels domaines sont liés et parqués, ainsi que les proxys nginx actifs.";
"onboarding.tour.isolation.title" = "Isoler les Domaines";
"onboarding.tour.isolation" = "Si vous avez installé Valet 3 ou une version plus récente, vous pouvez même utiliser l'isolation de domaine en faisant un clic droit sur un domaine donné dans la fenêtre Domaines. Cela vous permet de choisir une version spécifique de PHP à utiliser pour ce domaine, et ce domaine seulement.";
"onboarding.tour.feature_unavailable" = "Cette fonctionnalité n'est actuellement pas disponible et nécessite l'installation de Laravel Valet.";
"onboarding.tour.once" = "Vous ne verrez la visite de bienvenue qu'une seule fois. Vous pouvez rouvrir la visite de bienvenue ultérieurement via l'icône de la barre de menu (disponible dans le menu, sous Premiers Secours et Services).";
"onboarding.tour.close" = "Fermer la Visite d'Accueil";
// LANGUAGE CHOICE
"prefs.language" = "Langue :";
"prefs.language_options_desc" = "Choisissez une autre langue à utiliser avec PHP Monitor. Pour appliquer pleinement ce changement, vous devez redémarrer l'application.";
"alert.language_changed.title" = "Vous devriez redémarrer PHP Monitor !";
"alert.language_changed.subtitle" = "Vous venez de changer la langue d'affichage de PHP Monitor. Le menu utilisera immédiatement la bonne langue, mais vous devrez peut-être redémarrer l'application pour que tout le texte dans l'application reflète votre nouveau choix de langue.";

View File

@ -13,7 +13,11 @@
"mi_no_php_linked" = "Geen PHP versie gelinkt!";
"mi_fix_php_link" = "Automatisch oplossen...";
"mi_no_php_linked_explain" = "What is dit?";
"mi_php_version_manager" = "PHP versies beheren...";
"mi_php_version_manager" = "Beheer PHP Installaties...";
"mi_php_ext_manager" = "Beheer PHP Extensies...";
"mi_php_config_manager" = "PHP Configuratie Editor...";
"mi_manage_limits" = "Beheer Limieten...";
"mi_diagnostics" = "Diagnostische informatie";
"mi_active_services" = "Actieve services";
@ -82,6 +86,26 @@
"mi_xdebug_actions" = "Acties";
"mi_xdebug_disable_all" = "Alle modes uitschakelen";
// PHPEXTMAN
"phpextman.window.title" = "Extensies";
"phpextman.description" = "**PHP Extension Manager** stelt u in staat om verschillende PHP-extensies te beheren met een simpele klik op de knop. Omdat Homebrew wordt gebruikt, hoeven extensies niet ter plekke te worden gecompileerd met `pecl`.";
"phpextman.disclaimer" = "Bepaalde extensies kunnen vereisen dat andere afhankelijkheden worden geïnstalleerd, maar over het algemeen zou het installeren van extensies veel sneller moeten zijn dan het installeren van PHP-versies.";
"phpextman.warnings.removal.title" = "De extensie `%@` verwijderen?";
"phpextman.warnings.removal.desc" = "De extensie en het unieke configuratiebestand zullen worden verwijderd. De functionaliteit van de extensie zal niet meer beschikbaar zijn voor deze PHP-installatie. Weet u het zeker?
(Als de extensie is ingeschakeld met een afwijkende bestandsnaam, zal deze niet worden verwijderd. Dus als u niet wilt dat het .ini-bestand wordt verwijderd, kunt u het best een andere naam geven. In dat geval zal de extensie alleen worden uitgeschakeld als onderdeel van het opruimproces.)";
"phpextman.warnings.removal.button" = "Verwijderen";
"phpextman.list.showing_count" = "Momenteel worden %@ extensies weergegeven voor:";
"phpextman.list.depends_on" = "Is afhankelijk van:";
"phpextman.list.status.external" = "Deze extensie is al geïnstalleerd via een andere bron en kan niet worden beheerd.";
"phpextman.list.status.installable" = "Deze extensie kan worden geïnstalleerd.";
"phpextman.list.status.dependent" = "U kunt dit niet deïnstalleren voordat u %@ deïnstalleert.";
"phpextman.list.status.can_manage" = "Deze extensie is geïnstalleerd en kan worden beheerd door PHP Monitor.";
// PHPMAN
"phpman.busy.title" = "Bezig met zoeken naar updates!";
@ -91,6 +115,7 @@
"phpman.version.has_update" = "Versie %@ geïnstalleerd, %@ beschikbaar.";
"phpman.version.installed" = "Versie %@ is momenteel geïnstalleerd.";
"phpman.version.available_for_installation" = "Deze versie kan worden geïnstalleerd.";
"phpman.version.automatic_upgrade" = "Deze versie zal automatisch geïnstalleerd worden door een upgrade.";
"phpman.buttons.uninstall" = "Verwijderen";
"phpman.buttons.install" = "Installeren";
"phpman.buttons.update" = "Bijwerken";
@ -213,6 +238,9 @@ Tijdens het verwijderingsproces kan u worden gevraagd om uw wachtwoord indien de
"domain_list.columns.kind" = "Type";
"domain_list.columns.project_type" = "Projecttype";
"domain_list.extensions" = "Extensies in-/uitschakelen";
"domain_list.applies_to" = "Van toepassing op PHP %@";
// CHOOSE WHAT TO ADD
"selection.title" = "Wat voor soort domein wilt u instellen?";
@ -787,3 +815,11 @@ Houd er rekening mee dat sommige functies (hieronder grijs weergegeven) momentee
"onboarding.tour.feature_unavailable" = "Deze functie is momenteel niet beschikbaar en vereist de installatie van Laravel Valet.";
"onboarding.tour.once" = "U zult de Welkomsttour slechts één keer zien. U kunt de Welkomsttour later opnieuw openen via het menubalkpictogram (beschikbaar in het menu onder First Aid & Services).";
"onboarding.tour.close" = "Tour sluiten";
// LANGUAGE CHOICE
"prefs.language" = "Taal:";
"prefs.language_options_desc" = "Kies een andere taal om te gebruiken met PHP Monitor. Om deze wijziging volledig toe te passen, moet u de app herstarten.";
"alert.language_changed.title" = "U moet PHP Monitor herstarten!";
"alert.language_changed.subtitle" = "U heeft zojuist de weergavetaal van PHP Monitor gewijzigd. Het menu zal onmiddellijk de juiste taal gebruiken, maar u moet mogelijk de app herstarten om overal de nieuwe taal te zien.";

View File

@ -12,7 +12,11 @@
"mi_no_php_linked" = "Nenhuma versão PHP associada!";
"mi_fix_php_link" = "Corrigir automáticamente...";
"mi_no_php_linked_explain" = "O que é isto?";
"mi_php_version_manager" = "Gestor de versões PHP...";
"mi_php_version_manager" = "Gerenciar instalações PHP...";
"mi_php_ext_manager" = "Gerenciar extensões PHP...";
"mi_php_config_manager" = "Editor de configuração PHP...";
"mi_manage_limits" = "Gerenciar limites...";
"mi_diagnostics" = "Diagnóstico";
"mi_active_services" = "Serviços ativos";
@ -81,6 +85,26 @@
"mi_xdebug_actions" = "Ações";
"mi_xdebug_disable_all" = "Desativar todos os modos";
// PHPEXTMAN
"phpextman.window.title" = "Extensões";
"phpextman.description" = "**PHP Extension Manager** permite que você gerencie diferentes extensões PHP com um simples clique no botão. Como o Homebrew é usado, as extensões não precisam ser compiladas em tempo real usando `pecl`.";
"phpextman.disclaimer" = "Algumas extensões podem exigir que outras dependências sejam instaladas, mas, em geral, a instalação de extensões deve ser muito mais rápida do que a instalação de versões do PHP.";
"phpextman.warnings.removal.title" = "Desinstalar a extensão `%@`?";
"phpextman.warnings.removal.desc" = "A extensão e seu arquivo de configuração único serão removidos. A funcionalidade da extensão não estará mais disponível para esta instalação PHP. Você tem certeza?
(Se a extensão estiver ativada usando um nome de arquivo não padrão, ela não será removida. Então, se você não quer que o arquivo .ini seja removido, é melhor simplesmente renomeá-lo. Nesse caso, a extensão será apenas desativada como parte do processo de limpeza.)";
"phpextman.warnings.removal.button" = "Desinstalar";
"phpextman.list.showing_count" = "Atualmente mostrando %@ extensões para:";
"phpextman.list.depends_on" = "Depende de:";
"phpextman.list.status.external" = "Esta extensão já está instalada por outra fonte e não pode ser gerenciada.";
"phpextman.list.status.installable" = "Esta extensão pode ser instalada.";
"phpextman.list.status.dependent" = "Você não pode desinstalar isso antes de desinstalar %@.";
"phpextman.list.status.can_manage" = "Esta extensão está instalada e pode ser gerenciada pelo PHP Monitor.";
// PHPMAN
"phpman.busy.title" = "Procurando atualizações!";
@ -90,6 +114,7 @@
"phpman.version.has_update" = "Versão %@ instalada, %@ disponível.";
"phpman.version.installed" = "Versão %@ atualmente instalada.";
"phpman.version.available_for_installation" = "Esta versão pode ser instalada.";
"phpman.version.automatic_upgrade" = "Esta versão será instalada automaticamente ao atualizar uma versão mais antiga.";
"phpman.buttons.uninstall" = "Desinstalar";
"phpman.buttons.install" = "Instalar";
"phpman.buttons.update" = "Atualizar";
@ -212,6 +237,10 @@ Poderá ser-lhe solicitada a sua palavra-passe durante o processo de desinstala
"domain_list.columns.kind" = "Tipo";
"domain_list.columns.project_type" = "Tipo de projeto";
"domain_list.extensions" = "Alternar Extensões";
"domain_list.applies_to" = "Aplica-se ao PHP %@";
// CHOOSE WHAT TO ADD
"selection.title" = "Que tipo de domínio pretende configurar?";
@ -784,3 +813,11 @@ Tenha em conta que algumas funcionalidades (esmaecidos abaixo) estão indisponí
"onboarding.tour.feature_unavailable" = "Esta funcionalidade não está disponível de momento e requer a instalação do Laravel Valet.";
"onboarding.tour.once" = "Apenas irá ver este 'Boas-vindas' uma vez. Pode visualiza-lo mais tarde através da barra de menum em 'Primeiros Socorros e Serviços'.";
"onboarding.tour.close" = "Fechar";
// LANGUAGE CHOICE
"prefs.language" = "Idioma:";
"prefs.language_options_desc" = "Escolha um idioma diferente para usar com o PHP Monitor. Para aplicar completamente esta mudança, deve reiniciar a aplicação.";
"alert.language_changed.title" = "Deve reiniciar o PHP Monitor!";
"alert.language_changed.subtitle" = "Acabou de mudar o idioma de exibição do PHP Monitor. O menu usará imediatamente a língua correta, mas pode ser necessário reiniciar a aplicação para que todo o texto na aplicação reflita a sua nova escolha de idioma.";

View File

@ -12,7 +12,11 @@
"mi_no_php_linked" = "Không có phiên bản PHP nào được liên kết!";
"mi_fix_php_link" = "Sửa tự động...";
"mi_no_php_linked_explain" = "Cái này là gì?";
"mi_php_version_manager" = "Quản lý Phiên bản PHP...";
"mi_php_version_manager" = "Quản lý cài đặt PHP...";
"mi_php_ext_manager" = "Quản lý tiện ích mở rộng PHP...";
"mi_php_config_manager" = "Trình chỉnh sửa cấu hình PHP...";
"mi_manage_limits" = "Quản lý giới hạn...";
"mi_diagnostics" = "Chẩn đoán";
"mi_active_services" = "Dịch vụ hoạt động";
@ -81,6 +85,26 @@
"mi_xdebug_actions" = "Hành động";
"mi_xdebug_disable_all" = "Tắt Tất cả Chế độ";
// PHPEXTMAN
"phpextman.window.title" = "Tiện ích mở rộng";
"phpextman.description" = "**PHP Extension Manager** cho phép bạn quản lý các tiện ích mở rộng PHP khác nhau chỉ bằng một cú nhấp chuột đơn giản. Vì sử dụng Homebrew, các tiện ích mở rộng không cần phải được biên dịch trực tiếp bằng `pecl`.";
"phpextman.disclaimer" = "Một số tiện ích mở rộng có thể yêu cầu cài đặt các phụ thuộc khác, nhưng nói chung, việc cài đặt tiện ích mở rộng sẽ nhanh hơn nhiều so với việc cài đặt các phiên bản PHP.";
"phpextman.warnings.removal.title" = "Gỡ cài đặt tiện ích mở rộng `%@`?";
"phpextman.warnings.removal.desc" = "Tiện ích mở rộng và tệp cấu hình duy nhất của nó sẽ bị xóa. Chức năng của tiện ích mở rộng sẽ không còn sẵn có cho cài đặt PHP này. Bạn có chắc không?
(Nếu tiện ích mở rộng được kích hoạt bằng một tên tệp không chuẩn, nó sẽ không bị xóa. Vì vậy, nếu bạn không muốn tệp .ini bị xóa, tốt nhất là đơn giản hãy đổi tên nó. Trong trường hợp đó, tiện ích mở rộng chỉ sẽ bị vô hiệu hóa như một phần của quá trình dọn dẹp.)";
"phpextman.warnings.removal.button" = "Gỡ cài đặt";
"phpextman.list.showing_count" = "Hiện đang hiển thị %@ tiện ích mở rộng cho:";
"phpextman.list.depends_on" = "Phụ thuộc vào:";
"phpextman.list.status.external" = "Tiện ích mở rộng này đã được cài đặt thông qua một nguồn khác và không thể được quản lý.";
"phpextman.list.status.installable" = "Tiện ích mở rộng này có thể được cài đặt.";
"phpextman.list.status.dependent" = "Bạn không thể gỡ cài đặt điều này trước khi gỡ cài đặt %@.";
"phpextman.list.status.can_manage" = "Tiện ích mở rộng này đã được cài đặt và có thể được quản lý bởi PHP Monitor.";
// PHPMAN
"phpman.busy.title" = "Đang kiểm tra cập nhật!";
@ -90,6 +114,7 @@
"phpman.version.has_update" = "Phiên bản %@ được cài đặt, Phiên bản %@ có sẵn.";
"phpman.version.installed" = "Phiên bản %@ hiện đã được cài đặt.";
"phpman.version.available_for_installation" = "Phiên bản này có thể được cài đặt.";
"phpman.version.automatic_upgrade" = "Phiên bản này sẽ được cài đặt tự động bằng cách nâng cấp từ một phiên bản cũ hơn.";
"phpman.buttons.uninstall" = "Gỡ cài đặt";
"phpman.buttons.install" = "Cài đặt";
"phpman.buttons.update" = "Cập nhật";
@ -292,6 +317,9 @@ Bạn có thể được yêu cầu nhập mật khẩu của mình trong quá t
"domain_list.columns.kind" = "Loại";
"domain_list.columns.project_type" = "Loại dự án";
"domain_list.extensions" = "Bật/tắt Tiện ích mở rộng";
"domain_list.applies_to" = "Áp dụng cho PHP %@";
// DRIVERS
"driver.not_detected" = "Khác";
@ -781,3 +809,11 @@ Vui lòng lưu ý rằng một số tính năng (xám bên dưới) hiện khôn
"onboarding.tour.feature_unavailable" = "Tính năng này hiện không khả dụng và yêu cầu Laravel Valet được cài đặt.";
"onboarding.tour.once" = "Bạn chỉ sẽ thấy Hướng Dẫn Chào Mừng một lần. Bạn có thể mở lại Hướng Dẫn Chào Mừng sau này qua biểu tượng thanh menu (có sẵn trong menu, ở dưới Cứu hộ và Các dịch vụ).";
"onboarding.tour.close" = "Đóng Tour";
// LANGUAGE CHOICE
"prefs.language" = "Ngôn ngữ:";
"prefs.language_options_desc" = "Chọn một ngôn ngữ khác để sử dụng với PHP Monitor. Để áp dụng hoàn toàn thay đổi này, bạn phải khởi động lại ứng dụng.";
"alert.language_changed.title" = "Bạn nên khởi động lại PHP Monitor!";
"alert.language_changed.subtitle" = "Bạn vừa thay đổi ngôn ngữ hiển thị của PHP Monitor. Menu sẽ ngay lập tức sử dụng ngôn ngữ đúng, nhưng bạn có thể cần phải khởi động lại ứng dụng để toàn bộ văn bản trong ứng dụng phản ánh sự lựa chọn ngôn ngữ mới của bạn.";

View File

@ -181,9 +181,6 @@ class TestableConfigurations {
"/opt/homebrew/bin/php -r echo ini_get('memory_limit');": "512M",
"/opt/homebrew/bin/php -r echo ini_get('upload_max_filesize');": "512M",
"/opt/homebrew/bin/php -r echo ini_get('post_max_size');": "512M",
"/opt/homebrew/opt/php@8.2/bin/php -v": "OK (no full output needed for testing)",
"/opt/homebrew/opt/php@8.1/bin/php -v": "OK (no full output needed for testing)",
"/opt/homebrew/opt/php@8.0/bin/php -v": "OK (no full output needed for testing)"
],
preferenceOverrides: [
.automaticBackgroundUpdateCheck: false
@ -191,7 +188,8 @@ class TestableConfigurations {
phpVersions: [
VersionNumber(major: 8, minor: 2, patch: 6),
VersionNumber(major: 8, minor: 1, patch: 0),
VersionNumber(major: 8, minor: 0, patch: 0)
VersionNumber(major: 8, minor: 0, patch: 0),
VersionNumber(major: 7, minor: 4, patch: 33)
]
)
}

View File

@ -54,6 +54,16 @@ final class MainMenuTest: UITestCase {
app.mainMenuItem(withText: "mi_about".localized).click()
}
final func test_can_open_config_editor() throws {
let app = launch(openMenu: true)
app.buttons["phpConfigButton"].click()
Thread.sleep(forTimeInterval: 0.5)
assertExists(app.staticTexts["confman.title".localized], 1)
}
final func test_can_open_settings() throws {
let app = launch(openMenu: true)
app.mainMenuItem(withText: "mi_preferences".localized).click()
@ -80,16 +90,17 @@ final class MainMenuTest: UITestCase {
// Should display loader
assertExists(app.staticTexts["phpman.busy.title".localized], 1)
// After loading, should display PHP 8.2
// After loading, should display PHP 8.2 and PHP 8.3
assertExists(app.staticTexts["PHP 8.2"], 5)
assertExists(app.staticTexts["PHP 8.3"])
// Should also display pre-release version
assertExists(app.staticTexts["PHP 8.3"])
assertExists(app.staticTexts["PHP 8.4"])
assertExists(app.staticTexts["phpman.version.prerelease".localized.uppercased()])
assertExists(app.staticTexts["phpman.version.available_for_installation".localized])
// But not PHP 8.4 (yet)
assertNotExists(app.staticTexts["PHP 8.4"])
// But not PHP 8.5 (yet)
assertNotExists(app.staticTexts["PHP 8.5"])
// Also, PHP 8.2 should have an update available
assertExists(app.staticTexts["phpman.version.has_update".localized(

View File

@ -0,0 +1,41 @@
//
// ExtensionEnumeratorTest.swift
// Unit Tests
//
// Created by Nico Verbruggen on 30/10/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import XCTest
final class ExtensionEnumeratorTest: XCTestCase {
override func setUp() async throws {
ActiveFileSystem.useTestable([
"\(Paths.tapPath)/shivammathur/homebrew-extensions/Formula/xdebug@8.1.rb": .fake(.text, "<test>"),
"\(Paths.tapPath)/shivammathur/homebrew-extensions/Formula/xdebug@8.2.rb": .fake(.text, "<test>"),
"\(Paths.tapPath)/shivammathur/homebrew-extensions/Formula/xdebug@8.3.rb": .fake(.text, "<test>"),
"\(Paths.tapPath)/shivammathur/homebrew-extensions/Formula/xdebug@8.4.rb": .fake(.text, "<test>"),
])
}
func testCanReadFormulae() throws {
let directory = "\(Paths.tapPath)/shivammathur/homebrew-extensions/Formula"
let files = try FileSystem.getShallowContentsOfDirectory(directory)
XCTAssertEqual(
Set(["xdebug@8.1.rb", "xdebug@8.2.rb", "xdebug@8.3.rb", "xdebug@8.4.rb"]),
Set(files)
)
}
func testCanParseFormulaeBasedOnSyntax() throws {
let formulae = BrewTapFormulae.from(tap: "shivammathur/homebrew-extensions")
XCTAssertEqual(formulae["8.1"], [BrewPhpExtension(path: "/", name: "xdebug", phpVersion: "8.1")])
XCTAssertEqual(formulae["8.2"], [BrewPhpExtension(path: "/", name: "xdebug", phpVersion: "8.2")])
XCTAssertEqual(formulae["8.3"], [BrewPhpExtension(path: "/", name: "xdebug", phpVersion: "8.3")])
XCTAssertEqual(formulae["8.4"], [BrewPhpExtension(path: "/", name: "xdebug", phpVersion: "8.4")])
}
}

View File

@ -17,7 +17,15 @@ class HomebrewUpgradableTest: XCTestCase {
func test_upgradable_php_versions_can_be_parsed() async throws {
ActiveShell.useTestable([
"/opt/homebrew/bin/brew update >/dev/null && /opt/homebrew/bin/brew outdated --json --formulae"
: .instant(try! String(contentsOf: Self.outdatedFileUrl))
: .instant(try! String(contentsOf: Self.outdatedFileUrl)),
"/opt/homebrew/bin/php --ini | grep -E -o '(/[^ ]+\\.ini)'"
: .instant("/opt/homebrew/etc/php/8.2/conf.d/php-memory-limits.ini"),
"/opt/homebrew/opt/php@8.1.16/bin/php --ini | grep -E -o '(/[^ ]+\\.ini)'"
: .instant("/opt/homebrew/etc/php/8.1/conf.d/php-memory-limits.ini"),
"/opt/homebrew/opt/php@8.2.3/bin/php --ini | grep -E -o '(/[^ ]+\\.ini)'"
: .instant("/opt/homebrew/etc/php/8.2/conf.d/php-memory-limits.ini"),
"/opt/homebrew/opt/php@7.4.11/bin/php --ini | grep -E -o '(/[^ ]+\\.ini)'"
: .instant("/opt/homebrew/etc/php/7.4/conf.d/php-memory-limits.ini")
])
let env = PhpEnvironments.shared
@ -27,7 +35,7 @@ class HomebrewUpgradableTest: XCTestCase {
"7.4": PhpInstallation("7.4.11")
]
let data = await BrewFormulaeHandler().loadPhpVersions(loadOutdated: true)
let data = await BrewPhpFormulaeHandler().loadPhpVersions(loadOutdated: true)
XCTAssertTrue(data.contains(where: { formula in
formula.installedVersion == "8.1.16" && formula.upgradeVersion == "8.1.17"

View File

@ -23,6 +23,14 @@ class RealShellTest: XCTestCase {
XCTAssertTrue(output.out.contains("Copyright (c) The PHP Group"))
}
func test_system_shell_can_be_used_synchronously() {
XCTAssertTrue(Shell is RealShell)
let output = Shell.sync("php -v")
XCTAssertTrue(output.out.contains("Copyright (c) The PHP Group"))
}
func test_system_shell_has_path() {
let systemShell = Shell as! RealShell

View File

@ -30,6 +30,35 @@ class TestableShellTest: XCTestCase {
XCTAssertEqual("Hello world\nGoodbye world", output.out)
}
func test_fake_shell_synchronous_output() {
let greeting = BatchFakeShellOutput(items: [
.instant("Hello world\n"),
.delayed(0.2, "Goodbye world")
])
let output = greeting.syncOutput()
XCTAssertEqual("Hello world\nGoodbye world", output.out)
}
func test_fake_shell_usage() {
let expectedOutput = """
PHP 8.3.0 (cli) (built: Nov 21 2023 14:40:35) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.0, Copyright (c) Zend Technologies
with Xdebug v3.2.2, Copyright (c) 2002-2023, by Derick Rethans
with Zend OPcache v8.3.0, Copyright (c), by Zend Technologies
"""
let shell = TestableShell(expectations: [
"php -v": .instant(expectedOutput),
"echo $PATH": .instant("/Users/user/bin:/opt/homebrew/bin")
])
XCTAssertEqual(expectedOutput, shell.sync("php -v").out)
XCTAssertEqual("/Users/user/bin:/opt/homebrew/bin", shell.sync("echo $PATH").out)
}
func test_fake_shell_has_path() {
ActiveShell.useTestable([:])