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

Compare commits

...

396 Commits
v1.1 ... v5.0

Author SHA1 Message Date
94da7fb255 🚀 Version 5.0
This release brings a plethora of new features to PHP Monitor.
Please check out the release notes for more details.

🔀 Merge branch 'dev/5.x'
2022-02-01 19:59:56 +01:00
46d2d35c1a 👌 Detect Drupal and WordPress projects (#112) 2022-02-01 18:36:37 +01:00
cba41b29bc 🐛 Fix storyboard issues 2022-02-01 17:58:06 +01:00
7a8f47b995 👌 Quality of life changes
- Moved DonationUrl to Constants
- Added additional menu items (visible if window is open)
- Fixed capitalisation of "WordPress" in PhpFrameworks
- Cleanup Stats
- Add new translation strings for menu items
2022-02-01 17:51:23 +01:00
40062c5091 🔧 Bump version number for new RC 2022-01-31 18:17:15 +01:00
6081ef6b02 👌 Verify switch succeeded (#111)
- Verify switch was successful
- Suggest "Fix My Valet"
- Restart nginx when switching PHP versions
2022-01-31 17:56:20 +01:00
4cffe5a662 👌 Rename "Force Load PHP" to "Fix My Valet" (#111) 2022-01-31 12:41:41 +01:00
813aec2b42 👌 Disable message in beta builds 2022-01-30 19:32:32 +01:00
f2452bbc70 👌 Keep track of times (successfully) switched
Some users might not reboot their computer and in that situation they
will never see the message in bba961269c.

This has been remedied by also checking how many times the version
switch has occurred.

The thresholds for the alert are now:

- Must have launched the app at least 7 times
OR
- Must have switched PHP versions at least 40 times

If the alert has been seen, it'll never be shown again. For more info
please consult the linked commit for the rationale behind this change.
2022-01-30 19:27:44 +01:00
28fb685bfc 🐛 Fix refreshing of PHP version 2022-01-30 00:40:11 +01:00
0661ca00ff 📝 Update README and SECURITY 2022-01-29 23:03:19 +01:00
0b04619003 ✏️ Update TODO for 5.1 2022-01-29 22:49:30 +01:00
85d7b6aa57 🔧 Switch to regular version (release candidate) 2022-01-29 21:36:55 +01:00
bba961269c Add successful launch count, sponsor alert
Okay, so this commit adds a sponsor alert. I wanted to elaborate.

Why? At this point I've invested so much of my free time in the app that
any and all donations would be incredibly welcome. Of course, phpmon
as it exists today must always remain free and open source.

(I dislike it when an app goes open source and then becomes paid.)

Obviously, I don't want to take useful features away from users:

1) usage of the old version is the only option for those who won't pay
2) piracy is an alternative and I don't want to deal with that
3) the positive sentiment around the app disappears ("sellout!")

Instead, I will nicely ask for donations once the app has been
successfully launched 7 times or more. This alert should only
appear once.

Fun fact: PHP Monitor started  as a single menu item with only
options to switch between version numbers.

Thanks to all the support, it has now become so much more.

To those who have already contributed: thank you very much.
I hope you continue to use and enjoy the app.

