Compare commits
83 Commits
Author | SHA1 | Date | |
---|---|---|---|
2e58bbb5e8 | |||
bb6abc5e9a | |||
e4f9d491c3 | |||
1ba7ee37db | |||
34cfbae3a9 | |||
9fabda545f | |||
7cd50aed7b | |||
cdc082071d | |||
d1479672c8 | |||
ffb112cfb2 | |||
f5af33c098 | |||
0f464f5814 | |||
776c2095e6 | |||
82a2833161 | |||
b14d7bdf07 | |||
ffa22eea25 | |||
8220e6409d | |||
927e1c02fa | |||
a13a17b106 | |||
18d6d73f94 | |||
54d101acbe | |||
76f720cb71 | |||
033e485ce1 | |||
82ee830de1 | |||
55cd1cccee | |||
5f13ba3d1b | |||
298aa0aa8d | |||
5d49be6f7e | |||
ab81bf5875 | |||
5d8765683a | |||
6c22bc0145 | |||
|
5c1908668f | ||
94da7fb255 | |||
46d2d35c1a | |||
cba41b29bc | |||
7a8f47b995 | |||
40062c5091 | |||
6081ef6b02 | |||
4cffe5a662 | |||
813aec2b42 | |||
f2452bbc70 | |||
28fb685bfc | |||
0661ca00ff | |||
0b04619003 | |||
85d7b6aa57 | |||
bba961269c | |||
d0f7d2c5e9 | |||
eeeb3eb184 | |||
f00f8d26f6 | |||
74817beec6 | |||
7b6809245c | |||
5b40a8fd41 | |||
193f459be1 | |||
c4c19a5b47 | |||
7d103c70e7 | |||
2ffe90948e | |||
8e61aaacde | |||
29c8fcbde2 | |||
8dd21f46aa | |||
e688dde2aa | |||
987e1e1bdb | |||
510257c436 | |||
bb1572f32a | |||
45276034b1 | |||
0d4a144524 | |||
a0e5102ca7 | |||
69c0f5ace9 | |||
d0962c2387 | |||
4670894cfd | |||
a2f6c70a03 | |||
ef469868d8 | |||
c9ba872529 | |||
1e15042be2 | |||
7647978da5 | |||
|
f82f3052f2 | ||
10b299ff65 | |||
e4ff0418fd | |||
a2b25e31ca | |||
c4772db808 | |||
38c2d9131b | |||
1566323fca | |||
|
bf0a923eb2 | ||
e372480249 |
3
.github/FUNDING.yml
vendored
@@ -1 +1,2 @@
|
||||
custom: ['https://nicoverbruggen.be/sponsor', 'https://paypal.me/nicoverbruggen']
|
||||
github: nicoverbruggen
|
||||
custom: ['https://nicoverbruggen.be/sponsor']
|
46
DEVELOPER.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# DEVELOPER README
|
||||
|
||||
## 🔧 Build instructions
|
||||
|
||||
<img src="./docs/build.png" width="404px" alt="build button in Xcode"/>
|
||||
|
||||
If you'd like to build PHP Monitor yourself, you need:
|
||||
|
||||
* Xcode (usually the latest version)
|
||||
* The contents of this repository
|
||||
|
||||
Once you have downloaded this repository, open `PHP Monitor.xcodeproj`, and you should be able to immediately build the app for your system by pressing Cmd-R. This will create a debug build. (If Xcode complains about code signing, you can turn it off.)
|
||||
|
||||
If you'd like to create a production build, choose "Any Mac" as the target and select Product > Archive.
|
||||
|
||||
## 🐛 Symbolication of crashes
|
||||
|
||||
If you have an archived build of the app and exported the DSYM, it is possible to symbolicate .ips crash logs.
|
||||
|
||||
For example, given the following crash (from an .ips file):
|
||||
|
||||
```
|
||||
Thread 2 Crashed:: Dispatch queue: com.apple.root.user-initiated-qos
|
||||
0 libswiftDispatch.dylib 0x7ff82aa3ab8c static OS_dispatch_source.makeProcessSource(identifier:eventMask:queue:) + 28
|
||||
1 PHP Monitor 0x1096907d8 0x10965e000 + 206808
|
||||
| |
|
||||
address load address
|
||||
2 PHP Monitor 0x1096903ac 0x10965e000 + 205740
|
||||
3 PHP Monitor 0x10968f88b 0x10965e000 + 202891
|
||||
```
|
||||
|
||||
You must use the correct order for the the address and load address in the command below:
|
||||
|
||||
```
|
||||
$ atos -arch x86_64 -o '/path/to/PHP Monitor.app.dSYM/Contents/Resources/DWARF/PHP Monitor' -l 0x10965e000 0x1096907d8
|
||||
| | | |
|
||||
architecture path to DSYM load address address
|
||||
```
|
||||
|
||||
This will return the relevant information, for example:
|
||||
|
||||
```
|
||||
FSWatcher.startMonitoring(_:behaviour:) (in PHP Monitor) (PhpConfigWatcher.swift:95)
|
||||
```
|
||||
|
||||
For more information, see [Apple's documentation](https://developer.apple.com/documentation/xcode/adding-identifiable-symbol-names-to-a-crash-report).
|
@@ -9,13 +9,11 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
5420395926135DC100FB00FA /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; };
|
||||
5420395F2613607600FB00FA /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; };
|
||||
54AB03262763858F00A29D5F /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AB03252763858F00A29D5F /* Timer.swift */; };
|
||||
54AB03272763858F00A29D5F /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AB03252763858F00A29D5F /* Timer.swift */; };
|
||||
54B48B5F275F66AE006D90C5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Application.swift */; };
|
||||
54B48B60275F66AE006D90C5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Application.swift */; };
|
||||
54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; };
|
||||
54FCFD26276C883F004CE748 /* CheckboxPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD25276C883F004CE748 /* CheckboxPreferenceView.xib */; };
|
||||
54FCFD27276C883F004CE748 /* CheckboxPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD25276C883F004CE748 /* CheckboxPreferenceView.xib */; };
|
||||
54FCFD26276C883F004CE748 /* SelectPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD25276C883F004CE748 /* SelectPreferenceView.xib */; };
|
||||
54FCFD27276C883F004CE748 /* SelectPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD25276C883F004CE748 /* SelectPreferenceView.xib */; };
|
||||
54FCFD2A276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD29276C8AA4004CE748 /* CheckboxPreferenceView.swift */; };
|
||||
54FCFD2B276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD29276C8AA4004CE748 /* CheckboxPreferenceView.swift */; };
|
||||
54FCFD2D276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD2C276C8D67004CE748 /* HotkeyPreferenceView.xib */; };
|
||||
@@ -24,30 +22,31 @@
|
||||
54FCFD31276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */; };
|
||||
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */; };
|
||||
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */; };
|
||||
C4068CA427B0780A00544CD5 /* CheckboxPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C4068CA327B0780A00544CD5 /* CheckboxPreferenceView.xib */; };
|
||||
C4068CA527B0780A00544CD5 /* CheckboxPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C4068CA327B0780A00544CD5 /* CheckboxPreferenceView.xib */; };
|
||||
C4068CA727B07A1300544CD5 /* SelectPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA627B07A1300544CD5 /* SelectPreferenceView.swift */; };
|
||||
C4068CA827B07A1300544CD5 /* SelectPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA627B07A1300544CD5 /* SelectPreferenceView.swift */; };
|
||||
C4068CAA27B0890D00544CD5 /* MenuBarIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA927B0890D00544CD5 /* MenuBarIcons.swift */; };
|
||||
C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA927B0890D00544CD5 /* MenuBarIcons.swift */; };
|
||||
C40B24F127A3106D0018C7D2 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E67279DE0540010F296 /* ServicesView.swift */; };
|
||||
C40B24F227A310770018C7D2 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; };
|
||||
C40B24F427A310830018C7D2 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; };
|
||||
C40B24F527A3108B0018C7D2 /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E6C279DF87A0010F296 /* Async.swift */; };
|
||||
C40C7F1E2772136000DDDCDC /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; };
|
||||
C40C7F1F2772136000DDDCDC /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; };
|
||||
C40C7F202772136000DDDCDC /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; };
|
||||
C40C7F2227721F8200DDDCDC /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; };
|
||||
C40C7F2327721F8200DDDCDC /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; };
|
||||
C40C7F2427721F8200DDDCDC /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; };
|
||||
C40C7F2527721F9800DDDCDC /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; };
|
||||
C40C7F2627721FA200DDDCDC /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; };
|
||||
C40C7F2827721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */; };
|
||||
C40C7F2927721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */; };
|
||||
C40C7F2B2772201C00DDDCDC /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; };
|
||||
C40C7F3027722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; };
|
||||
C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; };
|
||||
C40C7F3227722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; };
|
||||
C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; };
|
||||
C415937F27A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; };
|
||||
C415938027A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; };
|
||||
C415D3B72770F294005EF286 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; };
|
||||
C415D3B82770F294005EF286 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; };
|
||||
C415D3E12770F34D005EF286 /* AllowedArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3DE2770F34D005EF286 /* AllowedArguments.swift */; };
|
||||
C415D3E62770F540005EF286 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3E52770F540005EF286 /* main.swift */; };
|
||||
C415D3E82770F692005EF286 /* AppDelegate+InterApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */; };
|
||||
C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */; };
|
||||
C417DC74277614690015E6EE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; };
|
||||
C417DC75277614690015E6EE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; };
|
||||
C417DC76277614690015E6EE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; };
|
||||
C4188989275FE8CB001EF227 /* Filesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4188988275FE8CB001EF227 /* Filesystem.swift */; };
|
||||
C418898A275FE8CB001EF227 /* Filesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4188988275FE8CB001EF227 /* Filesystem.swift */; };
|
||||
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; };
|
||||
@@ -72,6 +71,10 @@
|
||||
C44C198E276E3A1C0072762D /* ProgressWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* ProgressWindow.swift */; };
|
||||
C44C1991276E44CB0072762D /* ProgressWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C44C1990276E44CB0072762D /* ProgressWindow.storyboard */; };
|
||||
C44C1992276E44CB0072762D /* ProgressWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C44C1990276E44CB0072762D /* ProgressWindow.storyboard */; };
|
||||
C44CCD4027AFE2FC00CE40E5 /* AlertableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */; };
|
||||
C44CCD4127AFE2FC00CE40E5 /* AlertableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */; };
|
||||
C44CCD4927AFF3B700CE40E5 /* MainMenu+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */; };
|
||||
C44CCD4A27AFF3BC00CE40E5 /* MainMenu+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */; };
|
||||
C464ADAC275A7A3F003FCD53 /* SiteListWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* SiteListWC.swift */; };
|
||||
C464ADAD275A7A3F003FCD53 /* SiteListWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* SiteListWC.swift */; };
|
||||
C464ADAF275A7A69003FCD53 /* SiteListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAE275A7A69003FCD53 /* SiteListVC.swift */; };
|
||||
@@ -94,13 +97,15 @@
|
||||
C48D0CA325CC992000CC7490 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0CA225CC992000CC7490 /* StatsView.swift */; };
|
||||
C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; };
|
||||
C48D6C71279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; };
|
||||
C48D6C72279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; };
|
||||
C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */; };
|
||||
C4927F0B27B2DFC200C55AFD /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; };
|
||||
C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; };
|
||||
C493084A279F331F009C240B /* AddSiteVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4930849279F331F009C240B /* AddSiteVC.swift */; };
|
||||
C493084B279F331F009C240B /* AddSiteVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4930849279F331F009C240B /* AddSiteVC.swift */; };
|
||||
C4998F0626175E7200B2526E /* HotKey in Frameworks */ = {isa = PBXBuildFile; productRef = C4998F0526175E7200B2526E /* HotKey */; };
|
||||
C4998F0A2617633900B2526E /* PrefsWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PrefsWC.swift */; };
|
||||
C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PrefsWC.swift */; };
|
||||
C49E171F27A5736E00787921 /* PMServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49E171E27A5736E00787921 /* PMServicesView.swift */; };
|
||||
C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; };
|
||||
C4AF9F71275445FF00D44ED0 /* valet-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AF9F70275445FF00D44ED0 /* valet-config.json */; };
|
||||
C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AF9F70275445FF00D44ED0 /* valet-config.json */; };
|
||||
@@ -113,13 +118,10 @@
|
||||
C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B56360276AB0A500F12CCB /* VersionExtractorTest.swift */; };
|
||||
C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; };
|
||||
C4B5853F2770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; };
|
||||
C4B585402770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; };
|
||||
C4B585412770FE3900DA4FBE /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* Shell.swift */; };
|
||||
C4B585422770FE3900DA4FBE /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* Shell.swift */; };
|
||||
C4B585432770FE3900DA4FBE /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* Shell.swift */; };
|
||||
C4B585442770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; };
|
||||
C4B585452770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; };
|
||||
C4B585462770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; };
|
||||
C4B97B75275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */; };
|
||||
C4B97B76275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */; };
|
||||
C4B97B78275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */; };
|
||||
@@ -134,39 +136,41 @@
|
||||
C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */; };
|
||||
C4CCBA6C275C567B008C7055 /* PMWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CCBA6B275C567B008C7055 /* PMWindowController.swift */; };
|
||||
C4CCBA6D275C567B008C7055 /* PMWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CCBA6B275C567B008C7055 /* PMWindowController.swift */; };
|
||||
C4CE3BB827B31F2E0086CA49 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; };
|
||||
C4CE3BBA27B31F670086CA49 /* MainMenu+Composer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* MainMenu+Composer.swift */; };
|
||||
C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; };
|
||||
C4CE3BBC27B324250086CA49 /* MainMenu+Composer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* MainMenu+Composer.swift */; };
|
||||
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; };
|
||||
C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D89BC52783C99400A02B68 /* ComposerJson.swift */; };
|
||||
C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */; };
|
||||
C4D9ADC0277610E1007277F4 /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */; };
|
||||
C4D9ADC1277610E1007277F4 /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */; };
|
||||
C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; };
|
||||
C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; };
|
||||
C4D9ADCA277611A0007277F4 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; };
|
||||
C4DEB7D427A5D60B00834718 /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DEB7D327A5D60B00834718 /* Stats.swift */; };
|
||||
C4EC1E66279DE0380010F296 /* ServicesView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C4EC1E65279DE0380010F296 /* ServicesView.xib */; };
|
||||
C4EC1E68279DE0540010F296 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E67279DE0540010F296 /* ServicesView.swift */; };
|
||||
C4EC1E6D279DF87A0010F296 /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E6C279DF87A0010F296 /* Async.swift */; };
|
||||
C4EC1E6E279DF87A0010F296 /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E6C279DF87A0010F296 /* Async.swift */; };
|
||||
C4EC1E73279DFCF40010F296 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; };
|
||||
C4EC1E74279DFCF40010F296 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; };
|
||||
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; };
|
||||
C4EE55A927708B9E001DF387 /* PMHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A627708B9E001DF387 /* PMHeaderView.swift */; };
|
||||
C4EE55AA27708B9E001DF387 /* PMHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A627708B9E001DF387 /* PMHeaderView.swift */; };
|
||||
C4EE55AB27708B9E001DF387 /* Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A727708B9E001DF387 /* Preview.swift */; };
|
||||
C4EE55AC27708B9E001DF387 /* Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A727708B9E001DF387 /* Preview.swift */; };
|
||||
C4EE55AD27708B9E001DF387 /* PMStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A827708B9E001DF387 /* PMStatsView.swift */; };
|
||||
C4EE55AE27708B9E001DF387 /* PMStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A827708B9E001DF387 /* PMStatsView.swift */; };
|
||||
C4EED88927A48778006D7272 /* InterAppHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EED88827A48778006D7272 /* InterAppHandler.swift */; };
|
||||
C4EED88A27A48778006D7272 /* InterAppHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EED88827A48778006D7272 /* InterAppHandler.swift */; };
|
||||
C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; };
|
||||
C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; };
|
||||
C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; };
|
||||
C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; };
|
||||
C4F30B03278E16BA00755FCE /* HomebrewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F30B02278E16BA00755FCE /* HomebrewService.swift */; };
|
||||
C4F30B04278E16BA00755FCE /* HomebrewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F30B02278E16BA00755FCE /* HomebrewService.swift */; };
|
||||
C4F30B05278E16BA00755FCE /* HomebrewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F30B02278E16BA00755FCE /* HomebrewService.swift */; };
|
||||
C4F30B07278E195800755FCE /* brew-services.json in Resources */ = {isa = PBXBuildFile; fileRef = C4F30B06278E195800755FCE /* brew-services.json */; };
|
||||
C4F30B08278E195800755FCE /* brew-services.json in Resources */ = {isa = PBXBuildFile; fileRef = C4F30B06278E195800755FCE /* brew-services.json */; };
|
||||
C4F30B09278E1A0E00755FCE /* CustomPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */; };
|
||||
C4F30B0A278E1A1A00755FCE /* ComposerJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D89BC52783C99400A02B68 /* ComposerJson.swift */; };
|
||||
C4F30B0B278E203C00755FCE /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; };
|
||||
C4F319C927B034A500AFF46F /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DEB7D327A5D60B00834718 /* Stats.swift */; };
|
||||
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F7809B25D80344000DBC97 /* CommandTest.swift */; };
|
||||
C4F780A825D80AE8000DBC97 /* php.ini in Resources */ = {isa = PBXBuildFile; fileRef = C4F780A725D80AE8000DBC97 /* php.ini */; };
|
||||
C4F780AE25D80B37000DBC97 /* ExtensionParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F780AD25D80B37000DBC97 /* ExtensionParserTest.swift */; };
|
||||
@@ -179,7 +183,6 @@
|
||||
C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; };
|
||||
C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; };
|
||||
C4F780C625D80B75000DBC97 /* XibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9225CC804200CC7490 /* XibLoadable.swift */; };
|
||||
C4F780C725D80B75000DBC97 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; };
|
||||
C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; };
|
||||
C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; };
|
||||
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; };
|
||||
@@ -201,38 +204,27 @@
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
C415D3D42770F341005EF286 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = /usr/share/man/man1/;
|
||||
dstSubfolderSpec = 0;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 1;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
5420395826135DC100FB00FA /* PrefsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsVC.swift; sourceTree = "<group>"; };
|
||||
5420395E2613607600FB00FA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
|
||||
54AB03252763858F00A29D5F /* Timer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timer.swift; sourceTree = "<group>"; };
|
||||
54B48B5E275F66AE006D90C5 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
|
||||
54FCFD25276C883F004CE748 /* CheckboxPreferenceView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CheckboxPreferenceView.xib; sourceTree = "<group>"; };
|
||||
54FCFD25276C883F004CE748 /* SelectPreferenceView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SelectPreferenceView.xib; sourceTree = "<group>"; };
|
||||
54FCFD29276C8AA4004CE748 /* CheckboxPreferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxPreferenceView.swift; sourceTree = "<group>"; };
|
||||
54FCFD2C276C8D67004CE748 /* HotkeyPreferenceView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HotkeyPreferenceView.xib; sourceTree = "<group>"; };
|
||||
54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HotkeyPreferenceView.swift; sourceTree = "<group>"; };
|
||||
C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = InternetAccessPolicy.strings; sourceTree = "<group>"; };
|
||||
C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = "<group>"; };
|
||||
C4068CA327B0780A00544CD5 /* CheckboxPreferenceView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CheckboxPreferenceView.xib; sourceTree = "<group>"; };
|
||||
C4068CA627B07A1300544CD5 /* SelectPreferenceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectPreferenceView.swift; sourceTree = "<group>"; };
|
||||
C4068CA927B0890D00544CD5 /* MenuBarIcons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarIcons.swift; sourceTree = "<group>"; };
|
||||
C40C7F1D2772136000DDDCDC /* PhpEnv.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpEnv.swift; sourceTree = "<group>"; };
|
||||
C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ActivePhpInstallation+Checks.swift"; sourceTree = "<group>"; };
|
||||
C40C7F2F27722E8D00DDDCDC /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||
C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackage.swift; sourceTree = "<group>"; };
|
||||
C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpFrameworks.swift; sourceTree = "<group>"; };
|
||||
C415D3B62770F294005EF286 /* Actions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = "<group>"; };
|
||||
C415D3D62770F341005EF286 /* phpmon-cli */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "phpmon-cli"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C415D3DE2770F34D005EF286 /* AllowedArguments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllowedArguments.swift; sourceTree = "<group>"; };
|
||||
C415D3E52770F540005EF286 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||
C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+InterApp.swift"; sourceTree = "<group>"; };
|
||||
C4168F4427ADB4A3003B6C39 /* DEVELOPER.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = DEVELOPER.md; sourceTree = "<group>"; };
|
||||
C417DC73277614690015E6EE /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
|
||||
C4188988275FE8CB001EF227 /* Filesystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filesystem.swift; sourceTree = "<group>"; };
|
||||
C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PHP Monitor.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -254,6 +246,8 @@
|
||||
C43A8A2325D9D20D00591B77 /* BrewJsonParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewJsonParserTest.swift; sourceTree = "<group>"; };
|
||||
C44C198C276E3A1C0072762D /* ProgressWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressWindow.swift; sourceTree = "<group>"; };
|
||||
C44C1990276E44CB0072762D /* ProgressWindow.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ProgressWindow.storyboard; sourceTree = "<group>"; };
|
||||
C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertableError.swift; sourceTree = "<group>"; };
|
||||
C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Async.swift"; sourceTree = "<group>"; };
|
||||
C464ADAB275A7A3F003FCD53 /* SiteListWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteListWC.swift; sourceTree = "<group>"; };
|
||||
C464ADAE275A7A69003FCD53 /* SiteListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteListVC.swift; sourceTree = "<group>"; };
|
||||
C464ADB1275A87CA003FCD53 /* SiteListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteListCell.swift; sourceTree = "<group>"; };
|
||||
@@ -271,8 +265,10 @@
|
||||
C48D0CA225CC992000CC7490 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; };
|
||||
C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpVersionNumber.swift; sourceTree = "<group>"; };
|
||||
C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpVersionNumberTest.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>"; };
|
||||
C4998F092617633900B2526E /* PrefsWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsWC.swift; sourceTree = "<group>"; };
|
||||
C49E171E27A5736E00787921 /* PMServicesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PMServicesView.swift; sourceTree = "<group>"; };
|
||||
C4ACA38E25C754C100060C66 /* PhpExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtension.swift; sourceTree = "<group>"; };
|
||||
C4AF9F70275445FF00D44ED0 /* valet-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "valet-config.json"; sourceTree = "<group>"; };
|
||||
C4AF9F76275447F100D44ED0 /* ValetConfigParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetConfigParserTest.swift; sourceTree = "<group>"; };
|
||||
@@ -291,10 +287,13 @@
|
||||
C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "App+ConfigWatch.swift"; sourceTree = "<group>"; };
|
||||
C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpConfigWatcher.swift; sourceTree = "<group>"; };
|
||||
C4CCBA6B275C567B008C7055 /* PMWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PMWindowController.swift; sourceTree = "<group>"; };
|
||||
C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Switcher.swift"; sourceTree = "<group>"; };
|
||||
C4CE3BB927B31F670086CA49 /* MainMenu+Composer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Composer.swift"; sourceTree = "<group>"; };
|
||||
C4D8016522B1584700C6DA1B /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = "<group>"; };
|
||||
C4D89BC52783C99400A02B68 /* ComposerJson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerJson.swift; sourceTree = "<group>"; };
|
||||
C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpSwitcher.swift; sourceTree = "<group>"; };
|
||||
C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalSwitcher.swift; sourceTree = "<group>"; };
|
||||
C4DEB7D327A5D60B00834718 /* Stats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stats.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>"; };
|
||||
C4EC1E65279DE0380010F296 /* ServicesView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ServicesView.xib; sourceTree = "<group>"; };
|
||||
@@ -305,6 +304,7 @@
|
||||
C4EE55A627708B9E001DF387 /* PMHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PMHeaderView.swift; sourceTree = "<group>"; };
|
||||
C4EE55A727708B9E001DF387 /* Preview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preview.swift; sourceTree = "<group>"; };
|
||||
C4EE55A827708B9E001DF387 /* PMStatsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PMStatsView.swift; sourceTree = "<group>"; };
|
||||
C4EED88827A48778006D7272 /* InterAppHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterAppHandler.swift; sourceTree = "<group>"; };
|
||||
C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewDiagnostics.swift; sourceTree = "<group>"; };
|
||||
C4F2E4392752F7D00020E974 /* PhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpInstallation.swift; sourceTree = "<group>"; };
|
||||
C4F30B02278E16BA00755FCE /* HomebrewService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewService.swift; sourceTree = "<group>"; };
|
||||
@@ -321,13 +321,6 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
C415D3D32770F341005EF286 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
C41C1B3022B0097F00E7CF16 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -353,6 +346,8 @@
|
||||
5420395826135DC100FB00FA /* PrefsVC.swift */,
|
||||
5420395E2613607600FB00FA /* Preferences.swift */,
|
||||
C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */,
|
||||
C4068CA927B0890D00544CD5 /* MenuBarIcons.swift */,
|
||||
C4DEB7D327A5D60B00834718 /* Stats.swift */,
|
||||
C41CD0272628D8E20065BBED /* Keybinds */,
|
||||
54FCFD28276C88C0004CE748 /* Views */,
|
||||
);
|
||||
@@ -375,8 +370,10 @@
|
||||
54FCFD28276C88C0004CE748 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54FCFD25276C883F004CE748 /* CheckboxPreferenceView.xib */,
|
||||
C4068CA327B0780A00544CD5 /* CheckboxPreferenceView.xib */,
|
||||
54FCFD29276C8AA4004CE748 /* CheckboxPreferenceView.swift */,
|
||||
54FCFD25276C883F004CE748 /* SelectPreferenceView.xib */,
|
||||
C4068CA627B07A1300544CD5 /* SelectPreferenceView.swift */,
|
||||
54FCFD2C276C8D67004CE748 /* HotkeyPreferenceView.xib */,
|
||||
54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */,
|
||||
);
|
||||
@@ -418,26 +415,16 @@
|
||||
path = Core;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C415D3D72770F341005EF286 /* phpmon-cli */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C415D3E52770F540005EF286 /* main.swift */,
|
||||
C415D3DE2770F34D005EF286 /* AllowedArguments.swift */,
|
||||
);
|
||||
path = "phpmon-cli";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C41C1B2A22B0097F00E7CF16 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C4F8C0A522D4FA41002EFE61 /* README.md */,
|
||||
C4E713562570150F00007428 /* SECURITY.md */,
|
||||
C4F7807425D7F7E5000DBC97 /* RELEASE.md */,
|
||||
C4168F4427ADB4A3003B6C39 /* DEVELOPER.md */,
|
||||
C4E713572570151400007428 /* docs */,
|
||||
C41C1B3522B0097F00E7CF16 /* phpmon */,
|
||||
C4F7807A25D7F84B000DBC97 /* phpmon-tests */,
|
||||
C415D3D72770F341005EF286 /* phpmon-cli */,
|
||||
C4B5853A2770FE2500DA4FBE /* phpmon-common */,
|
||||
C41C1B3422B0097F00E7CF16 /* Products */,
|
||||
C4D309E72770EF2F00958BCF /* Frameworks */,
|
||||
);
|
||||
@@ -448,7 +435,6 @@
|
||||
children = (
|
||||
C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */,
|
||||
C4F7807925D7F84B000DBC97 /* phpmon-tests.xctest */,
|
||||
C415D3D62770F341005EF286 /* phpmon-cli */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -456,6 +442,7 @@
|
||||
C41C1B3522B0097F00E7CF16 /* phpmon */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C4B5853A2770FE2500DA4FBE /* Common */,
|
||||
C41E181722CB61EB0072CF09 /* Domain */,
|
||||
C41C1B3F22B0098000E7CF16 /* Info.plist */,
|
||||
C4232EE42612526500158FC6 /* Credits.html */,
|
||||
@@ -479,15 +466,13 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C4AF9F6B275445D300D44ED0 /* Integrations */,
|
||||
C4B13B1D25C4915000548C3A /* Core */,
|
||||
C4B13B1D25C4915000548C3A /* App */,
|
||||
C4D9ADBD27761084007277F4 /* PHP */,
|
||||
C47331A0247093AC009A0597 /* Menu */,
|
||||
C464ADAA275A7A25003FCD53 /* SiteList */,
|
||||
5420395726135DB800FB00FA /* Preferences */,
|
||||
C44C198F276E3A380072762D /* Progress */,
|
||||
C4C8E81D276F5686003AC782 /* Watcher */,
|
||||
C4811D2822D70D9C00B5F6B3 /* Helpers */,
|
||||
C4F8C0A222D4F100002EFE61 /* Extensions */,
|
||||
C4EE55B027708BB2001DF387 /* SwiftUI */,
|
||||
);
|
||||
path = Domain;
|
||||
@@ -502,6 +487,15 @@
|
||||
path = Progress;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C44CCD4327AFE93300CE40E5 /* Errors */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */,
|
||||
C4927F0A27B2DFC200C55AFD /* Errors.swift */,
|
||||
);
|
||||
path = Errors;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C464ADAA275A7A25003FCD53 /* SiteList */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -519,7 +513,10 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */,
|
||||
C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */,
|
||||
C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */,
|
||||
C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */,
|
||||
C4CE3BB927B31F670086CA49 /* MainMenu+Composer.swift */,
|
||||
C47331A1247093B7009A0597 /* StatusMenu.swift */,
|
||||
C48D0C9525CC80B100CC7490 /* HeaderView.swift */,
|
||||
C48D0C9925CC888B00CC7490 /* HeaderView.xib */,
|
||||
@@ -540,7 +537,6 @@
|
||||
C474B00524C0E98C00066A22 /* LocalNotification.swift */,
|
||||
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */,
|
||||
C4CCBA6B275C567B008C7055 /* PMWindowController.swift */,
|
||||
54AB03252763858F00A29D5F /* Timer.swift */,
|
||||
C4B5635D276AB09000F12CCB /* VersionExtractor.swift */,
|
||||
C4EC1E6C279DF87A0010F296 /* Async.swift */,
|
||||
);
|
||||
@@ -582,7 +578,7 @@
|
||||
path = Homebrew;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C4B13B1D25C4915000548C3A /* Core */ = {
|
||||
C4B13B1D25C4915000548C3A /* App */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C41C1B3C22B0098000E7CF16 /* Main.storyboard */,
|
||||
@@ -594,17 +590,21 @@
|
||||
C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */,
|
||||
C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */,
|
||||
C4D8016522B1584700C6DA1B /* Startup.swift */,
|
||||
C4EED88827A48778006D7272 /* InterAppHandler.swift */,
|
||||
);
|
||||
path = Core;
|
||||
path = App;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C4B5853A2770FE2500DA4FBE /* phpmon-common */ = {
|
||||
C4B5853A2770FE2500DA4FBE /* Common */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C40C7F2127721F7300DDDCDC /* Core */,
|
||||
54B20EDF263AA22C00D3250E /* PHP */,
|
||||
C44CCD4327AFE93300CE40E5 /* Errors */,
|
||||
C4F8C0A222D4F100002EFE61 /* Extensions */,
|
||||
C4811D2822D70D9C00B5F6B3 /* Helpers */,
|
||||
);
|
||||
path = "phpmon-common";
|
||||
path = Common;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C4C8E81D276F5686003AC782 /* Watcher */ = {
|
||||
@@ -627,6 +627,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C4D89BC52783C99400A02B68 /* ComposerJson.swift */,
|
||||
C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */,
|
||||
);
|
||||
path = Composer;
|
||||
sourceTree = "<group>";
|
||||
@@ -651,6 +652,7 @@
|
||||
C4EE55B027708BB2001DF387 /* SwiftUI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C49E171E27A5736E00787921 /* PMServicesView.swift */,
|
||||
C4EE55A627708B9E001DF387 /* PMHeaderView.swift */,
|
||||
C4EE55A827708B9E001DF387 /* PMStatsView.swift */,
|
||||
C4EE55A727708B9E001DF387 /* Preview.swift */,
|
||||
@@ -699,23 +701,6 @@
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
C415D3D52770F341005EF286 /* phpmon-cli */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = C415D3DA2770F341005EF286 /* Build configuration list for PBXNativeTarget "phpmon-cli" */;
|
||||
buildPhases = (
|
||||
C415D3D22770F341005EF286 /* Sources */,
|
||||
C415D3D32770F341005EF286 /* Frameworks */,
|
||||
C415D3D42770F341005EF286 /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "phpmon-cli";
|
||||
productName = "phpmon-cli";
|
||||
productReference = C415D3D62770F341005EF286 /* phpmon-cli */;
|
||||
productType = "com.apple.product-type.tool";
|
||||
};
|
||||
C41C1B3222B0097F00E7CF16 /* PHP Monitor */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = C41C1B4322B0098000E7CF16 /* Build configuration list for PBXNativeTarget "PHP Monitor" */;
|
||||
@@ -764,9 +749,6 @@
|
||||
LastUpgradeCheck = 1320;
|
||||
ORGANIZATIONNAME = "Nico Verbruggen";
|
||||
TargetAttributes = {
|
||||
C415D3D52770F341005EF286 = {
|
||||
CreatedOnToolsVersion = 13.2.1;
|
||||
};
|
||||
C41C1B3222B0097F00E7CF16 = {
|
||||
CreatedOnToolsVersion = 10.2.1;
|
||||
};
|
||||
@@ -794,7 +776,6 @@
|
||||
targets = (
|
||||
C41C1B3222B0097F00E7CF16 /* PHP Monitor */,
|
||||
C4F7807825D7F84B000DBC97 /* phpmon-tests */,
|
||||
C415D3D52770F341005EF286 /* phpmon-cli */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -811,9 +792,10 @@
|
||||
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */,
|
||||
C44C1991276E44CB0072762D /* ProgressWindow.storyboard in Resources */,
|
||||
C4232EE52612526500158FC6 /* Credits.html in Resources */,
|
||||
54FCFD26276C883F004CE748 /* CheckboxPreferenceView.xib in Resources */,
|
||||
54FCFD26276C883F004CE748 /* SelectPreferenceView.xib in Resources */,
|
||||
C473319F2470923A009A0597 /* Localizable.strings in Resources */,
|
||||
C4F30B07278E195800755FCE /* brew-services.json in Resources */,
|
||||
C4068CA427B0780A00544CD5 /* CheckboxPreferenceView.xib in Resources */,
|
||||
C4EC1E66279DE0380010F296 /* ServicesView.xib in Resources */,
|
||||
54FCFD2D276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */,
|
||||
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */,
|
||||
@@ -825,9 +807,10 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
54FCFD27276C883F004CE748 /* CheckboxPreferenceView.xib in Resources */,
|
||||
54FCFD27276C883F004CE748 /* SelectPreferenceView.xib in Resources */,
|
||||
54FCFD2E276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */,
|
||||
C4F780A825D80AE8000DBC97 /* php.ini in Resources */,
|
||||
C4068CA527B0780A00544CD5 /* CheckboxPreferenceView.xib in Resources */,
|
||||
C43A8A2025D9D1D700591B77 /* brew.json in Resources */,
|
||||
C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */,
|
||||
C44C1992276E44CB0072762D /* ProgressWindow.storyboard in Resources */,
|
||||
@@ -838,33 +821,6 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
C415D3D22770F341005EF286 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C40C7F2427721F8200DDDCDC /* PhpExtension.swift in Sources */,
|
||||
C40C7F2627721FA200DDDCDC /* Constants.swift in Sources */,
|
||||
C4B585402770FE3900DA4FBE /* Paths.swift in Sources */,
|
||||
C415D3E62770F540005EF286 /* main.swift in Sources */,
|
||||
C40C7F2227721F8200DDDCDC /* PhpInstallation.swift in Sources */,
|
||||
C4B585432770FE3900DA4FBE /* Shell.swift in Sources */,
|
||||
C4D9ADC1277610E1007277F4 /* PhpSwitcher.swift in Sources */,
|
||||
C4F30B05278E16BA00755FCE /* HomebrewService.swift in Sources */,
|
||||
C4EC1E6E279DF87A0010F296 /* Async.swift in Sources */,
|
||||
C40C7F2327721F8200DDDCDC /* ActivePhpInstallation.swift in Sources */,
|
||||
C4B585462770FE3900DA4FBE /* Command.swift in Sources */,
|
||||
C4D9ADCA277611A0007277F4 /* InternalSwitcher.swift in Sources */,
|
||||
C4EC1E74279DFCF40010F296 /* Events.swift in Sources */,
|
||||
C48D6C72279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */,
|
||||
C40C7F2527721F9800DDDCDC /* HomebrewPackage.swift in Sources */,
|
||||
C417DC76277614690015E6EE /* Helpers.swift in Sources */,
|
||||
C40C7F3227722E8D00DDDCDC /* Logger.swift in Sources */,
|
||||
C40C7F2B2772201C00DDDCDC /* Actions.swift in Sources */,
|
||||
C415D3E12770F34D005EF286 /* AllowedArguments.swift in Sources */,
|
||||
C40C7F202772136000DDDCDC /* PhpEnv.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
C41C1B2F22B0097F00E7CF16 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -878,6 +834,8 @@
|
||||
C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */,
|
||||
5420395926135DC100FB00FA /* PrefsVC.swift in Sources */,
|
||||
C43603A0275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */,
|
||||
C4068CA727B07A1300544CD5 /* SelectPreferenceView.swift in Sources */,
|
||||
C49E171F27A5736E00787921 /* PMServicesView.swift in Sources */,
|
||||
C4EE55AD27708B9E001DF387 /* PMStatsView.swift in Sources */,
|
||||
C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
|
||||
54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */,
|
||||
@@ -897,6 +855,8 @@
|
||||
C4C3ED4327834C5200AB15D8 /* CustomPrefs.swift in Sources */,
|
||||
54B48B5F275F66AE006D90C5 /* Application.swift in Sources */,
|
||||
C4B97B78275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */,
|
||||
C4CE3BB827B31F2E0086CA49 /* MainMenu+Switcher.swift in Sources */,
|
||||
C415937F27A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */,
|
||||
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */,
|
||||
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */,
|
||||
C4F30B03278E16BA00755FCE /* HomebrewService.swift in Sources */,
|
||||
@@ -908,7 +868,7 @@
|
||||
C41CA5ED2774F8EE00A2C80E /* SiteListVC+Actions.swift in Sources */,
|
||||
C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */,
|
||||
C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */,
|
||||
54AB03262763858F00A29D5F /* Timer.swift in Sources */,
|
||||
C4068CAA27B0890D00544CD5 /* MenuBarIcons.swift in Sources */,
|
||||
C4C8E81B276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */,
|
||||
C417DC74277614690015E6EE /* Helpers.swift in Sources */,
|
||||
C415D3E82770F692005EF286 /* AppDelegate+InterApp.swift in Sources */,
|
||||
@@ -916,15 +876,20 @@
|
||||
C42759672627662800093CAE /* NSMenuExtension.swift in Sources */,
|
||||
C464ADAF275A7A69003FCD53 /* SiteListVC.swift in Sources */,
|
||||
C4EC1E6D279DF87A0010F296 /* Async.swift in Sources */,
|
||||
C44CCD4927AFF3B700CE40E5 /* MainMenu+Async.swift in Sources */,
|
||||
C4EC1E73279DFCF40010F296 /* Events.swift in Sources */,
|
||||
C4927F0B27B2DFC200C55AFD /* Errors.swift in Sources */,
|
||||
C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */,
|
||||
C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */,
|
||||
C44CCD4027AFE2FC00CE40E5 /* AlertableError.swift in Sources */,
|
||||
C4188989275FE8CB001EF227 /* Filesystem.swift in Sources */,
|
||||
C4B97B7B275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */,
|
||||
C4EED88927A48778006D7272 /* InterAppHandler.swift in Sources */,
|
||||
C40C7F1E2772136000DDDCDC /* PhpEnv.swift in Sources */,
|
||||
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
|
||||
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
|
||||
C48D0C9625CC80B100CC7490 /* HeaderView.swift in Sources */,
|
||||
C4CE3BBA27B31F670086CA49 /* MainMenu+Composer.swift in Sources */,
|
||||
C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */,
|
||||
C4B5635E276AB09000F12CCB /* VersionExtractor.swift in Sources */,
|
||||
C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */,
|
||||
@@ -936,6 +901,7 @@
|
||||
C464ADB2275A87CA003FCD53 /* SiteListCell.swift in Sources */,
|
||||
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */,
|
||||
C493084A279F331F009C240B /* AddSiteVC.swift in Sources */,
|
||||
C4DEB7D427A5D60B00834718 /* Stats.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -947,31 +913,32 @@
|
||||
C4EE55AE27708B9E001DF387 /* PMStatsView.swift in Sources */,
|
||||
C41CA5EE2774F8EE00A2C80E /* SiteListVC+Actions.swift in Sources */,
|
||||
C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */,
|
||||
54AB03272763858F00A29D5F /* Timer.swift in Sources */,
|
||||
54FCFD2B276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */,
|
||||
C415D3B82770F294005EF286 /* Actions.swift in Sources */,
|
||||
C4EE55AC27708B9E001DF387 /* Preview.swift in Sources */,
|
||||
54B48B60275F66AE006D90C5 /* Application.swift in Sources */,
|
||||
C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */,
|
||||
C493084B279F331F009C240B /* AddSiteVC.swift in Sources */,
|
||||
C4D9ADC0277610E1007277F4 /* PhpSwitcher.swift in Sources */,
|
||||
C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */,
|
||||
C4F780B125D80B4D000DBC97 /* PhpExtension.swift in Sources */,
|
||||
C4068CA827B07A1300544CD5 /* SelectPreferenceView.swift in Sources */,
|
||||
C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */,
|
||||
C40C7F2927721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */,
|
||||
C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */,
|
||||
C43A8A2425D9D20D00591B77 /* BrewJsonParserTest.swift in Sources */,
|
||||
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */,
|
||||
C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */,
|
||||
C4F319C927B034A500AFF46F /* Stats.swift in Sources */,
|
||||
C4F30B04278E16BA00755FCE /* HomebrewService.swift in Sources */,
|
||||
C4AF9F7B2754499000D44ED0 /* Valet.swift in Sources */,
|
||||
C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */,
|
||||
C4CCBA6D275C567B008C7055 /* PMWindowController.swift in Sources */,
|
||||
C40B24F527A3108B0018C7D2 /* Async.swift in Sources */,
|
||||
C4B5635F276AB09000F12CCB /* VersionExtractor.swift in Sources */,
|
||||
C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */,
|
||||
C4F780AE25D80B37000DBC97 /* ExtensionParserTest.swift in Sources */,
|
||||
C4F780C725D80B75000DBC97 /* StatusMenu.swift in Sources */,
|
||||
C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
|
||||
C4EED88A27A48778006D7272 /* InterAppHandler.swift in Sources */,
|
||||
C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */,
|
||||
C43603A1275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */,
|
||||
C42759682627662800093CAE /* NSMenuExtension.swift in Sources */,
|
||||
@@ -980,14 +947,19 @@
|
||||
C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */,
|
||||
C41E871B2763D42300161EE0 /* SiteListVC+ContextMenu.swift in Sources */,
|
||||
C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */,
|
||||
C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */,
|
||||
C4F30B09278E1A0E00755FCE /* CustomPrefs.swift in Sources */,
|
||||
C464ADB3275A87CA003FCD53 /* SiteListCell.swift in Sources */,
|
||||
C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */,
|
||||
C4AF9F78275447F100D44ED0 /* ValetConfigParserTest.swift in Sources */,
|
||||
C4CE3BBC27B324250086CA49 /* MainMenu+Composer.swift in Sources */,
|
||||
C40B24F427A310830018C7D2 /* StatusMenu.swift in Sources */,
|
||||
C417DC75277614690015E6EE /* Helpers.swift in Sources */,
|
||||
C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */,
|
||||
C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */,
|
||||
C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */,
|
||||
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */,
|
||||
C44CCD4127AFE2FC00CE40E5 /* AlertableError.swift in Sources */,
|
||||
C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */,
|
||||
54FCFD31276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */,
|
||||
C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */,
|
||||
@@ -995,14 +967,19 @@
|
||||
C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */,
|
||||
C4F780C325D80B75000DBC97 /* HeaderView.swift in Sources */,
|
||||
C44C198E276E3A1C0072762D /* ProgressWindow.swift in Sources */,
|
||||
C415938027A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */,
|
||||
C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */,
|
||||
C4F30B0B278E203C00755FCE /* MainMenu+Startup.swift in Sources */,
|
||||
C40B24F227A310770018C7D2 /* Events.swift in Sources */,
|
||||
C4F30B0A278E1A1A00755FCE /* ComposerJson.swift in Sources */,
|
||||
C4AF9F7D275454A900D44ED0 /* ValetTest.swift in Sources */,
|
||||
C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */,
|
||||
C4B585452770FE3900DA4FBE /* Command.swift in Sources */,
|
||||
C40B24F127A3106D0018C7D2 /* ServicesView.swift in Sources */,
|
||||
C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */,
|
||||
C4F780B725D80B5D000DBC97 /* App.swift in Sources */,
|
||||
C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */,
|
||||
C44CCD4A27AFF3BC00CE40E5 /* MainMenu+Async.swift in Sources */,
|
||||
C48D6C71279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */,
|
||||
C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */,
|
||||
C4B5853F2770FE3900DA4FBE /* Paths.swift in Sources */,
|
||||
@@ -1041,34 +1018,6 @@
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
C415D3DB2770F341005EF286 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 8M54J5J787;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
C415D3DC2770F341005EF286 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 8M54J5J787;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
C41C1B4122B0098000E7CF16 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -1189,12 +1138,12 @@
|
||||
C41C1B4422B0098000E7CF16 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconBeta;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 500;
|
||||
CURRENT_PROJECT_VERSION = 610;
|
||||
DEVELOPMENT_TEAM = 8M54J5J787;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = phpmon/Info.plist;
|
||||
@@ -1203,8 +1152,8 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
MARKETING_VERSION = "5.0-b1";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.beta;
|
||||
MARKETING_VERSION = 5.0.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -1214,12 +1163,12 @@
|
||||
C41C1B4522B0098000E7CF16 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconBeta;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 500;
|
||||
CURRENT_PROJECT_VERSION = 610;
|
||||
DEVELOPMENT_TEAM = 8M54J5J787;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = phpmon/Info.plist;
|
||||
@@ -1228,8 +1177,8 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
MARKETING_VERSION = "5.0-b1";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.beta;
|
||||
MARKETING_VERSION = 5.0.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -1281,15 +1230,6 @@
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
C415D3DA2770F341005EF286 /* Build configuration list for PBXNativeTarget "phpmon-cli" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
C415D3DB2770F341005EF286 /* Debug */,
|
||||
C415D3DC2770F341005EF286 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
C41C1B2E22B0097F00E7CF16 /* Build configuration list for PBXProject "PHP Monitor" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
@@ -1,84 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1320"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C415D3D52770F341005EF286"
|
||||
BuildableName = "phpmon-cli"
|
||||
BlueprintName = "phpmon-cli"
|
||||
ReferencedContainer = "container:PHP Monitor.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C415D3D52770F341005EF286"
|
||||
BuildableName = "phpmon-cli"
|
||||
BlueprintName = "phpmon-cli"
|
||||
ReferencedContainer = "container:PHP Monitor.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<CommandLineArguments>
|
||||
<CommandLineArgument
|
||||
argument = "help"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C415D3D52770F341005EF286"
|
||||
BuildableName = "phpmon-cli"
|
||||
BlueprintName = "phpmon-cli"
|
||||
ReferencedContainer = "container:PHP Monitor.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
108
README.md
@@ -1,15 +1,17 @@
|
||||
# PHP Monitor
|
||||
|
||||
> If this software has been useful to you, I ask that you **please star the repository**, that way I know that the software is being used. Also, please consider leaving [a one-time donation](https://nicoverbruggen.be/sponsor) to support the project.
|
||||
> You can also send me [feedback](https://twitter.com/nicoverbruggen) if the app came in handy.<br>**Thank you!** ⭐️
|
||||
|
||||
<img src="./phpmon/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png" alt="phpmon icon" width="128px" />
|
||||
<h1 align="center"><b>PHP Monitor</b> (phpmon)</h1>
|
||||
|
||||
**PHP Monitor** (or phpmon) is a lightweight macOS utility app that runs on your Mac and displays the active PHP version in your status bar. It's tightly integrated with [Laravel Valet](https://github.com/laravel/valet), so you need to have it set up before you can use this.
|
||||
<p align="center">
|
||||
<img src="./phpmon/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png" alt="phpmon icon" width="128px" />
|
||||
</p>
|
||||
|
||||
<img src="./docs/screenshot41.jpg" width="800px" alt="phpmon screenshot (menu bar app)"/>
|
||||
**PHP Monitor** (or *phpmon*) is a lightweight macOS utility app that runs on your Mac and displays the active PHP version in your status bar. It's tightly integrated with [Laravel Valet](https://github.com/laravel/valet), so <u>you need to have it set up before you can use this</u>.
|
||||
|
||||
<small><i>Screenshot: A menu showing all of the functionality of PHP Monitor.</i></small>
|
||||
<img src="./docs/screenshot50.jpg" width="1085px" alt="phpmon screenshot (menu bar app)"/>
|
||||
|
||||
<small><i>Screenshot: Showing the key functionality of PHP Monitor. You can also add new domains as links, manage various services, and perform First Aid to fix all kinds of common PHP link issues.</i></small>
|
||||
|
||||
It's super convenient to switch between different versions of PHP. You'll even get notifications (only if you choose to opt-in, of course)!
|
||||
|
||||
@@ -19,7 +21,7 @@ PHP Monitor also gives you quick access to various useful functionality (like ac
|
||||
|
||||
## 🖥 System requirements
|
||||
|
||||
PHP Monitor is a universal application that runs on Apple Silicon **and** Intel-based Macs.
|
||||
PHP Monitor is a universal application that runs natively on Apple Silicon **and** Intel-based Macs.
|
||||
|
||||
* macOS 11 Big Sur or higher (supports macOS 12 Monterey)
|
||||
* Homebrew is installed in `/usr/local/homebrew` or `/opt/homebrew`
|
||||
@@ -30,7 +32,7 @@ _You may need to update your Valet installation to keep everything working if a
|
||||
|
||||
## 🚀 How to install
|
||||
|
||||
You can install via Homebrew (recommended), or may download the latest release on GitHub.
|
||||
Again, make sure you have **Laravel Valet** installed first. Once that's done, you can install via Homebrew (recommended), or may download the latest release on GitHub.
|
||||
|
||||
To install via Homebrew, run:
|
||||
|
||||
@@ -41,7 +43,11 @@ To upgrade your existing installation, run:
|
||||
|
||||
brew upgrade phpmon
|
||||
|
||||
_The app is signed and notarized, meaning all you have to do is approve its first launch._
|
||||
(You may need to run `brew update` first in order to update the cask file if you ran a Homebrew operation recently.)
|
||||
|
||||
## 🔑 Is the app signed & notarized?
|
||||
|
||||
Yes, the app is signed and notarized, meaning all you have to do is approve its first launch (or whenever it updates).
|
||||
|
||||
## 👨💻 Why build this?
|
||||
|
||||
@@ -55,6 +61,8 @@ PHP Monitor performs some integrity checks to ensure a good experience when usin
|
||||
|
||||
**Follow instructions as specified in the alert in order to resolve any issues.**
|
||||
|
||||
(If the app crashes at launch without showing you any of these messages, you might have a non-standard Homebrew and Valet setup. Those are not supported.)
|
||||
|
||||
## 🙋♂️ FAQ & Troubleshooting
|
||||
|
||||
> If you are having issues, the first thing you should be doing is installing the latest version of PHP Monitor _and_ Laravel Valet. This can resolve a variety of issues. To upgrade Valet, run `composer global update`. Don't forget to run `valet install` after upgrading.
|
||||
@@ -258,12 +266,63 @@ You can add your own apps by creating and editing a `~/.phpmon.conf.json` file,
|
||||
You can put as many apps as you'd like in the `scan_apps` array, and PHP Monitor will check for the existence of these apps. You do not need to set the full path, just the name of the app should work. Not all apps support opening a folder, though, so your success might vary.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>How can the app integrate with third party tools, like Alfred?</strong></summary>
|
||||
|
||||
There's an Alfred workflow usually included in the release list, you can grab it by going to releases and downloading the asset `phpmon.alfredworkflow`.
|
||||
|
||||
If you'd like to integrate something yourself, all you need to to is use the `phpmon://` protocol and ensure that third party app integrations are enabled in Preferences (in PHP Monitor).
|
||||
|
||||
Using app callbacks, macOS and PHP Monitor allow for the following to be called:
|
||||
|
||||
* phpmon://list
|
||||
* phpmon://services/stop
|
||||
* phpmon://services/restart/all
|
||||
* phpmon://services/restart/nginx
|
||||
* phpmon://services/restart/php
|
||||
* phpmon://services/restart/dnsmasq
|
||||
* phpmon://locate/config
|
||||
* phpmon://locate/composer
|
||||
* phpmon://locate/valet
|
||||
* phpmon://phpinfo
|
||||
* phpmon://switch/php/{version}
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>How does the app know what PHP version is required for my app?</strong></summary>
|
||||
|
||||
The `composer.json` file in the root of the folder (if it exists) is scanned and interpreted.
|
||||
|
||||
If the version is set in `platform`, it takes precendence.
|
||||
If the version is not set in `platform` but it is in `require` (most common) then that version is used.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>What do the checkmarks next to the PHP version mean in the site list?</strong></summary>
|
||||
|
||||
You'll see a checkmark next to the version number if the currently enabled PHP version is compatible with the version required to run the site.
|
||||
|
||||
This is determined by evaluating the PHP requirement constraint (e.g. `^8.0`, `~8.0` or a specific version: `8.0`).
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Why can't I see the driver type any more? It says "Project Type" now.</strong></summary>
|
||||
|
||||
PHP Monitor currently checks your `composer.json` file to try to figure out what project you are running.
|
||||
|
||||
This approach is a lot faster than asking for a driver when you have many sites linked, but is slightly less reliable since the framework or type of project inferred via `composer.json` might not be 100% accurate.
|
||||
|
||||
You can always still ask Valet using the command line, should it be necessary. In my experience fetching the drivers slowed down the app unnecessarily.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>After running PHP Monitor, Homebrew sometimes has issues with `brew upgrade` or `brew cleanup`!</strong></summary>
|
||||
|
||||
<strike>This is a security feature of Homebrew. When you start a service as an administrator, the root user becomes the owner of relevant binaries. You will need to manually clean up those folders yourself using `rm -rf` (or by manually removing those folders via Finder).</strike>
|
||||
You can now use **First Aid & Services > Restore Homebrew Permissions** to (temporarily) resolve this issue and allow for a clean and painless `brew upgrade` or `brew cleanup` process.
|
||||
|
||||
**Update**: If you are using the Valet switcher (currently not available in the latest stable build) you will not encounter this issue. For more information on this, see [this issue](https://github.com/nicoverbruggen/phpmon/issues/17) and also [this issue about switching to Valet's switcher](https://github.com/nicoverbruggen/phpmon/issues/34).
|
||||
If you would like to know more, consult [this issue](https://github.com/nicoverbruggen/phpmon/issues/85) for more information about why this is needed.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -272,6 +331,10 @@ You can put as many apps as you'd like in the `scan_apps` array, and PHP Monitor
|
||||
|
||||
Please get in touch and open an issue. PHP Monitor shouldn't crash... (unless you are actually removing PHP *while* the app is running, that’s considered normal behaviour!)
|
||||
|
||||
If you would like to report a crash, please include the associated **log files** so I can find out what exactly went wrong.
|
||||
|
||||
To find the logs, take a look in `~/Library/Logs/DiagnosticReports` (in Finder) and see if there's any (log) files that start with "PHP Monitor".
|
||||
|
||||
</details>
|
||||
|
||||
## 📝 Having another issue?
|
||||
@@ -291,12 +354,10 @@ Donations really help with the Apple Developer Program cost, and keep me motivat
|
||||
While I did make this application during my own free time, I have been lucky enough to do various experiments during work hours at [DIVE](https://dive.be). I'd also like to shout out the following folks:
|
||||
|
||||
* My colleagues at [DIVE](https://dive.be)
|
||||
* The [Homebrew](https://brew.sh/) team who maintain
|
||||
* The [developers & maintainers of Valet](https://github.com/laravel/valet/graphs/contributors)
|
||||
* The [Homebrew](https://brew.sh/) team & [Valet maintainers](https://github.com/laravel/valet/graphs/contributors)
|
||||
* Various folks who [reached](https://twitter.com/stauffermatt) [out](https://twitter.com/marcelpociot) when PHP Monitor was still very much a small app with a handful of stars or so
|
||||
* Everyone in the Laravel community who shared the app (thanks!)
|
||||
* Various folks who [reached](https://twitter.com/stauffermatt) [out](https://twitter.com/marcelpociot)
|
||||
* Everyone who left feedback via issues
|
||||
* Everyone who donated to keep the project up and running
|
||||
* Everyone who left feedback via issues & who donated to keep the project up and running
|
||||
|
||||
Thank you very much for your contributions, kind words and support.
|
||||
|
||||
@@ -326,7 +387,7 @@ If an extension or other process writes to a single file a bunch of times in a s
|
||||
|
||||
1. **Location of your sites**: PHP Monitor uses the Valet configuration file to determine which folders to look into. Each folder is scanned and then PHP Monitor will validate if a composer.json file exists to determine the desired PHP version.
|
||||
1. **Sites secured or not secured**: Whether the directory has been secured is determined by checking if a matching certificate exists under Valet's `Certificates` directory for that site name.
|
||||
1. **Site drivers**: PHP Monitor runs `valet which` to determine which driver is currently in use for each individual site. This command is executed once for each site whenever the site list is refreshed.
|
||||
1. **Project type**: PHP Monitor checks your `composer.json` file for "notable dependencies". If you have `laravel/framework` in your `require`, there's a good chance the project type is `Laravel`, after all.
|
||||
|
||||
*Note*: If you have linked a folder in Documents, Desktop or Downloads you might need to grant PHP Monitor access to those directories for PHP Monitor to work correctly.
|
||||
|
||||
@@ -334,15 +395,8 @@ If an extension or other process writes to a single file a bunch of times in a s
|
||||
|
||||
If you want to know more about how this works, I recommend you check out the source code.
|
||||
|
||||
## 🔧 Build instructions
|
||||
I have done my best to annotate as much as humanly possible, and have avoided using an overly complicated architecture to keep the code as easy to maintain as possible. The code isn't perfect by a long shot (lots of cleanup can still happen!) but the application works well.
|
||||
|
||||
<img src="./docs/build.png" width="404px" alt="build button in Xcode"/>
|
||||
I also have a few tests for key parts of the application that I found needed to be tested. In the future, I would like to add even more tests for some of the UI stuff, but for now the tests are more unit tests than feature tests.
|
||||
|
||||
If you'd like to build PHP Monitor yourself, you need:
|
||||
|
||||
* Xcode (usually the latest version)
|
||||
* The contents of this repository
|
||||
|
||||
Once you have downloaded this repository, open `PHP Monitor.xcodeproj`, and you should be able to immediately build the app for your system by pressing Cmd-R. This will create a debug build. (If Xcode complains about code signing, you can turn it off.)
|
||||
|
||||
If you'd like to create a production build, choose "Any Mac" as the target and select Product > Archive.
|
||||
For more detailed information for developers, please see [the documentation file for developers](./DEVS.md).
|
||||
|
@@ -4,15 +4,15 @@
|
||||
|
||||
Generally speaking, only the latest version of **PHP Monitor** is supported, except during transition periods (for example, when particular system requirements go up):
|
||||
|
||||
| Version | Apple silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version |
|
||||
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version |
|
||||
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
|
||||
| 5.x | ✅ Universal binary | ✅ Yes | Big Sur (11.0) and Monterey (12.0) | macOS 11+ | PHP 5.6—PHP 8.2 | TBD |
|
||||
| 5.0 | ✅ Universal binary | ✅ Yes | Big Sur (11.0) and Monterey (12.0) | macOS 11+ | PHP 5.6—PHP 8.2 | 2.16.2 |
|
||||
|
||||
## Legacy versions
|
||||
|
||||
These versions of PHP Monitor are no longer supported, but if you’re using an older computer with an older version of Homebrew, Valet or macOS, you might want to use one of these versions.
|
||||
|
||||
| Version | Apple silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version |
|
||||
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version |
|
||||
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
|
||||
| 4.1 | ✅ Universal binary | ❌ | Big Sur (11.0) and Monterey (12.0) | macOS 11+ | PHP 5.6—PHP 8.2 | 2.16.2 |
|
||||
| 4.0 | ✅ Universal binary | ❌ | Big Sur (11.0) and Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 | 2.13 |
|
||||
|
BIN
assets/icons.afdesign
Normal file
Before Width: | Height: | Size: 396 KiB |
BIN
docs/screenshot50.jpg
Normal file
After Width: | Height: | Size: 370 KiB |
BIN
integrations/phpmon.alfredworkflow
Normal file
@@ -1,26 +0,0 @@
|
||||
//
|
||||
// AllowedArguments.swift
|
||||
// phpmon-cli
|
||||
//
|
||||
// Created by Nico Verbruggen on 20/12/2021.
|
||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum AllowedArguments: String, CaseIterable {
|
||||
case use = "use"
|
||||
case performSwitch = "switch"
|
||||
case fix = "fix"
|
||||
case help = "help"
|
||||
|
||||
static func has(_ string: String) -> Bool {
|
||||
return Self.allCases.contains { arg in
|
||||
return arg.rawValue == string
|
||||
}
|
||||
}
|
||||
|
||||
static var rawValues: [String] {
|
||||
return Self.allCases.map { $0.rawValue }
|
||||
}
|
||||
}
|
@@ -1,103 +0,0 @@
|
||||
//
|
||||
// main.swift
|
||||
// phpmon-cli
|
||||
//
|
||||
// Created by Nico Verbruggen on 20/12/2021.
|
||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
let toolver = "0.1 (early access)"
|
||||
|
||||
let log = Log.shared
|
||||
log.verbosity = .info
|
||||
|
||||
if CommandLine.arguments.contains("-q") || CommandLine.arguments.contains("--quiet") {
|
||||
Log.shared.verbosity = .warning
|
||||
}
|
||||
if CommandLine.arguments.contains("-p") || CommandLine.arguments.contains("--performance") {
|
||||
Log.shared.verbosity = .performance
|
||||
}
|
||||
|
||||
var argument = "help"
|
||||
if CommandLine.arguments.count > 1 {
|
||||
argument = CommandLine.arguments[1]
|
||||
}
|
||||
|
||||
if !AllowedArguments.has(argument) {
|
||||
Log.err("The supported arguments are: \(AllowedArguments.rawValues)")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
let action = AllowedArguments.init(rawValue: argument)
|
||||
|
||||
switch action {
|
||||
case .use, .performSwitch:
|
||||
if !Shell.fileExists("\(Paths.binPath)/php") {
|
||||
Log.err("PHP is currently not linked. Attempting quick fix...")
|
||||
_ = Shell.user.executeSynchronously("brew link php", requiresPath: true)
|
||||
}
|
||||
|
||||
let phpenv = PhpEnv.shared
|
||||
PhpEnv.detectPhpVersions()
|
||||
|
||||
if CommandLine.arguments.count < 3 {
|
||||
Log.err("You must enter at least two additional arguments when using this command.")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
let version = CommandLine.arguments[2].replacingOccurrences(of: "php@", with: "")
|
||||
if phpenv.availablePhpVersions.contains(version) {
|
||||
Log.info("Switching to PHP \(version)...")
|
||||
Actions.switchToPhpVersion(
|
||||
version: version,
|
||||
availableVersions: phpenv.availablePhpVersions,
|
||||
completed: {
|
||||
Log.info("The switch has been completed.")
|
||||
exit(0)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
Log.err("A PHP installation with version \(version) is not installed.")
|
||||
Log.err("The installed versions are: \(phpenv.availablePhpVersions.joined(separator: ", ")).")
|
||||
Log.err("If this version is available, you may be able to install it by using `brew install php@\(version)`.")
|
||||
exit(1)
|
||||
}
|
||||
case .fix:
|
||||
Log.info("Fixing your PHP installation...")
|
||||
Actions.fixMyPhp()
|
||||
Log.info("All operations completed. You can check which version of PHP is linked by using `php -v`.")
|
||||
exit(0)
|
||||
case .help:
|
||||
print("""
|
||||
===============================================================
|
||||
PHP MONITOR CLI \(toolver)
|
||||
by Nico Verbruggen
|
||||
===============================================================
|
||||
|
||||
Gives access to the quick version switcher from PHP Monitor,
|
||||
but without the GUI and 100% of the speed!
|
||||
|
||||
SUPPORTED COMMANDS
|
||||
|
||||
* use {version}: Switch to a specific version of PHP.
|
||||
(e.g. `phpmon-cli use 8.0`)
|
||||
* switch {version}: Alias for the `use` command.
|
||||
* fix Attempts to unlink all PHP versions,
|
||||
and link the latest version of PHP.
|
||||
* help: Show this help.
|
||||
|
||||
SUPPORTED FLAGS
|
||||
|
||||
* `-q / --quiet`: Silences all logs except for warnings and exceptions.
|
||||
* `-p / --perf`: Enables performance mode.
|
||||
|
||||
""")
|
||||
exit(0)
|
||||
case .none:
|
||||
Log.err("Action not recognized!")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
RunLoop.main.run()
|
@@ -11,6 +11,10 @@ import XCTest
|
||||
class PhpVersionNumberTest: XCTestCase {
|
||||
|
||||
func testCanDeconstructPhpVersion() throws {
|
||||
XCTAssertEqual(
|
||||
try! PhpVersionNumber.parse("PHP 8.1.0RC5-dev"),
|
||||
PhpVersionNumber(major: 8, minor: 1, patch: 0)
|
||||
)
|
||||
XCTAssertEqual(
|
||||
PhpVersionNumber.make(from: "8.0.11"),
|
||||
PhpVersionNumber(major: 8, minor: 0, patch: 11)
|
||||
@@ -29,6 +33,12 @@ class PhpVersionNumberTest: XCTestCase {
|
||||
)
|
||||
}
|
||||
|
||||
func testPhpVersionNumberParse() throws {
|
||||
XCTAssertThrowsError(try PhpVersionNumber.parse("OOF")) { error in
|
||||
XCTAssertTrue(error is VersionParseError)
|
||||
}
|
||||
}
|
||||
|
||||
func testCanCheckFixedConstraints() throws {
|
||||
XCTAssertEqual(
|
||||
PhpVersionNumberCollection
|
||||
@@ -191,4 +201,86 @@ class PhpVersionNumberTest: XCTestCase {
|
||||
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all
|
||||
)
|
||||
}
|
||||
|
||||
func testCanCheckGreaterThanOrEqualConstraints() throws {
|
||||
XCTAssertEqual(
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||
.matching(constraint: ">=7.0", strict: true),
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]).all
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||
.matching(constraint: ">=7.0.0", strict: true),
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]).all
|
||||
)
|
||||
|
||||
// Strict check (>7.2.5 is too new for 7.2 which resolves to 7.2.0)
|
||||
XCTAssertEqual(
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||
.matching(constraint: ">=7.2.5", strict: true),
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.4", "7.3"]).all
|
||||
)
|
||||
|
||||
// Non-strict check (ignoring patch, 7.2 resolves to 7.2.999)
|
||||
XCTAssertEqual(
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||
.matching(constraint: ">=7.2.5", strict: false),
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.4", "7.3", "7.2"]).all
|
||||
)
|
||||
}
|
||||
|
||||
func testCanCheckGreaterThanConstraints() throws {
|
||||
XCTAssertEqual(
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||
.matching(constraint: ">7.0"),
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.4", "7.3", "7.2", "7.1"]).all
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||
.matching(constraint: ">7.2.5"),
|
||||
// 7.2 will be valid due to non-strict mode (resolves to 7.2.999)
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.4", "7.3", "7.2"]).all
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||
.matching(constraint: ">7.2.5", strict: true),
|
||||
// 7.2 will not be valid due to strict mode (resolves to 7.2.0)
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.4", "7.3"]).all
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.3.1", "7.2.9", "7.2.8", "7.2.6", "7.2.5", "7.2"])
|
||||
.matching(constraint: ">7.2.8"),
|
||||
// 7.2 will be valid due to non-strict mode (resolves to 7.2.999)
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.3.1", "7.2.9", "7.2"]).all
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.3.1", "7.2.9", "7.2.8", "7.2.6", "7.2.5", "7.2"])
|
||||
.matching(constraint: ">7.2.8", strict: true),
|
||||
// 7.2 will not be valid due to strict mode (resolves to 7.2.0)
|
||||
PhpVersionNumberCollection
|
||||
.make(from: ["7.3.1", "7.2.9"]).all
|
||||
)
|
||||
}
|
||||
}
|
||||
|
38
phpmon/Assets.xcassets/IconColorGreen.colorset/Contents.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.501",
|
||||
"green" : "0.697",
|
||||
"red" : "0.247"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.501",
|
||||
"green" : "0.765",
|
||||
"red" : "0.247"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.000",
|
||||
"green" : "0.000",
|
||||
"red" : "0.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
38
phpmon/Assets.xcassets/IconColorRed.colorset/Contents.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.180",
|
||||
"green" : "0.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.426",
|
||||
"green" : "0.363",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Linked.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "link.svg",
|
||||
"filename" : "Linked@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
|
BIN
phpmon/Assets.xcassets/IconLinked.imageset/Linked.png
vendored
Normal file
After Width: | Height: | Size: 978 B |
BIN
phpmon/Assets.xcassets/IconLinked.imageset/Linked@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.5 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M326.612 185.391c59.747 59.809 58.927 155.698.36 214.59-.11.12-.24.25-.36.37l-67.2 67.2c-59.27 59.27-155.699 59.262-214.96 0-59.27-59.26-59.27-155.7 0-214.96l37.106-37.106c9.84-9.84 26.786-3.3 27.294 10.606.648 17.722 3.826 35.527 9.69 52.721 1.986 5.822.567 12.262-3.783 16.612l-13.087 13.087c-28.026 28.026-28.905 73.66-1.155 101.96 28.024 28.579 74.086 28.749 102.325.51l67.2-67.19c28.191-28.191 28.073-73.757 0-101.83-3.701-3.694-7.429-6.564-10.341-8.569a16.037 16.037 0 0 1-6.947-12.606c-.396-10.567 3.348-21.456 11.698-29.806l21.054-21.055c5.521-5.521 14.182-6.199 20.584-1.731a152.482 152.482 0 0 1 20.522 17.197zM467.547 44.449c-59.261-59.262-155.69-59.27-214.96 0l-67.2 67.2c-.12.12-.25.25-.36.37-58.566 58.892-59.387 154.781.36 214.59a152.454 152.454 0 0 0 20.521 17.196c6.402 4.468 15.064 3.789 20.584-1.731l21.054-21.055c8.35-8.35 12.094-19.239 11.698-29.806a16.037 16.037 0 0 0-6.947-12.606c-2.912-2.005-6.64-4.875-10.341-8.569-28.073-28.073-28.191-73.639 0-101.83l67.2-67.19c28.239-28.239 74.3-28.069 102.325.51 27.75 28.3 26.872 73.934-1.155 101.96l-13.087 13.087c-4.35 4.35-5.769 10.79-3.783 16.612 5.864 17.194 9.042 34.999 9.69 52.721.509 13.906 17.454 20.446 27.294 10.606l37.106-37.106c59.271-59.259 59.271-155.699.001-214.959z"/></svg>
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Parked.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "car-alt.svg",
|
||||
"filename" : "Parked@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
|
BIN
phpmon/Assets.xcassets/IconParked.imageset/Parked.png
vendored
Normal file
After Width: | Height: | Size: 868 B |
BIN
phpmon/Assets.xcassets/IconParked.imageset/Parked@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.2 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M438.66 212.33l-11.24-28.1-19.93-49.83C390.38 91.63 349.57 64 303.5 64h-127c-46.06 0-86.88 27.63-103.99 70.4l-19.93 49.83-11.24 28.1C17.22 221.5 0 244.66 0 272v48c0 16.12 6.16 30.67 16 41.93V416c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32v-32h256v32c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32v-54.07c9.84-11.25 16-25.8 16-41.93v-48c0-27.34-17.22-50.5-41.34-59.67zm-306.73-54.16c7.29-18.22 24.94-30.17 44.57-30.17h127c19.63 0 37.28 11.95 44.57 30.17L368 208H112l19.93-49.83zM80 319.8c-19.2 0-32-12.76-32-31.9S60.8 256 80 256s48 28.71 48 47.85-28.8 15.95-48 15.95zm320 0c-19.2 0-48 3.19-48-15.95S380.8 256 400 256s32 12.76 32 31.9-12.8 31.9-32 31.9z"/></svg>
|
Before Width: | Height: | Size: 918 B |
6
phpmon/Assets.xcassets/Menu Bar Icons/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
22
phpmon/Assets.xcassets/Menu Bar Icons/MenuBar_elephant.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Menu Bar Elephant.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Menu Bar Elephant@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
phpmon/Assets.xcassets/Menu Bar Icons/MenuBar_elephant.imageset/Menu Bar Elephant.png
vendored
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
phpmon/Assets.xcassets/Menu Bar Icons/MenuBar_elephant.imageset/Menu Bar Elephant@2x.png
vendored
Normal file
After Width: | Height: | Size: 2.5 KiB |
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Menu Bar.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "php@2x.png",
|
||||
"filename" : "Menu Bar@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
BIN
phpmon/Assets.xcassets/Menu Bar Icons/MenuBar_php.imageset/Menu Bar.png
vendored
Normal file
After Width: | Height: | Size: 658 B |
BIN
phpmon/Assets.xcassets/Menu Bar Icons/MenuBar_php.imageset/Menu Bar@2x.png
vendored
Normal file
After Width: | Height: | Size: 793 B |
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ServiceLoading.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "question-circle.svg",
|
||||
"filename" : "ServiceLoading@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
|
BIN
phpmon/Assets.xcassets/ServiceLoading.imageset/ServiceLoading.png
vendored
Normal file
After Width: | Height: | Size: 854 B |
BIN
phpmon/Assets.xcassets/ServiceLoading.imageset/ServiceLoading@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.3 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 448c-110.532 0-200-89.431-200-200 0-110.495 89.472-200 200-200 110.491 0 200 89.471 200 200 0 110.53-89.431 200-200 200zm107.244-255.2c0 67.052-72.421 68.084-72.421 92.863V300c0 6.627-5.373 12-12 12h-45.647c-6.627 0-12-5.373-12-12v-8.659c0-35.745 27.1-50.034 47.579-61.516 17.561-9.845 28.324-16.541 28.324-29.579 0-17.246-21.999-28.693-39.784-28.693-23.189 0-33.894 10.977-48.942 29.969-4.057 5.12-11.46 6.071-16.666 2.124l-27.824-21.098c-5.107-3.872-6.251-11.066-2.644-16.363C184.846 131.491 214.94 112 261.794 112c49.071 0 101.45 38.304 101.45 88.8zM298 368c0 23.159-18.841 42-42 42s-42-18.841-42-42 18.841-42 42-42 42 18.841 42 42z"/></svg>
|
Before Width: | Height: | Size: 966 B |
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ServiceOff.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "times-circle.svg",
|
||||
"filename" : "ServiceOff@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
|
BIN
phpmon/Assets.xcassets/ServiceOff.imageset/ServiceOff.png
vendored
Normal file
After Width: | Height: | Size: 826 B |
BIN
phpmon/Assets.xcassets/ServiceOff.imageset/ServiceOff@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.2 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm101.8-262.2L295.6 256l62.2 62.2c4.7 4.7 4.7 12.3 0 17l-22.6 22.6c-4.7 4.7-12.3 4.7-17 0L256 295.6l-62.2 62.2c-4.7 4.7-12.3 4.7-17 0l-22.6-22.6c-4.7-4.7-4.7-12.3 0-17l62.2-62.2-62.2-62.2c-4.7-4.7-4.7-12.3 0-17l22.6-22.6c4.7-4.7 12.3-4.7 17 0l62.2 62.2 62.2-62.2c4.7-4.7 12.3-4.7 17 0l22.6 22.6c4.7 4.7 4.7 12.3 0 17z"/></svg>
|
Before Width: | Height: | Size: 685 B |
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ServiceOn.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "check-circle.svg",
|
||||
"filename" : "ServiceOn@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
|
BIN
phpmon/Assets.xcassets/ServiceOn.imageset/ServiceOn.png
vendored
Normal file
After Width: | Height: | Size: 819 B |
BIN
phpmon/Assets.xcassets/ServiceOn.imageset/ServiceOn@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.2 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M256 8C119.033 8 8 119.033 8 256s111.033 248 248 248 248-111.033 248-248S392.967 8 256 8zm0 48c110.532 0 200 89.451 200 200 0 110.532-89.451 200-200 200-110.532 0-200-89.451-200-200 0-110.532 89.451-200 200-200m140.204 130.267l-22.536-22.718c-4.667-4.705-12.265-4.736-16.97-.068L215.346 303.697l-59.792-60.277c-4.667-4.705-12.265-4.736-16.97-.069l-22.719 22.536c-4.705 4.667-4.736 12.265-.068 16.971l90.781 91.516c4.667 4.705 12.265 4.736 16.97.068l172.589-171.204c4.704-4.668 4.734-12.266.067-16.971z"/></svg>
|
Before Width: | Height: | Size: 718 B |
Before Width: | Height: | Size: 940 B |
@@ -34,17 +34,39 @@ class Actions {
|
||||
brew("services stop dnsmasq", sudo: true)
|
||||
}
|
||||
|
||||
/**
|
||||
Kindly asks Valet to switch to a specific PHP version.
|
||||
*/
|
||||
public static func switchToPhpVersionUsingValet(
|
||||
version: String,
|
||||
availableVersions: [String],
|
||||
completed: @escaping () -> Void
|
||||
) {
|
||||
Log.info("Switching to \(version) using Valet")
|
||||
Log.info(valet("use php@\(version)"))
|
||||
completed()
|
||||
public static func fixHomebrewPermissions() throws
|
||||
{
|
||||
var servicesCommands = [
|
||||
"\(Paths.brew) services stop nginx",
|
||||
"\(Paths.brew) services stop dnsmasq",
|
||||
]
|
||||
var cellarCommands = [
|
||||
"chown -R \(Paths.whoami):staff \(Paths.cellarPath)/nginx",
|
||||
"chown -R \(Paths.whoami):staff \(Paths.cellarPath)/dnsmasq"
|
||||
]
|
||||
|
||||
PhpEnv.shared.availablePhpVersions.forEach { version in
|
||||
let formula = version == PhpEnv.brewPhpVersion
|
||||
? "php"
|
||||
: "php@\(version)"
|
||||
servicesCommands.append("\(Paths.brew) services stop \(formula)")
|
||||
cellarCommands.append("chown -R \(Paths.whoami):staff \(Paths.cellarPath)/\(formula)")
|
||||
}
|
||||
|
||||
let script =
|
||||
servicesCommands.joined(separator: " && ")
|
||||
+ " && "
|
||||
+ cellarCommands.joined(separator: " && ")
|
||||
|
||||
let appleScript = NSAppleScript(
|
||||
source: "do shell script \"\(script)\" with administrator privileges"
|
||||
)
|
||||
|
||||
let eventResult: NSAppleEventDescriptor? = appleScript?.executeAndReturnError(nil)
|
||||
|
||||
if (eventResult == nil) {
|
||||
throw HomebrewPermissionError(kind: .applescriptNilError)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Finding Config Files
|
||||
@@ -88,7 +110,7 @@ class Actions {
|
||||
return URL(string: "file:///private/tmp/phpmon_phpinfo.html")!
|
||||
}
|
||||
|
||||
// MARK: - Quick Fix
|
||||
// MARK: - Fix My Valet
|
||||
|
||||
/**
|
||||
Detects all currently available PHP versions,
|
||||
@@ -100,7 +122,7 @@ class Actions {
|
||||
If this does not solve the issue, the user may need to install additional
|
||||
extensions and/or run `composer global update`.
|
||||
*/
|
||||
public static func fixMyPhp()
|
||||
public static func fixMyValet()
|
||||
{
|
||||
brew("services restart dnsmasq", sudo: true)
|
||||
|
||||
@@ -111,11 +133,14 @@ class Actions {
|
||||
brew("services stop \(formula)", sudo: true)
|
||||
}
|
||||
|
||||
brew("services stop dnsmasq")
|
||||
brew("services stop php")
|
||||
brew("services stop nginx")
|
||||
brew("link php")
|
||||
|
||||
brew("link php --overwrite --force")
|
||||
|
||||
brew("services restart dnsmasq", sudo: true)
|
||||
brew("services stop php", sudo: true)
|
||||
brew("services stop nginx", sudo: true)
|
||||
brew("services restart php", sudo: true)
|
||||
brew("services restart nginx", sudo: true)
|
||||
}
|
||||
}
|
@@ -51,4 +51,9 @@ class Constants {
|
||||
"8.2"
|
||||
]
|
||||
|
||||
/**
|
||||
The URL that people can visit if they wish to help support the project.
|
||||
*/
|
||||
static let DonationUrl = URL(string: "https://nicoverbruggen.be/sponsor#pay-now")!
|
||||
|
||||
}
|
@@ -27,13 +27,13 @@ class Log {
|
||||
|
||||
static func err(_ item: Any) {
|
||||
if Verbosity.error.isApplicable() {
|
||||
print(item)
|
||||
print("[ERR] \(item)")
|
||||
}
|
||||
}
|
||||
|
||||
static func warn(_ item: Any) {
|
||||
if Verbosity.warning.isApplicable() {
|
||||
print(item)
|
||||
print("[WRN] \(item)")
|
||||
}
|
||||
}
|
||||
|
@@ -8,8 +8,8 @@
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
The `Paths` class is used to locate various binaries on the system,
|
||||
and provides a full
|
||||
The `Paths` class is used to locate various binaries on the system.
|
||||
The path to the Homebrew directory and the user's name are fetched only once, at boot.
|
||||
*/
|
||||
public class Paths {
|
||||
|
||||
@@ -17,8 +17,11 @@ public class Paths {
|
||||
|
||||
private var baseDir : Paths.HomebrewDir
|
||||
|
||||
private var userName : String
|
||||
|
||||
init() {
|
||||
baseDir = Shell.fileExists("\(HomebrewDir.opt.rawValue)/bin/brew") ? .opt : .usr
|
||||
baseDir = FileManager.default.fileExists(atPath: "\(HomebrewDir.opt.rawValue)/bin/brew") ? .opt : .usr
|
||||
userName = String(Shell.pipe("whoami").split(separator: "\n")[0])
|
||||
}
|
||||
|
||||
// - MARK: Binaries
|
||||
@@ -42,7 +45,11 @@ public class Paths {
|
||||
// - MARK: Paths
|
||||
|
||||
public static var whoami: String {
|
||||
return String(Shell.pipe("whoami").split(separator: "\n")[0])
|
||||
return shared.userName
|
||||
}
|
||||
|
||||
public static var cellarPath: String {
|
||||
return "\(shared.baseDir.rawValue)/Cellar"
|
||||
}
|
||||
|
||||
public static var binPath: String {
|
@@ -105,12 +105,12 @@ public class Shell {
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if a file exists at the provided path.
|
||||
Uses `/bin/echo` instead of the `builtin` (which does not support `-n`).
|
||||
Checks if a file exists at a certain path.
|
||||
Used to be done with a shell command, now uses the native FileManager class instead.
|
||||
*/
|
||||
public static func fileExists(_ path: String) -> Bool {
|
||||
let escapedPath = path.replacingOccurrences(of: " ", with: "\\ ")
|
||||
return Shell.pipe("if [ -f \(escapedPath) ]; then /bin/echo -n \"0\"; fi") == "0"
|
||||
let fullPath = path.replacingOccurrences(of: "~", with: "/Users/\(Paths.whoami)")
|
||||
return FileManager.default.fileExists(atPath: fullPath)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,7 +123,7 @@ public class Shell {
|
||||
|
||||
let task = Process()
|
||||
task.launchPath = self.shell
|
||||
task.arguments = ["--login", "-c", tailoredCommand]
|
||||
task.arguments = ["--noprofile", "-norc", "--login", "-c", tailoredCommand]
|
||||
|
||||
return task
|
||||
}
|
13
phpmon/Common/Errors/AlertableError.swift
Normal file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Errors.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 06/02/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol AlertableError {
|
||||
func getErrorMessageKey() -> String
|
||||
}
|
29
phpmon/Common/Errors/Errors.swift
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// VersionParseError.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 08/02/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - Alertable Errors
|
||||
// These errors must be resolved by the user.
|
||||
|
||||
struct HomebrewPermissionError: Error, AlertableError {
|
||||
enum Kind: String {
|
||||
case applescriptNilError = "homebrew_permissions.applescript_returned_nil"
|
||||
}
|
||||
|
||||
let kind: Kind
|
||||
|
||||
func getErrorMessageKey() -> String {
|
||||
return "alert.errors.\(self.kind.rawValue)"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Errors that do not have an associated alert message
|
||||
// The errors must be resolved by the developer.
|
||||
|
||||
struct VersionParseError: Error {}
|
@@ -16,3 +16,14 @@ extension NSMenu {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@IBDesignable class LocalizedMenuItem: NSMenuItem {
|
||||
|
||||
@IBInspectable
|
||||
var localizationKey: String? {
|
||||
didSet {
|
||||
self.title = localizationKey?.localized ?? self.title
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -51,6 +51,9 @@ class Alert {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Notify the user about something by showing an alert.
|
||||
*/
|
||||
public static func notify(message: String, info: String, style: NSAlert.Style = .informational) {
|
||||
_ = present(
|
||||
messageText: message,
|
||||
@@ -60,4 +63,19 @@ class Alert {
|
||||
style: style
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Notify the user about a particular error (which must be `Alertable`)
|
||||
by showing an alert.
|
||||
*/
|
||||
public static func notify(about error: Error & AlertableError) {
|
||||
let key = error.getErrorMessageKey()
|
||||
_ = present(
|
||||
messageText: "\(key).title".localized,
|
||||
informativeText: "\(key).description".localized,
|
||||
buttonTitle: "OK",
|
||||
secondButtonTitle: "",
|
||||
style: .critical
|
||||
)
|
||||
}
|
||||
}
|
29
phpmon/Common/Helpers/Async.swift
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// Async.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 23/01/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
This generic async helper is something I'd like to use in more places.
|
||||
|
||||
The `DispatchQueue.global` into `DispatchQueue.main.async` logic is common in the app.
|
||||
|
||||
I could also use try `async` support which was introduced in Swift but that would
|
||||
require too much refactoring at this time to consider. I also need to read up on async
|
||||
in order to properly grasp all the gotchas. Looking into that later at some point.
|
||||
*/
|
||||
public func runAsync(_ execute: @escaping () -> Void, completion: @escaping () -> Void = {})
|
||||
{
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
execute()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
@@ -42,7 +42,7 @@ class MenuBarImageGenerator {
|
||||
|
||||
let targetImage: NSImage = NSImage(size: image.size)
|
||||
|
||||
let rep: NSBitmapImageRep = NSBitmapImageRep(
|
||||
let representation: NSBitmapImageRep = NSBitmapImageRep(
|
||||
bitmapDataPlanes: nil,
|
||||
pixelsWide: Int(image.size.width),
|
||||
pixelsHigh: Int(image.size.height),
|
||||
@@ -55,7 +55,7 @@ class MenuBarImageGenerator {
|
||||
bitsPerPixel: 0
|
||||
)!
|
||||
|
||||
targetImage.addRepresentation(rep)
|
||||
targetImage.addRepresentation(representation)
|
||||
targetImage.lockFocus()
|
||||
|
||||
image.draw(in: imageRect)
|
||||
@@ -65,33 +65,73 @@ class MenuBarImageGenerator {
|
||||
return targetImage
|
||||
}
|
||||
|
||||
/**
|
||||
The same as before, but also attempts to add an icon to the left.
|
||||
*/
|
||||
public static func textToImageWithIcon(text: String) -> NSImage {
|
||||
let textImage = self.textToImage(text: text)
|
||||
let iconImage = NSImage(named: "StatusBarPHP")!
|
||||
let iconWidthSize = iconImage.size.width
|
||||
let divider = iconWidthSize
|
||||
|
||||
// We'll start out with the image containing the text
|
||||
let textImage = self.textToImage(text: text)
|
||||
|
||||
// Then we'll fetch the image we want on the left
|
||||
var iconType = Preferences.preferences[.iconTypeToDisplay] as? String
|
||||
if iconType == nil {
|
||||
Log.warn("Invalid icon type found, using the default")
|
||||
iconType = MenuBarIcon.iconPhp.rawValue
|
||||
}
|
||||
|
||||
let iconImage = NSImage(named: "MenuBar_\(iconType!)")!
|
||||
|
||||
// We'll need to reference the width of the icon a bunch of times
|
||||
let iconWidthSize = iconImage.size.width
|
||||
|
||||
// There will also be an additional divider between the image and the text (image)
|
||||
let divider: CGFloat = 3
|
||||
|
||||
// Use a fixed size for the height of the menu bar (18pt)
|
||||
let imageRect = CGRect(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: textImage.size.width + divider,
|
||||
height: textImage.size.height
|
||||
width: textImage.size.width + iconWidthSize + divider,
|
||||
height: 18
|
||||
)
|
||||
|
||||
// Create a new image, we'll draw the text and our icon in there
|
||||
let image: NSImage = NSImage(size: imageRect.size)
|
||||
image.lockFocus()
|
||||
|
||||
let difference = imageRect.size.width - textImage.size.width
|
||||
// Calculate the offset between the image and the text
|
||||
let offset = imageRect.size.width - textImage.size.width
|
||||
|
||||
textImage.draw(in: imageRect, from: NSRect(
|
||||
x: -difference,
|
||||
y: 0, width: textImage.size.width + difference,
|
||||
height: textImage.size.height
|
||||
), operation: .overlay, fraction: 1)
|
||||
// Draw the text with a negative x offset (so there is room on the left for the icon)
|
||||
textImage.draw(
|
||||
in: imageRect,
|
||||
from: NSRect(
|
||||
x: -offset,
|
||||
y: 0,
|
||||
width: textImage.size.width + offset,
|
||||
height: textImage.size.height
|
||||
),
|
||||
operation: .overlay,
|
||||
fraction: 1
|
||||
)
|
||||
|
||||
iconImage.draw(in: imageRect, from: NSRect(x: 0, y: 0, width: imageRect.size.width * 1.6, height: imageRect.size.height * 2.0), operation: .overlay, fraction: 1)
|
||||
// Draw the icon directly in the left of the imageRect (where we left space)
|
||||
iconImage.draw(
|
||||
in: imageRect,
|
||||
from: NSRect(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: imageRect.size.width,
|
||||
height: imageRect.size.height
|
||||
),
|
||||
operation: .overlay,
|
||||
fraction: 1
|
||||
)
|
||||
|
||||
// We're done with this image
|
||||
image.unlockFocus()
|
||||
|
||||
return image
|
||||
}
|
||||
|
44
phpmon/Common/Helpers/VersionExtractor.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// VersionExtractor.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 16/12/2021.
|
||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class VersionExtractor {
|
||||
|
||||
/**
|
||||
This attempts to extract the version number from the command line output of Valet.
|
||||
*/
|
||||
public static func from(_ string: String) -> String? {
|
||||
do {
|
||||
let regex = try NSRegularExpression(
|
||||
pattern: #"(?<version>(\d+)(.)(\d+)((.)(\d+))?)"#,
|
||||
options: []
|
||||
)
|
||||
|
||||
let match = regex.matches(
|
||||
in: string,
|
||||
options: [],
|
||||
range: NSMakeRange(0, string.count)
|
||||
).first
|
||||
|
||||
guard let match = match else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let range = Range(
|
||||
match.range(withName: "version"),
|
||||
in: string
|
||||
)!
|
||||
|
||||
return String(string[range])
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -8,11 +8,6 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol PhpSwitcherDelegate: AnyObject {
|
||||
func switcherDidStartSwitching()
|
||||
func switcherDidCompleteSwitch()
|
||||
}
|
||||
|
||||
class PhpEnv {
|
||||
|
||||
// MARK: - Initializer
|
||||
@@ -155,4 +150,16 @@ class PhpEnv {
|
||||
.matching(constraint: $0.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Validates whether the currently running version matches the provided version.
|
||||
*/
|
||||
public func validate(_ version: String) -> Bool {
|
||||
if self.currentInstall.version.short == version {
|
||||
Log.info("Switching to version \(version) seems to have succeeded. Validation passed.")
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
@@ -32,7 +32,7 @@ public struct PhpVersionNumberCollection: Equatable {
|
||||
https://getcomposer.org/doc/articles/versions.md#writing-version-constraints
|
||||
|
||||
- Parameter constraint: The full constraint as a string (e.g. "^7.0")
|
||||
- Parameter strict: Whether the minor version check is strict. See more below.
|
||||
- Parameter strict: Whether the patch version check is strict. See more below.
|
||||
|
||||
The strict mode does not matter if a patch version is provided for all versions in the collection.
|
||||
|
||||
@@ -45,7 +45,8 @@ public struct PhpVersionNumberCollection: Equatable {
|
||||
|
||||
Given versions 8.0.? and 8.1.?, but the requirement is ^8.0.1, in strict mode only 8.1.? will
|
||||
be considered valid (8.0 translates to 8.0.0 and as such is older than 8.0.1, 8.1.0 is OK).
|
||||
When checking against actual PHP versions installed by the user, use strict mode.
|
||||
When checking against actual PHP versions installed by the user (with patch precision), use
|
||||
strict mode.
|
||||
|
||||
**NON-STRICT MODE (= patch precision off)**
|
||||
|
||||
@@ -58,43 +59,34 @@ public struct PhpVersionNumberCollection: Equatable {
|
||||
public func matching(constraint: String, strict: Bool = false) -> [PhpVersionNumber] {
|
||||
if let version = PhpVersionNumber.make(from: constraint, type: .versionOnly) {
|
||||
// Strict constraint (e.g. "7.0") -> returns specific version
|
||||
return self.versions.filter {
|
||||
$0.major == version.major
|
||||
&& $0.minor == version.minor
|
||||
&& (strict ? $0.patch(strict, version) == version.patch(strict) : true)
|
||||
}
|
||||
return self.versions.filter { $0.isSameAs(version, strict) }
|
||||
}
|
||||
|
||||
if let version = PhpVersionNumber.make(from: constraint, type: .caretVersionRange) {
|
||||
// Caret range means that the major version is never higher but minor version can be higher
|
||||
// ^7.2 will be compatible with all versions between 7.2 and 8.0
|
||||
return self.versions.filter {
|
||||
$0.major == version.major &&
|
||||
(
|
||||
// Either the minor version is the same and the patch is higher or equal
|
||||
$0.minor == version.minor && $0.patch(strict) >= version.patch(strict, $0)
|
||||
// or the minor version number has been bumped
|
||||
|| $0.minor > version.minor
|
||||
)
|
||||
}
|
||||
return self.versions.filter { $0.hasNewerMinorVersionOrPatch(version, strict) }
|
||||
}
|
||||
|
||||
if let version = PhpVersionNumber.make(from: constraint, type: .tildeVersionRange) {
|
||||
// Tilde range means that most specific digit is used as the basis.
|
||||
if version.patch != nil {
|
||||
return self.versions.filter {
|
||||
version.patch != nil
|
||||
// If a patch is provided then the minor version cannot be bumped.
|
||||
return self.versions.filter {
|
||||
$0.major == version.major && $0.minor == version.minor
|
||||
&& $0.patch(strict, version) >= version.patch!
|
||||
}
|
||||
} else {
|
||||
? $0.hasSameMajorAndMinorButNewerOrSamePatch(version, strict)
|
||||
// If a patch is not provided then the major version cannot be bumped.
|
||||
return self.versions.filter {
|
||||
$0.major == version.major && $0.minor >= version.minor
|
||||
}
|
||||
: $0.hasSameMajorButNewerOrSameMinor(version, strict)
|
||||
}
|
||||
}
|
||||
|
||||
if let version = PhpVersionNumber.make(from: constraint, type: .greaterThanOrEqual) {
|
||||
return self.versions.filter { $0.isSameAs(version, strict) || $0.isNewerThan(version, strict) }
|
||||
}
|
||||
|
||||
if let version = PhpVersionNumber.make(from: constraint, type: .greaterThan) {
|
||||
return self.versions.filter { $0.isNewerThan(version, strict) }
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -104,7 +96,7 @@ public struct PhpVersionNumber: Equatable {
|
||||
let minor: Int
|
||||
let patch: Int?
|
||||
|
||||
public func patch(_ strictFallback: Bool, _ constraint: PhpVersionNumber? = nil) -> Int {
|
||||
public func patch(_ strictFallback: Bool = true, _ constraint: PhpVersionNumber? = nil) -> Int {
|
||||
return patch ?? (strictFallback ? 0 : constraint?.patch ?? 999)
|
||||
}
|
||||
|
||||
@@ -116,6 +108,22 @@ public struct PhpVersionNumber: Equatable {
|
||||
case versionOnly = #"^(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||
case caretVersionRange = #"^\^(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||
case tildeVersionRange = #"^~(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||
case greaterThanOrEqual = #"^>=(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||
case greaterThan = #"^>(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||
|
||||
// TODO: (5.1) Handle these cases (even though I suspect these are uncommon)
|
||||
/*
|
||||
case smallerThanOrEqual = #"^<=(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||
case smallerThan = #"^<(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||
*/
|
||||
}
|
||||
|
||||
public static func parse(_ text: String) throws -> Self {
|
||||
guard let versionText = VersionExtractor.from(text) else {
|
||||
throw VersionParseError()
|
||||
}
|
||||
|
||||
return Self.make(from: versionText)!
|
||||
}
|
||||
|
||||
public static func make(from versionString: String, type: MatchType = .versionOnly) -> Self? {
|
||||
@@ -138,4 +146,39 @@ public struct PhpVersionNumber: Equatable {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: Comparison Logic
|
||||
|
||||
internal func isSameAs(_ version: PhpVersionNumber, _ strict: Bool) -> Bool {
|
||||
return self.major == version.major
|
||||
&& self.minor == version.minor
|
||||
&& (strict ? self.patch(strict, version) == version.patch(strict) : true)
|
||||
}
|
||||
|
||||
internal func isNewerThan(_ version: PhpVersionNumber, _ strict: Bool) -> Bool {
|
||||
return (
|
||||
self.major > version.major ||
|
||||
self.major == version.major && self.minor > version.minor ||
|
||||
self.major == version.major && self.minor == version.minor
|
||||
&& self.patch(strict) > version.patch(strict)
|
||||
)
|
||||
}
|
||||
|
||||
internal func hasNewerMinorVersionOrPatch(_ version: PhpVersionNumber, _ strict: Bool) -> Bool {
|
||||
return self.major == version.major &&
|
||||
(
|
||||
(self.minor == version.minor && self.patch(strict) >= version.patch(strict, self))
|
||||
|| self.minor > version.minor
|
||||
)
|
||||
}
|
||||
|
||||
internal func hasSameMajorAndMinorButNewerOrSamePatch(_ version: PhpVersionNumber, _ strict: Bool) -> Bool {
|
||||
return self.major == version.major && self.minor == version.minor
|
||||
&& self.patch(strict, version) >= version.patch(strict)
|
||||
}
|
||||
|
||||
internal func hasSameMajorButNewerOrSameMinor(_ version: PhpVersionNumber, _ strict: Bool) -> Bool {
|
||||
return self.major == version.major
|
||||
&& self.minor >= version.minor
|
||||
}
|
||||
}
|
@@ -27,9 +27,10 @@ class PhpInstallation {
|
||||
arguments: ["--version"]
|
||||
).trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
self.longVersion = PhpVersionNumber.make(
|
||||
from: String(longVersionString.split(separator: "-")[0])
|
||||
)!
|
||||
// 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.
|
||||
// TODO: Alert the user that the version number could not be parsed.
|
||||
self.longVersion = try! PhpVersionNumber.parse(longVersionString)
|
||||
}
|
||||
}
|
||||
|
@@ -50,6 +50,9 @@ class InternalSwitcher: PhpSwitcher {
|
||||
brew("link \(formula) --overwrite --force")
|
||||
brew("services start \(formula)", sudo: true)
|
||||
|
||||
Log.info("Restarting nginx, just to be sure!")
|
||||
brew("services restart nginx", sudo: true)
|
||||
|
||||
Log.info("The new version has been linked!")
|
||||
completion()
|
||||
}
|
@@ -8,6 +8,14 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol PhpSwitcherDelegate: AnyObject {
|
||||
|
||||
func switcherDidStartSwitching(to: String)
|
||||
|
||||
func switcherDidCompleteSwitch(to: String)
|
||||
|
||||
}
|
||||
|
||||
protocol PhpSwitcher {
|
||||
|
||||
func performSwitch(to version: String, completion: @escaping () -> Void)
|
@@ -16,6 +16,7 @@
|
||||
<p><b>Want to spread the love?</b> Leave a <a href="https://github.com/nicoverbruggen/phpmon">star on GitHub</a>!</p>
|
||||
<p><b>Having issues?</b> Consult the <a href="https://github.com/nicoverbruggen/phpmon#%EF%B8%8F-faq--troubleshooting">FAQ & Troubleshooting</a> section.</p>
|
||||
<p><b>Want to support me?</b> You can <a href="https://nicoverbruggen.be/sponsor">financially support</a> the continued development of this app.</p>
|
||||
<p><b>Get the latest on Twitter</b> Give me a <a href="https://twitter.com/nicoverbruggen">follow on Twitter</a> to learn about the latest and greatest updates of this app.</p>
|
||||
<br>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -8,7 +8,7 @@
|
||||
import Cocoa
|
||||
import HotKey
|
||||
|
||||
class App: PhpSwitcherDelegate {
|
||||
class App {
|
||||
|
||||
// MARK: Static Vars
|
||||
|
||||
@@ -73,20 +73,4 @@ class App: PhpSwitcherDelegate {
|
||||
The `PhpConfigWatcher` is responsible for watching the `.ini` files and the `.conf.d` folder.
|
||||
*/
|
||||
var watcher: PhpConfigWatcher!
|
||||
|
||||
// MARK: - PhpSwitcherDelegate
|
||||
|
||||
func switcherDidStartSwitching() {
|
||||
}
|
||||
|
||||
func switcherDidCompleteSwitch() {
|
||||
PhpEnv.shared.currentInstall = ActivePhpInstallation()
|
||||
handlePhpConfigWatcher()
|
||||
|
||||
if let window = siteListWindowController {
|
||||
DispatchQueue.main.async {
|
||||
window.contentVC.reloadSites()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
47
phpmon/Domain/App/AppDelegate+InterApp.swift
Normal file
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// AppDelegate+InterApp.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 20/12/2021.
|
||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import Foundation
|
||||
|
||||
extension AppDelegate {
|
||||
|
||||
/**
|
||||
This is an entry point for future development for integrating with the PHP Monitor
|
||||
application URL. You can use the `phpmon://` protocol to communicate with the app.
|
||||
|
||||
At this time you can trigger the site list using Alfred (or some other application)
|
||||
by opening the following URL: `phpmon://list`.
|
||||
|
||||
Please note that PHP Monitor needs to be running in the background for this to work.
|
||||
*/
|
||||
func application(_ application: NSApplication, open urls: [URL]) {
|
||||
|
||||
if !Preferences.isEnabled(.allowProtocolForIntegrations) {
|
||||
Log.info("Acting on commands via phpmon:// has been disabled.")
|
||||
return
|
||||
}
|
||||
|
||||
guard let url = urls.first else { return }
|
||||
|
||||
self.interpretCommand(
|
||||
url.absoluteString.replacingOccurrences(of: "phpmon://", with: ""),
|
||||
commands: InterApp.getCommands()
|
||||
)
|
||||
}
|
||||
|
||||
private func interpretCommand(_ command: String, commands: [InterApp.Action]) {
|
||||
commands.forEach { action in
|
||||
if command.starts(with: action.command) {
|
||||
let lastElement = String(command.split(separator: "/").last!)
|
||||
action.action(lastElement)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AppKit
|
||||
|
||||
/**
|
||||
Any outlets connected to the app's main menu (not the menu that shows when the icon in
|
||||
@@ -24,6 +25,13 @@ extension AppDelegate {
|
||||
|
||||
// MARK: - Menu Interactions
|
||||
|
||||
@IBAction func addSiteLinkPressed(_ sender: Any) {
|
||||
SiteListVC.show()
|
||||
|
||||
guard let windowController = App.shared.siteListWindowController else { return }
|
||||
windowController.pressedAddLink(nil)
|
||||
}
|
||||
|
||||
@IBAction func reloadSiteListPressed(_ sender: Any) {
|
||||
let vc = App.shared.siteListWindowController?
|
||||
.window?.contentViewController as? SiteListVC
|
||||
@@ -37,4 +45,11 @@ extension AppDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func focusSearchField(_ sender: Any) {
|
||||
SiteListVC.show()
|
||||
|
||||
guard let windowController = App.shared.siteListWindowController else { return }
|
||||
windowController.searchToolbarItem.searchField.becomeFirstResponder()
|
||||
}
|
||||
|
||||
}
|
@@ -13,6 +13,10 @@ extension AppDelegate {
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
/**
|
||||
Sets up notifications. That does mean we need to ask for permission first.
|
||||
If we cannot get permission, we should log this.
|
||||
*/
|
||||
public func setupNotifications() {
|
||||
let notificationCenter = UNUserNotificationCenter.current()
|
||||
notificationCenter.delegate = self
|
@@ -45,12 +45,16 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
||||
let valet: Valet
|
||||
|
||||
/**
|
||||
The PhpSwitcher singleton that handles PHP version
|
||||
The PhpEnv singleton that handles PHP version
|
||||
detection, as well as switching. It is initialized
|
||||
when the app is ready and passed all checks.
|
||||
*/
|
||||
var switcher: PhpEnv! = nil
|
||||
var phpEnvironment: PhpEnv! = nil
|
||||
|
||||
/**
|
||||
The logger is responsible for different levels of logging.
|
||||
You can tweak the verbosity in the `init` method here.
|
||||
*/
|
||||
var logger = Log.shared
|
||||
|
||||
// MARK: - Initializer
|
||||
@@ -59,7 +63,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
||||
When the application initializes, create all singletons.
|
||||
*/
|
||||
override init() {
|
||||
logger.verbosity = .performance
|
||||
logger.verbosity = .info
|
||||
Log.info("==================================")
|
||||
Log.info("PHP MONITOR by Nico Verbruggen")
|
||||
Log.info("Version \(App.version)")
|
||||
@@ -73,8 +77,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
||||
}
|
||||
|
||||
func initializeSwitcher() {
|
||||
self.switcher = PhpEnv.shared
|
||||
self.switcher.delegate = self.state
|
||||
self.phpEnvironment = PhpEnv.shared
|
||||
}
|
||||
|
||||
// MARK: - Lifecycle
|
@@ -4,6 +4,7 @@
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
|
||||
<capability name="Image references" minToolsVersion="12.0"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Search Toolbar Item" minToolsVersion="12.0" minSystemVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@@ -49,11 +50,31 @@
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Sites" id="YTZ-bb-TOG">
|
||||
<items>
|
||||
<menuItem title="Reload Site List" keyEquivalent="r" id="Ema-AU-Nbr">
|
||||
<menuItem title="add-as-link" keyEquivalent="n" id="du1-bO-N2U" userLabel="Add Link" customClass="LocalizedMenuItem" customModule="PHP_Monitor" customModuleProvider="target">
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="localizationKey" value="mm_add_folder_as_link"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="addSiteLinkPressed:" target="Voe-Tx-rLC" id="DzS-MY-6g0"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="reload-list" keyEquivalent="r" id="Ema-AU-Nbr" customClass="LocalizedMenuItem" customModule="PHP_Monitor" customModuleProvider="target">
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="localizationKey" value="mm_reload_site_list"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="reloadSiteListPressed:" target="Voe-Tx-rLC" id="geC-Ld-haX"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="2ux-8Q-UjK"/>
|
||||
<menuItem title="focus-find" keyEquivalent="f" id="I95-fb-EL7" customClass="LocalizedMenuItem" customModule="PHP_Monitor" customModuleProvider="target">
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="localizationKey" value="mm_find_in_site_list"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="focusSearchField:" target="Voe-Tx-rLC" id="O8j-1B-hll"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
@@ -298,7 +319,7 @@
|
||||
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="PHP_Monitor" customModuleProvider="target"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-484" y="32"/>
|
||||
<point key="canvasLocation" x="-495" y="-44"/>
|
||||
</scene>
|
||||
<!--Window Controller-->
|
||||
<scene sceneID="PQa-AT-b2a">
|
||||
@@ -474,7 +495,7 @@ Gw
|
||||
</string>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="pressedCancel:" target="glS-wF-sEU" id="MZS-Vg-Vjf"/>
|
||||
<action selector="pressedCancel:" target="glS-wF-sEU" id="q0L-YZ-F3J"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZX9-s1-23i">
|
||||
@@ -544,6 +565,7 @@ Gw
|
||||
<constraint firstAttribute="bottom" secondItem="SwS-o8-pbl" secondAttribute="bottom" constant="20" symbolic="YES" id="24Z-vC-4E8"/>
|
||||
<constraint firstItem="900-Z2-tID" firstAttribute="centerY" secondItem="PVw-cM-qAB" secondAttribute="centerY" id="578-2f-4x8"/>
|
||||
<constraint firstItem="ZX9-s1-23i" firstAttribute="leading" secondItem="6JT-Vt-3q0" secondAttribute="trailing" constant="-440" id="6eF-GS-Xcn"/>
|
||||
<constraint firstItem="SwS-o8-pbl" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="900-Z2-tID" secondAttribute="trailing" constant="10" id="9uc-R7-CZk"/>
|
||||
<constraint firstItem="6JT-Vt-3q0" firstAttribute="top" secondItem="P0B-Ht-R8n" secondAttribute="bottom" constant="8" symbolic="YES" id="DGN-4k-X0h"/>
|
||||
<constraint firstItem="P0B-Ht-R8n" firstAttribute="top" secondItem="JJJ-T9-Yuv" secondAttribute="top" constant="20" symbolic="YES" id="F2r-6E-qxh"/>
|
||||
<constraint firstItem="mmQ-7e-dlb" firstAttribute="top" secondItem="KZf-b0-9cm" secondAttribute="bottom" constant="8" symbolic="YES" id="G21-Vd-tgl"/>
|
||||
@@ -560,16 +582,18 @@ Gw
|
||||
<constraint firstAttribute="trailing" secondItem="mmQ-7e-dlb" secondAttribute="trailing" constant="20" symbolic="YES" id="hjv-Xq-cxV"/>
|
||||
<constraint firstItem="6JT-Vt-3q0" firstAttribute="leading" secondItem="P0B-Ht-R8n" secondAttribute="leading" id="jxP-vM-eA9"/>
|
||||
<constraint firstItem="P0B-Ht-R8n" firstAttribute="leading" secondItem="JJJ-T9-Yuv" secondAttribute="leading" constant="20" symbolic="YES" id="msC-eG-Fop"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="P0B-Ht-R8n" secondAttribute="trailing" constant="20" symbolic="YES" id="nvj-Ij-dcd"/>
|
||||
<constraint firstItem="VzR-5a-cmT" firstAttribute="top" secondItem="ZX9-s1-23i" secondAttribute="bottom" constant="8" symbolic="YES" id="sVP-EV-07F"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ZX9-s1-23i" secondAttribute="trailing" constant="20" symbolic="YES" id="tZ3-2X-JC9"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="KZf-b0-9cm" secondAttribute="trailing" constant="20" symbolic="YES" id="zq0-Ce-sCs"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="buttonCancel" destination="SwS-o8-pbl" id="N1v-uy-2Mi"/>
|
||||
<outlet property="buttonCreateLink" destination="PVw-cM-qAB" id="0Oo-xW-He7"/>
|
||||
<outlet property="buttonSecure" destination="KZf-b0-9cm" id="5A7-Bn-NB7"/>
|
||||
<outlet property="linkName" destination="ZX9-s1-23i" id="yT6-80-Zr1"/>
|
||||
<outlet property="pathControl" destination="6JT-Vt-3q0" id="f5K-8h-VOd"/>
|
||||
<outlet property="pressedCancel" destination="SwS-o8-pbl" id="cLR-Yn-TSs"/>
|
||||
<outlet property="previewText" destination="VzR-5a-cmT" id="qwd-wX-645"/>
|
||||
<outlet property="textFieldError" destination="900-Z2-tID" id="qUk-FE-IKW"/>
|
||||
<outlet property="textFieldSecure" destination="mmQ-7e-dlb" id="LeA-YS-hRM"/>
|
||||
@@ -588,6 +612,10 @@ Gw
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="309"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<customView id="j65-Lf-0lG">
|
||||
<rect key="frame" x="9" y="0.0" width="581" height="203"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
</customView>
|
||||
<scrollView autohidesScrollers="YES" horizontalLineScroll="54" horizontalPageScroll="10" verticalLineScroll="54" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p0j-eB-I2i">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="309"/>
|
||||
<clipView key="contentView" id="6IL-DW-37w">
|
||||
@@ -690,7 +718,7 @@ Gw
|
||||
<constraint firstAttribute="width" constant="14" id="wrl-lJ-3eN"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="Checkmark" id="R5o-Cd-a91"/>
|
||||
<color key="contentTintColor" name="systemGreenColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="contentTintColor" name="IconColorGreen"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
@@ -723,6 +751,7 @@ Gw
|
||||
<outlet property="imageViewPhpVersionOK" destination="5aN-ZI-D7U" id="ePz-Cb-dWk"/>
|
||||
<outlet property="imageViewType" destination="0NQ-ZD-CqD" id="Cph-FN-LaY"/>
|
||||
<outlet property="labelDriver" destination="TbX-e2-3QL" id="qJh-Ak-Dge"/>
|
||||
<outlet property="labelDriverType" destination="jKi-Ls-7FZ" id="ZTq-pP-qUC"/>
|
||||
<outlet property="labelPathName" destination="CXK-Q9-CpO" id="iVZ-cL-azB"/>
|
||||
<outlet property="labelSiteName" destination="XJL-Uw-frD" id="f0t-vd-W68"/>
|
||||
</connections>
|
||||
@@ -742,7 +771,7 @@ Gw
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="600" id="iRQ-sz-oyv"/>
|
||||
</constraints>
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="TDE-ff-DQT">
|
||||
<rect key="frame" x="1" y="293" width="598" height="15"/>
|
||||
<rect key="frame" x="1" y="292" width="598" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="wFn-93-f10">
|
||||
@@ -774,14 +803,17 @@ Gw
|
||||
</viewController>
|
||||
<customObject id="HgD-aB-bQb" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="251" y="742"/>
|
||||
<point key="canvasLocation" x="251" y="741.5"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="Checkmark" width="512" height="512"/>
|
||||
<image name="IconLinked" width="512" height="512"/>
|
||||
<image name="IconLinked" width="25" height="25"/>
|
||||
<image name="Lock" width="30" height="30"/>
|
||||
<image name="arrow.clockwise" catalog="system" width="14" height="16"/>
|
||||
<image name="plus" catalog="system" width="14" height="13"/>
|
||||
<namedColor name="IconColorGreen">
|
||||
<color red="0.24699999392032623" green="0.69700002670288086" blue="0.50099998712539673" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
</resources>
|
||||
</document>
|
64
phpmon/Domain/App/InterAppHandler.swift
Normal file
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// InterAppHandler.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 28/01/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class InterApp {
|
||||
|
||||
public static var bindings: [Action] = []
|
||||
|
||||
public static func register(_ action: Action) {
|
||||
self.bindings.append(action)
|
||||
}
|
||||
|
||||
public struct Action {
|
||||
let command: String
|
||||
let action: (String) -> Void
|
||||
}
|
||||
|
||||
static func getCommands() -> [InterApp.Action] { return [
|
||||
InterApp.Action(command: "list", action: { _ in
|
||||
SiteListVC.show()
|
||||
}),
|
||||
InterApp.Action(command: "services/stop", action: { _ in
|
||||
MainMenu.shared.stopAllServices()
|
||||
}),
|
||||
InterApp.Action(command: "services/restart/all", action: { _ in
|
||||
MainMenu.shared.restartAllServices()
|
||||
}),
|
||||
InterApp.Action(command: "services/restart/nginx", action: { _ in
|
||||
MainMenu.shared.restartNginx()
|
||||
}),
|
||||
InterApp.Action(command: "services/restart/php", action: { _ in
|
||||
MainMenu.shared.restartPhpFpm()
|
||||
}),
|
||||
InterApp.Action(command: "services/restart/dnsmasq", action: { _ in
|
||||
MainMenu.shared.restartDnsMasq()
|
||||
}),
|
||||
InterApp.Action(command: "locate/config", action: { _ in
|
||||
MainMenu.shared.openActiveConfigFolder()
|
||||
}),
|
||||
InterApp.Action(command: "locate/composer", action: { _ in
|
||||
MainMenu.shared.openGlobalComposerFolder()
|
||||
}),
|
||||
InterApp.Action(command: "locate/valet", action: { _ in
|
||||
MainMenu.shared.openValetConfigFolder()
|
||||
}),
|
||||
InterApp.Action(command: "phpinfo", action: { _ in
|
||||
MainMenu.shared.openPhpInfo()
|
||||
}),
|
||||
InterApp.Action(command: "switch/php/", action: { version in
|
||||
if PhpEnv.shared.availablePhpVersions.contains(version) {
|
||||
MainMenu.shared.switchToPhpVersion(version)
|
||||
} else {
|
||||
Alert.notify(message: "Unsupported version", info: "PHP Monitor can't switch to PHP \(version), as it may not be installed or available.")
|
||||
}
|
||||
}),
|
||||
]}
|
||||
|
||||
}
|
@@ -49,6 +49,13 @@ class Startup {
|
||||
breaking: true
|
||||
)
|
||||
|
||||
performEnvironmentCheck(
|
||||
HomebrewDiagnostics.cannotLoadService(),
|
||||
messageText: "startup.errors.services_json_error.title".localized,
|
||||
informativeText: "startup.errors.services_json_error.desc".localized,
|
||||
breaking: true
|
||||
)
|
||||
|
||||
performEnvironmentCheck(
|
||||
!Shell.pipe("cat /private/etc/sudoers.d/brew").contains("\(Paths.binPath)/brew"),
|
||||
messageText: "startup.errors.sudoers_brew.title".localized,
|
||||
@@ -66,12 +73,13 @@ class Startup {
|
||||
breaking: true
|
||||
)
|
||||
|
||||
let services = Shell.pipe("\(Paths.brew) services list | grep php")
|
||||
// Determine the Valet version only AFTER confirming the correct permission is in place
|
||||
Valet.shared.version = VersionExtractor.from(valet("--version"))
|
||||
performEnvironmentCheck(
|
||||
(services.countInstances(of: "started") > 1),
|
||||
messageText: "startup.errors.services.title".localized,
|
||||
informativeText: "startup.errors.services.desc".localized,
|
||||
breaking: false
|
||||
Valet.shared.version == nil,
|
||||
messageText: "startup.errors.valet_version_unknown.title".localized,
|
||||
informativeText: "startup.errors.valet_version_unknown.desc".localized,
|
||||
breaking: true
|
||||
)
|
||||
|
||||
if (!failed) {
|
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// AppDelegate+InterApp.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 20/12/2021.
|
||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import Foundation
|
||||
|
||||
extension AppDelegate {
|
||||
|
||||
func application(_ application: NSApplication, open urls: [URL]) {
|
||||
print(urls)
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// Async.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 23/01/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public func runAsync(_ execute: @escaping () -> Void, completion: @escaping () -> Void = {})
|
||||
{
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
execute()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// BenchmarkTimer.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 10/12/2021.
|
||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class BenchmarkTimer {
|
||||
let startTime: CFAbsoluteTime
|
||||
var endTime: CFAbsoluteTime?
|
||||
|
||||
init() {
|
||||
startTime = CFAbsoluteTimeGetCurrent()
|
||||
}
|
||||
|
||||
func stop() -> CFAbsoluteTime {
|
||||
endTime = CFAbsoluteTimeGetCurrent()
|
||||
|
||||
return duration!
|
||||
}
|
||||
|
||||
var duration: CFAbsoluteTime? {
|
||||
if let endTime = endTime {
|
||||
return endTime - startTime
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
//
|
||||
// VersionExtractor.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 16/12/2021.
|
||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class VersionExtractor {
|
||||
|
||||
public static func from(_ string: String) -> String? {
|
||||
let regex = try! NSRegularExpression(
|
||||
pattern: #"Laravel Valet (?<version>(\d+)(.)(\d+)((.)(\d+))?)"#,
|
||||
options: []
|
||||
)
|
||||
|
||||
let match = regex.matches(
|
||||
in: string,
|
||||
options: [],
|
||||
range: NSMakeRange(0, string.count)
|
||||
).first
|
||||
|
||||
guard let match = match else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let range = Range(
|
||||
match.range(withName: "version"),
|
||||
in: string
|
||||
)!
|
||||
|
||||
return String(string[range])
|
||||
}
|
||||
|
||||
}
|
@@ -8,11 +8,37 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
This `Decodable` class is used to directly map `composer.json`
|
||||
to this object.
|
||||
*/
|
||||
struct ComposerJson: Decodable {
|
||||
|
||||
// MARK: - JSON structure
|
||||
|
||||
let dependencies: Dictionary<String, String>?
|
||||
let devDependencies: Dictionary<String, String>?
|
||||
let configuration: Config?
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case dependencies = "require"
|
||||
case devDependencies = "require-dev"
|
||||
case configuration = "config"
|
||||
}
|
||||
|
||||
struct Config: Decodable {
|
||||
let platform: Platform?
|
||||
}
|
||||
struct Platform: Decodable {
|
||||
let php: String?
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
/**
|
||||
Checks what the PHP version constraint is.
|
||||
Returns a tuple (constraint, location of constraint).
|
||||
*/
|
||||
public func getPhpVersion() -> (String, String) {
|
||||
// Check if in platform
|
||||
if configuration?.platform?.php != nil {
|
||||
@@ -25,12 +51,18 @@ struct ComposerJson: Decodable {
|
||||
}
|
||||
|
||||
// Unknown!
|
||||
return ("", "unknown")
|
||||
return ("???", "unknown")
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if any notable dependencies can be resolved.
|
||||
Only notable dependencies are saved.
|
||||
*/
|
||||
public func getNotableDependencies() -> [String: String] {
|
||||
var notable: [String: String] = [:]
|
||||
let scan = ["php", "laravel/framework"]
|
||||
|
||||
var scan = Array(PhpFrameworks.DependencyList.keys)
|
||||
scan.append("php")
|
||||
|
||||
scan.forEach { dependency in
|
||||
if dependencies?[dependency] != nil {
|
||||
@@ -41,19 +73,6 @@ struct ComposerJson: Decodable {
|
||||
return notable
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case dependencies = "require"
|
||||
case devDependencies = "require-dev"
|
||||
case configuration = "config"
|
||||
}
|
||||
|
||||
struct Config: Decodable {
|
||||
let platform: Platform?
|
||||
}
|
||||
|
||||
struct Platform: Decodable {
|
||||
let php: String?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
82
phpmon/Domain/Integrations/Composer/PhpFrameworks.swift
Normal file
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// Frameworks.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 26/01/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct PhpFrameworks {
|
||||
|
||||
/**
|
||||
This list should probably be reversed when checked, because some of these
|
||||
will also require either `laravel/framework` or `symfony/symfony`.
|
||||
*/
|
||||
public static let DependencyList = [
|
||||
|
||||
// COMMON FRAMEWORKS
|
||||
"laravel/framework" : "Laravel",
|
||||
"symfony/symfony": "Symfony",
|
||||
"laravel/lumen": "Lumen",
|
||||
|
||||
// VARIOUS CMS
|
||||
"roots/bedrock": "Bedrock",
|
||||
"cakephp/app": "CakePHP",
|
||||
"craftcms/craft": "Craft",
|
||||
"drupal/core": "Drupal",
|
||||
"flarum/core": "Flarum",
|
||||
"tightenco/jigsaw": "Jigsaw",
|
||||
"joomla/uri": "Joomla",
|
||||
"themsaid/katana": "Katana",
|
||||
"getkirby/cms": "Kirby",
|
||||
"october/october": "OctoberCMS",
|
||||
"sculpin/sculpin": "Sculpin",
|
||||
"statamic/cms": "Statamic",
|
||||
"johnpbloch/wordpress-core": "WordPress",
|
||||
"zendframework/zendframework": "Zend",
|
||||
"zendframework/zend-mvc": "Zend"
|
||||
|
||||
// TODO (5.1): Handle these in v5.1
|
||||
// "magento/*": "Magento",
|
||||
// "concrete5/*": "Concrete5",
|
||||
// "contao/*": "Contao",
|
||||
// "slim/*": "Slim",
|
||||
]
|
||||
|
||||
public static let FileMapping: [String: [String]] = [
|
||||
"Drupal": [
|
||||
// Legacy installations
|
||||
"/misc/drupal.js",
|
||||
"/core/lib/Drupal.php",
|
||||
// The default (new) installation w/ Composer puts the modules in /web
|
||||
"/web/misc/drupal.js",
|
||||
"/web/core/lib/Drupal.php"
|
||||
],
|
||||
"WordPress": [
|
||||
"/wp-config.php",
|
||||
"/wp-config-sample.php"
|
||||
],
|
||||
]
|
||||
|
||||
/**
|
||||
There are two cases where users are unlikely to use `composer`,
|
||||
when setting up a Drupal or a WordPress project. For performance
|
||||
reasons, we only check that here!
|
||||
*/
|
||||
public static func detectFallbackDependency(_ basePath: String) -> String? {
|
||||
for entry in Self.FileMapping {
|
||||
let found = entry.value
|
||||
.map { path in return Filesystem.fileExists(basePath + path) }
|
||||
.contains(true)
|
||||
|
||||
if found {
|
||||
return entry.key
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
@@ -10,19 +10,6 @@ import Foundation
|
||||
|
||||
class HomebrewDiagnostics {
|
||||
|
||||
enum Errors: String {
|
||||
case aliasConflict = "alias_conflict"
|
||||
}
|
||||
|
||||
static let shared = HomebrewDiagnostics()
|
||||
var errors: [HomebrewDiagnostics.Errors] = []
|
||||
|
||||
init() {
|
||||
if determineAliasConflicts() {
|
||||
errors.append(.aliasConflict)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
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`).
|
||||
@@ -30,7 +17,7 @@ class HomebrewDiagnostics {
|
||||
|
||||
This check only needs to be performed if the `shivammathur/php` tap is active.
|
||||
*/
|
||||
public func determineAliasConflicts() -> Bool
|
||||
public static func hasAliasConflict() -> Bool
|
||||
{
|
||||
let tapAlias = Shell.pipe("\(Paths.brew) info shivammathur/php/php --json")
|
||||
|
||||
@@ -67,4 +54,21 @@ class HomebrewDiagnostics {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
In order to see if we support the --json syntax, we'll query nginx.
|
||||
If the JSON response cannot be parsed, Homebrew is probably out of date.
|
||||
*/
|
||||
public static func cannotLoadService(_ name: String = "nginx") -> Bool
|
||||
{
|
||||
let serviceInfo = try? JSONDecoder().decode(
|
||||
[HomebrewService].self,
|
||||
from: Shell.pipe(
|
||||
"sudo \(Paths.brew) services info \(name) --json",
|
||||
requiresPath: true
|
||||
).data(using: .utf8)!
|
||||
)
|
||||
|
||||
return serviceInfo == nil
|
||||
}
|
||||
}
|
||||
|
@@ -13,31 +13,48 @@ class Valet {
|
||||
static let shared = Valet()
|
||||
|
||||
/// The version of Valet that was detected.
|
||||
var version: String
|
||||
var version: String! = nil
|
||||
|
||||
/// The Valet configuration file.
|
||||
var config: Valet.Configuration
|
||||
var config: Valet.Configuration!
|
||||
|
||||
/// A cached list of sites that were detected after analyzing the paths set up for Valet.
|
||||
var sites: [Site] = []
|
||||
|
||||
init() {
|
||||
version = VersionExtractor.from(valet("--version"))
|
||||
?? "UNKNOWN"
|
||||
/// Whether we're busy with some blocking operation.
|
||||
var isBusy: Bool = false
|
||||
|
||||
/// When initialising the Valet singleton, extract the Valet version and assume no sites loaded.
|
||||
init() {
|
||||
self.version = nil
|
||||
self.sites = []
|
||||
}
|
||||
|
||||
/**
|
||||
We don't want to load the initial config.json file as soon as the class is initialised.
|
||||
Instead, we'll defer the loading of the configuration file once the initial app checks
|
||||
have passed: if the user does not have Valet installed, we'll crash the app because we
|
||||
force unwrap the file. Currently, this does also mean that if the JSON is invalid or
|
||||
incompatible with the `Decodable` `Valet.Configuration` class, that the app will crash.
|
||||
*/
|
||||
public func loadConfiguration() {
|
||||
let file = FileManager.default.homeDirectoryForCurrentUser
|
||||
.appendingPathComponent(".config/valet/config.json")
|
||||
|
||||
// TODO: (5.1) Fix loading of invalid JSON: do not crash the app
|
||||
config = try! JSONDecoder().decode(
|
||||
Valet.Configuration.self,
|
||||
from: try! String(contentsOf: file, encoding: .utf8).data(using: .utf8)!
|
||||
)
|
||||
|
||||
self.sites = []
|
||||
}
|
||||
|
||||
/**
|
||||
Starts the preload of sites, but only if the maximum amount of sites is 30.
|
||||
For users with more sites, the site list is loaded when they bring up the site list window.
|
||||
(This is done to keep the startup speed as fast as possible.)
|
||||
*/
|
||||
public func startPreloadingSites() {
|
||||
let maximumPreload = 10
|
||||
let maximumPreload = 30
|
||||
let foundSites = self.countPaths()
|
||||
if foundSites <= maximumPreload {
|
||||
// Preload the sites and their drivers
|
||||
@@ -48,23 +65,32 @@ class Valet {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Reloads the list of sites, assuming that the list isn't being reloaded at the time.
|
||||
We don't want to do duplicate or parallel work!
|
||||
*/
|
||||
public func reloadSites() {
|
||||
if (isBusy) {
|
||||
return
|
||||
}
|
||||
|
||||
resolvePaths(tld: config.tld)
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the version of Valet is more recent than the minimum version required for PHP Monitor to function.
|
||||
Should this procedure fail, the user will get an alert notifying them that the version of Valet they have
|
||||
installed is not recent enough.
|
||||
*/
|
||||
public func validateVersion() -> Void {
|
||||
if version == "UNKNOWN" {
|
||||
return Log.warn("The Valet version could not be extracted... that does not bode well.")
|
||||
}
|
||||
|
||||
if version.versionCompare(Constants.MinimumRecommendedValetVersion) == .orderedAscending {
|
||||
let version = version
|
||||
Log.warn("Valet version \(version) is too old! (recommended: \(Constants.MinimumRecommendedValetVersion))")
|
||||
Log.warn("Valet version \(version!) is too old! (recommended: \(Constants.MinimumRecommendedValetVersion))")
|
||||
DispatchQueue.main.async {
|
||||
Alert.notify(message: "alert.min_valet_version.title".localized, info: "alert.min_valet_version.info".localized(version, Constants.MinimumRecommendedValetVersion))
|
||||
Alert.notify(message: "alert.min_valet_version.title".localized, info: "alert.min_valet_version.info".localized(version!, Constants.MinimumRecommendedValetVersion))
|
||||
}
|
||||
} else {
|
||||
Log.info("Valet version \(version) is recent enough, OK (recommended: \(Constants.MinimumRecommendedValetVersion))")
|
||||
Log.info("Valet version \(version!) is recent enough, OK (recommended: \(Constants.MinimumRecommendedValetVersion))")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +114,8 @@ class Valet {
|
||||
Resolves all paths and creates linked or parked site instances that can be referenced later.
|
||||
*/
|
||||
private func resolvePaths(tld: String) {
|
||||
isBusy = true
|
||||
|
||||
sites = []
|
||||
|
||||
for path in config.paths {
|
||||
@@ -98,6 +126,8 @@ class Valet {
|
||||
}
|
||||
|
||||
sites = sites.sorted { $0.absolutePath < $1.absolutePath }
|
||||
|
||||
isBusy = false
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,7 +163,7 @@ class Valet {
|
||||
|
||||
// We should also check that we can interpret the path correctly
|
||||
if URL(fileURLWithPath: siteDir).lastPathComponent == "" {
|
||||
print("Warning: could not parse the site: \(siteDir), skipping!")
|
||||
Log.warn("Could not parse the site: \(siteDir), skipping!")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -153,6 +183,13 @@ class Valet {
|
||||
/// The absolute path to the directory that is served.
|
||||
var absolutePath: String!
|
||||
|
||||
/// The absolute path to the directory that is served,
|
||||
/// replacing the user's home folder with ~.
|
||||
lazy var absolutePathRelative: String = {
|
||||
return self.absolutePath
|
||||
.replacingOccurrences(of: "/Users/\(Paths.whoami)", with: "~")
|
||||
}()
|
||||
|
||||
/// Location of the alias. If set, this is a linked domain.
|
||||
var aliasPath: String?
|
||||
|
||||
@@ -162,12 +199,18 @@ class Valet {
|
||||
/// What driver is currently in use. If not detected, defaults to nil.
|
||||
var driver: String? = nil
|
||||
|
||||
/// Whether the driver was determined by checking the Composer file.
|
||||
var driverDeterminedByComposer: Bool = false
|
||||
|
||||
/// A list of notable Composer dependencies.
|
||||
var notableComposerDependencies: [String: String] = [:]
|
||||
|
||||
/// The PHP version as discovered in composer.json.
|
||||
/// The PHP version as discovered in `composer.json`.
|
||||
var composerPhp: String = "???"
|
||||
|
||||
/// Check whether the PHP version is valid for the currently linked version.
|
||||
var composerPhpCompatibleWithLinked: Bool = false
|
||||
|
||||
/// How the PHP version was determined.
|
||||
var composerPhpSource: String = "unknown"
|
||||
|
||||
@@ -179,8 +222,8 @@ class Valet {
|
||||
self.name = URL(fileURLWithPath: absolutePath).lastPathComponent
|
||||
self.aliasPath = nil
|
||||
determineSecured(tld)
|
||||
determineDriver()
|
||||
determineComposerPhpVersion()
|
||||
determineDriver()
|
||||
}
|
||||
|
||||
convenience init(aliasPath: String, tld: String) {
|
||||
@@ -189,28 +232,31 @@ class Valet {
|
||||
self.name = URL(fileURLWithPath: aliasPath).lastPathComponent
|
||||
self.aliasPath = aliasPath
|
||||
determineSecured(tld)
|
||||
determineDriver()
|
||||
determineComposerPhpVersion()
|
||||
determineDriver()
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if a certificate file can be found in the `valet/Certificates` directory.
|
||||
- Note: The file is not validated, only its presence is checked.
|
||||
*/
|
||||
public func determineSecured(_ tld: String) {
|
||||
secured = Shell.fileExists("~/.config/valet/Certificates/\(self.name!).\(tld).key")
|
||||
}
|
||||
|
||||
public func determineDriver() {
|
||||
let driver = Shell.pipe("cd '\(absolutePath!)' && valet which", requiresPath: true)
|
||||
if driver.contains("This site is served by") {
|
||||
self.driver = driver
|
||||
// TODO: Use a regular expression to retrieve the driver instead?
|
||||
.replacingOccurrences(of: "This site is served by [", with: "")
|
||||
.replacingOccurrences(of: "ValetDriver].\n", with: "")
|
||||
} else {
|
||||
self.driver = nil
|
||||
}
|
||||
}
|
||||
/**
|
||||
Checks if `composer.json` exists in the folder, and extracts notable information:
|
||||
|
||||
- The PHP version required (the constraint, so it could be `^8.0`, for example)
|
||||
- Where the PHP version was found (`require` or `platform`)
|
||||
- Notable PHP dependencies (determined via `PhpFrameworks.DependencyList`)
|
||||
|
||||
The method then also checks if the determined constraint (if found) is compatible
|
||||
with the currently linked version of PHP (see `composerPhpMatchesSystem`).
|
||||
*/
|
||||
public func determineComposerPhpVersion() {
|
||||
let path = "\(absolutePath!)/composer.json"
|
||||
|
||||
do {
|
||||
if Filesystem.fileExists(path) {
|
||||
let decoded = try JSONDecoder().decode(
|
||||
@@ -224,6 +270,60 @@ class Valet {
|
||||
} catch {
|
||||
Log.err("Something went wrong reading the composer JSON file.")
|
||||
}
|
||||
|
||||
if self.composerPhp == "???" {
|
||||
return
|
||||
}
|
||||
|
||||
// Split the composer list (on "|") to evaluate multiple constraints
|
||||
// For example, for Laravel 8 projects the value is "^7.3|^8.0"
|
||||
self.composerPhpCompatibleWithLinked =
|
||||
self.composerPhp.split(separator: "|").map { string in
|
||||
return PhpVersionNumberCollection.make(from: [PhpEnv.phpInstall.version.long])
|
||||
.matching(constraint: string.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
.count > 0
|
||||
}.contains(true)
|
||||
}
|
||||
|
||||
/**
|
||||
Determine the driver to be displayed in the list of sites. In v5.0, this has been changed
|
||||
to load the "framework" or "project type" instead.
|
||||
*/
|
||||
public func determineDriver() {
|
||||
self.determineDriverViaComposer()
|
||||
|
||||
if self.driver == nil {
|
||||
self.driver = PhpFrameworks.detectFallbackDependency(self.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Check the dependency list and see if a particular dependency can't be found.
|
||||
We'll revert the dependency list so that Laravel and Symfony are detected last.
|
||||
|
||||
(Some other frameworks might use Laravel, so if we found it first the detection would be incorrect:
|
||||
this would happen with Statamic, for example.)
|
||||
*/
|
||||
private func determineDriverViaComposer() {
|
||||
self.driverDeterminedByComposer = true
|
||||
|
||||
PhpFrameworks.DependencyList.reversed().forEach { (key: String, value: String) in
|
||||
if self.notableComposerDependencies.keys.contains(key) {
|
||||
self.driver = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "determineDriver")
|
||||
private func determineDriverViaValet() {
|
||||
let driver = Shell.pipe("cd '\(absolutePath!)' && valet which", requiresPath: true)
|
||||
if driver.contains("This site is served by") {
|
||||
self.driver = driver
|
||||
.replacingOccurrences(of: "This site is served by [", with: "")
|
||||
.replacingOccurrences(of: "ValetDriver].\n", with: "")
|
||||
} else {
|
||||
self.driver = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,8 +335,8 @@ class Valet {
|
||||
/// The paths that need to be checked.
|
||||
let paths: [String]
|
||||
|
||||
/// The loopback address.
|
||||
let loopback: String
|
||||
/// The loopback address. Optional.
|
||||
let loopback: String?
|
||||
|
||||
/// The default site that is served if the domain is not found. Optional.
|
||||
let defaultSite: String?
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -10,7 +10,7 @@
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customView id="c22-O7-iKe" customClass="HeaderView" customModule="PHP_Monitor" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="350" height="24"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="270" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ddg-VQ-cOT">
|
||||
@@ -29,7 +29,7 @@
|
||||
<connections>
|
||||
<outlet property="textField" destination="ddg-VQ-cOT" id="aaQ-Xb-o2X"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="-75" y="38"/>
|
||||
<point key="canvasLocation" x="177" y="105"/>
|
||||
</customView>
|
||||
</objects>
|
||||
</document>
|
||||
|
96
phpmon/Domain/Menu/MainMenu+Async.swift
Normal file
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// MainMenu+Async.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 06/02/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension MainMenu {
|
||||
|
||||
// MARK: - Nicer callbacks
|
||||
|
||||
enum AsyncBehaviour {
|
||||
case setsBusyUI
|
||||
case reloadsPhpInstallation
|
||||
case updatesMenuBarContents
|
||||
case broadcastServicesUpdate
|
||||
}
|
||||
|
||||
/**
|
||||
Attempts asynchronous execution of a callback that may throw an `Error`.
|
||||
While the callback is being executed, the UI will be marked as busy.
|
||||
|
||||
(Preferably, if an `Error` is thrown, it should also be an `AlertableError`,
|
||||
which will make presenting errors easier.)
|
||||
|
||||
- Parameter execute: Required callback of the work that needs to happen.
|
||||
|
||||
- Parameter success: Optional callback that is fired when all was OK.
|
||||
- Parameter failure: Optional callback that is fired when an `Error` was thrown.
|
||||
- Parameter behaviours: Various behaviours that can be tweaked, but usually best left to the default.
|
||||
The default will set the UI to busy, reload PHP info, update the menu bar,
|
||||
and broadcast to the services view that the list has been updated.
|
||||
*/
|
||||
func asyncExecution(
|
||||
_ execute: @escaping () throws -> Void,
|
||||
success: @escaping () -> Void = {},
|
||||
failure: @escaping (Error) -> Void = { _ in },
|
||||
behaviours: [AsyncBehaviour] = [
|
||||
.setsBusyUI,
|
||||
.reloadsPhpInstallation,
|
||||
.updatesMenuBarContents,
|
||||
.broadcastServicesUpdate
|
||||
]
|
||||
) {
|
||||
if behaviours.contains(.reloadsPhpInstallation) {
|
||||
PhpEnv.shared.isBusy = true
|
||||
}
|
||||
|
||||
if behaviours.contains(.setsBusyUI) {
|
||||
setBusyImage()
|
||||
}
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
var error: Error? = nil
|
||||
|
||||
do { try execute() } catch let e { error = e }
|
||||
|
||||
if behaviours.contains(.setsBusyUI) {
|
||||
PhpEnv.shared.isBusy = false
|
||||
}
|
||||
|
||||
DispatchQueue.main.async { [self] in
|
||||
if behaviours.contains(.reloadsPhpInstallation) {
|
||||
PhpEnv.shared.currentInstall = ActivePhpInstallation()
|
||||
}
|
||||
|
||||
if behaviours.contains(.updatesMenuBarContents) {
|
||||
updatePhpVersionInStatusBar()
|
||||
} else if behaviours.contains(.setsBusyUI) {
|
||||
refreshIcon()
|
||||
}
|
||||
|
||||
if behaviours.contains(.broadcastServicesUpdate) {
|
||||
NotificationCenter.default.post(name: Events.ServicesUpdated, object: nil)
|
||||
}
|
||||
|
||||
error == nil ? success() : failure(error!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func asyncWithBusyUI(
|
||||
_ execute: @escaping () throws -> Void,
|
||||
completion: @escaping () -> Void = {}
|
||||
) {
|
||||
asyncExecution({
|
||||
try! execute()
|
||||
}, success: {
|
||||
completion()
|
||||
}, behaviours: [.setsBusyUI])
|
||||
}
|
||||
|
||||
}
|
98
phpmon/Domain/Menu/MainMenu+Composer.swift
Normal file
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// MainMenu+Composer.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 08/02/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension MainMenu {
|
||||
|
||||
/**
|
||||
Updates the global dependencies and runs the completion callback when done.
|
||||
This method should probably be broken up into several smaller methods at some point.
|
||||
*/
|
||||
func updateGlobalDependencies(notify: Bool, completion: @escaping (Bool) -> Void) {
|
||||
if !Shell.fileExists("/usr/local/bin/composer") {
|
||||
Alert.notify(
|
||||
message: "alert.composer_missing.title".localized,
|
||||
info: "alert.composer_missing.info".localized
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
PhpEnv.shared.isBusy = true
|
||||
setBusyImage()
|
||||
self.rebuild()
|
||||
|
||||
let noLongerBusy = {
|
||||
PhpEnv.shared.isBusy = false
|
||||
DispatchQueue.main.async { [self] in
|
||||
self.updatePhpVersionInStatusBar()
|
||||
self.rebuild()
|
||||
}
|
||||
}
|
||||
|
||||
var window: ProgressWindowController? = ProgressWindowController.display(
|
||||
title: "alert.composer_progress.title".localized,
|
||||
description: "alert.composer_progress.info".localized
|
||||
)
|
||||
window?.setType(info: true)
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
let task = Shell.user.createTask(
|
||||
for: "/usr/local/bin/composer global update", requiresPath: true
|
||||
)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
window?.addToConsole("/usr/local/bin/composer global update\n")
|
||||
}
|
||||
|
||||
Shell.captureOutput(
|
||||
task,
|
||||
didReceiveStdOutData: { string in
|
||||
DispatchQueue.main.async {
|
||||
window?.addToConsole(string)
|
||||
}
|
||||
Log.perf("\(string.trimmingCharacters(in: .newlines))")
|
||||
},
|
||||
didReceiveStdErrData: { string in
|
||||
DispatchQueue.main.async {
|
||||
window?.addToConsole(string)
|
||||
}
|
||||
Log.perf("\(string.trimmingCharacters(in: .newlines))")
|
||||
}
|
||||
)
|
||||
|
||||
task.launch()
|
||||
task.waitUntilExit()
|
||||
Shell.haltCapturingOutput(task)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if task.terminationStatus <= 0 {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
||||
window?.close()
|
||||
if (notify) {
|
||||
LocalNotification.send(
|
||||
title: "alert.composer_success.title".localized,
|
||||
subtitle: "alert.composer_success.info".localized
|
||||
)
|
||||
}
|
||||
window = nil
|
||||
noLongerBusy()
|
||||
completion(true)
|
||||
}
|
||||
} else {
|
||||
window?.setType(info: false)
|
||||
window?.progressView?.labelTitle.stringValue = "alert.composer_failure.title".localized
|
||||
window?.progressView?.labelDescription.stringValue = "alert.composer_failure.info".localized
|
||||
window = nil
|
||||
noLongerBusy()
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -30,7 +30,7 @@ extension MainMenu {
|
||||
private func onEnvironmentPass() {
|
||||
PhpEnv.detectPhpVersions()
|
||||
|
||||
if HomebrewDiagnostics.shared.errors.contains(.aliasConflict) {
|
||||
if HomebrewDiagnostics.hasAliasConflict() {
|
||||
DispatchQueue.main.async {
|
||||
Alert.notify(
|
||||
message: "alert.php_alias_conflict.title".localized,
|
||||
@@ -69,8 +69,11 @@ extension MainMenu {
|
||||
App.shared.loadGlobalHotkey()
|
||||
|
||||
// Attempt to find out more info about Valet
|
||||
Log.info("PHP Monitor has extracted the version number of Valet: \(Valet.shared.version)")
|
||||
if Valet.shared.version != nil {
|
||||
Log.info("PHP Monitor has extracted the version number of Valet: \(Valet.shared.version!)")
|
||||
}
|
||||
|
||||
Valet.shared.loadConfiguration()
|
||||
Valet.shared.validateVersion()
|
||||
Valet.shared.startPreloadingSites()
|
||||
|
||||
@@ -88,6 +91,9 @@ extension MainMenu {
|
||||
repeats: true
|
||||
)
|
||||
}
|
||||
|
||||
Stats.incrementSuccessfulLaunchCount()
|
||||
Stats.evaluateSponsorMessageShouldBeDisplayed()
|
||||
}
|
||||
|
||||
/**
|
||||
|
84
phpmon/Domain/Menu/MainMenu+Switcher.swift
Normal file
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// MainMenu+PhpSwitcherDelegate.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 08/02/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension MainMenu {
|
||||
|
||||
// MARK: - PhpSwitcherDelegate
|
||||
|
||||
func switcherDidStartSwitching(to version: String) {}
|
||||
|
||||
func switcherDidCompleteSwitch(to version: String) {
|
||||
// Update the PHP version
|
||||
PhpEnv.shared.currentInstall = ActivePhpInstallation()
|
||||
|
||||
// Ensure the config watcher gets reloaded
|
||||
App.shared.handlePhpConfigWatcher()
|
||||
|
||||
// Mark as no longer busy
|
||||
PhpEnv.shared.isBusy = false
|
||||
|
||||
// Reload the site list
|
||||
self.reloadSiteListData()
|
||||
|
||||
// Perform UI updates on main thread
|
||||
DispatchQueue.main.async { [self] in
|
||||
updatePhpVersionInStatusBar()
|
||||
rebuild()
|
||||
|
||||
if !PhpEnv.shared.validate(version) {
|
||||
self.suggestFixMyValet(failed: version)
|
||||
return
|
||||
}
|
||||
|
||||
// Run composer updates
|
||||
if Preferences.isEnabled(.autoComposerGlobalUpdateAfterSwitch) {
|
||||
self.updateGlobalDependencies(notify: false, completion: { _ in
|
||||
self.notifyAboutVersionChange(to: version)
|
||||
})
|
||||
} else {
|
||||
self.notifyAboutVersionChange(to: version)
|
||||
}
|
||||
|
||||
// Update stats
|
||||
Stats.incrementSuccessfulSwitchCount()
|
||||
Stats.evaluateSponsorMessageShouldBeDisplayed()
|
||||
}
|
||||
}
|
||||
|
||||
private func suggestFixMyValet(failed version: String) {
|
||||
let outcome = Alert.present(
|
||||
messageText: "alert.php_switch_failed.title".localized(version),
|
||||
informativeText: "alert.php_switch_failed.info".localized(version),
|
||||
buttonTitle: "alert.php_switch_failed.confirm".localized,
|
||||
secondButtonTitle: "alert.php_switch_failed.cancel".localized, style: .informational)
|
||||
if outcome {
|
||||
MainMenu.shared.fixMyValet()
|
||||
}
|
||||
}
|
||||
|
||||
private func reloadSiteListData() {
|
||||
if let window = App.shared.siteListWindowController {
|
||||
DispatchQueue.main.async {
|
||||
window.contentVC.reloadSites()
|
||||
}
|
||||
} else {
|
||||
Valet.shared.reloadSites()
|
||||
}
|
||||
}
|
||||
|
||||
private func notifyAboutVersionChange(to version: String) {
|
||||
LocalNotification.send(
|
||||
title: String(format: "notification.version_changed_title".localized, version),
|
||||
subtitle: String(format: "notification.version_changed_desc".localized, version)
|
||||
)
|
||||
|
||||
PhpEnv.phpInstall.notifyAboutBrokenPhpFpm()
|
||||
}
|
||||
}
|
@@ -7,7 +7,7 @@
|
||||
|
||||
import Cocoa
|
||||
|
||||
class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
||||
class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate {
|
||||
|
||||
static let shared = MainMenu()
|
||||
|
||||
@@ -23,52 +23,67 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
||||
// MARK: - UI related
|
||||
|
||||
/**
|
||||
Update the menu's contents, based on what's going on.
|
||||
This will rebuild the entire menu, so this can take a few moments.
|
||||
Rebuilds the menu (either asynchronously or synchronously).
|
||||
Defaults to rebuilding the menu asynchronously.
|
||||
*/
|
||||
func rebuild() {
|
||||
func rebuild(async: Bool = true) {
|
||||
if !async {
|
||||
self.rebuildMenu()
|
||||
return
|
||||
}
|
||||
|
||||
// Update the menu item on the main thread
|
||||
DispatchQueue.main.async { [self] in
|
||||
// Create a new menu
|
||||
let menu = StatusMenu()
|
||||
|
||||
// Add the PHP versions (or error messages)
|
||||
menu.addPhpVersionMenuItems()
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
// Add the possible actions
|
||||
menu.addPhpActionMenuItems()
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
// Add Valet interactions
|
||||
menu.addValetMenuItems()
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
// Add services
|
||||
menu.addPhpConfigurationMenuItems()
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
// Add about & quit menu items
|
||||
menu.addItem(NSMenuItem(title: "mi_preferences".localized, action: #selector(openPrefs), keyEquivalent: ","))
|
||||
menu.addItem(NSMenuItem(title: "mi_about".localized, action: #selector(openAbout), keyEquivalent: ""))
|
||||
menu.addItem(NSMenuItem(title: "mi_quit".localized, action: #selector(terminateApp), keyEquivalent: "q"))
|
||||
|
||||
// Make sure every item can be interacted with
|
||||
menu.items.forEach({ (item) in
|
||||
item.target = self
|
||||
})
|
||||
|
||||
statusItem.menu = menu
|
||||
statusItem.menu?.delegate = self
|
||||
self.rebuildMenu()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Update the menu's contents, based on what's going on.
|
||||
This will rebuild the entire menu, so this can take a few moments.
|
||||
|
||||
Use `rebuild(async:)` to ensure the rebuilding happens in the background.
|
||||
*/
|
||||
private func rebuildMenu() {
|
||||
// Create a new menu
|
||||
let menu = StatusMenu()
|
||||
|
||||
// Add the PHP versions (or error messages)
|
||||
menu.addPhpVersionMenuItems()
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
// Add the possible actions
|
||||
menu.addPhpActionMenuItems()
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
// Add Valet interactions
|
||||
menu.addValetMenuItems()
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
// Add services
|
||||
menu.addPhpConfigurationMenuItems()
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
// Add about & quit menu items
|
||||
menu.addItem(NSMenuItem(title: "mi_preferences".localized, action: #selector(openPrefs), keyEquivalent: ","))
|
||||
menu.addItem(NSMenuItem(title: "mi_about".localized, action: #selector(openAbout), keyEquivalent: ""))
|
||||
menu.addItem(NSMenuItem(title: "mi_quit".localized, action: #selector(terminateApp), keyEquivalent: "q"))
|
||||
|
||||
// Make sure every item can be interacted with
|
||||
menu.items.forEach({ (item) in
|
||||
item.target = self
|
||||
})
|
||||
|
||||
statusItem.menu = menu
|
||||
statusItem.menu?.delegate = self
|
||||
}
|
||||
|
||||
/**
|
||||
Sets the status bar image based on a version string.
|
||||
*/
|
||||
func setStatusBarImage(version: String) {
|
||||
setStatusBar(
|
||||
image: Preferences.isEnabled(.shouldDisplayPhpHintInIcon)
|
||||
image: (Preferences.preferences[.iconTypeToDisplay] as! String != MenuBarIcon.noIcon.rawValue)
|
||||
? MenuBarImageGenerator.textToImageWithIcon(text: version)
|
||||
: MenuBarImageGenerator.textToImage(text: version)
|
||||
)
|
||||
@@ -85,34 +100,9 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Nicer callbacks
|
||||
|
||||
/**
|
||||
Executes a specific callback and fires the completion callback,
|
||||
while updating the UI as required. As long as the completion callback
|
||||
does not fire, the app is presumed to be busy and the UI reflects this.
|
||||
|
||||
- Parameter execute: Callback of the work that needs to happen.
|
||||
- Parameter completion: Callback that is fired when the work is done.
|
||||
*/
|
||||
private func waitAndExecute(_ execute: @escaping () -> Void, completion: @escaping () -> Void = {})
|
||||
{
|
||||
PhpEnv.shared.isBusy = true
|
||||
setBusyImage()
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
execute()
|
||||
PhpEnv.shared.isBusy = false
|
||||
|
||||
DispatchQueue.main.async { [self] in
|
||||
updatePhpVersionInStatusBar()
|
||||
NotificationCenter.default.post(name: Events.ServicesUpdated, object: nil)
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - User Interface
|
||||
|
||||
/** Reloads which PHP versions is currently active. */
|
||||
@objc func refreshActiveInstallation() {
|
||||
if !PhpEnv.shared.isBusy {
|
||||
PhpEnv.shared.currentInstall = ActivePhpInstallation()
|
||||
@@ -122,12 +112,38 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
/** Updates the icon (refresh icon) and rebuilds the menu. */
|
||||
@objc func updatePhpVersionInStatusBar() {
|
||||
refreshIcon()
|
||||
rebuild()
|
||||
}
|
||||
|
||||
func refreshIcon() {
|
||||
/**
|
||||
Reloads the menu in the foreground.
|
||||
This mimics the exact behaviours of `asyncExecution` as set in the method below.
|
||||
*/
|
||||
@objc func reloadPhpMonitorMenuInForeground() {
|
||||
refreshActiveInstallation()
|
||||
refreshIcon()
|
||||
rebuild(async: false)
|
||||
NotificationCenter.default.post(name: Events.ServicesUpdated, object: nil)
|
||||
}
|
||||
|
||||
/** Reloads the menu in the background, using `asyncExecution`. */
|
||||
@objc func reloadPhpMonitorMenuInBackground() {
|
||||
asyncExecution({
|
||||
// This automatically reloads the menu
|
||||
Log.info("Reloading information about the PHP installation (in the background)...")
|
||||
}, behaviours: [
|
||||
.setsBusyUI,
|
||||
.reloadsPhpInstallation,
|
||||
.broadcastServicesUpdate,
|
||||
.updatesMenuBarContents
|
||||
])
|
||||
}
|
||||
|
||||
/** Refreshes the icon with the PHP version. */
|
||||
@objc func refreshIcon() {
|
||||
DispatchQueue.main.async { [self] in
|
||||
if (App.busy) {
|
||||
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
@@ -144,20 +160,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
@objc func reloadPhpMonitorMenuInBackground() {
|
||||
waitAndExecute {
|
||||
// This automatically reloads the menu
|
||||
Log.info("Reloading information about the PHP installation (in the background)...")
|
||||
}
|
||||
}
|
||||
|
||||
@objc func reloadPhpMonitorMenu() {
|
||||
waitAndExecute {
|
||||
// This automatically reloads the menu
|
||||
Log.info("Reloading information about the PHP installation...")
|
||||
}
|
||||
}
|
||||
|
||||
/** Updates the icon to be displayed as busy. */
|
||||
@objc func setBusyImage() {
|
||||
DispatchQueue.main.async { [self] in
|
||||
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
@@ -166,18 +169,42 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc func fixHomebrewPermissions() {
|
||||
if !Alert.present(
|
||||
messageText: "alert.fix_homebrew_permissions.title".localized,
|
||||
informativeText: "alert.fix_homebrew_permissions.info".localized,
|
||||
buttonTitle: "alert.fix_homebrew_permissions.ok".localized,
|
||||
secondButtonTitle: "alert.fix_homebrew_permissions.cancel".localized,
|
||||
style: .warning
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
asyncExecution {
|
||||
try Actions.fixHomebrewPermissions()
|
||||
} success: {
|
||||
Alert.notify(
|
||||
message: "alert.fix_homebrew_permissions_done.title".localized,
|
||||
info: "alert.fix_homebrew_permissions_done.info".localized,
|
||||
style: .warning
|
||||
)
|
||||
} failure: { error in
|
||||
Alert.notify(about: error as! HomebrewPermissionError)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func restartPhpFpm() {
|
||||
waitAndExecute {
|
||||
asyncExecution {
|
||||
Actions.restartPhpFpm()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func restartAllServices() {
|
||||
waitAndExecute {
|
||||
asyncExecution {
|
||||
Actions.restartDnsMasq()
|
||||
Actions.restartPhpFpm()
|
||||
Actions.restartNginx()
|
||||
} completion: {
|
||||
} success: {
|
||||
DispatchQueue.main.async {
|
||||
LocalNotification.send(
|
||||
title: "notification.services_restarted".localized,
|
||||
@@ -188,9 +215,9 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
||||
}
|
||||
|
||||
@objc func stopAllServices() {
|
||||
waitAndExecute {
|
||||
asyncExecution {
|
||||
Actions.stopAllServices()
|
||||
} completion: {
|
||||
} success: {
|
||||
DispatchQueue.main.async {
|
||||
LocalNotification.send(
|
||||
title: "notification.services_stopped".localized,
|
||||
@@ -201,19 +228,19 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
||||
}
|
||||
|
||||
@objc func restartNginx() {
|
||||
waitAndExecute {
|
||||
asyncExecution {
|
||||
Actions.restartNginx()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func restartDnsMasq() {
|
||||
waitAndExecute {
|
||||
asyncExecution {
|
||||
Actions.restartDnsMasq()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func toggleExtension(sender: ExtensionMenuItem) {
|
||||
waitAndExecute {
|
||||
asyncExecution {
|
||||
sender.phpExtension?.toggle()
|
||||
|
||||
if Preferences.isEnabled(.autoServiceRestartAfterExtensionToggle) {
|
||||
@@ -225,28 +252,31 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
||||
@objc func openPhpInfo() {
|
||||
var url: URL? = nil
|
||||
|
||||
waitAndExecute {
|
||||
asyncWithBusyUI {
|
||||
url = Actions.createTempPhpInfoFile()
|
||||
} completion: {
|
||||
// When this has been completed, open the URL to the file in the browser
|
||||
NSWorkspace.shared.open(url!)
|
||||
if url != nil { NSWorkspace.shared.open(url!) }
|
||||
}
|
||||
}
|
||||
|
||||
@objc func forceRestartLatestPhp() {
|
||||
// Tell the user the switch is about to occur
|
||||
Alert.notify(
|
||||
message: "alert.force_reload.title".localized,
|
||||
info: "alert.force_reload.info".localized
|
||||
)
|
||||
@objc func fixMyValet() {
|
||||
if !Alert.present(
|
||||
messageText: "alert.fix_my_valet.title".localized,
|
||||
informativeText: "alert.fix_my_valet.info".localized(PhpEnv.brewPhpVersion),
|
||||
buttonTitle: "alert.fix_my_valet.ok".localized,
|
||||
secondButtonTitle: "alert.fix_my_valet.cancel".localized,
|
||||
style: .warning
|
||||
) {
|
||||
Log.info("The user has chosen to abort Fix My Valet")
|
||||
return
|
||||
}
|
||||
|
||||
// Start switching
|
||||
waitAndExecute {
|
||||
Actions.fixMyPhp()
|
||||
} completion: {
|
||||
asyncExecution {
|
||||
Actions.fixMyValet()
|
||||
} success: {
|
||||
Alert.notify(
|
||||
message: "alert.force_reload_done.title".localized,
|
||||
info: "alert.force_reload_done.info".localized
|
||||
message: "alert.fix_my_valet_done.title".localized,
|
||||
info: "alert.fix_my_valet_done.info".localized
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -281,49 +311,23 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
||||
@objc func switchToPhpVersion(_ version: String) {
|
||||
setBusyImage()
|
||||
PhpEnv.shared.isBusy = true
|
||||
PhpEnv.shared.delegate = self
|
||||
PhpEnv.shared.delegate?.switcherDidStartSwitching(to: version)
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
// Update the PHP version in the status bar
|
||||
updatePhpVersionInStatusBar()
|
||||
|
||||
// Update the menu
|
||||
rebuild()
|
||||
|
||||
let completion = {
|
||||
PhpEnv.shared.delegate?.switcherDidCompleteSwitch()
|
||||
|
||||
// Mark as no longer busy
|
||||
PhpEnv.shared.isBusy = false
|
||||
|
||||
// Perform UI updates on main thread
|
||||
DispatchQueue.main.async { [self] in
|
||||
updatePhpVersionInStatusBar()
|
||||
rebuild()
|
||||
|
||||
let sendLocalNotification = {
|
||||
LocalNotification.send(
|
||||
title: String(format: "notification.version_changed_title".localized, version),
|
||||
subtitle: String(format: "notification.version_changed_desc".localized, version)
|
||||
)
|
||||
PhpEnv.phpInstall.notifyAboutBrokenPhpFpm()
|
||||
}
|
||||
|
||||
// Run composer updates
|
||||
if Preferences.isEnabled(.autoComposerGlobalUpdateAfterSwitch) {
|
||||
self.updateGlobalDependencies(notify: false, completion: { _ in sendLocalNotification() })
|
||||
} else {
|
||||
sendLocalNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PhpEnv.switcher.performSwitch(
|
||||
to: version,
|
||||
completion: completion
|
||||
completion: {
|
||||
PhpEnv.shared.delegate?.switcherDidCompleteSwitch(to: version)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Menu Item Functionality
|
||||
|
||||
@objc func openAbout() {
|
||||
NSApplication.shared.activate(ignoringOtherApps: true)
|
||||
NSApplication.shared.orderFrontStandardAboutPanel()
|
||||
@@ -337,6 +341,10 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
||||
SiteListVC.show()
|
||||
}
|
||||
|
||||
@objc func openDonate() {
|
||||
NSWorkspace.shared.open(Constants.DonationUrl)
|
||||
}
|
||||
|
||||
@objc func terminateApp() {
|
||||
NSApplication.shared.terminate(nil)
|
||||
}
|
||||
@@ -352,85 +360,4 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
||||
// When the menu is closed, allow the shortcut to work again
|
||||
App.shared.shortcutHotkey?.isPaused = false
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
|
||||
/**
|
||||
|
||||
*/
|
||||
private func updateGlobalDependencies(notify: Bool, completion: @escaping (Bool) -> Void) {
|
||||
PhpEnv.shared.isBusy = true
|
||||
setBusyImage()
|
||||
self.rebuild()
|
||||
|
||||
let noLongerBusy = {
|
||||
PhpEnv.shared.isBusy = false
|
||||
DispatchQueue.main.async { [self] in
|
||||
self.updatePhpVersionInStatusBar()
|
||||
self.rebuild()
|
||||
}
|
||||
}
|
||||
|
||||
var window: ProgressWindowController? = ProgressWindowController.display(
|
||||
title: "alert.composer_progress.title".localized,
|
||||
description: "alert.composer_progress.info".localized
|
||||
)
|
||||
window?.setType(info: true)
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
let output = Shell.user.executeSynchronously(
|
||||
"composer global update", requiresPath: true
|
||||
)
|
||||
|
||||
let task = Shell.user.createTask(for: "composer global update", requiresPath: true)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
window?.addToConsole("composer global update\n")
|
||||
}
|
||||
|
||||
Shell.captureOutput(
|
||||
task,
|
||||
didReceiveStdOutData: { string in
|
||||
DispatchQueue.main.async {
|
||||
window?.addToConsole(string)
|
||||
}
|
||||
Log.perf("\(string.trimmingCharacters(in: .newlines))")
|
||||
},
|
||||
didReceiveStdErrData: { string in
|
||||
DispatchQueue.main.async {
|
||||
window?.addToConsole(string)
|
||||
}
|
||||
Log.perf("\(string.trimmingCharacters(in: .newlines))")
|
||||
}
|
||||
)
|
||||
|
||||
task.launch()
|
||||
task.waitUntilExit()
|
||||
Shell.haltCapturingOutput(task)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if output.task.terminationStatus <= 0 {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
||||
window?.close()
|
||||
if (notify) {
|
||||
LocalNotification.send(
|
||||
title: "alert.composer_success.title".localized,
|
||||
subtitle: "alert.composer_success.info".localized
|
||||
)
|
||||
}
|
||||
window = nil
|
||||
noLongerBusy()
|
||||
completion(true)
|
||||
}
|
||||
} else {
|
||||
window?.setType(info: false)
|
||||
window?.progressView?.labelTitle.stringValue = "alert.composer_failure.title".localized
|
||||
window?.progressView?.labelDescription.stringValue = "alert.composer_failure.info".localized
|
||||
window = nil
|
||||
noLongerBusy()
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|