Cheers!
2022-01-29 21:29:51 +01:00
d0f7d2c5e9 Handle >= and > constraints 2022-01-29 19:10:17 +01:00
eeeb3eb184 👌 Tweak strings to be completely accurate 2022-01-29 18:05:26 +01:00
f00f8d26f6 🐛 Enforce readable Valet version 2022-01-29 17:46:56 +01:00
74817beec6 🐛 Fix First Aid not working 2022-01-29 17:46:37 +01:00
7b6809245c 🐛 loopback might not exist (#104) 2022-01-29 17:05:00 +01:00
5b40a8fd41 👌 Updated goals, new asset, SwiftUI integration 2022-01-29 14:15:39 +01:00
193f459be1 ✏️ New comments 2022-01-29 14:13:38 +01:00
c4c19a5b47 📝 Updated README, new promo shot 2022-01-29 14:13:05 +01:00
7d103c70e7 📝 Updated README 2022-01-29 13:22:47 +01:00
2ffe90948e Add preference to disable integrations
I like the idea of the exposed phpmon:// protocol, but for those who
care about security it should be possible to disable the integrations.
2022-01-29 12:52:33 +01:00
8e61aaacde 🔧 Bump build 2022-01-29 00:12:38 +01:00
29c8fcbde2 👌 Force composer from /usr/local/bin (#102) 2022-01-29 00:11:12 +01:00
8dd21f46aa 🍱 Fix colors for dark mode (#101) 2022-01-28 23:40:00 +01:00
e688dde2aa 👌 Add example Alfred workflow 2022-01-28 22:12:35 +01:00
987e1e1bdb 🔧 Bump version number 2022-01-28 22:06:53 +01:00
510257c436 👌 Complete work on inter app handler
Allowed commands:

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}
2022-01-28 22:05:53 +01:00
bb1572f32a Allow switching PHP versions via callback 2022-01-28 17:42:40 +01:00
45276034b1 Add initial start for scheme integration 2022-01-28 17:01:40 +01:00
0d4a144524 👌 Cleanup HomebrewDiagnostics 2022-01-28 16:42:46 +01:00
a0e5102ca7 👌 Add some comments for curious code readers 2022-01-28 16:30:07 +01:00
69c0f5ace9 Have all tests pass, refactor comparison logic 2022-01-27 19:39:02 +01:00
d0962c2387 🐛 Show question mark if service not found 2022-01-27 18:23:12 +01:00
4670894cfd 👌 Driver not detected (localised) 2022-01-27 01:15:54 +01:00
a2f6c70a03 🔀 Merge branch 'dev/4.x' 2022-01-27 00:46:12 +01:00
ef469868d8 📝 Update README (no more Valet switcher in 5.0) 2022-01-27 00:44:36 +01:00
c9ba872529 ️ Fix laggy scrolling and search
(Partial backport for the stable build.)
2022-01-27 00:43:08 +01:00
1e15042be2 🐛 Start a "clean" terminal every time (#99)
(Backported for the stable build.)
2022-01-27 00:34:54 +01:00
7647978da5 🐛 Start a "clean" terminal every time (#99) 2022-01-26 23:49:32 +01:00
f82f3052f2 Add Flarum to framework list (#95) 2022-01-26 21:56:27 +01:00
10b299ff65 🐛 Check if services command can run 2022-01-26 21:00:52 +01:00
e4ff0418fd ️ Faster search, faster scrolling 2022-01-26 20:31:37 +01:00
a2b25e31ca 👌 Delayed loading of config.json 2022-01-26 19:47:00 +01:00
c4772db808 👌 Determine "driver" by reading composer file
This is much faster than checking the actual driver, which might take
a while if you have many sites. If we're just checking the actual
composer file (which is already parsed) this should be much faster.
2022-01-26 19:06:57 +01:00
b59a5d31a5 👌 Save window size and position for site list 2022-01-25 21:40:11 +01:00
e4b1f75c53 👌 Handle errors when adding moved folder 2022-01-25 21:02:21 +01:00
338a87d503 👌 Show what prevents creation of link
- Site name already exists?
- Site name empty?
2022-01-25 18:50:35 +01:00
0f0e91273e 👌 Localisation improvements 2022-01-25 18:28:10 +01:00
20959501c9 👌 Fix flickering of incorrect data on first load 2022-01-25 00:09:50 +01:00
aeeecd6996 👌 Suggestions should also check all constraints 2022-01-25 00:09:36 +01:00
e9ae989200 👌 Check multiple constraints (e.g. "^7.3|^8.0") 2022-01-24 23:56:26 +01:00
f6378e7b73 Add option to add a linked site 2022-01-24 23:42:22 +01:00
d7e8652f5f 👌 Populate new ServicesView with stale data
This means that the user cannot tell we swapped out the view for another
view. The services are re-fetched upon creating the new view, but there
is a slight delay. This change conveniently "hides" this delay.

BEFORE
- Upon creating ServicesView2, ServicesView is deinitialized
- ServicesView2 shows question marks (no services data persisted)
- ServicesView2 async loads services, when done question marks removed

AFTER
- Upon creating ServicesView2, ServicesView is deinitialized
- ServicesView2 loads stale data (services data was persisted)
- ServicesView2 async loads services, when done stale data replaced
2022-01-24 23:41:47 +01:00
5293c437d1 🔧 Prepare for 5.0 beta 1 2022-01-24 01:22:13 +01:00
f75bfc9c4a Initial version of #72 2022-01-24 01:16:17 +01:00
42fc0e3698 🔥 Due to closing #34, removed switcher pref 2022-01-23 20:06:20 +01:00
626b7a735d 👌 Checkmark on site list 2022-01-23 19:37:10 +01:00
567373f8da Version constraint checks (#84)
The version constraint checks will also be used in the future to
evaluate whether any given site's PHP constraint (if set) is
valid for the currently linked version of PHP.

For example, assuming you have PHP 8.1.2 linked, we could evaluate:

* A site requires "8.0" -> invalid
* A site requires "^8.0" -> valid
* A site requires "^8.0.0" -> valid
* A site requires "~8.0" -> valid
* A site requires "~8.0.0" -> invalid

Currently, this constraint check is used to determine which versions
that are currently installed are good suggestions to switch to.

If you have a site with constraint "^8.0" for example, and you have
PHP 8.0 and 8.1 installed (with 8.1 linked), then you will get a
suggestion to switch back to 8.0.
2022-01-23 03:59:29 +01:00
32e8878a68 🏗 WIP: UI for switch to version options (#84) 2022-01-22 22:08:45 +01:00
46005a3c68 👌 Load notable dependencies (incl. laravel, #80) 2022-01-22 21:35:32 +01:00
03a409281a 👌 Sort site list by absolute path (#81) 2022-01-22 20:54:59 +01:00
e0dd778bb3 👌 Add debounce to site search (#82) 2022-01-22 20:50:17 +01:00
c3f8a53ac3 Updated preferences (added option to disable PHP hint next to icon)
- Only works with dynamic icon enabled
- Preference defaults to true on new or existing installs
  (because we want to display PHP next to the version number by default)

For those who love a minimal menu bar setup but still want to see what
PHP version is currently enabled, this is perfect.
2022-01-16 13:14:54 +01:00
d8579bd7d1 🐛 Fix incorrect change in AppDelegate 2022-01-11 21:30:18 +01:00
d2cd567fd2 Filter only needed services (#72) 2022-01-11 21:22:17 +01:00
a5212b436e Tweaked test copy (#72) 2022-01-11 21:22:17 +01:00
b16250c2be Added tests for Homebrew service parsing (#72) 2022-01-11 21:22:17 +01:00
3b4a1a0654 Enable parsing of Homebrew services JSON (#72) 2022-01-11 21:22:17 +01:00
9ab6231337 📝 PR template 2022-01-04 20:50:06 +01:00
38c2d9131b 📝 PR template 2022-01-04 20:49:57 +01:00
1566323fca 📝 PR template 2022-01-04 20:49:44 +01:00
dc44538a7b 👌 Adjust preference description (see also #78) 2022-01-04 20:17:26 +01:00
bf0a923eb2 👌 Add more detail to full PHP version setting name (#78) 2022-01-04 19:41:01 +01:00
04bf5a3251 📝 Updated README 2022-01-04 02:56:00 +01:00
23f3204fa8 📝 Phrasing fix 2022-01-04 02:52:33 +01:00
6dc74e94aa 📝 Document functionality in README 2022-01-04 02:51:33 +01:00
422aefe831 Read composer.json for version requirement 2022-01-04 02:33:53 +01:00
3c3a0c8b45 ♻️ Rework loading of custom preferences 2022-01-04 00:32:30 +01:00
0cdeeec0a4 ♻️ Load the custom preferences file (#73) 2022-01-04 00:30:27 +01:00
e4c3e78a8a 📝 Adjust README 2022-01-04 00:21:52 +01:00
7f320897be 👌 Make user-supplied apps available (#73) 2022-01-04 00:20:47 +01:00
9ef184331e 🐛 Fix issue with Valet path 2022-01-03 17:08:45 +01:00
e372480249 🐛 Fix issue with Valet precedence (#77) 2022-01-03 17:01:51 +01:00
3bca3117f9 Load custom preferences file 2022-01-03 16:49:50 +01:00
722e082526 👌 Rename method from update to rebuild 2022-01-03 16:20:53 +01:00
40a0bd6cab 👌 Prevent crash when refresh and switcher run at same time 2022-01-03 16:19:18 +01:00
78510ea3fe 👌 Even more cleanup 2022-01-03 16:09:49 +01:00
8624573e74 👌 Cleanup 2022-01-03 16:02:18 +01:00
dd251936b9 ♻️ Refactor PhpSwitcher into PhpEnv 2021-12-24 16:09:51 +01:00
c647aee8ea 🔀 Merge branch 'main' into dev/5.x 2021-12-23 20:19:21 +01:00
b7766aeec2 👌 Improve UI and warn about spaces in folder names 2021-12-23 20:09:23 +01:00
5af1f09ee1 🐛 Ensure app can handle interactions with path w/ spaces (#74) 2021-12-23 13:27:19 +01:00
6646ceda76 🐛 Fix preloaded site logic 2021-12-23 12:52:10 +01:00
0b05bb44a2 🐛 Fix initialization of Site objects (#74) 2021-12-23 12:29:12 +01:00
1fbb1a8aa8 🔀 Merge branch 'main' into dev/5.x 2021-12-23 00:16:19 +01:00
8cb2074d76 🔀 Merge branch 'dev/4.x' 2021-12-22 23:49:33 +01:00
c408d62118 📝 Final README adjustment for 4.1 2021-12-22 23:46:29 +01:00
665bba86dd 🔧 Tweak default verbosity 2021-12-22 16:55:32 +01:00
0d29fbf796 Add phpmon-cli fix command 2021-12-22 16:54:27 +01:00
69042042ea 👌 Cleanup 2021-12-21 18:00:07 +01:00
63f4f8b078 Added prototype binary to switch quickly 2021-12-21 17:52:13 +01:00
e76c6e14e4 ♻️ Added logger class 2021-12-21 17:06:03 +01:00
ceb168c6cf ♻️ Rework various common classes 2021-12-21 16:00:27 +01:00
a6387e96e7 ♻️ Separate some of the PHP config logic from the app 2021-12-21 15:30:50 +01:00
2dbf775ad6 🔥 Remove Swift Package for common data 2021-12-20 19:10:58 +01:00
acdcce7f7a Add scheme to support inter-app communication (#59) 2021-12-20 18:39:59 +01:00
7a3dc9a145 👌 Fix incorrect main.swift file 2021-12-20 18:31:16 +01:00
1ca49f6cbc ♻️ Reorganise code for optimal code sharing, add phpmon-cli
- Moved over common functionality to package
- Added phpmon-cli target (for fast switching via the terminal)
2021-12-20 18:25:52 +01:00
fa2de1f77c 🔀 Merge in changes from SwiftUI previews branch 2021-12-20 17:20:43 +01:00
fe695bb026 👌 Nicer logging of multiple paths 2021-12-19 14:16:49 +01:00
f82a3bb008 🔀 Merge in remaining changes from 4.x 2021-12-19 14:14:43 +01:00
e7df254dcc Make sure that watchers reload if the .conf.d dir contents change 2021-12-19 14:11:25 +01:00
ea9538f116 Ensure watcher does not fire too many times 2021-12-19 13:58:25 +01:00
0d75e4c3b2 🔀 Merge WIP changes from feature/config-watcher into 5.x 2021-12-19 12:56:16 +01:00
a90703e525 🔥 Remove unneeded icon 2021-12-19 12:46:35 +01:00
f74f9f69b2 📝 Update README 2021-12-19 12:42:39 +01:00
a950587e84 📝 Prepare for release 2021-12-19 12:29:20 +01:00
267a1dac94 👌 Various QoL improvements
- Ensure composer global update cannot run twice (#71)
- Set busy status when updating dependencies (#71)
- Further reorganized menu items (#69)
- Use consistent capitals in menu items
- Fix preferences screen layout (auto newlines due to fixed width)
2021-12-19 12:27:34 +01:00
ed49362291 Add option to automatically run composer global update 2021-12-18 19:45:50 +01:00
3f0f070245 👌 Clear out extra newlines 2021-12-18 16:17:59 +01:00
bd79f42e96 👌 Cleanup 2021-12-18 16:06:39 +01:00
35ae681c2d ♻️ Rework how output is handled 2021-12-18 15:53:04 +01:00
313e806414 Added menu item to run composer global update 2021-12-17 16:10:45 +01:00
a8dc366038 🔧 Bump build and RC version 2021-12-17 13:16:31 +01:00
eaf653e3c0 🔧 Disable valet switching for next release (#34) 2021-12-17 13:11:22 +01:00
5c391917d2 ♻️ Rework preferences 2021-12-17 13:02:08 +01:00
09b5aa7f93 📝 Updated promo image 2021-12-16 23:01:54 +01:00
66a8c17f1f 👌 Tweak order of menu items (#69) 2021-12-16 22:57:37 +01:00
adc31984a8 👌 Tweak order of operations to speed up boot 2021-12-16 18:56:51 +01:00
8114eef381 🔧 Bump build version 2021-12-16 01:49:42 +01:00
9190420c66 📝 Add Valet upgrade instructions 2021-12-16 01:47:22 +01:00
e5ba074936 Check Valet version and compare to recommended version
As part of the boot procedure, recommend upgrading Valet if the version
seems to be too old. For version 4.1 of PHP Monitor, the version has
been hard-coded to 2.16.2 (for PHP 8.1 compatibility).
2021-12-16 01:44:43 +01:00
e4f1efe26a 👌 Updated hotkeys and screenshot 2021-12-16 00:22:02 +01:00
498f4e7b79 👌 Polished context menu order and code 2021-12-10 19:39:08 +01:00
d9a526e828 🔥 Cleanup empty line 2021-12-10 18:06:55 +01:00
2f93b4980b 📝 Update application FAQ 2021-12-10 18:05:45 +01:00
ac0ca06d7f 🔧 Use production icon for RC 2021-12-10 17:42:49 +01:00
17320a19cf 📝 New promo shot (more detected apps) 2021-12-10 17:42:17 +01:00
3faa251216 👌 Fix launching apps with spaces in name, add window position (#68) 2021-12-10 17:31:26 +01:00
a9f140fabc ♻️ Change app detection, detect apps upfront 2021-12-10 17:10:36 +01:00
b6b5a94bbd ♻️ Change app detection 2021-12-10 12:42:06 +01:00
c05f0fe5cb 📝 Updated FAQ 2021-12-09 19:53:51 +01:00
eaf1423fb1 ️ Performance fixes
- Avoid preloading list of sites twice
- Avoid loading Valet info twice
- Preload list of sites if <= 10 sites linked + parked
- Added fallback for missing instructions
- Improved description
2021-12-09 19:49:16 +01:00
7feb13856d Ensure editor binaries exist or notify user (#67) 2021-12-09 19:39:58 +01:00
ca2ca9df3b 📝 Updated README with sponsor link 2021-12-08 14:15:56 +01:00
4259915ff6 📝 Updated README with sponsor link 2021-12-08 12:23:21 +01:00
89e7a9b1ea 🐛 Detect missing core php formula (#66)
In rare cases, the version corresponding to the `php` formula might not
be installed, but another alias is linked correctly. That means that the
PHP binary is found, but the core formula is not. PHP Monitor will
incorrectly report that it exists, which means the user can break their
PHP installation. Oops.

This quick fix handles that situation:

- Checks if the PHP binary for the `php` aliased version exists
  (located in $optdir/php/bin/php)
- Disables the force load option if the aliased version is missing
  (including a tweaked label if the version is missing)
2021-12-08 11:12:44 +01:00
8c25d23d09 📝 Promo images 2021-12-07 22:12:19 +01:00
f44811b9dc Add icon next to PHP version (#64) 2021-12-07 22:09:02 +01:00
f65fd513f2 ️ Only load sites when view opens for the first time 2021-12-07 20:04:19 +01:00
327c88a745 Allow unlinking of sites 2021-12-07 19:54:21 +01:00
63aa8c2f44 👌 Information density, open URL on double click (#65) 2021-12-07 19:16:47 +01:00
afbfc55088 👌 Information density (#65) 2021-12-07 17:45:21 +01:00
d13714c1ea ♻️ Improved code editor detection (#60)
Now correctly detects the following apps that can open a directory:

- PhpStorm (installed via Toolbox)
- Sublime Text
- Sublime Merge

This in addition to:

- PhpStorm (manual installation)
- Visual Studio Code

These need to be installed in the default location.
For VS Code to work, you need to have added `code` to your PATH.
2021-12-07 12:42:20 +01:00
92a6d506dc ♻️ Cleanup, updated target for tests 2021-12-06 17:19:14 +01:00
e381880675 🐛 Prevent #61 from crashing the app 2021-12-06 13:24:03 +01:00
7e185154ef 👌 Bump project deployment target 2021-12-06 13:17:10 +01:00
27c25378b1 Add preference to use internal switcher (#62) 2021-12-06 13:15:57 +01:00
1159a6cc2e 🐛 Prevent #61 from crashing the app 2021-12-06 12:29:00 +01:00
489bf13707 🚀 Prepare beta 3 2021-12-05 15:20:35 +01:00
5d3faceb5a 🔧 Bump version number, use regular icon 2021-12-05 15:09:43 +01:00
29d34a6b62 👌 Polish for v5.0 2021-12-05 15:08:43 +01:00
be80d74141 ♻️ Cleanup and comments 2021-12-05 14:31:49 +01:00
d37e86ce2c 👌 Fix busy status and fix notifications when app is active 2021-12-05 04:29:05 +01:00
d8fc857d23 👌 Show spinner when busy 2021-12-05 04:14:55 +01:00
e0bec333ed Improve multi-window handling 2021-12-05 03:53:30 +01:00
46867ad25e ♻️ Require at least macOS 11, various refactors 2021-12-05 02:54:03 +01:00
924edf6f96 👌 Quality of life changes, reload button 2021-12-04 21:26:47 +01:00
010c8eddde Secure and unsecure, new search bar (#58) 2021-12-04 20:42:45 +01:00
96602b1a9c 👌 Cleanup 2021-12-03 21:55:45 +01:00
d536499799 👌 Update minimal size of site list 2021-12-03 21:53:29 +01:00
ad016c54b2 ♻️ Reorganize menu 2021-12-03 21:50:32 +01:00
f8b0b38e9e 👌 Add menu items so common shortcuts work again 2021-12-03 21:44:32 +01:00
912e549104 🚀 Prepare beta 2 2021-12-03 21:36:48 +01:00
93bdb0ed7f Add search functionality 2021-12-03 21:25:10 +01:00
87713bbe64 Add options to open site with Code / PhpStorm (#58) 2021-12-03 20:55:38 +01:00
dce27059ff 🏗 WIP: Ensure the right-click menu works correctly 2021-12-03 19:49:03 +01:00
c919326480 🏗 WIP: Linked & parked sites UI (#58) 2021-12-03 19:44:21 +01:00
1e124a90f3 🏗 WIP: Linked & parked sites UI (#58) 2021-12-03 18:41:41 +01:00
d1fc9de4bd Detect default site (default key in JSON) (#58)
Now supports: https://laravel.com/docs/8.x/valet#serving-a-default-site
2021-12-03 01:56:08 +01:00
04db3f50ed 🏗 WIP: Detect linked and parked sites (#58) 2021-12-03 01:46:25 +01:00
4e347adf69 🚀 Prepare beta 1 2021-11-29 18:33:20 +01:00
79de14c9aa 📝 Clarify previous behaviour 2021-11-29 18:33:20 +01:00
7448e89965 📝 Update README 2021-11-29 18:33:20 +01:00
967743715b ♻️ Cleanup 2021-11-29 18:33:20 +01:00
d37913005b Read Valet configuration (#58) 2021-11-29 18:33:20 +01:00
0986b97051 ♻️ Add valet command (#34), read Valet configuration (#58) 2021-11-29 18:33:20 +01:00
bbb04f7907 📝 Updated support document 2021-11-29 18:32:25 +01:00
015f406ddf 📝 Update SECURITY.md 2021-11-28 15:38:07 +01:00
e1a97672b5 🔧 Bump build, reorganize files 2021-11-28 15:11:00 +01:00
493b5945f9 👌 Major changes to PHP version detection
* The information extracted from Homebrew's JSON command now also
  includes information about linked keg and installations.

* The mapped versions in the App class now contain information about
  the Homebrew installation as well.

* A HomebrewDiagnostics class has been added, which is currently able
  to detect conflicts between the `php` formulae of core and the
  `shivammathur/php` tap (which is currently an issue, see #54)

* Alerts are now displayed as critical if they are truly problematic.

* PhpInstallation was renamed to ActivePhpInstallation, to make room
  for a generic PhpInstallation object which contains cached info.

* Shell.pipe() now returns the contents of standardError if
  standardOutput was empty and there was some data in standardError.
  This makes it easier to debug the output of commands that output to
  standardError. (For example, failed brew commands might.)
2021-11-28 02:20:56 +01:00
52606aae8b 👌 Calling detectPhpVersions always immediately caches the info 2021-11-25 18:49:14 +01:00
2d6ca0f841 Also show full PHP version in dropdown (#53) 2021-11-25 18:41:21 +01:00
34900f929f Use gsed so we can follow symlinks to .ini files (#39, #47) 2021-11-13 21:18:01 +01:00
5dbd05fdfb Add option to auto-restart services (#32) 2021-11-13 20:50:33 +01:00
fe3cf9adb1 Add option to view long PHP version in menu bar 2021-11-13 19:11:05 +01:00
9bc8460cce 👌 Updated notification for Monterey 2021-10-19 21:42:17 +02:00
4cbd2fd6eb 📝 Updated documentation 2021-10-19 00:03:36 +02:00
6fef3fe37a 📝 Updated SECURITY.md 2021-10-19 00:02:13 +02:00
72a20d1ed9 🍱 New build screenshot of Xcode 13.1 2021-10-18 23:54:32 +02:00
73ed80434a 📝 Update README to reflect Monterey compatibility 2021-10-18 23:49:55 +02:00
a78672927b Support for upcoming releases of PHP 8.1 and 8.2 (dev) 2021-10-18 23:48:57 +02:00
4256eae442 👌 CS 2021-10-18 18:48:40 +02:00
76412b68f3 👌 Tests have OS X 10.14 as deployment target too 2021-08-31 11:08:00 +02:00
9153bb140a 👌 Code style fixes (empty line before class closes) 2021-08-31 11:03:55 +02:00
c9c15d10f9 👌 Improve handling of global hotkey load on startup 2021-08-31 10:52:49 +02:00
e8c2277ef5 🐛 Omit initial space (if uncommented, in #45) 2021-06-07 19:18:13 +02:00
23720c5dc9 🐛 Fix #45: Adjusted regex to support spaces 2021-06-07 19:13:52 +02:00
f881f07cba 👌 Cleanup 2021-05-07 15:29:47 +02:00
b072ee8dec 🚚 Improved project organisation, updated README 2021-05-03 16:52:51 +02:00
acfbc0b66f 👌 Clean up how file checks are done 2021-04-27 17:00:56 +02:00
c738a03934 📝 Update README & screenshot 2021-04-20 17:05:15 +02:00
84d62f3583 Ensure all tests pass 2021-04-20 17:05:05 +02:00
f9faa03b92 #41: Notify about broken PHP-FPM configuration 2021-04-19 20:30:37 +02:00
55f6c3c6cd Add "Locate global composer.json file" 2021-04-19 13:14:34 +02:00
a0c6753761 🐛 Use php_ini_scanned_files for .ini scan
Using `php-config --ini-dir` seems to fail on PHP 7.2 and below, likely
because said option was not available in these earlier versions. Because
all we need are the additional .ini files, calling php_ini_scanned_files
is a better solution since it is supported from PHP 5 and up.

This commit fixes the crash issue that was caused by running the failing
`php-config` command.

More information: https://www.php.net/manual/en/function.php-ini-scanned-files.php
2021-04-19 10:26:14 +02:00
327125608a 👌 Polish preferences screen 2021-04-15 23:16:42 +02:00
6c0045302b 📝 Updated README 2021-04-14 20:13:19 +02:00
9c85bebe72 Add option to turn all services off (#35) 2021-04-14 20:08:50 +02:00
fb56cd551e 👌 Improved parallelization 2021-04-14 19:40:07 +02:00
e83d507e79 Parallelize unlinking PHP versions 2021-04-14 18:46:33 +02:00
0c0e7fc87d Extension loading improvements (#31) and more
* The README has been updated with additional information
* The acknowledgements section has been added to the README
* The php@X.X/opt/bin/php-config binary is now used (#39)
* Extensions are now loaded from all possible .ini files
* PHP Monitor's preferences window can now be triggered via hotkey
* The first nine extensions can be triggered via hotkey
2021-04-07 16:58:05 +02:00
faf49fbe1d 👌 Prevent hotkey multi-fire (#33) 2021-04-02 18:32:07 +02:00
2925b0ff79 🏗 WIP: Global shortcut key (#33) 2021-04-02 17:50:46 +02:00
acb18474c8 👌 Fine-tuning the README 2021-04-01 21:17:35 +02:00
ed61490398 📝 Update README 2021-04-01 21:14:03 +02:00
abb76273c9 📝 New screenshot 2021-04-01 20:56:28 +02:00
2f15af4ff8 Improved test 2021-04-01 20:44:53 +02:00
e29e8416d5 Fix tests, add test for version detection 2021-04-01 20:38:24 +02:00
5d423210dd 👌 Improved PHP version filter 2021-04-01 20:19:55 +02:00
340c36fdf8 🔧 Bump version & build number 2021-04-01 00:18:54 +02:00
3085158b80 🐛 Validate detected PHP version (#30) 2021-04-01 00:18:33 +02:00
c2585f9bf4 👌 Cleanup, add separator before refresh item 2021-03-31 23:57:17 +02:00
d478137742 🔧 Bump version & build number 2021-03-31 19:50:38 +02:00
827bd182b1 🐛 Avoid duplicates (#30) 2021-03-31 19:38:30 +02:00
f7500637fe 🐛 Be more lenient about Valet checks (#27) 2021-03-31 12:21:06 +02:00
7c884610b1 👌 Use snake case for all key names 2021-03-30 16:38:09 +02:00
d3d219751e 🍱 Updated layout constraints 2021-03-30 16:35:13 +02:00
16d2e7d06f Add preferences dialog to enable static icon (#25) 2021-03-30 16:31:28 +02:00
47b86ff9fa 🚩 New prerelease build 2021-03-29 22:29:44 +02:00
6e574b9154 👌 Make sure 'refresh' reopens the menu afterwards (#24) 2021-03-29 22:24:15 +02:00
485001403d Add refresh button (#24)
By pressing Command-R while the menu is open, the information about the
current PHP installation will be refreshed. 

You'll need to open the menu again, but now it will contain up-to-date 
information. You could also just press the menu item, of course.
2021-03-29 21:50:40 +02:00
694c5e7f7d Add credits 2021-03-29 20:49:55 +02:00
d9ff26385a 🍱 Optimized assets 2021-03-29 20:04:08 +02:00
0cfb7c65bb 👌 Avoid self 2021-03-19 16:15:05 +01:00
3cbc2a0367 👌 Avoid self. (and capture using [self] in) 2021-03-19 16:01:03 +01:00
7733c90206 👌 Whitespace, remove print() 2021-03-19 15:50:43 +01:00
0ad6e5cb1c 👌 Multiple trailing closures 2021-03-19 15:44:26 +01:00
8a47cfd5f1 🐛 Remove which valet check 2021-03-17 14:50:26 +01:00
db4c0399fd 📝 Update README 2021-03-12 12:26:55 +01:00
b0f29b72cd 💰 Add funding option to GitHub 2021-03-12 12:24:57 +01:00
98a1622ccc 🔀 Merge branch 'develop'
* develop:
  💰 Add funding option to GitHub
  📝 Update SECURITY
2021-03-12 12:10:12 +01:00
a6ac737590 💰 Add funding option to GitHub 2021-03-12 12:09:49 +01:00
bba719012f 📝 Update SECURITY 2021-03-12 10:01:46 +01:00
cdc0cea01c 📝 Update SECURITY 2021-03-12 10:01:32 +01:00
d3a4f2091c 🚀 Version 3.1
* develop:
  📝 Tweak error message string
  🔧 Bump build version
  🐛 Handle `valet` install in `/opt/homebrew/bin`
  👌 Cleanup
  📝 Updated README
  🔧 Determine default shell based on macOS version
   Added tests
  🚚 Moved files around
  📝 Update copyright information
2021-03-12 09:54:40 +01:00
c7dc17dded 📝 Tweak error message string 2021-03-12 09:54:12 +01:00
d70f8627bb 🔧 Bump build version 2021-03-12 09:50:29 +01:00
7661f4643d 🐛 Handle valet install in /opt/homebrew/bin 2021-03-12 09:46:38 +01:00
11d74277e5 👌 Cleanup 2021-02-19 18:59:39 +01:00
e4096ab662 📝 Updated README 2021-02-19 17:35:48 +01:00
d367f1f353 🔧 Determine default shell based on macOS version
This fixes #21 (fixes the issue on Mojave) and closes #22 since the min
SDK does not need to be bumped.
2021-02-19 17:28:57 +01:00
79fbdef41b Added tests 2021-02-14 22:54:45 +01:00
37ede7e2a3 🚚 Moved files around 2021-02-14 22:54:38 +01:00
80eabe3bf3 📝 Update copyright information 2021-02-12 10:00:25 +01:00
aa07a42d8f 🚀 Version 3.0
* develop: (29 commits)
  🔧 Bump version for release
  👌 All methods are internal by default
  📝 Move all FAQ to README
  📝 Reorganized document
  📝 Updated quick troubleshooting
  📝 New screenshot
  🔧 Bump build version
  🎨 Use Self to refer to current type
   Improved byte value parsing
  📝 Updated SECURITY
  📝 Updated README, new screenshot
  🔧 Bump build version
  🚚 Move files around
  🔥 Crash if the view is unavailable
  🎨 Fix threading issue
   Completed new design for v3.0
   Tweak custom views
   Custom views
  🎨 Updated localization, cleanup
  🔥 Cleanup
  ...
2021-02-12 09:47:28 +01:00
b6b3a9a74c 🔧 Bump version for release 2021-02-12 09:38:09 +01:00
ec4371e6da 👌 All methods are internal by default 2021-02-10 16:35:23 +01:00
c7a92d502f 📝 Move all FAQ to README 2021-02-07 11:52:53 +01:00
172a38e459 📝 Reorganized document 2021-02-05 10:59:55 +01:00
d18e8298b6 📝 Updated quick troubleshooting 2021-02-05 10:54:09 +01:00
df325474c6 📝 New screenshot 2021-02-05 10:27:34 +01:00
60278d7817 🔧 Bump build version 2021-02-05 10:11:15 +01:00
9e8117d31f 🎨 Use Self to refer to current type 2021-02-05 10:07:48 +01:00
3d042b24ed Improved byte value parsing 2021-02-05 09:56:02 +01:00
fcf3483dd0 📝 Updated SECURITY 2021-02-04 22:35:37 +01:00
3db333dd77 📝 Updated README, new screenshot 2021-02-04 22:32:46 +01:00
b0f0fd8c6b 🔧 Bump build version 2021-02-04 22:09:36 +01:00
69755cd8cc 🚚 Move files around 2021-02-04 22:07:09 +01:00
6b2fa0c605 🔥 Crash if the view is unavailable 2021-02-04 22:04:29 +01:00
dd41b5a1e0 🎨 Fix threading issue 2021-02-04 22:00:58 +01:00
a3fe09ed8a Completed new design for v3.0 2021-02-04 21:41:36 +01:00
ad81c4a3db Tweak custom views 2021-02-04 21:27:52 +01:00
572b0f69dd Custom views 2021-02-04 21:23:41 +01:00
c1b80552a0 🎨 Updated localization, cleanup 2021-02-03 22:09:53 +01:00
0493f61dc8 🔥 Cleanup 2021-02-03 19:39:49 +01:00
9824ed2b83 Load certain info from ini files 2021-02-03 18:01:25 +01:00
3e0a16cc73 📝 Document extension detection & toggling 2021-01-31 23:06:57 +01:00
5a50e7fdab Allow toggling of PHP extensions in php.ini 2021-01-31 22:50:39 +01:00
1dc85a4d48 🔧 Bump version and build 2021-01-29 20:48:49 +01:00
85ef3ff067 ♻️ Paths now uses static variables instead of methods 2021-01-29 20:39:55 +01:00
2958378411 ♻️ Add static methods to Shell 2021-01-29 20:36:33 +01:00
3a2cd3bff6 ♻️ Rework file checks & replacement into separate methods 2021-01-29 20:32:12 +01:00
fbe791902c 🎨 Update copyright, minor bracket cleanup 2021-01-29 20:08:27 +01:00
77f4de8ca9 🎨 Cleanup code 2021-01-29 19:57:13 +01:00
0f6e47a594 📝 Added build instructions 2021-01-09 13:46:45 +01:00
7408da2a91 ♻️ Assets now available as SVG 2021-01-09 13:38:32 +01:00
12a66f3467 📝 Updated Homebrew instructions 2021-01-06 18:09:16 +01:00
d3615138dd 🚀 Version 2.6
* develop:
  📝 Updated quick troubleshooting
  📝 Updated documentation
  📝 Updated SECURITY
  📝 Updated README
   New build number, updated shortcut keys
  🌐 Add info about `valet install` when running force reload
   Fix my PHP should also restart dnsmasq
   Add menu item to restart dnsmasq
  🔧 Bump build number
  ♻️ Cleanup, updated README (#20)
  ♻️ Refactoring, version bump for M1 support (#20)
   Add support for Homebrew in /opt/homebrew (#20)
  👌 Clean up switchToPhpVersion() and fixMyPhp()
  👌 Avoid using sender.tag
  👌 Improved pipe() method
  🌐 Localized startup environment checks
  🐛 Perform the phpinfo.html on another thread and show busy
  📝 Updated instructions
2021-01-06 17:51:29 +01:00
dd27b91527 📝 Updated quick troubleshooting 2021-01-06 17:49:26 +01:00
bc093cc945 📝 Updated documentation 2021-01-06 17:42:09 +01:00
fd46ce6b35 📝 Updated SECURITY 2021-01-06 17:29:24 +01:00
e35acadeaf 📝 Updated README 2021-01-06 17:18:59 +01:00
7b8aab85d6 New build number, updated shortcut keys
- Cmd-F now [F]orce reloads
- Cmd-S now restarts all [S]ervices
- Cmd-P now restarts the [P]hp service
- Cmd-D now restarts the [D]nsmasq service
2021-01-01 23:55:37 +01:00
ec59715b3d 🌐 Add info about valet install when running force reload 2021-01-01 23:49:51 +01:00
6f21913ae2 Fix my PHP should also restart dnsmasq 2021-01-01 23:48:12 +01:00
b53fbe471b Add menu item to restart dnsmasq 2021-01-01 23:19:31 +01:00
209f3e889d 🔧 Bump build number 2021-01-01 23:06:24 +01:00
5825e8d0b0 ♻️ Cleanup, updated README (#20) 2021-01-01 23:05:16 +01:00
4ea11c5f59 ♻️ Refactoring, version bump for M1 support (#20) 2021-01-01 22:58:27 +01:00
94f086881a Add support for Homebrew in /opt/homebrew (#20) 2021-01-01 22:54:03 +01:00
e73474e30c 👌 Clean up switchToPhpVersion() and fixMyPhp() 2020-12-19 18:38:00 +01:00
c7a0e25336 👌 Avoid using sender.tag 2020-12-13 19:32:43 +01:00
e353fb7524 👌 Improved pipe() method 2020-12-13 19:16:51 +01:00
458868d051 🌐 Localized startup environment checks 2020-12-08 23:18:52 +01:00
932fafc728 🐛 Perform the phpinfo.html on another thread and show busy 2020-11-27 17:01:30 +01:00
b70c4f690a 📝 Updated instructions 2020-11-27 16:40:37 +01:00
4147cc7b4b 🚀 Version 2.5 2020-11-27 16:32:42 +01:00
72b309a716 📝 Be more explicit in main README 2020-11-27 16:31:54 +01:00
12c2716715 📝 Add information about running valet install 2020-11-27 16:29:02 +01:00
4d4019204b 📝 Updated README with new screenshot 2020-11-27 16:24:06 +01:00
157033a3b3 🚀 Version 2.5 (RC) 2020-11-27 11:32:26 +01:00
717cddacdd 🐛 Fix issue with php install now showing up 2020-11-27 11:29:40 +01:00
f13ed5dd90 Add option to open output of phpinfo() in browser 2020-11-27 01:33:06 +01:00
a8f823cd04 📝 Update README.md, ADDITIONAL.md 2020-11-26 18:59:49 +01:00
51fd22f595 Cleanup storyboard, add Brew package name to switcher 2020-11-26 18:53:15 +01:00
bf6ebff3bf Detect what version of PHP the php package is linked to 2020-11-26 17:58:34 +01:00
1887b19329 🚧 WIP: Get JSON about current PHP version 2020-11-26 17:02:24 +01:00
a53972404c 🚀 Version 2.4
* develop:
  📝 Updated SECURITY to reflect new support status
  🔧 Updated build settings
  🔧 Add PHP 8.0 to list of detected PHP versions
  📝 Universal application
2020-11-14 15:23:19 +01:00
64a605235a 📝 Updated SECURITY to reflect new support status 2020-11-14 02:33:46 +01:00
a194ecdebe 🔧 Updated build settings 2020-11-14 02:20:26 +01:00
03158a568c 🔧 Add PHP 8.0 to list of detected PHP versions 2020-11-14 02:19:42 +01:00
bdc6be7384 📝 Universal application 2020-11-14 02:19:28 +01:00
4908dba57e 📝 Update support information 2020-08-16 16:45:42 +02:00
0f0aa176b6 📝 Update README 2020-08-16 16:43:08 +02:00
485729f9a5 🚀 Version 2.3
Merge branch 'develop' into main

* develop:
  📝 Updated README, SECURITY docs
  🍱 Updated screenshot to Big Sur
  ♻️ Cleanup
  🚧 WIP: Notify the user about PHP switch completion (#15)
2020-08-16 16:27:38 +02:00
5eb36a9bdf 📝 Updated README, SECURITY docs 2020-07-20 17:28:59 +02:00
86113d2067 🍱 Updated screenshot to Big Sur 2020-07-19 13:17:55 +02:00
70c04d4dc7 ♻️ Cleanup
- Bump version number to 2.3
- Disable Metal validation in project configuration
- Added comments to various files to clarify code
- Moved various strings to Localizable.strings
- Removed references to old view controller for command log
- No longer log previously ran commands
2020-07-16 23:16:39 +02:00
5d69b423c1 🚧 WIP: Notify the user about PHP switch completion (#15) 2020-07-16 22:25:51 +02:00
1617b57b1e 🚀 Version 2.2
Merge branch 'develop'

* develop:
  📝 Update README
  📝 Amend modal copy
  🐛 Improved startup procedure
  🚧 Add Internet Access Policy
   Add shared scheme
2020-07-16 19:37:21 +02:00
33825e7b66 📝 Update README 2020-07-16 19:34:38 +02:00
464b7106b2 📝 Amend modal copy 2020-07-16 19:29:01 +02:00
0cf85f9958 🐛 Improved startup procedure
- First issue encountered will notify the user
- User has the option to Retry or Terminate the app
- Retry will go through launch checks again
- Some issues are marked as non-breaking, meaning the user will be
  notified, but the app will just start as usual
2020-07-16 19:26:29 +02:00
00cf2bc360 🚧 Add Internet Access Policy 2020-07-11 11:18:31 +02:00
dbb5329908 Add shared scheme 2020-07-11 10:54:05 +02:00
9fd8e7042a 🚀 Version 2.1
Merge branch 'develop'

* develop:
  📝 Add installation instructions
  📝 Added docs for release procedure
  📝 Updated README
  🔧 Hardened runtime (required for notarization)
  🍱 Updated icon for macOS 11 style, updated alert
2020-07-11 10:41:11 +02:00
cb62a20f2a 📝 Add installation instructions 2020-07-11 10:40:02 +02:00
bb1742a390 📝 Added docs for release procedure 2020-07-11 10:05:47 +02:00
6db43beddf 📝 Updated README 2020-07-11 01:07:51 +02:00
7e2c7cdd59 🔧 Hardened runtime (required for notarization) 2020-07-11 01:00:31 +02:00
e76dd0daeb 🍱 Updated icon for macOS 11 style, updated alert 2020-07-11 00:49:32 +02:00
0c33e8f8cc 🎉 Version 2.0 2020-05-17 11:36:31 +02:00
a6a196518a 🔀 Merge branch 'develop'
* develop:
  📝 Changes to the README, bump version number
  ♻️ Menu refactor
  ♻️ Use Localizable.strings
  ♻️ Change default shell for commands, cleanup files
2020-05-17 00:22:40 +02:00
a287ebf6e4 📝 Changes to the README, bump version number 2020-05-17 00:17:45 +02:00
2200b395f1 Merge branch 'cleanup' into develop
* cleanup:
  ♻️ Menu refactor
  ♻️ Use Localizable.strings
  ♻️ Change default shell for commands, cleanup files
2020-05-16 23:56:33 +02:00
bc8a572072 ♻️ Menu refactor 2020-05-16 23:55:04 +02:00
1c455ea05a ♻️ Use Localizable.strings 2020-05-16 23:32:34 +02:00
f2d01748be ♻️ Change default shell for commands, cleanup files
The shell that is now invoked is /bin/sh as opposed to /bin/bash, which
has proven to be faster in various cases.
2020-05-16 23:06:52 +02:00
d3e59e560f New beta for version 1.9 2020-05-13 17:34:51 +02:00
55e849c21b Various fixes and improvements
- Prevent crashes with incorrectly loading modules
- Prevent crashes when dyld libraries are missing
- Indicate when the PHP installation is broken
- New warning added at boot (multiple services)
- Added "Force load latest PHP version" option

It is known that PHP 5.6, 7.0 and 7.1 are causing issues with the
newer versions of certain libraries ("dyld library" warning).

It is recommended to only use PHP 7.2, PHP 7.3 and PHP 7.4 for a minimal
amount of issues. Otherwise, there are certain fixes that are possible
but they are not supported via PHP Monitor since they require manual
formula changes.

For more information, see:
https://github.com/eXolnet/homebrew-deprecated/issues/23#issuecomment-619976586
2020-05-13 17:26:00 +02:00
50f6afb3c5 🔀 Merge branch 'develop'
* develop:
   Add option to restart nginx, open valet config
   Allow restarting of PHP-FPM
  📝 Update copyright message
2020-02-20 17:24:48 +01:00
67e80aac8d Add option to restart nginx, open valet config 2020-02-20 17:24:21 +01:00
e683b6bc9a Allow restarting of PHP-FPM 2020-02-20 16:12:38 +01:00
e854ebe114 📝 Update copyright message 2020-02-20 11:58:16 +01:00
b792f55e5f 🔀 Merge branch 'develop'
* develop:
  ♻️ Change how the active PHP version is switched
2020-02-20 11:49:09 +01:00
cbf5526881 ♻️ Change how the active PHP version is switched 2020-02-20 11:47:49 +01:00
f62297ee3d 🔀 Merge branch 'develop'
* develop:
  🔥 Remove broken log
  🔧 Version bump
  🚧 WIP: Adds support to PHP 7.4
  ️ Use Command.execute to retrieve PHP version
  🚧 WIP: Add toggle to enable or disable Xdebug
  📝 Update readme
2019-12-20 11:59:04 +01:00
2f017f6732 📢 Version 1.6 for personal use 2019-12-20 11:55:25 +01:00
1c62a100af 🔥 Remove broken log
(Will just log this in a file - the right way)
2019-12-20 11:54:15 +01:00
e176d6fa2d 🔧 Version bump 2019-12-20 11:48:20 +01:00
ea60626c47 🚧 WIP: Adds support to PHP 7.4
(PHP 7.4 isn't out for Homebrew just yet.)
2019-11-28 12:46:14 +01:00
69c3386088 ️ Use Command.execute to retrieve PHP version 2019-11-20 08:43:09 +01:00
3e898d435b 🚧 WIP: Add toggle to enable or disable Xdebug 2019-10-17 19:04:49 +02:00
a4dcd0cd3d 📝 Update readme 2019-10-15 10:39:42 +02:00
bb382c0f74 📝 Update readme 2019-10-14 11:14:28 +02:00
c87c532d1c 📝 Update readme for Catalina compatibility 2019-10-14 10:54:03 +02:00
b26099369f Fix text position 2019-07-15 17:31:22 +02:00
90a8e7c336 Updated margins of shell output window 2019-07-15 17:25:57 +02:00
398dad9d5e Minor cleanup, shell output window title 2019-07-15 17:23:08 +02:00
48d0878dfd 📝 Adds LICENSE 2019-07-15 17:19:51 +02:00
4ba3415bf4 ♻️ phpmon -> PHP Monitor 2019-07-15 17:14:09 +02:00
0fc7f2c905 📝 Minor tweaks, updated README 2019-07-15 08:14:51 +02:00
eebfa823f1 No more fixed width menu bar icon 2019-07-15 07:55:42 +02:00
edc23f97de 🐛 Fixes being unable to quit after setting target 2019-07-11 09:49:04 +02:00
7b79cb7fe8 ♻️ Move logic away from AppDelegate 2019-07-11 09:11:41 +02:00
88d81f343e ♻️ Move window logic to VC 2019-07-11 08:30:46 +02:00
8486997f01 🔥 Additional cleanup 2019-07-11 08:27:44 +02:00
af55e7fabc 🔥 Performance improvements, memory deallocation 2019-07-09 19:15:42 +02:00
2025c951e8 📝 Updated README 2019-07-09 18:39:52 +02:00
a71c6afc0d ♻️ Rework Shell as singleton, logging 2019-07-09 18:33:48 +02:00
1bfcdd546b 🐛 Fixes #3: Bad Gateway caused by valet use
Note: The terminal output work-in-progress is currently disabled in this
particular commit.
2019-07-08 10:43:56 +02:00
78702ae325 🚧 Adds window to view terminal output 2019-07-08 08:40:29 +02:00
180 changed files with 13639 additions and 956 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
custom: ['https://nicoverbruggen.be/sponsor', 'https://paypal.me/nicoverbruggen']

35
.github/pull_request_template.md vendored Normal file
View File

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

3
.gitignore vendored
View File

@ -1,2 +1,5 @@
phpmon.xcodeproj/project.xcworkspace
phpmon.xcodeproj/xcuserdata
PHP Monitor.xcodeproj/project.xcworkspace
PHP Monitor.xcodeproj/xcuserdata
.DS_Store

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Nico Verbruggen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,84 @@
<?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>

View File

@ -0,0 +1,89 @@
<?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 = "C41C1B3222B0097F00E7CF16"
BuildableName = "PHP Monitor.app"
BlueprintName = "PHP Monitor"
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>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C4F7807825D7F84B000DBC97"
BuildableName = "phpmon-tests.xctest"
BlueprintName = "phpmon-tests"
ReferencedContainer = "container:PHP Monitor.xcodeproj">
</BuildableReference>
</TestableReference>
</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"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C41C1B3222B0097F00E7CF16"
BuildableName = "PHP Monitor.app"
BlueprintName = "PHP Monitor"
ReferencedContainer = "container:PHP Monitor.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C41C1B3222B0097F00E7CF16"
BuildableName = "PHP Monitor.app"
BlueprintName = "PHP Monitor"
ReferencedContainer = "container:PHP Monitor.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

415
README.md
View File

@ -1,30 +1,413 @@
# phpmon
> 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="./docs/phpmon.png" width="306px" alt="phpmon screenshot"/>
<h1 align="center"><b>PHP Monitor</b> (phpmon)</h1>
phpmon is a macOS utility that runs on your Mac and displays the active PHP version in your status bar. Handy if you're running multiple versions of PHP with Homebrew and wish to see which version is currently linked & active with Laravel Valet.
<p align="center">
<img src="./phpmon/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png" alt="phpmon icon" width="128px" />
</p>
## How it works
**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>.
### Version detection
<img src="./docs/screenshot50.jpg" width="1085px" alt="phpmon screenshot (menu bar app)"/>
This utility runs `php -r 'print phpversion();'` in the background periodically and extracts the version number.
<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)!
<img src="./docs/notification.png" width="370px" alt="phpmon screenshot (notification)"/>
PHP Monitor also gives you quick access to various useful functionality (like accessing configuration files, restarting services, and more).
## 🖥 System requirements
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`
* The brew formula `php` has to be installed (which version is detected)
* Laravel Valet 2.16.2 or higher (older versions might be compatible but are not supported)
_You may need to update your Valet installation to keep everything working if a major version update of PHP has been released. You can do this by running `composer global update && valet install`._
## 🚀 How to install
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:
brew tap nicoverbruggen/homebrew-cask
brew install --cask phpmon
To upgrade your existing installation, run:
brew upgrade phpmon
(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?
I wanted to be able to **see at a glance** which version of PHP was linked, and handle dealing with Laravel Valet in a simple app without having to deal with the terminal every time.
Initially, I had an Alfred workflow for this — but it has now been replaced with this utility, which also does a good job at displaying additional information at a glance, like the current PHP version, memory limits, and more.
## 🤬 The app won't start?!
PHP Monitor performs some integrity checks to ensure a good experience when using the app. You'll get a message telling you that PHP Monitor won't work correctly in a variety of scenarios.
**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.
If you're still having issues, here's a few common questions & answers, as well as issues and solutions:
<details>
<summary><strong>Which versions of PHP are supported?</strong></summary>
<ul>
<li>PHP 5.6</li>
<li>PHP 7.0</li>
<li>PHP 7.1</li>
<li>PHP 7.2</li>
<li>PHP 7.3</li>
<li>PHP 7.4</li>
<li>PHP 8.0</li>
<li>PHP 8.1</li>
<li>PHP 8.2 (experimental)</li>
</ul>
For more details, consult the [constants file](https://github.com/nicoverbruggen/phpmon/blob/main/phpmon/Constants.swift#L16) file to see which versions are supported.
</details>
<details>
<summary><strong>I want PHP Monitor to start up when I boot my Mac!</strong></summary>
You can do this by dragging *PHP Monitor.app* into the **Login Items** section in **System Preferences > Users & Groups** for your account.
Super convenient!
</details>
<details>
<summary><strong>I want to set up PHP Monitor from scratch! I don't have Homebrew installed either, where do I begin?</strong></summary>
If you want to set up your computer for the very first time with PHP Monitor, here's how I do it:
Install [Homebrew](https://brew.sh) first.
Install PHP, composer, add to path:
brew install php
brew install composer
nano .zshrc
Make sure the following line is not in the comments:
# on an Intel Mac
export PATH=$HOME/bin:/usr/local/bin:$PATH
If you're on an Apple Silicon-based Mac, you'll need to add:
# on an M1 Mac
export PATH=$HOME/bin:/opt/homebrew/bin:$PATH
and add the following to your .zshrc, but add this BEFORE the homebrew PATH additions:
export PATH=$HOME/bin:~/.composer/vendor/bin:$PATH
If you're adding composer and Homebrew binaries, ensure that Homebrew binaries are preferred by adding these to the path last. On my system, that looks like this:
export PATH=$HOME/bin:/usr/local/bin:$PATH
export PATH=$HOME/bin:~/.composer/vendor/bin:$PATH
export PATH=$HOME/bin:/opt/homebrew/bin:$PATH
Make sure PHP is linked correctly:
which php
should return: `/usr/local/bin/php` (or `/opt/homebrew/bin/php`)
composer global require laravel/valet
valet install
This should install `dnsmasq` and set up Valet. Great, almost there!
valet trust
Finally, run PHP Monitor. Since the app is notarized and signed with a developer ID, it should work.
</details>
<details>
<summary><strong>PHP Monitor tells me `php` is not installed...</strong></summary>
Try installing again using `brew install php`.
This should resolve the issue! If that does not fix the issue, run `brew link php --force`. (Afterwards, you may need to restart your terminal to make sure the new linked version is detected.)
brew install php
brew link php --force
</details>
<details>
<summary><strong>Valet sites won't load. I'm getting a 502 Bad Gateway error!</strong></summary>
If you're visiting your `.test` domain, and you're getting a 502 (Bad Gateway) after switching to a different PHP version, you're dealing with a common issue.
This problem is usually resolved by upgrading Valet and running `valet install` again.
composer global update
valet install
</details>
<details>
<summary><strong>PHP Monitor tells me my installation is broken, but I don't see why!</strong></summary>
PHP Monitor tells you that a PHP installation is broken, if the configuration is causing warnings or errors when determining the version number.
Since PHP Monitor changes the linked version via Homebrew, both Valet *and* your terminal (CLI) should use the new PHP version.
However, this might not be the case on your system. You _might_ have a specific version of PHP linked if that is not the case. In that case, you may need to change your `.bashrc` or `.zshrc` file where the PATH is set (depending on the terminal you use).
You can find out which version of PHP is being used by running `which php`.
You can find out what exactly is causing the issue by running a command. On Intel, you can run (replace `7.4` with the version that is broken):
```
/usr/local/opt/php@7.4/bin/php -r "print phpversion();"
```
On Apple Silicon, you can run (replace `7.4` with the version that is broken):
```
/opt/homebrew/opt/php@7.4/bin/php -r "print phpversion();"
```
You should see an error or a warning here in the output.
Usually this is a duplicate extension declaration causing issues, or an extension that couldn't be loaded. You'll have to solve that issue yourself (usually by removing the offending extension or reinstalling).
</details>
<details>
<summary><strong>One of the limits (memory limit, max POST size, max upload size) shows an exclamation mark!</strong></summary>
The value you provided in your INI file is invalid. If that is the case, PHP will attempt to parse your value as bytes, which is usually unintended. (`1GB` will resolve to merely a few bytes, and all of your applications will run out of memory!)
You must a provide a value like so: `1024K`, `256M`, `1G`. Alternatively, `-1` is also allowed, or just an integer (which will result in N amount of bytes being the limit).
**Example**: Trying to use `1GB` as the memory limit, for example, will result in this exclamation mark. The correct way to set a 1GB limit is by using `1G` as the value. (Note: The displayed value will append `B` for clarity, so if you set `1G`, the value reported by PHP Monitor will be 1 GB.)
</details>
<details>
<summary><strong>One of my commented out extensions is not being detected...</strong></summary>
The app searches in the relevant `.ini` files for a specific pattern. For regular extensions:
* `extension="*.so"`
* `; extension="*.so"`
For Zend extensions:
* `zend_extension="*.so"`
* `; zend_extension="*.so"`
The `*` is a wildcard and the name of the extension. If you've commented out the extension, make sure you've commented it out with a semicolon (;) and a single space after the semicolon for PHP Monitor to detect it.
Since v3.4 all of the loaded .ini files are sourced to determine which extensions are enabled.
</details>
<details>
<summary><strong>I've got two Homebrew installations on my Apple Silicon Mac, can I choose which installation to use with PHP Monitor?</strong></summary>
Not at this time, no. PHP Monitor will prefer the `/opt/homebrew` installation over the classic installation directory.
</details>
<details>
<summary><strong>Why is the app doing network requests?</strong></summary>
It's Homebrew. I can't prevent `brew` from doing things via the network when I invoke it.
PHP Monitor itself doesn't do any network requests. Feel free to check the source code or intercept the traffic, if you don't believe me.
</details>
<details>
<summary><strong>How do I get various applications to show up in the domain list's right-click menu?</strong></summary>
When you select and right-click on a domain, you can open these directories with various applications. This can help speed up your workflow. However, for these apps to show up, they must be detected first.
The supported apps are: <i>PhpStorm, Visual Studio Code, Sublime Text, Sublime Merge, iTerm</i>.
All of these apps should just be detected correctly, no matter their location on your system. If you can open it using `open -a "appname"`, the app should be detected and work. If you have renamed the app, there might be an issue getting it detected.
To see which files are checked to determine availability, see [this file](./phpmon/Domain/Helpers/Application.swift).
You can add your own apps by creating and editing a `~/.phpmon.conf.json` file, with the following entry:
<pre>
{
"scan_apps": ["Xcode", "Kraken"]
}
</pre>
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>
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).
If you would like to know more, consult [this issue](https://github.com/nicoverbruggen/phpmon/issues/85) for more information.
</details>
<details>
<summary><strong>The app has crashed!</strong></summary>
Please get in touch and open an issue. PHP Monitor shouldn't crash... (unless you are actually removing PHP *while* the app is running, thats 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?
I did not include any tracking or analytics software, so if you encounter issues, let me know [via an issue](https://github.com/nicoverbruggen/phpmon/issues/new).
## 💵 Support me?
PHP Monitor is available entirely **free of charge**, but if you can afford it a donation helps keep the project alive and the app maintained.
You can find a [sponsor](https://nicoverbruggen.be/sponsor) link at the top of this repo or you could click the link here to be taken to my sponsorship page.
Donations really help with the Apple Developer Program cost, and keep me motivated to keep working on PHP Monitor outside of work hours (I do have a day job!).
## 😎 Acknowledgements
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 & [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!)
* 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.
## 🚜 How it works
### Loading info about PHP in the background
This utility runs `php-config --version` in the background periodically. It also checks your `.ini` files for extensions and loads more information about your limits (memory limit, POST limit, upload limit).
In order to save power, this only happens once every 60 seconds.
### Switching PHP versions
This utility will detect which PHP versions you have installed via Homebrew, and then allows you to switch between them.
This means:
The switcher will disable all PHP-FPM services not belonging to the version you wish to use, and link the desired version of PHP. Then, it'll restart your desired PHP version's FPM process. This all happens in parallel, so this should be much faster than Valets switcher.
- You have at least the latest version of PHP installed (`php@7.3`)
- You have installed Laravel Valet (`which valet` returns `/usr/local/bin/valet`)
- You ran `valet trust`, which means Valet commands can be run without using sudo
### Config change detection
The utility runs the following commands:
PHP Monitor watches your filesystem in the relevant `conf.d` directory for the currently linked PHP version.
- Unlink all detected PHP versions
- Switch to PHP 7.3 (this is done in order to ensure that Valet works, even when attempting to use PHP 5.6)
- Tell Valet to switch to a specific PHP version
- Link the desired version of PHP
Whenever an .ini file is modified, PHP Monitor will attempt to reload the current information about the active PHP installation.
If you want to know more about how this works, you can find the file Services.swift under the Helpers directory to learn more about how switching PHP versions works. In the end, this just executes some shell commands.
If an extension or other process writes to a single file a bunch of times in a short span of time (&lt; 1 sec), PHP Monitor will only reload the active configuration information after a while (with a slight delay).
### Site detection
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. **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.
### Want to know more?
If you want to know more about how this works, I recommend you check out the source code.
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.
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.
## 🔧 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.

13
RELEASE.md Normal file
View File

@ -0,0 +1,13 @@
# Release Procedure
1. Merge into `main`
2. Create tag
3. Add changes to changelog + update security document
4. Archive
5. Notarize and prepare for own distribution
6. After notarization, export .app
7. Create zipped version
8. Calculate SHA256: `openssl dgst -sha256 phpmon.zip`
9. Upload to GitHub and add to tagged release
10. Update Cask with new version + hash
11. Check new version can be installed via Cask

28
SECURITY.md Normal file
View File

@ -0,0 +1,28 @@
# Security Policy
## Supported versions
Generally speaking, only the latest version of **PHP Monitor** is supported, except during transition periods (for example, when particular system requirements go up):
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version |
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
| 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 youre 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 |
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
| 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 |
| 3.5 | ✅ Universal binary | ❌ | Big Sur (11.0) and Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 | 2.13 |
| 3.0—3.4 | ✅ Universal binary | ❌ | Big Sur (11.0) | macOS 10.14+ | PHP 5.6—PHP 8.1 | 2.13 |
| 2.6 | ✅ Universal binary | ❌ | Big Sur (11.0) | macOS 10.14+ | PHP 5.6—PHP 8.0 | 2.13 |
| 2.5 | ✴️ Universal binary<br/>`/usr/local/homebrew` installations only | ❌ | Big Sur (11.0)<br/>Catalina (10.15) | macOS 10.14+ | not applicable | not applicable |
| 2.4 | ✴️ Universal binary<br/>`/usr/local/homebrew` installations only | ❌ | Big Sur (11.0)<br/>Catalina (10.15) | macOS 10.14+ | not applicable | not applicable |
| < 2.4 | Intel binary<br/>`/usr/local/homebrew` installations only | ❌ | Catalina (10.15) | macOS 10.14+ | not applicable | not applicable |
## Reporting a vulnerability
Contact me (Nico Verbruggen) at the email address used for the commits in the repository. Please include "PHP Monitor" in the subject.

Binary file not shown.

1
assets/icon.svg Normal file
View File

@ -0,0 +1 @@
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><filter x="-4.6%" y="-3.4%" width="109.2%" height="109.2%" filterUnits="objectBoundingBox" id="b"><feOffset dy="5" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="5.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.26 0" in="shadowBlurOuter1"/></filter><filter x="-8.6%" y="-11%" width="117.1%" height="121.9%" filterUnits="objectBoundingBox" id="d"><feOffset dx="1" dy="2" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="6.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.372049825 0" in="shadowBlurOuter1" result="shadowMatrixOuter1"/><feMerge><feMergeNode in="shadowMatrixOuter1"/><feMergeNode in="SourceGraphic"/></feMerge></filter><linearGradient x1="50.023%" y1="-15.482%" x2="50.023%" y2="104.493%" id="e"><stop stop-color="#51C2FB" offset=".053%"/><stop stop-color="#1C91FE" offset="100%"/></linearGradient><path d="M167.939 50H344.06c41.01 0 55.881 4.27 70.874 12.288 14.992 8.018 26.759 19.785 34.777 34.777C457.73 112.058 462 126.93 462 167.94V344.06c0 41.01-4.27 55.881-12.288 70.874-8.018 14.992-19.785 26.759-34.777 34.777C399.942 457.73 385.07 462 344.06 462H167.94c-41.01 0-55.881-4.27-70.874-12.288-14.992-8.018-26.759-19.785-34.777-34.777C54.27 399.942 50 385.07 50 344.06V167.94c0-41.01 4.27-55.881 12.288-70.874 8.018-14.992 19.785-26.759 34.777-34.777C112.058 54.27 126.93 50 167.94 50z" id="a"/></defs><g fill="none" fill-rule="evenodd"><mask id="c" fill="#fff"><use xlink:href="#a"/></mask><use filter="url(#b)" xlink:href="#a" fill-rule="nonzero" fill="#000"/><g mask="url(#c)"><path fill="#FFF" d="M50 50h412v412H50z"/></g><g filter="url(#d)" mask="url(#c)"><path fill="#FFF" d="M131.535 155.144l63.377-8.907 33.68 239.645-63.377 8.907zM315.79 263.497l63.377-8.907 15.17 107.94-63.377 8.906z"/><path fill="#FFF" d="M315.79 263.497l63.377-8.907 15.17 107.94-63.377 8.906z"/><path fill="#FFF" d="M316.01 173.224l84.001 68.319-53.919 66.92-84-68.318z"/><path fill="#FFF" d="M93.155 157.813l164.384-23.102 21.433 152.5-164.385 23.104z"/><path d="M133.3 83.75h-12.9V54.437c0-2.303-1.935-4.187-4.3-4.187h-8.6c-2.365 0-4.3 1.884-4.3 4.188v41.874c0 2.304 1.935 4.188 4.3 4.188h25.8c2.365 0 4.3-1.884 4.3-4.188v-8.374c0-2.304-1.935-4.188-4.3-4.188zm202.1 100.5h-8.6v-56.584c0-6.647-2.741-13.033-7.579-17.744L265.525 57.63c-4.837-4.71-11.395-7.38-18.221-7.38H223.6V25.125C223.6 11.254 212.044 0 197.8 0h-172C11.556 0 0 11.254 0 25.125v167.5c0 13.871 11.556 25.125 25.8 25.125h8.6C34.4 245.492 57.513 268 86 268c28.487 0 51.6-22.508 51.6-50.25h68.8c0 27.742 23.112 50.25 51.6 50.25 28.488 0 51.6-22.508 51.6-50.25h25.8c4.73 0 8.6-3.769 8.6-8.375v-16.75c0-4.606-3.87-8.375-8.6-8.375zM86 242.875c-14.244 0-25.8-11.254-25.8-25.125s11.556-25.125 25.8-25.125c14.244 0 25.8 11.254 25.8 25.125s-11.556 25.125-25.8 25.125zm25.8-92.125c-33.271 0-60.2-26.224-60.2-58.625 0-32.4 26.929-58.625 60.2-58.625S172 59.724 172 92.125c0 32.4-26.929 58.625-60.2 58.625zM258 242.875c-14.244 0-25.8-11.254-25.8-25.125s11.556-25.125 25.8-25.125c14.244 0 25.8 11.254 25.8 25.125s-11.556 25.125-25.8 25.125zM301 134h-77.4V75.375h23.704L301 127.666V134z" fill="url(#e)" fill-rule="nonzero" transform="rotate(-8 1129.292 -453.38)"/></g></g></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

1
assets/menubar.svg Normal file
View File

@ -0,0 +1 @@
<svg width="22" height="22" xmlns="http://www.w3.org/2000/svg"><path d="M8.975 8.375H8.3V6.844a.223.223 0 00-.225-.219h-.45a.223.223 0 00-.225.219V9.03c0 .12.101.219.225.219h1.35a.223.223 0 00.225-.219v-.437a.223.223 0 00-.225-.219zm10.575 5.25h-.45v-2.956c0-.347-.143-.68-.397-.927l-2.81-2.731a1.37 1.37 0 00-.953-.386H13.7V5.312C13.7 4.588 13.095 4 12.35 4h-9C2.605 4 2 4.588 2 5.313v8.75c0 .724.605 1.312 1.35 1.312h.45C3.8 16.825 5.01 18 6.5 18c1.49 0 2.7-1.176 2.7-2.625h3.6c0 1.45 1.21 2.625 2.7 2.625 1.49 0 2.7-1.176 2.7-2.625h1.35c.247 0 .45-.197.45-.438v-.874a.445.445 0 00-.45-.438zM6.5 16.688c-.745 0-1.35-.588-1.35-1.313s.605-1.313 1.35-1.313c.745 0 1.35.588 1.35 1.313s-.605 1.313-1.35 1.313zm1.35-4.813c-1.74 0-3.15-1.37-3.15-3.063C4.7 7.12 6.11 5.75 7.85 5.75S11 7.12 11 8.813c0 1.692-1.41 3.062-3.15 3.062zm7.65 4.813c-.745 0-1.35-.588-1.35-1.313s.605-1.313 1.35-1.313c.745 0 1.35.588 1.35 1.313s-.605 1.313-1.35 1.313zM17.75 11H13.7V7.937h1.24l2.81 2.732V11z" fill="#000" fill-rule="nonzero"/></svg>

After

Width:  |  Height:  |  Size: 1017 B

87
assets/source.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 44 KiB

BIN
docs/build.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/notification.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 317 KiB

BIN
docs/screenshot50.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

View File

@ -0,0 +1,26 @@
//
// 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 }
}
}

103
phpmon-cli/main.swift Normal file
View File

@ -0,0 +1,103 @@
//
// 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()

View File

@ -0,0 +1,124 @@
//
// Services.swift
// PHP Monitor
//
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
import AppKit
class Actions {
// MARK: - Services
public static func restartPhpFpm()
{
brew("services restart \(PhpEnv.phpInstall.formula)", sudo: true)
}
public static func restartNginx()
{
brew("services restart nginx", sudo: true)
}
public static func restartDnsMasq()
{
brew("services restart dnsmasq", sudo: true)
}
public static func stopAllServices()
{
brew("services stop \(PhpEnv.phpInstall.formula)", sudo: true)
brew("services stop nginx", sudo: true)
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()
}
// MARK: - Finding Config Files
public static func openGenericPhpConfigFolder()
{
let files = [NSURL(fileURLWithPath: "\(Paths.etcPath)/php")];
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
}
public static func openGlobalComposerFolder()
{
let file = FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent(".composer/composer.json")
NSWorkspace.shared.activateFileViewerSelecting([file] as [URL])
}
public static func openPhpConfigFolder(version: String)
{
let files = [NSURL(fileURLWithPath: "\(Paths.etcPath)/php/\(version)/php.ini")];
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
}
public static func openValetConfigFolder()
{
let file = FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent(".config/valet")
NSWorkspace.shared.activateFileViewerSelecting([file] as [URL])
}
// MARK: - Other Actions
public static func createTempPhpInfoFile() -> URL
{
// Write a file called `phpmon_phpinfo.php` to /tmp
try! "<?php phpinfo();".write(toFile: "/tmp/phpmon_phpinfo.php", atomically: true, encoding: .utf8)
// Tell php-cgi to run the PHP and output as an .html file
Shell.run("\(Paths.binPath)/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html")
return URL(string: "file:///private/tmp/phpmon_phpinfo.html")!
}
// MARK: - Fix My Valet
/**
Detects all currently available PHP versions,
and unlinks each and every one of them.
After this, the brew services are also stopped,
the latest PHP version is linked, and php + nginx are restarted.
If this does not solve the issue, the user may need to install additional
extensions and/or run `composer global update`.
*/
public static func fixMyValet()
{
brew("services restart dnsmasq", sudo: true)
PhpEnv.shared.detectPhpVersions().forEach { (version) in
let formula = (version == PhpEnv.brewPhpVersion) ? "php" : "php@\(version)"
brew("unlink php@\(version)")
brew("services stop \(formula)")
brew("services stop \(formula)", sudo: true)
}
brew("services stop dnsmasq")
brew("services stop php")
brew("services stop nginx")
brew("link php --overwrite --force")
brew("services restart dnsmasq", sudo: true)
brew("services restart php", sudo: true)
brew("services restart nginx", sudo: true)
}
}

View File

@ -0,0 +1,40 @@
//
// Command.swift
// PHP Monitor
//
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Cocoa
public class Command {
/**
Immediately executes a command.
- Parameter path: The path of the command or program to invoke.
- Parameter arguments: A list of arguments that are passed on.
- Parameter trimNewlines: Removes empty new line output.
*/
public static func execute(path: String, arguments: [String], trimNewlines: Bool = false) -> String {
let task = Process()
task.launchPath = path
task.arguments = arguments
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String = String.init(data: data, encoding: String.Encoding.utf8)!
if (trimNewlines) {
return output.components(separatedBy: .newlines)
.filter({ !$0.isEmpty })
.joined(separator: "\n")
}
return output
}
}

View File

@ -0,0 +1,59 @@
//
// Constants.swift
// PHP Monitor
//
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Cocoa
class Constants {
/**
* The latest PHP version that is considered to be stable at the time of release.
* This version number is currently not used (only as a default fallback).
*/
static let LatestStablePhpVersion = "8.1"
/**
The minimum version of Valet that is recommended.
If the installed version is older, a notification will be shown
every time the app launches (with a recommendation to upgrade).
The minimum requirement is currently synced to PHP 8.1 compatibility.
See also: https://github.com/laravel/valet/releases/tag/v2.16.2
*/
static let MinimumRecommendedValetVersion = "2.16.2"
/**
* The PHP versions supported by this application.
* Versions that do not appear in this array are omitted from the list.
*/
static let SupportedPhpVersions = [
// ====================
// STABLE RELEASES
// ====================
// Versions of PHP that are stable and are supported.
"5.6",
"7.0",
"7.1",
"7.2",
"7.3",
"7.4",
"8.0",
"8.1",
// ====================
// EXPERIMENTAL SUPPORT
// ====================
// Every release that supports the next release will always support the next
// dev release. In this case, that means that the version below is detected.
"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")!
}

View File

@ -0,0 +1,15 @@
//
// Events.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 23/01/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
class Events {
static let ServicesUpdated = Notification.Name("ServicesUpdated")
}

View File

@ -0,0 +1,55 @@
//
// Helpers.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 24/12/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
// MARK: Common Shell Commands
/**
Runs a `valet` command.
*/
func valet(_ command: String) -> String
{
return Shell.pipe("sudo \(Paths.valet) \(command)", requiresPath: true)
}
/**
Runs a `brew` command. Can run as superuser.
*/
func brew(_ command: String, sudo: Bool = false)
{
Shell.run("\(sudo ? "sudo " : "")" + "\(Paths.brew) \(command)")
}
/**
Runs `sed` in order to replace all occurrences of a string in a specific file with another.
*/
func sed(file: String, original: String, replacement: String)
{
// Escape slashes (or `sed` won't work)
let e_original = original.replacingOccurrences(of: "/", with: "\\/")
let e_replacement = replacement.replacingOccurrences(of: "/", with: "\\/")
// Check if gsed exists; it is able to follow symlinks,
// which we want to do to toggle the extension
if Shell.fileExists("\(Paths.binPath)/gsed") {
Shell.run("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)")
} else {
Shell.run("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)")
}
}
/**
Uses `grep` to determine whether a particular query string can be found in a particular file.
*/
func grepContains(file: String, query: String) -> Bool
{
return Shell.pipe("""
grep -q '\(query)' \(file); [ $? -eq 0 ] && echo "YES" || echo "NO"
""")
.trimmingCharacters(in: .whitespacesAndNewlines)
.contains("YES")
}

View File

@ -0,0 +1,52 @@
//
// Logger.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 21/12/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
class Log {
static var shared = Log()
enum Verbosity: Int {
case error = 1,
warning = 2,
info = 3,
performance = 4
public func isApplicable() -> Bool {
return Log.shared.verbosity.rawValue >= self.rawValue
}
}
var verbosity: Verbosity = .warning
static func err(_ item: Any) {
if Verbosity.error.isApplicable() {
print(item)
}
}
static func warn(_ item: Any) {
if Verbosity.warning.isApplicable() {
print(item)
}
}
static func info(_ item: Any) {
if Verbosity.info.isApplicable() {
print(item)
}
}
static func perf(_ item: Any) {
if Verbosity.performance.isApplicable() {
print(item)
}
}
}

View File

@ -0,0 +1,70 @@
//
// Paths.swift
// PHP Monitor
//
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
/**
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 {
public static let shared = Paths()
private var baseDir : Paths.HomebrewDir
private var userName : String
init() {
baseDir = Shell.fileExists("\(HomebrewDir.opt.rawValue)/bin/brew") ? .opt : .usr
userName = String(Shell.pipe("whoami").split(separator: "\n")[0])
}
// - MARK: Binaries
public static var valet: String {
return "\(binPath)/valet"
}
public static var brew: String {
return "\(binPath)/brew"
}
public static var php: String {
return "\(binPath)/php"
}
public static var phpConfig: String {
return "\(binPath)/php-config"
}
// - MARK: Paths
public static var whoami: String {
return shared.userName
}
public static var binPath: String {
return "\(shared.baseDir.rawValue)/bin"
}
public static var optPath: String {
return "\(shared.baseDir.rawValue)/opt"
}
public static var etcPath: String {
return "\(shared.baseDir.rawValue)/etc"
}
// MARK: - Enum
public enum HomebrewDir: String {
case opt = "/opt/homebrew"
case usr = "/usr/local"
}
}

View File

@ -0,0 +1,180 @@
//
// Shell.swift
// PHP Monitor
//
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Cocoa
public class Shell {
// MARK: - Invoke static functions
public static func run(
_ command: String,
requiresPath: Bool = false
) {
Shell.user.run(command, requiresPath: requiresPath)
}
public static func pipe(
_ command: String,
requiresPath: Bool = false
) -> String {
return Shell.user.pipe(command, requiresPath: requiresPath)
}
// MARK: - Singleton
/**
We now require macOS 11, so no need to detect which terminal to use.
*/
public var shell: String = "/bin/sh"
/**
Singleton to access a user shell (with --login)
*/
public static let user = Shell()
/**
Runs a shell command without using the output.
Uses the default shell.
- Parameter command: The command to run
- Parameter requiresPath: By default, the PATH is not resolved but some binaries might require this
*/
private func run(
_ command: String,
requiresPath: Bool = false
) {
// Equivalent of piping to /dev/null; don't do anything with the string
_ = Shell.pipe(command, requiresPath: requiresPath)
}
/**
Runs a shell command and returns the output.
- Parameter command: The command to run
- Parameter requiresPath: By default, the PATH is not resolved but some binaries might require this
*/
private func pipe(
_ command: String,
requiresPath: Bool = false
) -> String {
let shellOutput = self.executeSynchronously(command, requiresPath: requiresPath)
let hasError = (
shellOutput.standardOutput == ""
&& shellOutput.errorOutput.lengthOfBytes(using: .utf8) > 0
)
return !hasError ? shellOutput.standardOutput : shellOutput.errorOutput
}
/**
Runs the command and returns a `ShellOutput` object, which contains info about the process.
- Parameter command: The command to run
- Parameter requiresPath: By default, the PATH is not resolved but some binaries might require this
- Parameter waitUntilExit: Waits for the command to complete before returning the `ShellOutput`
*/
public func executeSynchronously(
_ command: String,
requiresPath: Bool = false
) -> Shell.Output {
let outputPipe = Pipe()
let errorPipe = Pipe()
let task = self.createTask(for: command, requiresPath: requiresPath)
task.standardOutput = outputPipe
task.standardError = errorPipe
task.launch()
task.waitUntilExit()
return Shell.Output(
standardOutput: String(
data: outputPipe.fileHandleForReading.readDataToEndOfFile(),
encoding: .utf8
)!,
errorOutput: String(
data: errorPipe.fileHandleForReading.readDataToEndOfFile(),
encoding: .utf8
)!,
task: task
)
}
/**
Checks if a file exists at the provided path.
Uses `/bin/echo` instead of the `builtin` (which does not support `-n`).
*/
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"
}
/**
Creates a new process with the correct PATH and shell.
*/
public func createTask(for command: String, requiresPath: Bool) -> Process {
let tailoredCommand = requiresPath
? "export PATH=\(Paths.binPath):$PATH && \(command)"
: command
let task = Process()
task.launchPath = self.shell
task.arguments = ["--noprofile", "-norc", "--login", "-c", tailoredCommand]
return task
}
public static func captureOutput(
_ task: Process,
didReceiveStdOutData: @escaping (String) -> Void,
didReceiveStdErrData: @escaping (String) -> Void
) {
let outputPipe = Pipe()
let errorPipe = Pipe()
task.standardOutput = outputPipe
task.standardError = errorPipe
[(outputPipe, didReceiveStdOutData), (errorPipe, didReceiveStdErrData)].forEach {
(pipe: Pipe, callback: @escaping (String) -> Void) in
pipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
NotificationCenter.default.addObserver(
forName: NSNotification.Name.NSFileHandleDataAvailable,
object: pipe.fileHandleForReading,
queue: nil
) { notification in
if let outputString = String(data: pipe.fileHandleForReading.availableData, encoding: String.Encoding.utf8) {
callback(outputString)
}
pipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
}
}
}
public static func haltCapturingOutput(_ task: Process) {
if let pipe = task.standardOutput as? Pipe {
NotificationCenter.default.removeObserver(pipe.fileHandleForReading)
}
if let pipe = task.standardError as? Pipe {
NotificationCenter.default.removeObserver(pipe.fileHandleForReading)
}
}
public class Output {
public let standardOutput: String
public let errorOutput: String
public let task: Process
init(standardOutput: String,
errorOutput: String,
task: Process) {
self.standardOutput = standardOutput
self.errorOutput = errorOutput
self.task = task
}
}
}

View File

@ -0,0 +1,167 @@
//
// ActivePhpInstallation.swift
// PHP Monitor
//
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
/**
An installed version of PHP, that was detected by scanning the `/opt/php@version/bin` directory.
When initialized, that version's .ini files are also scanned (for active or inactive extensions).
Integrity checks can be performed to determine whether PHP-FPM is configured correctly.
- Note: Each installation has a separate version number.
Using `version.short` is advisable if you want to interact with Homebrew.
*/
class ActivePhpInstallation {
var version: Version!
var limits: Limits!
var extensions: [PhpExtension]!
// MARK: - Computed
var formula: String {
return (version.short == PhpEnv.brewPhpVersion) ? "php" : "php@\(version.short)"
}
// MARK: - Initializer
init() {
// Show information about the current version
getVersion()
// If an error occurred, exit early
if (version.error) {
limits = Limits()
extensions = []
return
}
// Load extension information
let path = URL(fileURLWithPath: "\(Paths.etcPath)/php/\(version.short)/php.ini")
extensions = PhpExtension.load(from: path)
// Get configuration values
limits = Limits(
memory_limit: getByteCount(key: "memory_limit"),
upload_max_filesize: getByteCount(key: "upload_max_filesize"),
post_max_size: getByteCount(key: "post_max_size")
)
// Return a list of .ini files parsed after php.ini
let paths = Command.execute(path: Paths.php, arguments: ["-r", "echo php_ini_scanned_files();"])
.replacingOccurrences(of: "\n", with: "")
.split(separator: ",")
.map { String($0) }
// See if any extensions are present in said .ini files
paths.forEach { (iniFilePath) in
let exts = PhpExtension.load(from: URL(fileURLWithPath: iniFilePath))
if exts.count > 0 {
extensions.append(contentsOf: exts)
}
}
}
/**
When the app tries to retrieve the version, the installation is considered broken if the output is nothing,
_or_ if the output contains the word "Warning" or "Error". In normal situations this should not be the case.
*/
private func getVersion() -> Void {
self.version = Version()
let version = Command.execute(path: Paths.phpConfig, arguments: ["--version"], trimNewlines: true)
if (version == "" || version.contains("Warning") || version.contains("Error")) {
self.version.short = "💩 BROKEN"
self.version.long = ""
self.version.error = true
return
}
// That's the long version
self.version.long = version
// Next up, let's strip away the minor version number
let segments = self.version.long.components(separatedBy: ".")
// Get the first two elements
self.version.short = segments[0...1].joined(separator: ".")
}
/**
Retrieves the display value for a specific key in the `.ini` file.
The following values are valid:
* -1: unlimited (show the infinity icon)
* 10000: an integer = amount of bytes
* 1K, 1M, 1G = shorthand for kilobytes, megabytes and gigabytes
If none of these notations are used, the _fallback_ value is used.
We'll show an emoji to indicate something has gone wrong here.
To clarify, B gets appended to valid values.
As a result, "5M" (valid) becomes "5MB", and "5MB" (invalid) becomes .
- Parameter key: The key of the `ini` value that needs to be retrieved. For example, you can use `memory_limit`.
*/
private func getByteCount(key: String) -> String {
let value = Command.execute(path: Paths.php, arguments: ["-r", "echo ini_get('\(key)');"])
// Check if the value is unlimited
if (value == "-1") {
return ""
}
// Check if the syntax is valid otherwise
let regex = try! NSRegularExpression(pattern: #"^([0-9]*)(K|M|G|)$"#, options: [])
let match = regex.matches(in: value, options: [], range: NSMakeRange(0, value.count)).first
return (match == nil) ? "⚠️" : "\(value)B"
}
/**
Determine if PHP-FPM is configured correctly.
For PHP 5.6, we'll check if `valet.sock` is included in the main `php-fpm.conf` file, but for more recent
versions of PHP, we can just check for the existence of the `valet-fpm.conf` file. If the check here fails,
that means that Valet won't work properly.
*/
func checkPhpFpmStatus() -> Bool {
if self.version.short == "5.6" {
// The main PHP config file should contain `valet.sock` and then we're probably fine?
let fileName = "\(Paths.etcPath)/php/5.6/php-fpm.conf"
return Shell.pipe("cat \(fileName)").contains("valet.sock")
}
// Make sure to check if valet-fpm.conf exists. If it does, we should be fine :)
return Shell.fileExists("\(Paths.etcPath)/php/\(self.version.short)/php-fpm.d/valet-fpm.conf")
}
// MARK: - Structs
/**
Struct containing information about the version number of the current PHP installation.
Also includes information about whether the install is considered "broken" or not.
If an error was found in the terminal output, `error` is set to `true` and the installation
can be considered broken. (The app will display this as well.)
*/
struct Version {
var short = "???"
var long = "???"
var error = false
}
/**
Struct containing information about the limits of the current PHP installation.
Includes: memory limit, max upload size and max post size.
*/
struct Limits {
var memory_limit = "???"
var upload_max_filesize = "???"
var post_max_size = "???"
}
}

View File

@ -0,0 +1,30 @@
//
// HomebrewPackage.swift
// PHP Monitor
//
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
struct HomebrewPackage: Decodable {
let name: String
let full_name: String
let aliases: [String]
let installed: [HomebrewInstalled]
let linked_keg: String?
public var version: String {
return aliases.first!
.replacingOccurrences(of: "php@", with: "")
}
}
struct HomebrewInstalled: Decodable {
let version: String
let built_as_bottle: Bool
let installed_as_dependency: Bool
let installed_on_request: Bool
}

View File

@ -0,0 +1,21 @@
//
// HomebrewService.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 11/01/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
struct HomebrewService: Decodable, Equatable {
let name: String
let service_name: String
let running: Bool
let loaded: Bool
let pid: Int?
let user: String?
let status: String?
let log_path: String?
let error_log_path: String?
}

View File

@ -0,0 +1,170 @@
//
// PhpSwitcher.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 21/12/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
protocol PhpSwitcherDelegate: AnyObject {
func switcherDidStartSwitching()
func switcherDidCompleteSwitch()
}
class PhpEnv {
// MARK: - Initializer
init() {
self.currentInstall = ActivePhpInstallation()
let brewPhpAlias = Shell.pipe("\(Paths.brew) info php --json");
self.homebrewPackage = try! JSONDecoder().decode(
[HomebrewPackage].self,
from: brewPhpAlias.data(using: .utf8)!
).first!
Log.info("When on your system, the `php` formula means version \(homebrewPackage.version)!")
}
// MARK: - Properties
/** The delegate that is informed of updates. */
weak var delegate: PhpSwitcherDelegate?
/** The static app instance. Accessible at any time. */
static let shared = PhpEnv()
/** Whether the switcher is busy performing any actions. */
var isBusy: Bool = false
/** All available versions of PHP. */
var availablePhpVersions: [String] = []
/** Cached information about the PHP installations. */
var cachedPhpInstallations: [String: PhpInstallation] = [:]
/** Information about the currently linked PHP installation. */
var currentInstall: ActivePhpInstallation
/**
The version that the `php` formula via Brew is aliased to on the current system.
If you're up to date, `php` will be aliased to the latest version,
but that might not be the case since not everyone keeps their
software up-to-date.
As such, we take that information from Homebrew.
*/
static var brewPhpVersion: String {
return Self.shared.homebrewPackage.version
}
/**
The currently linked and active PHP installation.
*/
static var phpInstall: ActivePhpInstallation {
return Self.shared.currentInstall
}
/**
Information we were able to discern from the Homebrew info command.
*/
var homebrewPackage: HomebrewPackage! = nil
// MARK: - Methods
public static var switcher: PhpSwitcher {
return InternalSwitcher()
}
public static func detectPhpVersions() -> Void {
_ = Self.shared.detectPhpVersions()
}
/**
Detects which versions of PHP are installed.
*/
public func detectPhpVersions() -> [String]
{
let files = Shell.pipe("ls \(Paths.optPath) | grep php@")
var versionsOnly = extractPhpVersions(from: files.components(separatedBy: "\n"))
// Make sure the aliased version is detected
// The user may have `php` installed, but not e.g. `php@8.0`
// We should also detect that as a version that is installed
let phpAlias = homebrewPackage.version
// Avoid inserting a duplicate
if (!versionsOnly.contains(phpAlias) && Shell.fileExists("\(Paths.optPath)/php/bin/php")) {
versionsOnly.append(phpAlias)
}
Log.info("The PHP versions that were detected are: \(versionsOnly)")
availablePhpVersions = versionsOnly
var mappedVersions: [String: PhpInstallation] = [:]
availablePhpVersions.forEach { version in
mappedVersions[version] = PhpInstallation(version)
}
cachedPhpInstallations = mappedVersions
return versionsOnly
}
/**
Extracts valid PHP versions from an array of strings.
This array of strings is usually retrieved from `grep`.
*/
public func extractPhpVersions(
from versions: [String],
checkBinaries: Bool = true
) -> [String] {
var output : [String] = []
versions.filter { (version) -> Bool in
// Omit everything that doesn't start with php@
// (e.g. something-php@8.0 won't be detected)
return version.starts(with: "php@")
}.forEach { (string) in
let version = string.components(separatedBy: "php@")[1]
// Only append the version if it doesn't already exist (avoid dupes),
// is supported and where the binary exists (avoids broken installs)
if !output.contains(version)
&& Constants.SupportedPhpVersions.contains(version)
&& (checkBinaries ? Shell.fileExists("\(Paths.optPath)/php@\(version)/bin/php") : true)
{
output.append(version)
}
}
return output
}
public func validVersions(for constraint: String) -> [PhpVersionNumber] {
constraint.split(separator: "|").flatMap {
return PhpVersionNumberCollection
.make(from: self.availablePhpVersions)
.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 {
print("Switching to version \(version) seems to have succeeded. Validation passed.")
return true
}
return false
}
}

View File

@ -0,0 +1,176 @@
//
// PhpVersionNumber.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 23/01/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
public struct PhpVersionNumberCollection: Equatable {
let versions: [PhpVersionNumber]
public static func make(from versions: [String]) -> Self {
return PhpVersionNumberCollection(
versions: versions.map { PhpVersionNumber.make(from: $0)! }
)
}
public var first: PhpVersionNumber? {
return self.versions.first
}
public var all: [PhpVersionNumber] {
return self.versions
}
/**
Checks if any versions of PHP are valid for the constraint provided.
Due to the complexity of evaluating these, a important test is maintained.
More information on these constraints can be found here:
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 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.
Strict mode assumes that any PHP version lacking precise patch information, e.g. inferred
from Homebrew corresponds to the .0 patch version of that version. The default, which is imprecise,
assumes that the patch version is .999, which means that in all cases the patch version check is
always going to pass.
**STRICT MODE (= patch precision on)**
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 (with patch precision), use
strict mode.
**NON-STRICT MODE (= patch precision off)**
Given versions 8.0.? and 8.1.?, but the requirement is ^8.0.1, in non-strict mode version 8.0
is assumed to be equal to version 8.0.999, which is actually fine if 8.0.1 is the required version.
In non-strict mode, the patch version is ignored for regular version checks (no caret / tilde).
If checking compatibility with general Homebrew versions of PHP, do NOT use strict mode, since
the patch version there is not used. (The formula php@8.0 suffices for ^8.0.1.)
*/
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.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.hasNewerMinorVersionOrPatch(version, strict) }
}
if let version = PhpVersionNumber.make(from: constraint, type: .tildeVersionRange) {
// Tilde range means that most specific digit is used as the basis.
return self.versions.filter {
version.patch != nil
// If a patch is provided then the minor version cannot be bumped.
? $0.hasSameMajorAndMinorButNewerOrSamePatch(version, strict)
// If a patch is not provided then the major version cannot be bumped.
: $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 []
}
}
public struct PhpVersionNumber: Equatable {
let major: Int
let minor: Int
let patch: Int?
public func patch(_ strictFallback: Bool = true, _ constraint: PhpVersionNumber? = nil) -> Int {
return patch ?? (strictFallback ? 0 : constraint?.patch ?? 999)
}
public var homebrewVersion: String {
return "\(major).\(minor)"
}
public enum MatchType: String {
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 make(from versionString: String, type: MatchType = .versionOnly) -> Self? {
let regex = try! NSRegularExpression(pattern: type.rawValue, options: [])
let match = regex.matches(in: versionString, options: [], range: NSMakeRange(0, versionString.count)).first
if match != nil {
let major = Int(
versionString[Range(match!.range(withName: "major"), in: versionString)!]
)!
let minor = Int(
versionString[Range(match!.range(withName: "minor"), in: versionString)!]
)!
var patch: Int? = nil
if let minorRange = Range(match!.range(withName: "patch"), in: versionString) {
patch = Int(versionString[minorRange])
}
return Self(major: major, minor: minor, patch: patch)
}
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
}
}

View File

@ -0,0 +1,108 @@
//
// PhpExtension.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 31/01/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
/**
A PHP extension that was detected in the php.ini file.
Please note that the extension may be disabled.
- Note: You need to know more about regular expressions to be able to deal with these NSRegularExpression
instances. You can find more information here: https://nshipster.com/swift-regular-expressions/
*/
class PhpExtension {
/// The file where this extension was located.
var file: String
/// The original string that was used to determine this extension is active.
var line: String
/// The name of the extension. This is always identical to the name found in the original string. If you want to display this name, capitalize this.
var name: String
/// Whether the extension has been enabled.
var enabled: Bool
/// The file where this extension was located, but only the filename, not the full path to the .ini file.
var fileNameOnly: String {
return String(file.split(separator: "/").last ?? "php.ini")
}
/**
This regular expression will allow us to identify lines which activate an extension.
It will match the following items:
* `extension="name.so"`
* `zend_extension="name.so"`
* `; extension="name.so"`
* `; zend_extension="name.so"`
- Note: Extensions that are disabled in a different way will not be detected. This is intentional.
*/
static let extensionRegex = #"^(extension|zend_extension|;(\s?)extension|;(\s?)zend_extension)(\s?)(=)(\s?)(?<name>["]?(?:\/?.\/?)+(?:\.so)"?)$"#
/**
When registering an extension, we do that based on the line found inside the .ini file.
*/
init(_ line: String, file: String) {
let regex = try! NSRegularExpression(pattern: Self.extensionRegex, options: [])
let match = regex.matches(in: line, options: [], range: NSMakeRange(0, line.count)).first
let range = Range(match!.range(withName: "name"), in: line)!
self.line = line
let fullPath = String(line[range])
.replacingOccurrences(of: "\"", with: "") // replace excess "
.replacingOccurrences(of: ".so", with: "") // replace excess .so
self.name = String(fullPath.split(separator: "/").last!) // take last segment
self.enabled = !line.contains(";")
self.file = file
}
/**
This simply toggles the extension in the .ini file. You may need to restart the other services in order for this change to apply.
*/
func toggle() {
let newLine = enabled
// DISABLED: Commented out line
? "; \(line)"
// ENABLED: Line where the comment delimiter (;) is removed
: line.replacingOccurrences(of: "; ", with: "")
sed(file: file, original: line, replacement: newLine)
enabled.toggle()
}
// MARK: - Static Methods
/**
This method will attempt to identify all extensions in the .ini file at a certain URL.
*/
static func load(from path: URL) -> [PhpExtension] {
let file = try? String(contentsOf: path, encoding: .utf8)
if (file == nil) {
Log.err("There was an issue reading the file. Assuming no extensions were found.")
return []
}
return file!.components(separatedBy: "\n")
.filter {
return $0.range(of: Self.extensionRegex, options: .regularExpression) != nil
}
.map {
return PhpExtension($0, file: path.path)
}
}
}

View File

@ -0,0 +1,36 @@
//
// PhpInstallation.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 28/11/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
class PhpInstallation {
var longVersion: PhpVersionNumber
/**
In order to determine details about a PHP installation, well simply run `php-config --version`
in the relevant directory.
*/
init(_ version: String) {
let phpConfigExecutablePath = "\(Paths.optPath)/php@\(version)/bin/php-config"
self.longVersion = PhpVersionNumber.make(from: version)!
if Shell.fileExists(phpConfigExecutablePath) {
let longVersionString = Command.execute(
path: phpConfigExecutablePath,
arguments: ["--version"]
).trimmingCharacters(in: .whitespacesAndNewlines)
self.longVersion = PhpVersionNumber.make(
from: String(longVersionString.split(separator: "-")[0])
)!
}
}
}

View File

@ -0,0 +1,61 @@
//
// InternalSwitcher.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 24/12/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
class InternalSwitcher: PhpSwitcher {
/**
Switching to a new PHP version involves:
- unlinking the current version
- stopping the active services
- linking the new desired version
Please note that depending on which version is installed,
the version that is switched to may or may not be identical to `php`
(without @version).
*/
func performSwitch(to version: String, completion: @escaping () -> Void)
{
Log.info("Switching to \(version), unlinking all versions...")
let group = DispatchGroup()
PhpEnv.shared.availablePhpVersions.forEach { (available) in
group.enter()
DispatchQueue.global(qos: .userInitiated).async {
let formula = (available == PhpEnv.brewPhpVersion)
? "php" : "php@\(available)"
brew("unlink \(formula)")
brew("services stop \(formula)", sudo: true)
Log.perf("Unlinked and stopped services for \(formula)")
group.leave()
}
}
group.notify(queue: .global(qos: .userInitiated)) {
Log.info("All versions have been unlinked!")
Log.info("Linking the new version!")
let formula = (version == PhpEnv.brewPhpVersion) ? "php" : "php@\(version)"
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()
}
}
}

View File

@ -0,0 +1,15 @@
//
// PhpVersionSwitchContract.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 24/12/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
protocol PhpSwitcher {
func performSwitch(to version: String, completion: @escaping () -> Void)
}

View File

@ -0,0 +1,85 @@
//
// BrewJsonParserTest.swift
// phpmon-tests
//
// Created by Nico Verbruggen on 14/02/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import XCTest
class BrewJsonParserTest: XCTestCase {
// - MARK: SYNTHETIC TESTS
static var jsonBrewFile: URL {
return Bundle(for: Self.self)
.url(forResource: "brew", withExtension: "json")!
}
func testCanLoadExtensionJson() throws {
let json = try! String(contentsOf: Self.jsonBrewFile, encoding: .utf8)
let package = try! JSONDecoder().decode(
[HomebrewPackage].self, from: json.data(using: .utf8)!
).first!
XCTAssertEqual(package.name, "php")
XCTAssertEqual(package.full_name, "php")
XCTAssertEqual(package.aliases.first!, "php@8.0")
XCTAssertEqual(package.installed.contains(where: { installed in
installed.version.starts(with: "8.0")
}), true)
}
static var jsonBrewServicesFile: URL {
return Bundle(for: Self.self)
.url(forResource: "brew-services", withExtension: "json")!
}
func testCanParseServicesJson() throws {
let json = try! String(contentsOf: Self.jsonBrewServicesFile, encoding: .utf8)
let services = try! JSONDecoder().decode(
[HomebrewService].self, from: json.data(using: .utf8)!
)
XCTAssertGreaterThan(services.count, 0)
XCTAssertEqual(services.first?.name, "dnsmasq")
XCTAssertEqual(services.first?.service_name, "homebrew.mxcl.dnsmasq")
}
// - MARK: LIVE TESTS
/// This test requires that you have a valid Homebrew installation set up,
/// and requires the Valet services to be installed: php, nginx and dnsmasq.
/// If this test fails, there is an issue with your Homebrew installation
/// or the JSON API of the Homebrew output may have changed.
func testCanParseServicesJsonFromCliOutput() throws {
let services = try! JSONDecoder().decode(
[HomebrewService].self,
from: Shell.pipe(
"sudo \(Paths.brew) services info --all --json",
requiresPath: true
).data(using: .utf8)!
).filter({ service in
return ["php", "nginx", "dnsmasq"].contains(service.name)
})
XCTAssertTrue(services.contains(where: {$0.name == "php"} ))
XCTAssertTrue(services.contains(where: {$0.name == "nginx"} ))
XCTAssertTrue(services.contains(where: {$0.name == "dnsmasq"} ))
XCTAssertEqual(services.count, 3)
}
/// This test requires that you have a valid Homebrew installation set up,
/// and requires the `php` formula to be installed.
/// If this test fails, there is an issue with your Homebrew installation
/// or the JSON API of the Homebrew output may have changed.
func testCanLoadExtensionJsonFromCliOutput() throws {
let package = try! JSONDecoder().decode(
[HomebrewPackage].self,
from: Shell.pipe("\(Paths.brew) info php --json", requiresPath: true).data(using: .utf8)!
).first!
XCTAssertTrue(package.name == "php")
}
}

View File

@ -0,0 +1,25 @@
//
// CommandTest.swift
// phpmon-tests
//
// Created by Nico Verbruggen on 13/02/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import XCTest
class CommandTest: XCTestCase {
func testDeterminePhpVersion() {
let version = Command.execute(
path: Paths.php,
arguments: ["-v"]
)
XCTAssert(version.contains("(cli)"))
XCTAssert(version.contains("NTS"))
XCTAssert(version.contains("built"))
XCTAssert(version.contains("Zend"))
}
}

View File

@ -0,0 +1,72 @@
//
// ExtensionParserTest.swift
// phpmon-tests
//
// Created by Nico Verbruggen on 13/02/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import XCTest
class ExtensionParserTest: XCTestCase {
static var phpIniFileUrl: URL {
return Bundle(for: Self.self).url(forResource: "php", withExtension: "ini")!
}
func testCanLoadExtension() throws {
let extensions = PhpExtension.load(from: Self.phpIniFileUrl)
XCTAssertGreaterThan(extensions.count, 0)
}
func testExtensionNameIsCorrect() throws {
let extensions = PhpExtension.load(from: Self.phpIniFileUrl)
let extensionNames = extensions.map { (ext) -> String in
return ext.name
}
// These 6 should be found
XCTAssertTrue(extensionNames.contains("xdebug"))
XCTAssertTrue(extensionNames.contains("imagick"))
XCTAssertTrue(extensionNames.contains("sodium-next"))
XCTAssertTrue(extensionNames.contains("opcache"))
XCTAssertTrue(extensionNames.contains("yaml"))
XCTAssertTrue(extensionNames.contains("custom"))
XCTAssertFalse(extensionNames.contains("fake"))
XCTAssertFalse(extensionNames.contains("nice"))
}
func testExtensionStatusIsCorrect() throws {
let extensions = PhpExtension.load(from: Self.phpIniFileUrl)
// xdebug should be enabled
XCTAssertEqual(extensions[0].enabled, true)
// imagick should be disabled
XCTAssertEqual(extensions[1].enabled, false)
}
func testToggleWorksAsExpected() throws {
let destination = Utility.copyToTemporaryFile(resourceName: "php", fileExtension: "ini")!
let extensions = PhpExtension.load(from: destination)
XCTAssertEqual(extensions.count, 6)
// Try to disable xdebug (should be detected first)!
let xdebug = extensions.first!
XCTAssertTrue(xdebug.name == "xdebug")
XCTAssertEqual(xdebug.enabled, true)
xdebug.toggle()
XCTAssertEqual(xdebug.enabled, false)
// Check if the file contains the appropriate data
let file = try! String(contentsOf: destination, encoding: .utf8)
XCTAssertTrue(file.contains("; zend_extension=\"xdebug.so\""))
// Make sure if we load the data again, it's disabled
XCTAssertEqual(PhpExtension.load(from: destination).first!.enabled, false)
}
}

22
phpmon-tests/Info.plist Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@ -0,0 +1,29 @@
//
// PhpVersionDetectionTest.swift
// phpmon-tests
//
// Created by Nico Verbruggen on 01/04/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import XCTest
class PhpVersionDetectionTest: XCTestCase {
func testCanDetectValidPhpVersions() throws {
let outcome = PhpEnv.shared.extractPhpVersions(from: [
"", // empty lines should be omitted
"php@8.0",
"php@8.0", // should only be detected once
"meta-php@8.0", // should be omitted, invalid
"php@8.0-coolio", // should be omitted, invalid
"php@7.0",
"",
"unrelatedphp@1.0", // should be omitted, invalid
"php@5.6",
"php@5.4" // should be omitted, not supported
], checkBinaries: false)
XCTAssertEqual(outcome, ["8.0", "7.0", "5.6"])
}
}

View File

@ -0,0 +1,276 @@
//
// PhpVersionNumberTest.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 23/01/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import XCTest
class PhpVersionNumberTest: XCTestCase {
func testCanDeconstructPhpVersion() throws {
XCTAssertEqual(
PhpVersionNumber.make(from: "8.0.11"),
PhpVersionNumber(major: 8, minor: 0, patch: 11)
)
XCTAssertEqual(
PhpVersionNumber.make(from: "7.4.2"),
PhpVersionNumber(major: 7, minor: 4, patch: 2)
)
XCTAssertEqual(
PhpVersionNumber.make(from: "7.4"),
PhpVersionNumber(major: 7, minor: 4, patch: nil)
)
XCTAssertEqual(
PhpVersionNumber.make(from: "7"),
nil
)
}
func testCanCheckFixedConstraints() throws {
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
.matching(constraint: "7.0"),
PhpVersionNumberCollection
.make(from: ["7.0"]).all
)
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4.3", "7.3.3", "7.2.3", "7.1.3", "7.0.3"])
.matching(constraint: "7.0.3"),
PhpVersionNumberCollection
.make(from: ["7.0.3"]).all
)
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
.matching(constraint: "7.0.3", strict: false),
PhpVersionNumberCollection
.make(from: ["7.0"]).all
)
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
.matching(constraint: "7.0.3", strict: true),
PhpVersionNumberCollection
.make(from: []).all
)
}
func testCanCheckCaretConstraints() throws {
// 1. Imprecise checks
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
)
// 2. Imprecise check with precise constraint (lenient AKA not strict)
// These versions are interpreted as 7.4.999, 7.3.999, 7.2.999, etc.
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
.matching(constraint: "^7.0.1", strict: false),
PhpVersionNumberCollection
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]).all
)
// 3. Imprecise check with precise constraint (strict mode)
// These versions are interpreted as 7.4.0, 7.3.0, 7.2.0, etc.
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
.matching(constraint: "^7.0.1", strict: true),
PhpVersionNumberCollection
.make(from: ["7.4", "7.3", "7.2", "7.1"]).all
)
// 4. Precise members and constraint all around
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"])
.matching(constraint: "^7.0.1", strict: true),
PhpVersionNumberCollection
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all
)
// 5. Precise members but imprecise constraint (strict mode)
// In strict mode the constraint's patch version is assumed to be 0
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"])
.matching(constraint: "^7.0", strict: true),
PhpVersionNumberCollection
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all
)
// 6. Precise members but imprecise constraint (lenient mode)
// In lenient mode the constraint's patch version is assumed to be equal
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"])
.matching(constraint: "^7.0", strict: false),
PhpVersionNumberCollection
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all
)
}
func testCanCheckTildeConstraints() throws {
// 1. Imprecise checks
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
)
// 2. Imprecise check with precise constraint (lenient AKA not strict)
// These versions are interpreted as 7.4.999, 7.3.999, 7.2.999, etc.
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
.matching(constraint: "~7.0.1", strict: false),
// One result because 7.0.1 to 7.0.x is expected.
// 7.0.999 (assumed due to no strictness) is valid.
// 7.1.0 and up are not valid (minor version is too high).
PhpVersionNumberCollection
.make(from: ["7.0"]).all
)
// 3. Imprecise check with precise constraint (strict mode)
// These versions are interpreted as 7.4.0, 7.3.0, 7.2.0, etc.
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
.matching(constraint: "~7.0.1", strict: true),
// No results because 7.0.1 to 7.0.x is expected.
// 7.0.0 (assumed due to strictness) is not valid.
// 7.1.0 and up are also not valid (minor version is too high).
PhpVersionNumberCollection
.make(from: []).all
)
// 4. Precise members and constraint all around
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"])
.matching(constraint: "~7.0.1", strict: true),
// Only 7.0 with a patch version of .1 or higher is OK.
// In this example, 7.0.10 is OK but all other versions are too new.
PhpVersionNumberCollection
.make(from: ["7.0.10"]).all
)
// 5. Precise members but imprecise constraint (strict mode)
// In strict mode the constraint's patch version is assumed to be 0.
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"])
.matching(constraint: "~7.0", strict: true),
PhpVersionNumberCollection
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all
)
// 6. Precise members but imprecise constraint (lenient mode)
// In lenient mode the constraint's patch version is assumed to be equal.
// (Strictness does not make any difference here, but both should be tested.)
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"])
.matching(constraint: "~7.0", strict: false),
PhpVersionNumberCollection
.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
)
}
}

View File

@ -0,0 +1,135 @@
[
{
"name": "dnsmasq",
"service_name": "homebrew.mxcl.dnsmasq",
"running": true,
"loaded": true,
"schedulable": false,
"pid": 106,
"exit_code": 0,
"user": "root",
"status": "started",
"file": "/Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist",
"command": "/opt/homebrew/opt/dnsmasq/sbin/dnsmasq --keep-in-foreground -C /opt/homebrew/etc/dnsmasq.conf -7 /opt/homebrew/etc/dnsmasq.d,*.conf",
"working_dir": null,
"root_dir": null,
"log_path": null,
"error_log_path": null,
"interval": null,
"cron": null
},
{
"name": "httpd",
"service_name": "homebrew.mxcl.httpd",
"running": false,
"loaded": false,
"schedulable": false,
"pid": null,
"exit_code": null,
"user": null,
"status": "none",
"file": "/opt/homebrew/opt/httpd/homebrew.mxcl.httpd.plist",
"command": "/opt/homebrew/opt/httpd/bin/httpd -D FOREGROUND",
"working_dir": null,
"root_dir": null,
"log_path": null,
"error_log_path": null,
"interval": null,
"cron": null
},
{
"name": "mailhog",
"service_name": "homebrew.mxcl.mailhog",
"running": false,
"loaded": false,
"schedulable": false,
"pid": null,
"exit_code": null,
"user": null,
"status": "none",
"file": "/opt/homebrew/opt/mailhog/homebrew.mxcl.mailhog.plist",
"command": "/opt/homebrew/opt/mailhog/bin/MailHog",
"working_dir": null,
"root_dir": null,
"log_path": "/opt/homebrew/var/log/mailhog.log",
"error_log_path": "/opt/homebrew/var/log/mailhog.log",
"interval": null,
"cron": null
},
{
"name": "nginx",
"service_name": "homebrew.mxcl.nginx",
"running": true,
"loaded": true,
"schedulable": false,
"pid": 116,
"exit_code": 0,
"user": "root",
"status": "started",
"file": "/Library/LaunchDaemons/homebrew.mxcl.nginx.plist",
"command": "/opt/homebrew/opt/nginx/bin/nginx -g daemon off;",
"working_dir": "/opt/homebrew",
"root_dir": null,
"log_path": null,
"error_log_path": null,
"interval": null,
"cron": null
},
{
"name": "php",
"service_name": "homebrew.mxcl.php",
"running": true,
"loaded": true,
"schedulable": false,
"pid": 142,
"exit_code": 0,
"user": "root",
"status": "started",
"file": "/Library/LaunchDaemons/homebrew.mxcl.php.plist",
"command": "/opt/homebrew/opt/php/sbin/php-fpm --nodaemonize",
"working_dir": "/opt/homebrew/var",
"root_dir": null,
"log_path": null,
"error_log_path": "/opt/homebrew/var/log/php-fpm.log",
"interval": null,
"cron": null
},
{
"name": "php@8.0",
"service_name": "homebrew.mxcl.php@8.0",
"running": false,
"loaded": false,
"schedulable": false,
"pid": null,
"exit_code": null,
"user": null,
"status": "none",
"file": "/opt/homebrew/opt/php@8.0/homebrew.mxcl.php@8.0.plist",
"command": "/opt/homebrew/opt/php@8.0/sbin/php-fpm --nodaemonize",
"working_dir": "/opt/homebrew/var",
"root_dir": null,
"log_path": null,
"error_log_path": "/opt/homebrew/var/log/php-fpm.log",
"interval": null,
"cron": null
},
{
"name": "unbound",
"service_name": "homebrew.mxcl.unbound",
"running": false,
"loaded": false,
"schedulable": false,
"pid": null,
"exit_code": null,
"user": null,
"status": "none",
"file": "/opt/homebrew/opt/unbound/homebrew.mxcl.unbound.plist",
"command": "/opt/homebrew/opt/unbound/sbin/unbound -d -c /opt/homebrew/etc/unbound/unbound.conf",
"working_dir": null,
"root_dir": null,
"log_path": null,
"error_log_path": null,
"interval": null,
"cron": null
}
]

View File

@ -0,0 +1,332 @@
[
{
"name":"php",
"full_name":"php",
"tap":"homebrew/core",
"oldname":null,
"aliases":[
"php@8.0"
],
"versioned_formulae":[
"php@7.4",
"php@7.3",
"php@7.2"
],
"desc":"General-purpose scripting language",
"license":"PHP-3.01",
"homepage":"https://www.php.net/",
"versions":{
"stable":"8.0.2",
"head":"HEAD",
"bottle":true
},
"urls":{
"stable":{
"url":"https://www.php.net/distributions/php-8.0.2.tar.xz",
"tag":null,
"revision":null
}
},
"revision":0,
"version_scheme":0,
"bottle":{
"stable":{
"rebuild":0,
"cellar":"/opt/homebrew/Cellar",
"prefix":"/opt/homebrew",
"root_url":"https://homebrew.bintray.com/bottles",
"files":{
"arm64_big_sur":{
"url":"https://homebrew.bintray.com/bottles/php-8.0.2.arm64_big_sur.bottle.tar.gz",
"sha256":"cbefa1db73d08b9af4593a44512b8d727e43033ee8517736bae5f16315501b12"
},
"big_sur":{
"url":"https://homebrew.bintray.com/bottles/php-8.0.2.big_sur.bottle.tar.gz",
"sha256":"6857142e12254b15da4e74c2986dd24faca57dac8d467b04621db349e277dd63"
},
"catalina":{
"url":"https://homebrew.bintray.com/bottles/php-8.0.2.catalina.bottle.tar.gz",
"sha256":"b651611134c18f93fdf121a4277b51b197a896a19ccb8020289b4e19e0638349"
},
"mojave":{
"url":"https://homebrew.bintray.com/bottles/php-8.0.2.mojave.bottle.tar.gz",
"sha256":"9583a51fcc6f804aadbb14e18f770d4fb4973deaed6ddc4770342e62974ffbca"
}
}
}
},
"keg_only":false,
"bottle_disabled":false,
"options":[
],
"build_dependencies":[
"httpd",
"pkg-config"
],
"dependencies":[
"apr",
"apr-util",
"argon2",
"aspell",
"autoconf",
"curl",
"freetds",
"gd",
"gettext",
"glib",
"gmp",
"icu4c",
"krb5",
"libffi",
"libpq",
"libsodium",
"libzip",
"oniguruma",
"openldap",
"openssl@1.1",
"pcre2",
"sqlite",
"tidy-html5",
"unixodbc"
],
"recommended_dependencies":[
],
"optional_dependencies":[
],
"uses_from_macos":[
{
"xz":"build"
},
"bzip2",
"libedit",
"libxml2",
"libxslt",
"zlib"
],
"requirements":[
],
"conflicts_with":[
],
"caveats":"To enable PHP in Apache add the following to httpd.conf and restart Apache:\n LoadModule php_module $(brew --prefix)/opt/php/lib/httpd/modules/libphp.so\n\n <FilesMatch \\.php$>\n SetHandler application/x-httpd-php\n </FilesMatch>\n\nFinally, check DirectoryIndex includes index.php\n DirectoryIndex index.php index.html\n\nThe php.ini and php-fpm.ini file can be found in:\n $(brew --prefix)/etc/php/8.0/\n",
"installed":[
{
"version":"8.0.2",
"used_options":[
],
"built_as_bottle":true,
"poured_from_bottle":true,
"runtime_dependencies":[
{
"full_name":"apr",
"version":"1.7.0"
},
{
"full_name":"openssl@1.1",
"version":"1.1.1i"
},
{
"full_name":"apr-util",
"version":"1.6.1"
},
{
"full_name":"argon2",
"version":"20190702"
},
{
"full_name":"aspell",
"version":"0.60.8"
},
{
"full_name":"autoconf",
"version":"2.69"
},
{
"full_name":"brotli",
"version":"1.0.9"
},
{
"full_name":"gettext",
"version":"0.21"
},
{
"full_name":"libunistring",
"version":"0.9.10"
},
{
"full_name":"libidn2",
"version":"2.3.0"
},
{
"full_name":"libmetalink",
"version":"0.1.3"
},
{
"full_name":"libssh2",
"version":"1.9.0"
},
{
"full_name":"c-ares",
"version":"1.17.1"
},
{
"full_name":"jemalloc",
"version":"5.2.1"
},
{
"full_name":"libev",
"version":"4.33"
},
{
"full_name":"nghttp2",
"version":"1.43.0"
},
{
"full_name":"openldap",
"version":"2.4.57"
},
{
"full_name":"rtmpdump",
"version":"2.4+20151223"
},
{
"full_name":"zstd",
"version":"1.4.8"
},
{
"full_name":"curl",
"version":"7.75.0"
},
{
"full_name":"libtool",
"version":"2.4.6"
},
{
"full_name":"unixodbc",
"version":"2.3.9"
},
{
"full_name":"freetds",
"version":"1.2.18"
},
{
"full_name":"libpng",
"version":"1.6.37"
},
{
"full_name":"freetype",
"version":"2.10.4"
},
{
"full_name":"fontconfig",
"version":"2.13.1"
},
{
"full_name":"jpeg",
"version":"9d"
},
{
"full_name":"libtiff",
"version":"4.2.0"
},
{
"full_name":"webp",
"version":"1.2.0"
},
{
"full_name":"gd",
"version":"2.3.1"
},
{
"full_name":"libffi",
"version":"3.3"
},
{
"full_name":"pcre",
"version":"8.44"
},
{
"full_name":"gdbm",
"version":"1.18.1"
},
{
"full_name":"readline",
"version":"8.1"
},
{
"full_name":"sqlite",
"version":"3.34.0"
},
{
"full_name":"tcl-tk",
"version":"8.6.11"
},
{
"full_name":"xz",
"version":"5.2.5"
},
{
"full_name":"python@3.9",
"version":"3.9.1"
},
{
"full_name":"glib",
"version":"2.66.6"
},
{
"full_name":"gmp",
"version":"6.2.1"
},
{
"full_name":"icu4c",
"version":"67.1"
},
{
"full_name":"krb5",
"version":"1.19"
},
{
"full_name":"libpq",
"version":"13.1"
},
{
"full_name":"libsodium",
"version":"1.0.18"
},
{
"full_name":"libzip",
"version":"1.7.3"
},
{
"full_name":"oniguruma",
"version":"6.9.6"
},
{
"full_name":"pcre2",
"version":"10.36"
},
{
"full_name":"tidy-html5",
"version":"5.6.0"
}
],
"installed_as_dependency":false,
"installed_on_request":true
}
],
"linked_keg":"8.0.2",
"pinned":false,
"outdated":false,
"deprecated":false,
"deprecation_date":null,
"deprecation_reason":null,
"disabled":false,
"disable_date":null,
"disable_reason":null
}
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
{
"tld": "test",
"paths": [
"/Users/username/.config/valet/Sites",
"/Users/username/Sites"
],
"loopback": "127.0.0.1"
}

View File

@ -0,0 +1,28 @@
//
// Utility.swift
// phpmon-tests
//
// Created by Nico Verbruggen on 14/02/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
class Utility {
public static func copyToTemporaryFile(resourceName: String, fileExtension: String) -> URL? {
if let bundleURL = Bundle(for: Self.self).url(forResource: resourceName, withExtension: fileExtension) {
let tempDirectoryURL = NSURL.fileURL(withPath: NSTemporaryDirectory(), isDirectory: true)
let targetURL = tempDirectoryURL.appendingPathComponent("\(UUID().uuidString).\(fileExtension)")
do {
try FileManager.default.copyItem(at: bundleURL, to: targetURL)
return targetURL
} catch let error {
Log.err("Unable to copy file: \(error)")
}
}
return nil
}
}

View File

@ -0,0 +1,38 @@
//
// ValetConfigParserTest.swift
// phpmon-tests
//
// Created by Nico Verbruggen on 29/11/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import XCTest
class ValetConfigParserTest: XCTestCase {
static var jsonConfigFileUrl: URL {
return Bundle(for: Self.self).url(
forResource: "valet-config",
withExtension: "json"
)!
}
func testCanLoadConfigFile() throws {
let json = try? String(
contentsOf: Self.jsonConfigFileUrl,
encoding: .utf8
)
let config = try! JSONDecoder().decode(
Valet.Configuration.self,
from: json!.data(using: .utf8)!
)
XCTAssertEqual(config.tld, "test")
XCTAssertEqual(config.paths, [
"/Users/username/.config/valet/Sites",
"/Users/username/Sites"
])
XCTAssertEqual(config.loopback, "127.0.0.1")
}
}

View File

@ -0,0 +1,18 @@
//
// ValetTest.swift
// phpmon-tests
//
// Created by Nico Verbruggen on 29/11/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import XCTest
class ValetTest: XCTestCase {
func testDetermineValetVersion() {
let version = valet("--version")
XCTAssert(version.contains("Laravel Valet 2."))
}
}

View File

@ -0,0 +1,25 @@
//
// VersionExtractorTest.swift
// phpmon-tests
//
// Created by Nico Verbruggen on 16/12/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import XCTest
class VersionExtractorTest: XCTestCase {
func testExtractVersion() {
XCTAssertEqual(VersionExtractor.from("Laravel Valet 2.17.1"), "2.17.1")
XCTAssertEqual(VersionExtractor.from("Laravel Valet 2.0"), "2.0")
}
func testVersionComparison() {
XCTAssertEqual("2.0".versionCompare("2.1"), .orderedAscending)
XCTAssertEqual("2.1".versionCompare("2.0"), .orderedDescending)
XCTAssertEqual("2.0".versionCompare("2.0"), .orderedSame)
XCTAssertEqual("2.17.0".versionCompare("2.17.1"), .orderedAscending)
}
}

View File

@ -1,396 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; };
C41C1B3922B0097F00E7CF16 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3822B0097F00E7CF16 /* ViewController.swift */; };
C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3A22B0098000E7CF16 /* Assets.xcassets */; };
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3C22B0098000E7CF16 /* Main.storyboard */; };
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4622B009A400E7CF16 /* Shell.swift */; };
C41C1B4922B00A9800E7CF16 /* ImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* ImageGenerator.swift */; };
C41C1B4B22B019FF00E7CF16 /* PhpVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */; };
C41C1B4D22B0215A00E7CF16 /* Services.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Services.swift */; };
C476FF9822B0DD830098105B /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; };
C4D8016622B1584700C6DA1B /* BootChecks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* BootChecks.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
C41C1B3322B0097F00E7CF16 /* phpmon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = phpmon.app; sourceTree = BUILT_PRODUCTS_DIR; };
C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
C41C1B3822B0097F00E7CF16 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
C41C1B3A22B0098000E7CF16 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
C41C1B3D22B0098000E7CF16 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
C41C1B3F22B0098000E7CF16 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = phpmon.entitlements; sourceTree = "<group>"; };
C41C1B4622B009A400E7CF16 /* Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shell.swift; sourceTree = "<group>"; };
C41C1B4822B00A9800E7CF16 /* ImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGenerator.swift; sourceTree = "<group>"; };
C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpVersion.swift; sourceTree = "<group>"; };
C41C1B4C22B0215A00E7CF16 /* Services.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Services.swift; sourceTree = "<group>"; };
C476FF9722B0DD830098105B /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = "<group>"; };
C4D8016522B1584700C6DA1B /* BootChecks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BootChecks.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
C41C1B3022B0097F00E7CF16 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
C41C1B2A22B0097F00E7CF16 = {
isa = PBXGroup;
children = (
C41C1B3522B0097F00E7CF16 /* phpmon */,
C41C1B3422B0097F00E7CF16 /* Products */,
);
sourceTree = "<group>";
};
C41C1B3422B0097F00E7CF16 /* Products */ = {
isa = PBXGroup;
children = (
C41C1B3322B0097F00E7CF16 /* phpmon.app */,
);
name = Products;
sourceTree = "<group>";
};
C41C1B3522B0097F00E7CF16 /* phpmon */ = {
isa = PBXGroup;
children = (
C41E181722CB61EB0072CF09 /* Classes */,
C41E181622CB61890072CF09 /* Startup */,
C41E181522CB614C0072CF09 /* Terminal */,
C41C1B4E22B024F100E7CF16 /* Helpers */,
C41E181822CB62200072CF09 /* View Controllers */,
C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */,
C41C1B3F22B0098000E7CF16 /* Info.plist */,
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */,
C41C1B3A22B0098000E7CF16 /* Assets.xcassets */,
);
path = phpmon;
sourceTree = "<group>";
};
C41C1B4E22B024F100E7CF16 /* Helpers */ = {
isa = PBXGroup;
children = (
C41C1B4822B00A9800E7CF16 /* ImageGenerator.swift */,
C476FF9722B0DD830098105B /* Alert.swift */,
);
path = Helpers;
sourceTree = "<group>";
};
C41E181522CB614C0072CF09 /* Terminal */ = {
isa = PBXGroup;
children = (
C41C1B4622B009A400E7CF16 /* Shell.swift */,
C41C1B4C22B0215A00E7CF16 /* Services.swift */,
);
path = Terminal;
sourceTree = "<group>";
};
C41E181622CB61890072CF09 /* Startup */ = {
isa = PBXGroup;
children = (
C4D8016522B1584700C6DA1B /* BootChecks.swift */,
);
path = Startup;
sourceTree = "<group>";
};
C41E181722CB61EB0072CF09 /* Classes */ = {
isa = PBXGroup;
children = (
C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */,
);
path = Classes;
sourceTree = "<group>";
};
C41E181822CB62200072CF09 /* View Controllers */ = {
isa = PBXGroup;
children = (
C41C1B3C22B0098000E7CF16 /* Main.storyboard */,
C41C1B3822B0097F00E7CF16 /* ViewController.swift */,
);
path = "View Controllers";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
C41C1B3222B0097F00E7CF16 /* phpmon */ = {
isa = PBXNativeTarget;
buildConfigurationList = C41C1B4322B0098000E7CF16 /* Build configuration list for PBXNativeTarget "phpmon" */;
buildPhases = (
C41C1B2F22B0097F00E7CF16 /* Sources */,
C41C1B3022B0097F00E7CF16 /* Frameworks */,
C41C1B3122B0097F00E7CF16 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = phpmon;
productName = phpmon;
productReference = C41C1B3322B0097F00E7CF16 /* phpmon.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
C41C1B2B22B0097F00E7CF16 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1020;
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = "Nico Verbruggen";
TargetAttributes = {
C41C1B3222B0097F00E7CF16 = {
CreatedOnToolsVersion = 10.2.1;
};
};
};
buildConfigurationList = C41C1B2E22B0097F00E7CF16 /* Build configuration list for PBXProject "phpmon" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = C41C1B2A22B0097F00E7CF16;
productRefGroup = C41C1B3422B0097F00E7CF16 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
C41C1B3222B0097F00E7CF16 /* phpmon */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
C41C1B3122B0097F00E7CF16 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */,
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
C41C1B2F22B0097F00E7CF16 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C4D8016622B1584700C6DA1B /* BootChecks.swift in Sources */,
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */,
C41C1B4D22B0215A00E7CF16 /* Services.swift in Sources */,
C41C1B4922B00A9800E7CF16 /* ImageGenerator.swift in Sources */,
C41C1B3922B0097F00E7CF16 /* ViewController.swift in Sources */,
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */,
C41C1B4B22B019FF00E7CF16 /* PhpVersion.swift in Sources */,
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
C41C1B3C22B0098000E7CF16 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
C41C1B3D22B0098000E7CF16 /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
C41C1B4122B0098000E7CF16 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
C41C1B4222B0098000E7CF16 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
C41C1B4422B0098000E7CF16 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
INFOPLIST_FILE = phpmon/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
C41C1B4522B0098000E7CF16 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
INFOPLIST_FILE = phpmon/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
C41C1B2E22B0097F00E7CF16 /* Build configuration list for PBXProject "phpmon" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C41C1B4122B0098000E7CF16 /* Debug */,
C41C1B4222B0098000E7CF16 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C41C1B4322B0098000E7CF16 /* Build configuration list for PBXNativeTarget "phpmon" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C41C1B4422B0098000E7CF16 /* Debug */,
C41C1B4522B0098000E7CF16 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = C41C1B2B22B0097F00E7CF16 /* Project object */;
}

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:phpmon.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -1,152 +0,0 @@
//
// AppDelegate.swift
// phpmon
//
// Created by Nico Verbruggen on 11/06/2019.
// Copyright © 2019 Nico Verbruggen. All rights reserved.
//
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
// MARK: - Variables
let statusItem = NSStatusBar.system.statusItem(withLength: 32)
var timer: Timer?
var version: PhpVersion? = nil
var availablePhpVersions : [String] = []
var busy: Bool = false
// MARK: - Lifecycle
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Start with the icon
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
// Perform environment boot checks
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
BootChecks.perform()
self.availablePhpVersions = Services.detectPhpVersions()
print("The following PHP versions were detected:")
print(self.availablePhpVersions)
self.updatePhpVersionInStatusBar()
// Schedule a request to fetch the PHP version every 15 seconds
Timer.scheduledTimer(
timeInterval: 15,
target: self,
selector: #selector(self.updatePhpVersionInStatusBar),
userInfo: nil,
repeats: true
)
}
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
// MARK: - UI related
func setStatusBarImage(version: String) {
self.setStatusBar(image: ImageGenerator.generateImageForStatusBar(width: 32.0, text: version))
}
func setStatusBar(image: NSImage) {
if let button = statusItem.button {
image.isTemplate = true
button.image = image
}
}
func updateMenu() {
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
let menu = NSMenu()
var string = "We are not sure what version of PHP you are running."
if (self.version != nil) {
string = "You are running PHP \(self.version!.long)"
}
menu.addItem(NSMenuItem(title: string, action: nil, keyEquivalent: ""))
if (self.version != nil) {
// Actions
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Open php.ini in Finder", action: #selector(self.openActiveConfigFolder), keyEquivalent: ""))
// menu.addItem(NSMenuItem(title: "Restart PHP \(self.version!.short) service", action: #selector(self.restartPhp), keyEquivalent: ""))
}
menu.addItem(NSMenuItem.separator())
if (self.availablePhpVersions.count > 0 && !self.busy) {
var shortcutKey = 1
for index in (0..<self.availablePhpVersions.count).reversed() {
let version = self.availablePhpVersions[index]
let action = #selector(self.switchToPhpVersion(sender:))
let menuItem = NSMenuItem(title: "Switch to PHP \(version)", action: (version == self.version?.short) ? nil : action, keyEquivalent: "\(shortcutKey)")
menuItem.tag = index
shortcutKey = shortcutKey + 1
menu.addItem(menuItem)
}
menu.addItem(NSMenuItem.separator())
}
if (self.busy) {
menu.addItem(NSMenuItem(title: "Switching PHP versions...", action: nil, keyEquivalent: ""))
menu.addItem(NSMenuItem.separator())
}
menu.addItem(NSMenuItem(title: "About phpmon", action: #selector(self.openAbout), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Quit phpmon", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
DispatchQueue.main.async {
self.statusItem.menu = menu
}
}
}
// MARK: - Callable via Obj-C (#selector)
@objc func updatePhpVersionInStatusBar() {
self.version = PhpVersion()
if (self.busy) {
DispatchQueue.main.async {
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
}
} else {
DispatchQueue.main.async {
self.setStatusBarImage(version: self.version!.short)
}
}
self.updateMenu()
}
@objc public func openAbout() {
NSApplication.shared.activate(ignoringOtherApps: true)
NSApplication.shared.orderFrontStandardAboutPanel()
}
@objc public func openActiveConfigFolder() {
Services.openPhpConfigFolder(version: self.version!.short)
}
@objc public func restartPhp() {
Services.restartPhp(version: self.version!.short)
}
@objc public func switchToPhpVersion(sender: AnyObject) {
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
let index = sender.tag!
let version = self.availablePhpVersions[index]
self.busy = true
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
// Update the PHP version in the status bar
self.updatePhpVersionInStatusBar()
// Update the menu
self.updateMenu()
// Switch the PHP version
Services.switchToPhpVersion(version: version, availableVersions: self.availablePhpVersions)
// Mark as no longer busy
self.busy = false
// Perform UI updates on main thread
DispatchQueue.main.async {
self.updatePhpVersionInStatusBar()
self.updateMenu()
}
}
}
}

View File

@ -1,68 +1,68 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "icon_16x16.png",
"scale" : "1x"
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "icon_16x16@2x.png",
"scale" : "2x"
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "icon_32x32.png",
"scale" : "1x"
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "icon_32x32@2x.png",
"scale" : "2x"
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "icon_128x128.png",
"scale" : "1x"
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "icon_128x128@2x.png",
"scale" : "2x"
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "icon_256x256.png",
"scale" : "1x"
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "icon_256x256@2x.png",
"scale" : "2x"
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "icon_512x512.png",
"scale" : "1x"
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "icon_512x512@2x.png",
"scale" : "2x"
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 647 B

After

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 KiB

After

Width:  |  Height:  |  Size: 148 KiB

View File

@ -0,0 +1,68 @@
{
"images" : [
{
"filename" : "icon_16x16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "icon_16x16@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "icon_32x32.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "icon_32x32@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "icon_128x128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "icon_128x128@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "icon_256x256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "icon_256x256@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "icon_512x512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "icon_512x512@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

View File

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

View File

@ -0,0 +1 @@
<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="M435.848 83.466L172.804 346.51l-96.652-96.652c-4.686-4.686-12.284-4.686-16.971 0l-28.284 28.284c-4.686 4.686-4.686 12.284 0 16.971l133.421 133.421c4.686 4.686 12.284 4.686 16.971 0l299.813-299.813c4.686-4.686 4.686-12.284 0-16.971l-28.284-28.284c-4.686-4.686-12.284-4.686-16.97 0z"/></svg>

After

Width:  |  Height:  |  Size: 497 B

View File

@ -1,6 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}

View 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
}
}

View File

@ -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
}
}

View 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
}
}

View File

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

View File

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

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

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

View File

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

After

Width:  |  Height:  |  Size: 918 B

View File

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

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<rect id="Locked" x="0" y="0" width="30" height="30" style="fill:none;"/>
<g id="Locked1" serif:id="Locked">
<g transform="matrix(0.0468317,0,0,0.0468317,4.50971,3.01112)">
<path d="M400,256L152,256L152,152.9C152,113.3 183.7,80.4 223.3,80C263.3,79.6 296,112.1 296,152L296,266.079C296,279.379 376,279.137 376,265.837L376,152C376,68 307.5,-0.3 223.5,0C139.5,0.3 72,69.5 72,153.5L72,256L48,256C21.5,256 0,277.5 0,304L0,464C0,490.5 21.5,512 48,512L400,512C426.5,512 448,490.5 448,464L448,304C448,277.5 426.5,256 400,256Z" style="fill-rule:nonzero;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

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

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<rect id="Locked" x="0" y="0" width="30" height="30" style="fill:none;"/>
<g id="Locked1" serif:id="Locked">
<g transform="matrix(0.0468317,0,0,0.0468317,4.50971,3.01112)">
<path d="M400,256L152,256L152,152.9C152,113.3 183.7,80.4 223.3,80C263.3,79.6 296,112.1 296,152L296,168C296,181.3 322.386,192 322.386,192L352,192C365.3,192 376,181.3 376,168L376,152C376,68 307.5,-0.3 223.5,0C139.5,0.3 72,69.5 72,153.5L72,256L48,256C21.5,256 0,277.5 0,304L0,464C0,490.5 21.5,512 48,512L400,512C426.5,512 448,490.5 448,464L448,304C448,277.5 426.5,256 400,256Z" style="fill-rule:nonzero;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

View File

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

After

Width:  |  Height:  |  Size: 966 B

View File

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

View File

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

After

Width:  |  Height:  |  Size: 685 B

View File

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

View File

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

After

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 B

After

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 780 B

After

Width:  |  Height:  |  Size: 500 B

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