Compare commits
244 Commits
Author | SHA1 | Date | |
---|---|---|---|
1066bdc653 | |||
e45db50f48 | |||
8a7f045bb2 | |||
10272f3d7e | |||
96ce462021 | |||
8b635f2a14 | |||
f8f3fd5c9b | |||
6801283597 | |||
ea7572ffbb | |||
4d5b5de84b | |||
721bb32087 | |||
1fe086d53e | |||
347a9b7345 | |||
e0c087dbcb | |||
fd34deb7bd | |||
ccb0d96453 | |||
d821968a49 | |||
7d7a38546a | |||
30059353fe | |||
090440abc8 | |||
ceeba611d0 | |||
f1feb11baa | |||
90a02f364a | |||
6b8c68b058 | |||
fb34ea7c89 | |||
507d4785aa | |||
0c0045aead | |||
1fdf687c15 | |||
1040bcd037 | |||
655cd52ac4 | |||
2229f01eb0 | |||
f95eb7023f | |||
320e1ad3c5 | |||
e1880d9dfc | |||
998bbd231a | |||
fbf2158488 | |||
94df551b4b | |||
01288154a7 | |||
29b4fe2962 | |||
5907d9f689 | |||
86d74619b1 | |||
0c09e808bd | |||
da8659adba | |||
bbebe78997 | |||
19aa804cbb | |||
64491c6fe1 | |||
382cb177be | |||
e9f0d19d9a | |||
7709cd9f6c | |||
83eac7bf04 | |||
db8197df3d | |||
40e404fe24 | |||
e7f80ebce8 | |||
990152d77d | |||
2e61479c75 | |||
0579ebb1c1 | |||
f9df86851c | |||
b0c62e226a | |||
1392b6e4a0 | |||
12163bc87b | |||
c645bb7610 | |||
e6574966da | |||
1e9cfff05e | |||
bd34c2b255 | |||
c040ac3200 | |||
6c6888c9cb | |||
78cb6922b3 | |||
c16377c688 | |||
540ea5c310 | |||
4ba2b25f18 | |||
81b75dcaa8 | |||
884784d024 | |||
e81ff2870d | |||
7c631099b2 | |||
f7a98b88a7 | |||
3fc21fff2a | |||
0306c2b726 | |||
9d822df54e | |||
f413b84a45 | |||
b82811e6bf | |||
af922664ab | |||
8b73e69495 | |||
29a9e14741 | |||
997fb27596 | |||
c171df0a93 | |||
1c15a4e07f | |||
5067c7b87f | |||
f679231ade | |||
f725e09f55 | |||
99881bf4cd | |||
2987464da8 | |||
4d04275c57 | |||
790f63e8c9 | |||
86b49812c3 | |||
ef9e0fd916 | |||
af8807f799 | |||
4eea13f059 | |||
ba93ed93e4 | |||
932a0fe176 | |||
3cff2d6469 | |||
df506e4128 | |||
eb80214785 | |||
80a4e361a4 | |||
2af88b2bee | |||
5048ccab8c | |||
66d13c92d5 | |||
836b076da9 | |||
1a75838a3b | |||
a18b7962a7 | |||
84548634ec | |||
419ebe61f7 | |||
c45817b127 | |||
2c0c0c5a11 | |||
1b8d6311ba | |||
f0f7a3f7d6 | |||
8304d774c3 | |||
faeea4e866 | |||
6470daf7d3 | |||
94139a3669 | |||
f3b1172d0e | |||
8057019898 | |||
9b59fc5dae | |||
75f4377de8 | |||
d3657716c4 | |||
a13990b96f | |||
4c7aa7fead | |||
32278533bf | |||
39908f7fc5 | |||
347d79c88d | |||
9bc117e9f5 | |||
ba5fbed9be | |||
211556d5ce | |||
066d7bc217 | |||
f072ceae37 | |||
273dac1ca7 | |||
f3b170ba14 | |||
78d8030ed6 | |||
a300d2f4cf | |||
96a658073e | |||
8395ba407d | |||
f80e3fed2b | |||
b48edf7409 | |||
e0a0eb089d | |||
60b126333d | |||
649a3f4fb5 | |||
9a326928f3 | |||
52f87ca18a | |||
ad2d2cb57f | |||
22c0021ada | |||
2cfc5731fb | |||
1d74e1536c | |||
e6b2ddf2ad | |||
62fda6224e | |||
083f8ebec8 | |||
aec8fb1168 | |||
d5d9b38ed6 | |||
9a6975e3d9 | |||
d1c40f2eb5 | |||
ad58661449 | |||
1d6cfd419e | |||
15182ea15a | |||
c43e00c88d | |||
25c7004368 | |||
02ba57cd64 | |||
c2dc6302c0 | |||
af9f30a123 | |||
28c5754800 | |||
48c1d48573 | |||
582bf0e12c | |||
46b30bbff4 | |||
372011ca08 | |||
7255792910 | |||
0c96b11b05 | |||
ea4da12d3b | |||
8419ebad10 | |||
09a5cf836a | |||
1a1a53b472 | |||
a8bad8447a | |||
ca8f5a8fbe | |||
a0e7aec228 | |||
26badc759e | |||
e21c2168ea | |||
589ab3664e | |||
48b4f9b160 | |||
139e416c3b | |||
ba4ed3b365 | |||
06a8022265 | |||
3b297e07dc | |||
68fa8e523e | |||
768bf06a9d | |||
6a8d66758a | |||
078e6e6f23 | |||
3f80bfb641 | |||
a34389c3a9 | |||
692d3c143f | |||
bc86a45925 | |||
2a412b794a | |||
bf673263d8 | |||
ce498d3019 | |||
e398f089af | |||
e8ba24e48b | |||
e0f40be188 | |||
42848764cf | |||
46ac0c339c | |||
a0fe68f3ab | |||
c952c3d031 | |||
c05cdeda72 | |||
ae7b285eb0 | |||
6b3c562af2 | |||
e3ae878cae | |||
dd43c94e6e | |||
0e8fe1fcfb | |||
293b7f564e | |||
634ffb4c57 | |||
9468a2e9f8 | |||
921ecd99d6 | |||
d06f92051d | |||
97d68f89f1 | |||
115863f1ee | |||
bc27a4d25a | |||
5a0b2f319b | |||
50f003a2bd | |||
bc0b50f5bf | |||
dd20b25900 | |||
d0469467ac | |||
61427ec505 | |||
6cd1d78572 | |||
0ad5049785 | |||
b5c1960260 | |||
e6fbe7c4a4 | |||
537536d443 | |||
f702d14749 | |||
74bb544f3c | |||
dae47e3779 | |||
dc91d0e00c | |||
6187eb3e4e | |||
0fa6d337f2 | |||
a10e1cad11 | |||
7c252deede | |||
|
224c5c4fe2 | ||
e945b5fe94 | |||
|
23e534c5c9 | ||
dcddf74830 | |||
9baf69394e |
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Something going wrong? File a bug report!
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: nicoverbruggen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Required information**
|
||||||
|
- Did you consult the FAQ in the README? [yes/no]
|
||||||
|
- Did you try "Fix My Valet"? [yes/no]
|
||||||
|
- OS: [e.g. macOS Monterey]
|
||||||
|
- PHP Monitor version [e.g. v5.0.1]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an enhancement.
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: nicoverbruggen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Enhancement requests that are not immediately approved will be moved to a discussion instead._
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
15
.swiftlint.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
disabled_rules:
|
||||||
|
- todo
|
||||||
|
- identifier_name
|
||||||
|
- force_try
|
||||||
|
- force_cast
|
||||||
|
|
||||||
|
opt_in_rules:
|
||||||
|
- empty_count
|
||||||
|
|
||||||
|
included:
|
||||||
|
- phpmon
|
||||||
|
- phpmon-tests
|
||||||
|
|
||||||
|
excluded:
|
||||||
|
- phpmon/Vendor
|
34
DEVELOPER.md
@@ -1,5 +1,19 @@
|
|||||||
# DEVELOPER README
|
# DEVELOPER README
|
||||||
|
|
||||||
|
## ✅ Linting
|
||||||
|
|
||||||
|
This project uses the [SwiftLint](https://github.com/realm/SwiftLint) linter. You must install it and can run it like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
swiftlint
|
||||||
|
```
|
||||||
|
|
||||||
|
It also automatically runs when you try to build the project. You'll get a warning if `swiftlint` is not installed, though. You can attempt to automatically fix issues:
|
||||||
|
|
||||||
|
```
|
||||||
|
swiftlint --fix
|
||||||
|
```
|
||||||
|
|
||||||
## 🔧 Build instructions
|
## 🔧 Build instructions
|
||||||
|
|
||||||
<img src="./docs/build.png" width="404px" alt="build button in Xcode"/>
|
<img src="./docs/build.png" width="404px" alt="build button in Xcode"/>
|
||||||
@@ -13,6 +27,26 @@ Once you have downloaded this repository, open `PHP Monitor.xcodeproj`, and you
|
|||||||
|
|
||||||
If you'd like to create a production build, choose "Any Mac" as the target and select Product > Archive.
|
If you'd like to create a production build, choose "Any Mac" as the target and select Product > Archive.
|
||||||
|
|
||||||
|
## 🚀 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
|
||||||
|
|
||||||
|
## 🍱 Marketing Mode
|
||||||
|
|
||||||
|
You can enable marketing mode by setting the `PHPMON_MARKETING_MODE` environment variable. It preloads a list of (fake) domains in the domain window list for screenshot & marketing purposes.
|
||||||
|
|
||||||
|
launchctl setenv PHPMON_MARKETING_MODE true
|
||||||
|
|
||||||
## 🐛 Symbolication of crashes
|
## 🐛 Symbolication of crashes
|
||||||
|
|
||||||
If you have an archived build of the app and exported the DSYM, it is possible to symbolicate .ips crash logs.
|
If you have an archived build of the app and exported the DSYM, it is possible to symbolicate .ips crash logs.
|
||||||
|
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019 Nico Verbruggen
|
Copyright (c) 2019-2022 Nico Verbruggen
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@@ -61,6 +61,19 @@
|
|||||||
ReferencedContainer = "container:PHP Monitor.xcodeproj">
|
ReferencedContainer = "container:PHP Monitor.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
|
<CommandLineArguments>
|
||||||
|
<CommandLineArgument
|
||||||
|
argument = "--v"
|
||||||
|
isEnabled = "NO">
|
||||||
|
</CommandLineArgument>
|
||||||
|
</CommandLineArguments>
|
||||||
|
<EnvironmentVariables>
|
||||||
|
<EnvironmentVariable
|
||||||
|
key = "PAINT_PHPMON_SWIFTUI_VIEWS"
|
||||||
|
value = ""
|
||||||
|
isEnabled = "NO">
|
||||||
|
</EnvironmentVariable>
|
||||||
|
</EnvironmentVariables>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Release"
|
||||||
|
176
README.md
@@ -1,34 +1,34 @@
|
|||||||
> 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.
|
> 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, as this is something I make in my free time. **Thank you!** ⭐️
|
||||||
> You can also send me [feedback](https://twitter.com/nicoverbruggen) if the app came in handy.<br>**Thank you!** ⭐️
|
|
||||||
|
|
||||||
<h1 align="center"><b>PHP Monitor</b> (phpmon)</h1>
|
<p align="center"><img src="./docs/logo.png" alt="PHP Monitor Logo" width="500px" /></p>
|
||||||
|
|
||||||
<p align="center">
|
**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 app</u> (consult the FAQ below with info about how to set up your environment).
|
||||||
<img src="./phpmon/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png" alt="phpmon icon" width="128px" />
|
|
||||||
</p>
|
|
||||||
|
|
||||||
**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>.
|
<img src="./docs/screenshot.jpg#gh-light-mode-only" width="1280px" alt="phpmon screenshot (menu bar app)"/>
|
||||||
|
<img src="./docs/screenshot-dark.jpg#gh-dark-mode-only" width="1280px" alt="phpmon screenshot (menu bar app)"/>
|
||||||
|
|
||||||
<img src="./docs/screenshot50.jpg" width="1085px" alt="phpmon screenshot (menu bar app)"/>
|
<small><i>Screenshot: Showing the key functionality of PHP Monitor.</i></small>
|
||||||
|
|
||||||
<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)!
|
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)"/>
|
<img src="./docs/notification.png#gh-light-mode-only" width="370px" alt="phpmon screenshot (notification)"/>
|
||||||
|
<img src="./docs/notification-dark.png#gh-dark-mode-only" 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).
|
PHP Monitor also gives you quick access to various useful functionality (like accessing configuration files, restarting services, and more).
|
||||||
|
|
||||||
|
You can also add new domains as links, isolate sites, manage various services, and perform First Aid to fix all kinds of common PHP link issues.
|
||||||
|
|
||||||
## 🖥 System requirements
|
## 🖥 System requirements
|
||||||
|
|
||||||
PHP Monitor is a universal application that runs natively on Apple Silicon **and** Intel-based Macs.
|
PHP Monitor is a universal application that runs natively on Apple Silicon **and** Intel-based Macs.
|
||||||
|
|
||||||
* macOS 11 Big Sur or higher (supports macOS 12 Monterey)
|
* Your user account can administer your computer (required for some functionality, e.g. certificate generation)
|
||||||
|
* macOS 11 Big Sur or later
|
||||||
* Homebrew is installed in `/usr/local/homebrew` or `/opt/homebrew`
|
* Homebrew is installed in `/usr/local/homebrew` or `/opt/homebrew`
|
||||||
* The brew formula `php` has to be installed (which version is detected)
|
* Homebrew `php` formula is installed
|
||||||
* Laravel Valet 2.16.2 or higher (older versions might be compatible but are not supported)
|
* Laravel Valet 3 recommended (but compatible with Valet 2)
|
||||||
|
|
||||||
_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`._
|
_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`. Some features are not supported when running Valet 2._
|
||||||
|
|
||||||
## 🚀 How to install
|
## 🚀 How to install
|
||||||
|
|
||||||
@@ -43,7 +43,13 @@ To upgrade your existing installation, run:
|
|||||||
|
|
||||||
brew upgrade phpmon
|
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.)
|
(You may need to run `brew update` or `brew update-reset` first in order to update the cask file if you ran a Homebrew operation recently.)
|
||||||
|
|
||||||
|
## ⚡️ Launchers (Alfred, Raycast)
|
||||||
|
|
||||||
|
If you would like to integrate with your launcher of choice, you can also download an [Alfred workflow](https://github.com/nicoverbruggen/phpmon/raw/main/integrations/phpmon.alfredworkflow) or [Raycast extension](https://www.raycast.com/nicoverbruggen/php-monitor) that works with PHP Monitor.
|
||||||
|
|
||||||
|
The app must be running in the background for these to work, and the _Allow third-party integrations_ checkbox must be enabled in Preferences (it is by default).
|
||||||
|
|
||||||
## 🔑 Is the app signed & notarized?
|
## 🔑 Is the app signed & notarized?
|
||||||
|
|
||||||
@@ -73,7 +79,7 @@ If you're still having issues, here's a few common questions & answers, as well
|
|||||||
<summary><strong>Which versions of PHP are supported?</strong></summary>
|
<summary><strong>Which versions of PHP are supported?</strong></summary>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>PHP 5.6</li>
|
<li>PHP 5.6 (only if you are running Valet 2)</li>
|
||||||
<li>PHP 7.0</li>
|
<li>PHP 7.0</li>
|
||||||
<li>PHP 7.1</li>
|
<li>PHP 7.1</li>
|
||||||
<li>PHP 7.2</li>
|
<li>PHP 7.2</li>
|
||||||
@@ -84,7 +90,7 @@ If you're still having issues, here's a few common questions & answers, as well
|
|||||||
<li>PHP 8.2 (experimental)</li>
|
<li>PHP 8.2 (experimental)</li>
|
||||||
</ul>
|
</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.
|
For more details, consult the [constants file](https://github.com/nicoverbruggen/phpmon/blob/main/phpmon/Common/Core/Constants.swift#L16) file to see which versions are supported.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -101,12 +107,10 @@ Super convenient!
|
|||||||
|
|
||||||
If you want to set up your computer for the very first time with PHP Monitor, here's how I do it:
|
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 [Homebrew](https://brew.sh) first. Follow the instructions there first!
|
||||||
|
|
||||||
Install PHP, composer, add to path:
|
Then, you'll need to set up your PATH.
|
||||||
|
|
||||||
brew install php
|
|
||||||
brew install composer
|
|
||||||
nano .zshrc
|
nano .zshrc
|
||||||
|
|
||||||
Make sure the following line is not in the comments:
|
Make sure the following line is not in the comments:
|
||||||
@@ -119,21 +123,27 @@ If you're on an Apple Silicon-based Mac, you'll need to add:
|
|||||||
# on an M1 Mac
|
# on an M1 Mac
|
||||||
export PATH=$HOME/bin:/opt/homebrew/bin:$PATH
|
export PATH=$HOME/bin:/opt/homebrew/bin:$PATH
|
||||||
|
|
||||||
and add the following to your .zshrc, but add this BEFORE the homebrew PATH additions:
|
and add the following to your `.zshrc` file, but add this BEFORE the homebrew PATH additions:
|
||||||
|
|
||||||
export PATH=$HOME/bin:~/.composer/vendor/bin:$PATH
|
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:
|
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:/usr/local/bin:$PATH
|
||||||
export PATH=$HOME/bin:~/.composer/vendor/bin:$PATH
|
export PATH=$HOME/bin:~/.composer/vendor/bin:$PATH
|
||||||
export PATH=$HOME/bin:/opt/homebrew/bin:$PATH
|
export PATH=$HOME/bin:/opt/homebrew/bin:$PATH
|
||||||
|
|
||||||
|
If you are *not* on Apple Silicon, you should remove the third line.
|
||||||
|
|
||||||
|
Install the `php` and `composer` formulae:
|
||||||
|
|
||||||
|
brew install php composer
|
||||||
|
|
||||||
Make sure PHP is linked correctly:
|
Make sure PHP is linked correctly:
|
||||||
|
|
||||||
which php
|
which php
|
||||||
|
|
||||||
should return: `/usr/local/bin/php` (or `/opt/homebrew/bin/php`)
|
should return: `/usr/local/bin/php` (or `/opt/homebrew/bin/php` if you are on Apple Silicon)
|
||||||
|
|
||||||
composer global require laravel/valet
|
composer global require laravel/valet
|
||||||
valet install
|
valet install
|
||||||
@@ -142,7 +152,44 @@ This should install `dnsmasq` and set up Valet. Great, almost there!
|
|||||||
|
|
||||||
valet trust
|
valet trust
|
||||||
|
|
||||||
Finally, run PHP Monitor. Since the app is notarized and signed with a developer ID, it should work.
|
You can now install PHP Monitor, if you haven't already:
|
||||||
|
|
||||||
|
brew tap nicoverbruggen/homebrew-cask
|
||||||
|
brew install --cask phpmon
|
||||||
|
|
||||||
|
Finally, run PHP Monitor. Since the app is notarized and signed with a developer ID, it should work. You will need to approve the initial launch of the app, but you should be ready to go now.
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>How frequently does PHP Monitor check for updates?</strong></summary>
|
||||||
|
|
||||||
|
PHP Monitor will check if an update is available every time you start the app.
|
||||||
|
|
||||||
|
You can disable this behaviour by going to Preferences (via the PHP Monitor icon in the menu bar) and unchecking "Automatically check for updates". You can always check for updates manually.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>I have PHP Monitor installed, and it works. I want to upgrade my PHP installations to the latest version, what's the best way to do this?</strong></summary>
|
||||||
|
|
||||||
|
It's easy to make a mistake here, and end up with an unlinked version of PHP or have versions missing from PHP Monitor.
|
||||||
|
|
||||||
|
Here's what I usually do:
|
||||||
|
|
||||||
|
* Open PHP Monitor and select **First Aid & Services** > **Restore Homebrew Permissions**.
|
||||||
|
* Close PHP Monitor after the pop-up tells you the permissions were restored.
|
||||||
|
* Run `brew update-reset`
|
||||||
|
* Run `brew upgrade`
|
||||||
|
|
||||||
|
If after this, any PHP versions are missing in PHP Monitor, please run the following for the versions that are missing:
|
||||||
|
|
||||||
|
* Run `brew uninstall php@x.x` (where `x.x` is the version)
|
||||||
|
* Run `brew cleanup` (if you get any permission issues you may need to manually clean up the folder)
|
||||||
|
* Run `brew install php@x.x` (where `x.x` is the version)
|
||||||
|
|
||||||
|
You may still need to run `brew link php` after upgrading, too.
|
||||||
|
|
||||||
|
That's it. Now start up PHP Monitor again and you should be golden!
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -197,6 +244,12 @@ 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).
|
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>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>The option to isolate a site is disabled! What's going on?</strong></summary>
|
||||||
|
|
||||||
|
Make sure you have at least **Valet 3.0** installed, since support for isolation was added in this version of Valet. (Please note that this version of Valet drops support for PHP 5.6.)
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><strong>One of the limits (memory limit, max POST size, max upload size) shows an exclamation mark!</strong></summary>
|
<summary><strong>One of the limits (memory limit, max POST size, max upload size) shows an exclamation mark!</strong></summary>
|
||||||
@@ -231,17 +284,60 @@ Since v3.4 all of the loaded .ini files are sourced to determine which extension
|
|||||||
<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>
|
<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.
|
If you are using PHP Monitor on an Intel machine or on an Apple Silicon machine with Rosetta enabled, PHP Monitor expects the main Homebrew binary in `/usr/local/bin/brew`.
|
||||||
|
|
||||||
|
If you are using PHP Monitor on Apple Silicon without Rosetta, PHP Monitor expects the main Homebrew binary in `/opt/homebrew/bin/brew`.
|
||||||
|
|
||||||
|
If there's an issue here, you'll get an alert at launch.
|
||||||
|
|
||||||
|
Make sure that the version of Homebrew that you are running normally is the same as the one that PHP Monitor expects. If you are on M1 hardware for example, but still using Rosetta for Homebrew, you'll need to run PHP Monitor under Rosetta as well.
|
||||||
|
|
||||||
|
PHP Monitor is a universal app and supports both architectures, so [find out here](https://support.apple.com/en-us/HT211861) how to enable Rosetta with PHP Monitor.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><strong>Why is the app doing network requests?</strong></summary>
|
<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.
|
The app will automatically check for updates, which is the most likely culprit.
|
||||||
|
|
||||||
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.
|
This happens at launch (unless disabled), and the app directly checks the Caskfile hosted on GitHub. This data is not, and will not be used for analytics (and, as far as I can tell, cannot).
|
||||||
|
|
||||||
|
I also can't prevent `brew` from doing things via the network when PHP Monitor uses the binary.
|
||||||
|
|
||||||
|
The app includes an Internet Access Policy file, so if you're using something like Little Snitch there should be a description why these calls occur.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>How do I various presets to show up?</strong></summary>
|
||||||
|
|
||||||
|
You must set these presets up in a JSON file, located in `~/.config/phpmon/config.json`.
|
||||||
|
|
||||||
|
You must have set up at least one valid preset for this presets to work in PHP Monitor.
|
||||||
|
|
||||||
|
Here's an example of a working preset:
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
{
|
||||||
|
"scan_apps": [],
|
||||||
|
"presets": [
|
||||||
|
{
|
||||||
|
"name": "Legacy Project",
|
||||||
|
"php": "8.0",
|
||||||
|
"extensions": {
|
||||||
|
"xdebug": false
|
||||||
|
},
|
||||||
|
"configuration": {
|
||||||
|
"memory_limit": "128M",
|
||||||
|
"upload_max_filesize": "128M",
|
||||||
|
"post_max_size": "128M"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
You can omit the `php` key in the preset if you do not wish for the preset to switch to a given PHP version.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -255,11 +351,12 @@ All of these apps should just be detected correctly, no matter their location on
|
|||||||
|
|
||||||
To see which files are checked to determine availability, see [this file](./phpmon/Domain/Helpers/Application.swift).
|
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:
|
You can add your own apps by creating and editing a `~/.config/phpmon/config.json` file, and make sure the `scan_apps` key is set:
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
{
|
{
|
||||||
"scan_apps": ["Xcode", "Kraken"]
|
"scan_apps": ["Xcode", "Kraken"],
|
||||||
|
"presets": []
|
||||||
}
|
}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
@@ -267,9 +364,11 @@ You can put as many apps as you'd like in the `scan_apps` array, and PHP Monitor
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><strong>How can the app integrate with third party tools, like Alfred?</strong></summary>
|
<summary><strong>How can the app integrate with third party tools, like Alfred or Raycast?</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`.
|
PHP Monitor supports third party app integrations by default, and this feature is enabled in Preferences unless you disable it.
|
||||||
|
|
||||||
|
You can grab the official [Alfred workflow](https://github.com/nicoverbruggen/phpmon/raw/main/integrations/phpmon.alfredworkflow) or [Raycast extension](https://www.raycast.com/nicoverbruggen/php-monitor).
|
||||||
|
|
||||||
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).
|
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).
|
||||||
|
|
||||||
@@ -351,13 +450,14 @@ Donations really help with the Apple Developer Program cost, and keep me motivat
|
|||||||
|
|
||||||
## 😎 Acknowledgements
|
## 😎 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:
|
While I did make this application during my own free time, PHP Monitor started out from various learning experiments during work hours at my employer, DIVE. I'd also like to shout out the following folks:
|
||||||
|
|
||||||
* My colleagues at [DIVE](https://dive.be)
|
* My colleagues at [DIVE](https://dive.be)
|
||||||
* The [Homebrew](https://brew.sh/) team & [Valet maintainers](https://github.com/laravel/valet/graphs/contributors)
|
* The [Homebrew](https://brew.sh/) team & [Valet maintainers](https://github.com/laravel/valet/graphs/contributors)
|
||||||
* Various folks who [reached](https://twitter.com/stauffermatt) [out](https://twitter.com/marcelpociot) when PHP Monitor was still very much a small app with a handful of stars or so
|
* 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
|
||||||
|
* My [GitHub Sponsors](https://github.com/sponsors/nicoverbruggen) and those who have donated
|
||||||
|
* Everyone who has left feedback and reported bugs (appreciate it!)
|
||||||
* Everyone in the Laravel community who shared the app (thanks!)
|
* 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.
|
Thank you very much for your contributions, kind words and support.
|
||||||
|
|
||||||
@@ -373,7 +473,9 @@ In order to save power, this only happens once every 60 seconds.
|
|||||||
|
|
||||||
This utility will detect which PHP versions you have installed via Homebrew, and then allows you to switch between them.
|
This utility will detect which PHP versions you have installed via Homebrew, and then allows you to switch between them.
|
||||||
|
|
||||||
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 Valet’s switcher.
|
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 a bit faster than Valet’s switcher.
|
||||||
|
|
||||||
|
If you're using Valet 3, versions of PHP-FPM required to keep isolated sites up and running will also be started or stopped as needed.
|
||||||
|
|
||||||
### Config change detection
|
### Config change detection
|
||||||
|
|
||||||
@@ -399,4 +501,4 @@ I have done my best to annotate as much as humanly possible, and have avoided us
|
|||||||
|
|
||||||
I also have a few tests for key parts of the application that I found needed to be tested. In the future, I would like to add even more tests for some of the UI stuff, but for now the tests are more unit tests than feature tests.
|
I also have a few tests for key parts of the application that I found needed to be tested. In the future, I would like to add even more tests for some of the UI stuff, but for now the tests are more unit tests than feature tests.
|
||||||
|
|
||||||
For more detailed information for developers, please see [the documentation file for developers](./DEVS.md).
|
For more detailed information for developers, please see [the documentation file for developers](./DEVELOPER.md).
|
||||||
|
13
RELEASE.md
@@ -1,13 +0,0 @@
|
|||||||
# 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
|
|
12
SECURITY.md
@@ -4,9 +4,11 @@
|
|||||||
|
|
||||||
Generally speaking, only the latest version of **PHP Monitor** is supported, except during transition periods (for example, when particular system requirements go up):
|
Generally speaking, only the latest version of **PHP Monitor** is supported, except during transition periods (for example, when particular system requirements go up):
|
||||||
|
|
||||||
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version |
|
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Recommended 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 |
|
| 5.x | ✅ Universal binary | ✅ Yes | Big Sur (11.0)<br/>Monterey (12.0)<br/>Ventura (13.0)* | macOS 11+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.2 (w/ Valet 3.x) | 3.0 recommended<br/> 2.16.2 minimum |
|
||||||
|
|
||||||
|
_(*) macOS Ventura (13.0) is not officially supported until it officially releases._
|
||||||
|
|
||||||
## Legacy versions
|
## Legacy versions
|
||||||
|
|
||||||
@@ -14,9 +16,9 @@ These versions of PHP Monitor are no longer supported, but if you’re using an
|
|||||||
|
|
||||||
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version |
|
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version |
|
||||||
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
|
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
|
||||||
| 4.1 | ✅ Universal binary | ❌ | Big Sur (11.0) and Monterey (12.0) | macOS 11+ | PHP 5.6—PHP 8.2 | 2.16.2 |
|
| 4.1 | ✅ Universal binary | ❌ | Big Sur (11.0)<br/>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 |
|
| 4.0 | ✅ Universal binary | ❌ | Big Sur (11.0)<br/>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.5 | ✅ Universal binary | ❌ | Big Sur (11.0)<br/>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 |
|
| 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.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.5 | ✴️ Universal binary<br/>`/usr/local/homebrew` installations only | ❌ | Big Sur (11.0)<br/>Catalina (10.15) | macOS 10.14+ | not applicable | not applicable |
|
||||||
|
BIN
assets/affinity/icon.afdesign
Normal file
BIN
assets/affinity/icons.afdesign
Normal file
BIN
docs/logo.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
docs/notification-dark.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
docs/screenshot-dark.jpg
Normal file
After Width: | Height: | Size: 454 KiB |
BIN
docs/screenshot.jpg
Normal file
After Width: | Height: | Size: 469 KiB |
Before Width: | Height: | Size: 370 KiB |
@@ -3,7 +3,7 @@
|
|||||||
// phpmon-tests
|
// phpmon-tests
|
||||||
//
|
//
|
||||||
// Created by Nico Verbruggen on 13/02/2021.
|
// Created by Nico Verbruggen on 13/02/2021.
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import XCTest
|
import XCTest
|
||||||
@@ -15,11 +15,11 @@ class CommandTest: XCTestCase {
|
|||||||
path: Paths.php,
|
path: Paths.php,
|
||||||
arguments: ["-v"]
|
arguments: ["-v"]
|
||||||
)
|
)
|
||||||
|
|
||||||
XCTAssert(version.contains("(cli)"))
|
XCTAssert(version.contains("(cli)"))
|
||||||
XCTAssert(version.contains("NTS"))
|
XCTAssert(version.contains("NTS"))
|
||||||
XCTAssert(version.contains("built"))
|
XCTAssert(version.contains("built"))
|
||||||
XCTAssert(version.contains("Zend"))
|
XCTAssert(version.contains("Zend"))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -3,18 +3,18 @@
|
|||||||
// phpmon-tests
|
// phpmon-tests
|
||||||
//
|
//
|
||||||
// Created by Nico Verbruggen on 14/02/2021.
|
// Created by Nico Verbruggen on 14/02/2021.
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
class BrewJsonParserTest: XCTestCase {
|
class HomebrewPackageTest: XCTestCase {
|
||||||
|
|
||||||
// - MARK: SYNTHETIC TESTS
|
// - MARK: SYNTHETIC TESTS
|
||||||
|
|
||||||
static var jsonBrewFile: URL {
|
static var jsonBrewFile: URL {
|
||||||
return Bundle(for: Self.self)
|
return Bundle(for: Self.self)
|
||||||
.url(forResource: "brew", withExtension: "json")!
|
.url(forResource: "brew-formula", withExtension: "json")!
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCanLoadExtensionJson() throws {
|
func testCanLoadExtensionJson() throws {
|
||||||
@@ -22,7 +22,7 @@ class BrewJsonParserTest: XCTestCase {
|
|||||||
let package = try! JSONDecoder().decode(
|
let package = try! JSONDecoder().decode(
|
||||||
[HomebrewPackage].self, from: json.data(using: .utf8)!
|
[HomebrewPackage].self, from: json.data(using: .utf8)!
|
||||||
).first!
|
).first!
|
||||||
|
|
||||||
XCTAssertEqual(package.name, "php")
|
XCTAssertEqual(package.name, "php")
|
||||||
XCTAssertEqual(package.full_name, "php")
|
XCTAssertEqual(package.full_name, "php")
|
||||||
XCTAssertEqual(package.aliases.first!, "php@8.0")
|
XCTAssertEqual(package.aliases.first!, "php@8.0")
|
||||||
@@ -30,23 +30,23 @@ class BrewJsonParserTest: XCTestCase {
|
|||||||
installed.version.starts(with: "8.0")
|
installed.version.starts(with: "8.0")
|
||||||
}), true)
|
}), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
static var jsonBrewServicesFile: URL {
|
static var jsonBrewServicesFile: URL {
|
||||||
return Bundle(for: Self.self)
|
return Bundle(for: Self.self)
|
||||||
.url(forResource: "brew-services", withExtension: "json")!
|
.url(forResource: "brew-services", withExtension: "json")!
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCanParseServicesJson() throws {
|
func testCanParseServicesJson() throws {
|
||||||
let json = try! String(contentsOf: Self.jsonBrewServicesFile, encoding: .utf8)
|
let json = try! String(contentsOf: Self.jsonBrewServicesFile, encoding: .utf8)
|
||||||
let services = try! JSONDecoder().decode(
|
let services = try! JSONDecoder().decode(
|
||||||
[HomebrewService].self, from: json.data(using: .utf8)!
|
[HomebrewService].self, from: json.data(using: .utf8)!
|
||||||
)
|
)
|
||||||
|
|
||||||
XCTAssertGreaterThan(services.count, 0)
|
XCTAssertGreaterThan(services.count, 0)
|
||||||
XCTAssertEqual(services.first?.name, "dnsmasq")
|
XCTAssertEqual(services.first?.name, "dnsmasq")
|
||||||
XCTAssertEqual(services.first?.service_name, "homebrew.mxcl.dnsmasq")
|
XCTAssertEqual(services.first?.service_name, "homebrew.mxcl.dnsmasq")
|
||||||
}
|
}
|
||||||
|
|
||||||
// - MARK: LIVE TESTS
|
// - MARK: LIVE TESTS
|
||||||
|
|
||||||
/// This test requires that you have a valid Homebrew installation set up,
|
/// This test requires that you have a valid Homebrew installation set up,
|
||||||
@@ -63,13 +63,13 @@ class BrewJsonParserTest: XCTestCase {
|
|||||||
).filter({ service in
|
).filter({ service in
|
||||||
return ["php", "nginx", "dnsmasq"].contains(service.name)
|
return ["php", "nginx", "dnsmasq"].contains(service.name)
|
||||||
})
|
})
|
||||||
|
|
||||||
XCTAssertTrue(services.contains(where: {$0.name == "php"} ))
|
XCTAssertTrue(services.contains(where: {$0.name == "php"}))
|
||||||
XCTAssertTrue(services.contains(where: {$0.name == "nginx"} ))
|
XCTAssertTrue(services.contains(where: {$0.name == "nginx"}))
|
||||||
XCTAssertTrue(services.contains(where: {$0.name == "dnsmasq"} ))
|
XCTAssertTrue(services.contains(where: {$0.name == "dnsmasq"}))
|
||||||
XCTAssertEqual(services.count, 3)
|
XCTAssertEqual(services.count, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This test requires that you have a valid Homebrew installation set up,
|
/// This test requires that you have a valid Homebrew installation set up,
|
||||||
/// and requires the `php` formula to be installed.
|
/// and requires the `php` formula to be installed.
|
||||||
/// If this test fails, there is an issue with your Homebrew installation
|
/// If this test fails, there is an issue with your Homebrew installation
|
||||||
@@ -79,7 +79,7 @@ class BrewJsonParserTest: XCTestCase {
|
|||||||
[HomebrewPackage].self,
|
[HomebrewPackage].self,
|
||||||
from: Shell.pipe("\(Paths.brew) info php --json", requiresPath: true).data(using: .utf8)!
|
from: Shell.pipe("\(Paths.brew) info php --json", requiresPath: true).data(using: .utf8)!
|
||||||
).first!
|
).first!
|
||||||
|
|
||||||
XCTAssertTrue(package.name == "php")
|
XCTAssertTrue(package.name == "php")
|
||||||
}
|
}
|
||||||
}
|
}
|
81
phpmon-tests/Parsers/NginxConfigurationTest.swift
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
//
|
||||||
|
// NginxConfigurationTest.swift
|
||||||
|
// phpmon-tests
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 29/11/2021.
|
||||||
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class NginxConfigurationTest: XCTestCase {
|
||||||
|
|
||||||
|
// MARK: - Test Files
|
||||||
|
|
||||||
|
static var regularUrl: URL {
|
||||||
|
return Bundle(for: Self.self).url(forResource: "nginx-site", withExtension: "test")!
|
||||||
|
}
|
||||||
|
|
||||||
|
static var isolatedUrl: URL {
|
||||||
|
return Bundle(for: Self.self).url(forResource: "nginx-site-isolated", withExtension: "test")!
|
||||||
|
}
|
||||||
|
|
||||||
|
static var proxyUrl: URL {
|
||||||
|
return Bundle(for: Self.self).url(forResource: "nginx-proxy", withExtension: "test")!
|
||||||
|
}
|
||||||
|
|
||||||
|
static var secureProxyUrl: URL {
|
||||||
|
return Bundle(for: Self.self).url(forResource: "nginx-secure-proxy", withExtension: "test")!
|
||||||
|
}
|
||||||
|
|
||||||
|
static var customTldProxyUrl: URL {
|
||||||
|
return Bundle(for: Self.self).url(forResource: "nginx-secure-proxy-custom-tld", withExtension: "test")!
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Tests
|
||||||
|
|
||||||
|
func testCanDetermineSiteNameAndTld() throws {
|
||||||
|
XCTAssertEqual(
|
||||||
|
"nginx-site",
|
||||||
|
NginxConfigurationFile.from(filePath: NginxConfigurationTest.regularUrl.path)?.domain
|
||||||
|
)
|
||||||
|
XCTAssertEqual(
|
||||||
|
"test",
|
||||||
|
NginxConfigurationFile.from(filePath: NginxConfigurationTest.regularUrl.path)?.tld
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCanDetermineIsolation() throws {
|
||||||
|
XCTAssertNil(
|
||||||
|
NginxConfigurationFile.from(filePath: NginxConfigurationTest.regularUrl.path)?.isolatedVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
XCTAssertEqual(
|
||||||
|
"8.1",
|
||||||
|
NginxConfigurationFile.from(filePath: NginxConfigurationTest.isolatedUrl.path)?.isolatedVersion
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCanDetermineProxy() throws {
|
||||||
|
let proxied = NginxConfigurationFile.from(filePath: NginxConfigurationTest.proxyUrl.path)!
|
||||||
|
XCTAssertTrue(proxied.contents.contains("# valet stub: proxy.valet.conf"))
|
||||||
|
XCTAssertEqual("http://127.0.0.1:90", proxied.proxy)
|
||||||
|
|
||||||
|
let normal = NginxConfigurationFile.from(filePath: NginxConfigurationTest.regularUrl.path)!
|
||||||
|
XCTAssertFalse(normal.contents.contains("# valet stub: proxy.valet.conf"))
|
||||||
|
XCTAssertEqual(nil, normal.proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCanDetermineSecuredProxy() throws {
|
||||||
|
let proxied = NginxConfigurationFile.from(filePath: NginxConfigurationTest.secureProxyUrl.path)!
|
||||||
|
XCTAssertTrue(proxied.contents.contains("# valet stub: secure.proxy.valet.conf"))
|
||||||
|
XCTAssertEqual("http://127.0.0.1:90", proxied.proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCanDetermineProxyWithCustomTld() throws {
|
||||||
|
let proxied = NginxConfigurationFile.from(filePath: NginxConfigurationTest.customTldProxyUrl.path)!
|
||||||
|
XCTAssertTrue(proxied.contents.contains("# valet stub: secure.proxy.valet.conf"))
|
||||||
|
XCTAssertEqual("http://localhost:8080", proxied.proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
84
phpmon-tests/Parsers/PhpConfigurationTest.swift
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
//
|
||||||
|
// PhpConfigurationTest.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 04/05/2022.
|
||||||
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class PhpConfigurationTest: XCTestCase {
|
||||||
|
|
||||||
|
static var phpIniFileUrl: URL {
|
||||||
|
return Bundle(for: Self.self).url(forResource: "php", withExtension: "ini")!
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCanLoadExtension() throws {
|
||||||
|
let iniFile = PhpConfigurationFile.from(filePath: Self.phpIniFileUrl.path)!
|
||||||
|
|
||||||
|
XCTAssertNotNil(iniFile)
|
||||||
|
|
||||||
|
XCTAssertGreaterThan(iniFile.extensions.count, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCanCheckKeyExistence() throws {
|
||||||
|
let iniFile = PhpConfigurationFile.from(filePath: Self.phpIniFileUrl.path)!
|
||||||
|
|
||||||
|
XCTAssertTrue(iniFile.has(key: "error_reporting"))
|
||||||
|
XCTAssertTrue(iniFile.has(key: "display_errors"))
|
||||||
|
XCTAssertFalse(iniFile.has(key: "my_unknown_key"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCanCheckKeyValue() throws {
|
||||||
|
let iniFile = PhpConfigurationFile.from(filePath: Self.phpIniFileUrl.path)!
|
||||||
|
|
||||||
|
XCTAssertNotNil(iniFile.get(for: "error_reporting"))
|
||||||
|
XCTAssert(iniFile.get(for: "error_reporting") == "E_ALL")
|
||||||
|
|
||||||
|
XCTAssertNotNil(iniFile.get(for: "display_errors"))
|
||||||
|
XCTAssert(iniFile.get(for: "display_errors") == "On")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCanCustomizeConfigurationValue() throws {
|
||||||
|
let destination = Utility
|
||||||
|
.copyToTemporaryFile(resourceName: "php", fileExtension: "ini")!
|
||||||
|
|
||||||
|
let configurationFile = PhpConfigurationFile
|
||||||
|
.from(filePath: destination.path)!
|
||||||
|
|
||||||
|
// 0. Verify the original value
|
||||||
|
XCTAssertEqual(configurationFile.get(for: "error_reporting"), "E_ALL")
|
||||||
|
|
||||||
|
// 1. Change the value
|
||||||
|
try! configurationFile.replace(
|
||||||
|
key: "error_reporting",
|
||||||
|
value: "E_ALL & ~E_DEPRECATED & ~E_STRICT"
|
||||||
|
)
|
||||||
|
XCTAssertEqual(
|
||||||
|
configurationFile.get(for: "error_reporting"),
|
||||||
|
"E_ALL & ~E_DEPRECATED & ~E_STRICT"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 2. Ensure that same key and value doesn't break subsequent saves
|
||||||
|
try! configurationFile.replace(
|
||||||
|
key: "error_reporting",
|
||||||
|
value: "error_reporting"
|
||||||
|
)
|
||||||
|
XCTAssertEqual(
|
||||||
|
configurationFile.get(for: "error_reporting"),
|
||||||
|
"error_reporting"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 3. Verify subsequent saves weren't broken
|
||||||
|
try! configurationFile.replace(
|
||||||
|
key: "error_reporting",
|
||||||
|
value: "E_ALL"
|
||||||
|
)
|
||||||
|
XCTAssertEqual(
|
||||||
|
configurationFile.get(for: "error_reporting"),
|
||||||
|
"E_ALL"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -3,30 +3,30 @@
|
|||||||
// phpmon-tests
|
// phpmon-tests
|
||||||
//
|
//
|
||||||
// Created by Nico Verbruggen on 13/02/2021.
|
// Created by Nico Verbruggen on 13/02/2021.
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
class ExtensionParserTest: XCTestCase {
|
class PhpExtensionTest: XCTestCase {
|
||||||
|
|
||||||
static var phpIniFileUrl: URL {
|
static var phpIniFileUrl: URL {
|
||||||
return Bundle(for: Self.self).url(forResource: "php", withExtension: "ini")!
|
return Bundle(for: Self.self).url(forResource: "php", withExtension: "ini")!
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCanLoadExtension() throws {
|
func testCanLoadExtension() throws {
|
||||||
let extensions = PhpExtension.load(from: Self.phpIniFileUrl)
|
let extensions = PhpExtension.from(filePath: Self.phpIniFileUrl.path)
|
||||||
|
|
||||||
XCTAssertGreaterThan(extensions.count, 0)
|
XCTAssertGreaterThan(extensions.count, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testExtensionNameIsCorrect() throws {
|
func testExtensionNameIsCorrect() throws {
|
||||||
let extensions = PhpExtension.load(from: Self.phpIniFileUrl)
|
let extensions = PhpExtension.from(filePath: Self.phpIniFileUrl.path)
|
||||||
|
|
||||||
let extensionNames = extensions.map { (ext) -> String in
|
let extensionNames = extensions.map { (ext) -> String in
|
||||||
return ext.name
|
return ext.name
|
||||||
}
|
}
|
||||||
|
|
||||||
// These 6 should be found
|
// These 6 should be found
|
||||||
XCTAssertTrue(extensionNames.contains("xdebug"))
|
XCTAssertTrue(extensionNames.contains("xdebug"))
|
||||||
XCTAssertTrue(extensionNames.contains("imagick"))
|
XCTAssertTrue(extensionNames.contains("imagick"))
|
||||||
@@ -34,39 +34,39 @@ class ExtensionParserTest: XCTestCase {
|
|||||||
XCTAssertTrue(extensionNames.contains("opcache"))
|
XCTAssertTrue(extensionNames.contains("opcache"))
|
||||||
XCTAssertTrue(extensionNames.contains("yaml"))
|
XCTAssertTrue(extensionNames.contains("yaml"))
|
||||||
XCTAssertTrue(extensionNames.contains("custom"))
|
XCTAssertTrue(extensionNames.contains("custom"))
|
||||||
|
|
||||||
XCTAssertFalse(extensionNames.contains("fake"))
|
XCTAssertFalse(extensionNames.contains("fake"))
|
||||||
XCTAssertFalse(extensionNames.contains("nice"))
|
XCTAssertFalse(extensionNames.contains("nice"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testExtensionStatusIsCorrect() throws {
|
func testExtensionStatusIsCorrect() throws {
|
||||||
let extensions = PhpExtension.load(from: Self.phpIniFileUrl)
|
let extensions = PhpExtension.from(filePath: Self.phpIniFileUrl.path)
|
||||||
|
|
||||||
// xdebug should be enabled
|
// xdebug should be enabled
|
||||||
XCTAssertEqual(extensions[0].enabled, true)
|
XCTAssertEqual(extensions[0].enabled, true)
|
||||||
|
|
||||||
// imagick should be disabled
|
// imagick should be disabled
|
||||||
XCTAssertEqual(extensions[1].enabled, false)
|
XCTAssertEqual(extensions[1].enabled, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testToggleWorksAsExpected() throws {
|
func testToggleWorksAsExpected() throws {
|
||||||
let destination = Utility.copyToTemporaryFile(resourceName: "php", fileExtension: "ini")!
|
let destination = Utility.copyToTemporaryFile(resourceName: "php", fileExtension: "ini")!
|
||||||
let extensions = PhpExtension.load(from: destination)
|
let extensions = PhpExtension.from(filePath: destination.path)
|
||||||
XCTAssertEqual(extensions.count, 6)
|
XCTAssertEqual(extensions.count, 6)
|
||||||
|
|
||||||
// Try to disable xdebug (should be detected first)!
|
// Try to disable xdebug (should be detected first)!
|
||||||
let xdebug = extensions.first!
|
let xdebug = extensions.first!
|
||||||
XCTAssertTrue(xdebug.name == "xdebug")
|
XCTAssertTrue(xdebug.name == "xdebug")
|
||||||
XCTAssertEqual(xdebug.enabled, true)
|
XCTAssertEqual(xdebug.enabled, true)
|
||||||
xdebug.toggle()
|
xdebug.toggle()
|
||||||
XCTAssertEqual(xdebug.enabled, false)
|
XCTAssertEqual(xdebug.enabled, false)
|
||||||
|
|
||||||
// Check if the file contains the appropriate data
|
// Check if the file contains the appropriate data
|
||||||
let file = try! String(contentsOf: destination, encoding: .utf8)
|
let file = try! String(contentsOf: destination, encoding: .utf8)
|
||||||
XCTAssertTrue(file.contains("; zend_extension=\"xdebug.so\""))
|
XCTAssertTrue(file.contains("; zend_extension=\"xdebug.so\""))
|
||||||
|
|
||||||
// Make sure if we load the data again, it's disabled
|
// Make sure if we load the data again, it's disabled
|
||||||
XCTAssertEqual(PhpExtension.load(from: destination).first!.enabled, false)
|
XCTAssertEqual(PhpExtension.from(filePath: destination.path).first!.enabled, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -3,20 +3,20 @@
|
|||||||
// phpmon-tests
|
// phpmon-tests
|
||||||
//
|
//
|
||||||
// Created by Nico Verbruggen on 29/11/2021.
|
// Created by Nico Verbruggen on 29/11/2021.
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
class ValetConfigParserTest: XCTestCase {
|
class ValetConfigurationTest: XCTestCase {
|
||||||
|
|
||||||
static var jsonConfigFileUrl: URL {
|
static var jsonConfigFileUrl: URL {
|
||||||
return Bundle(for: Self.self).url(
|
return Bundle(for: Self.self).url(
|
||||||
forResource: "valet-config",
|
forResource: "valet-config",
|
||||||
withExtension: "json"
|
withExtension: "json"
|
||||||
)!
|
)!
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCanLoadConfigFile() throws {
|
func testCanLoadConfigFile() throws {
|
||||||
let json = try? String(
|
let json = try? String(
|
||||||
contentsOf: Self.jsonConfigFileUrl,
|
contentsOf: Self.jsonConfigFileUrl,
|
||||||
@@ -26,13 +26,14 @@ class ValetConfigParserTest: XCTestCase {
|
|||||||
Valet.Configuration.self,
|
Valet.Configuration.self,
|
||||||
from: json!.data(using: .utf8)!
|
from: json!.data(using: .utf8)!
|
||||||
)
|
)
|
||||||
|
|
||||||
XCTAssertEqual(config.tld, "test")
|
XCTAssertEqual(config.tld, "test")
|
||||||
XCTAssertEqual(config.paths, [
|
XCTAssertEqual(config.paths, [
|
||||||
"/Users/username/.config/valet/Sites",
|
"/Users/username/.config/valet/Sites",
|
||||||
"/Users/username/Sites"
|
"/Users/username/Sites"
|
||||||
])
|
])
|
||||||
|
XCTAssertEqual(config.defaultSite, "/Users/username/default-site")
|
||||||
XCTAssertEqual(config.loopback, "127.0.0.1")
|
XCTAssertEqual(config.loopback, "127.0.0.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
81
phpmon-tests/Test Files/nginx/nginx-proxy.test
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# valet stub: proxy.valet.conf
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 127.0.0.1:80;
|
||||||
|
#listen 127.0.0.1:80; # valet loopback
|
||||||
|
server_name my-proxy.test www.my-proxy.test *.my-proxy.test;
|
||||||
|
root /;
|
||||||
|
charset utf-8;
|
||||||
|
client_max_body_size 128M;
|
||||||
|
|
||||||
|
location /41c270e4-5535-4daa-b23e-c269744c2f45/ {
|
||||||
|
internal;
|
||||||
|
alias /;
|
||||||
|
try_files $uri $uri/;
|
||||||
|
}
|
||||||
|
|
||||||
|
access_log off;
|
||||||
|
error_log "/Users/nicoverbruggen/.config/valet/Log/my-proxy.test-error.log";
|
||||||
|
|
||||||
|
error_page 404 "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:90;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Client-Verify SUCCESS;
|
||||||
|
proxy_set_header X-Client-DN $ssl_client_s_dn;
|
||||||
|
proxy_set_header X-SSL-Subject $ssl_client_s_dn;
|
||||||
|
proxy_set_header X-SSL-Issuer $ssl_client_i_dn;
|
||||||
|
proxy_set_header X-NginX-Proxy true;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_read_timeout 1800;
|
||||||
|
proxy_connect_timeout 1800;
|
||||||
|
chunked_transfer_encoding on;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_buffering off;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /\.ht {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 127.0.0.1:60;
|
||||||
|
#listen 127.0.0.1:60; # valet loopback
|
||||||
|
server_name my-proxy.test www.my-proxy.test *.my-proxy.test;
|
||||||
|
root /;
|
||||||
|
charset utf-8;
|
||||||
|
client_max_body_size 128M;
|
||||||
|
|
||||||
|
add_header X-Robots-Tag 'noindex, nofollow, nosnippet, noarchive';
|
||||||
|
|
||||||
|
location /41c270e4-5535-4daa-b23e-c269744c2f45/ {
|
||||||
|
internal;
|
||||||
|
alias /;
|
||||||
|
try_files $uri $uri/;
|
||||||
|
}
|
||||||
|
|
||||||
|
access_log off;
|
||||||
|
error_log "/Users/nicoverbruggen/.config/valet/Log/my-proxy.test-error.log";
|
||||||
|
|
||||||
|
error_page 404 "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:90;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /\.ht {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@@ -0,0 +1,57 @@
|
|||||||
|
# valet stub: secure.proxy.valet.conf
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 127.0.0.1:80;
|
||||||
|
#listen 127.0.0.1:80; # valet loopback
|
||||||
|
server_name live.whatagraph.dev.com www.live.whatagraph.dev.com *.live.whatagraph.dev.com;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 127.0.0.1:443 ssl http2;
|
||||||
|
#listen 127.0.0.1:443 ssl http2; # valet loopback
|
||||||
|
server_name live.whatagraph.dev.com www.live.whatagraph.dev.com *.live.whatagraph.dev.com;
|
||||||
|
root /;
|
||||||
|
charset utf-8;
|
||||||
|
client_max_body_size 128M;
|
||||||
|
http2_push_preload on;
|
||||||
|
|
||||||
|
location /41c270e4-5535-4daa-b23e-c269744c2f45/ {
|
||||||
|
internal;
|
||||||
|
alias /;
|
||||||
|
try_files $uri $uri/;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssl_certificate "/Users/phpmon/.config/valet/Certificates/live.whatagraph.dev.com.crt";
|
||||||
|
ssl_certificate_key "/Users/phpmon/.config/valet/Certificates/live.whatagraph.dev.com.key";
|
||||||
|
|
||||||
|
access_log off;
|
||||||
|
error_log "/Users/phpmon/.config/valet/Log/live.whatagraph.dev.com-error.log";
|
||||||
|
|
||||||
|
error_page 404 "/Users/phpmon/.composer/vendor/laravel/valet/server.php";
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:8080/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Client-Verify SUCCESS;
|
||||||
|
proxy_set_header X-Client-DN $ssl_client_s_dn;
|
||||||
|
proxy_set_header X-SSL-Subject $ssl_client_s_dn;
|
||||||
|
proxy_set_header X-SSL-Issuer $ssl_client_i_dn;
|
||||||
|
proxy_set_header X-NginX-Proxy true;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_read_timeout 1800;
|
||||||
|
proxy_connect_timeout 1800;
|
||||||
|
chunked_transfer_encoding on;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_buffering off;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /\.ht {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
57
phpmon-tests/Test Files/nginx/nginx-secure-proxy.test
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# valet stub: secure.proxy.valet.conf
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 127.0.0.1:80;
|
||||||
|
#listen 127.0.0.1:80; # valet loopback
|
||||||
|
server_name my-proxy.test www.my-proxy.test *.my-proxy.test;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 127.0.0.1:443 ssl http2;
|
||||||
|
#listen 127.0.0.1:443 ssl http2; # valet loopback
|
||||||
|
server_name my-proxy.test www.my-proxy.test *.my-proxy.test;
|
||||||
|
root /;
|
||||||
|
charset utf-8;
|
||||||
|
client_max_body_size 128M;
|
||||||
|
http2_push_preload on;
|
||||||
|
|
||||||
|
location /41c270e4-5535-4daa-b23e-c269744c2f45/ {
|
||||||
|
internal;
|
||||||
|
alias /;
|
||||||
|
try_files $uri $uri/;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssl_certificate "/Users/nicoverbruggen/.config/valet/Certificates/my-proxy.test.crt";
|
||||||
|
ssl_certificate_key "/Users/nicoverbruggen/.config/valet/Certificates/my-proxy.test.key";
|
||||||
|
|
||||||
|
access_log off;
|
||||||
|
error_log "/Users/nicoverbruggen/.config/valet/Log/my-proxy.test-error.log";
|
||||||
|
|
||||||
|
error_page 404 "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:90;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Client-Verify SUCCESS;
|
||||||
|
proxy_set_header X-Client-DN $ssl_client_s_dn;
|
||||||
|
proxy_set_header X-SSL-Subject $ssl_client_s_dn;
|
||||||
|
proxy_set_header X-SSL-Issuer $ssl_client_i_dn;
|
||||||
|
proxy_set_header X-NginX-Proxy true;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_read_timeout 1800;
|
||||||
|
proxy_connect_timeout 1800;
|
||||||
|
chunked_transfer_encoding on;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_buffering off;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /\.ht {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
94
phpmon-tests/Test Files/nginx/nginx-site-isolated.test
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
server {
|
||||||
|
listen 127.0.0.1:80;
|
||||||
|
#listen 127.0.0.1:80; # valet loopback
|
||||||
|
server_name nicoverbruggen.test www.nicoverbruggen.test *.nicoverbruggen.test;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 127.0.0.1:443 ssl http2;
|
||||||
|
#listen 127.0.0.1:443 ssl http2; # valet loopback
|
||||||
|
server_name nicoverbruggen.test www.nicoverbruggen.test *.nicoverbruggen.test;
|
||||||
|
root /;
|
||||||
|
charset utf-8;
|
||||||
|
client_max_body_size 512M;
|
||||||
|
http2_push_preload on;
|
||||||
|
|
||||||
|
location /41c270e4-5535-4daa-b23e-c269744c2f45/ {
|
||||||
|
internal;
|
||||||
|
alias /;
|
||||||
|
try_files $uri $uri/;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssl_certificate "/Users/nicoverbruggen/.config/valet/Certificates/nicoverbruggen.test.crt";
|
||||||
|
ssl_certificate_key "/Users/nicoverbruggen/.config/valet/Certificates/nicoverbruggen.test.key";
|
||||||
|
|
||||||
|
location / {
|
||||||
|
rewrite ^ "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php" last;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /favicon.ico { access_log off; log_not_found off; }
|
||||||
|
location = /robots.txt { access_log off; log_not_found off; }
|
||||||
|
|
||||||
|
access_log off;
|
||||||
|
error_log "/Users/nicoverbruggen/.config/valet/Log/nginx-error.log";
|
||||||
|
|
||||||
|
error_page 404 "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||||
|
|
||||||
|
location ~ [^/]\.php(/|$) {
|
||||||
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
|
# ISOLATED_PHP_VERSION=php@8.1
|
||||||
|
fastcgi_pass "unix:/Users/nicoverbruggen/.config/valet/valet81.sock";
|
||||||
|
fastcgi_index "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /\.ht {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 127.0.0.1:60;
|
||||||
|
#listen 127.0.0.1:60; # valet loopback
|
||||||
|
server_name nicoverbruggen.test www.nicoverbruggen.test *.nicoverbruggen.test;
|
||||||
|
root /;
|
||||||
|
charset utf-8;
|
||||||
|
client_max_body_size 128M;
|
||||||
|
|
||||||
|
add_header X-Robots-Tag 'noindex, nofollow, nosnippet, noarchive';
|
||||||
|
|
||||||
|
location /41c270e4-5535-4daa-b23e-c269744c2f45/ {
|
||||||
|
internal;
|
||||||
|
alias /;
|
||||||
|
try_files $uri $uri/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
rewrite ^ "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php" last;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /favicon.ico { access_log off; log_not_found off; }
|
||||||
|
location = /robots.txt { access_log off; log_not_found off; }
|
||||||
|
|
||||||
|
access_log off;
|
||||||
|
error_log "/Users/nicoverbruggen/.config/valet/Log/nginx-error.log";
|
||||||
|
|
||||||
|
error_page 404 "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||||
|
|
||||||
|
location ~ [^/]\.php(/|$) {
|
||||||
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
|
fastcgi_pass "unix:/Users/nicoverbruggen/.config/valet/valet.sock";
|
||||||
|
fastcgi_index "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /\.ht {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
93
phpmon-tests/Test Files/nginx/nginx-site.test
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
server {
|
||||||
|
listen 127.0.0.1:80;
|
||||||
|
#listen 127.0.0.1:80; # valet loopback
|
||||||
|
server_name nicoverbruggen.test www.nicoverbruggen.test *.nicoverbruggen.test;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 127.0.0.1:443 ssl http2;
|
||||||
|
#listen 127.0.0.1:443 ssl http2; # valet loopback
|
||||||
|
server_name nicoverbruggen.test www.nicoverbruggen.test *.nicoverbruggen.test;
|
||||||
|
root /;
|
||||||
|
charset utf-8;
|
||||||
|
client_max_body_size 512M;
|
||||||
|
http2_push_preload on;
|
||||||
|
|
||||||
|
location /41c270e4-5535-4daa-b23e-c269744c2f45/ {
|
||||||
|
internal;
|
||||||
|
alias /;
|
||||||
|
try_files $uri $uri/;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssl_certificate "/Users/nicoverbruggen/.config/valet/Certificates/nicoverbruggen.test.crt";
|
||||||
|
ssl_certificate_key "/Users/nicoverbruggen/.config/valet/Certificates/nicoverbruggen.test.key";
|
||||||
|
|
||||||
|
location / {
|
||||||
|
rewrite ^ "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php" last;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /favicon.ico { access_log off; log_not_found off; }
|
||||||
|
location = /robots.txt { access_log off; log_not_found off; }
|
||||||
|
|
||||||
|
access_log off;
|
||||||
|
error_log "/Users/nicoverbruggen/.config/valet/Log/nginx-error.log";
|
||||||
|
|
||||||
|
error_page 404 "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||||
|
|
||||||
|
location ~ [^/]\.php(/|$) {
|
||||||
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
|
fastcgi_pass "unix:/Users/nicoverbruggen/.config/valet/valet.sock";
|
||||||
|
fastcgi_index "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /\.ht {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 127.0.0.1:60;
|
||||||
|
#listen 127.0.0.1:60; # valet loopback
|
||||||
|
server_name nicoverbruggen.test www.nicoverbruggen.test *.nicoverbruggen.test;
|
||||||
|
root /;
|
||||||
|
charset utf-8;
|
||||||
|
client_max_body_size 128M;
|
||||||
|
|
||||||
|
add_header X-Robots-Tag 'noindex, nofollow, nosnippet, noarchive';
|
||||||
|
|
||||||
|
location /41c270e4-5535-4daa-b23e-c269744c2f45/ {
|
||||||
|
internal;
|
||||||
|
alias /;
|
||||||
|
try_files $uri $uri/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
rewrite ^ "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php" last;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /favicon.ico { access_log off; log_not_found off; }
|
||||||
|
location = /robots.txt { access_log off; log_not_found off; }
|
||||||
|
|
||||||
|
access_log off;
|
||||||
|
error_log "/Users/nicoverbruggen/.config/valet/Log/nginx-error.log";
|
||||||
|
|
||||||
|
error_page 404 "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||||
|
|
||||||
|
location ~ [^/]\.php(/|$) {
|
||||||
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
|
fastcgi_pass "unix:/Users/nicoverbruggen/.config/valet/valet.sock";
|
||||||
|
fastcgi_index "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /\.ht {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
34
phpmon-tests/Test Files/phpmon/phpmon-config.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"scan_apps": [],
|
||||||
|
"presets": [
|
||||||
|
{
|
||||||
|
"name": "Default PHP",
|
||||||
|
"extensions": {
|
||||||
|
"xdebug": false
|
||||||
|
},
|
||||||
|
"configuration": {
|
||||||
|
"memory_limit": "128M"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Personal Site",
|
||||||
|
"extensions": {
|
||||||
|
"xdebug": true
|
||||||
|
},
|
||||||
|
"configuration": {
|
||||||
|
"xdebug.mode": "coverage",
|
||||||
|
"memory_limit": "512M"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PHP Monitor",
|
||||||
|
"extensions": {
|
||||||
|
"xdebug": true
|
||||||
|
},
|
||||||
|
"configuration": {
|
||||||
|
"xdebug.mode": "coverage",
|
||||||
|
"memory_limit": "512M"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@@ -4,5 +4,6 @@
|
|||||||
"/Users/username/.config/valet/Sites",
|
"/Users/username/.config/valet/Sites",
|
||||||
"/Users/username/Sites"
|
"/Users/username/Sites"
|
||||||
],
|
],
|
||||||
"loopback": "127.0.0.1"
|
"loopback": "127.0.0.1",
|
||||||
|
"default": "/Users/username/default-site"
|
||||||
}
|
}
|
@@ -3,18 +3,18 @@
|
|||||||
// phpmon-tests
|
// phpmon-tests
|
||||||
//
|
//
|
||||||
// Created by Nico Verbruggen on 14/02/2021.
|
// Created by Nico Verbruggen on 14/02/2021.
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class Utility {
|
class Utility {
|
||||||
|
|
||||||
public static func copyToTemporaryFile(resourceName: String, fileExtension: String) -> URL? {
|
public static func copyToTemporaryFile(resourceName: String, fileExtension: String) -> URL? {
|
||||||
if let bundleURL = Bundle(for: Self.self).url(forResource: resourceName, withExtension: fileExtension) {
|
if let bundleURL = Bundle(for: Self.self).url(forResource: resourceName, withExtension: fileExtension) {
|
||||||
let tempDirectoryURL = NSURL.fileURL(withPath: NSTemporaryDirectory(), isDirectory: true)
|
let tempDirectoryURL = NSURL.fileURL(withPath: NSTemporaryDirectory(), isDirectory: true)
|
||||||
let targetURL = tempDirectoryURL.appendingPathComponent("\(UUID().uuidString).\(fileExtension)")
|
let targetURL = tempDirectoryURL.appendingPathComponent("\(UUID().uuidString).\(fileExtension)")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try FileManager.default.copyItem(at: bundleURL, to: targetURL)
|
try FileManager.default.copyItem(at: bundleURL, to: targetURL)
|
||||||
return targetURL
|
return targetURL
|
||||||
@@ -22,7 +22,7 @@ class Utility {
|
|||||||
Log.err("Unable to copy file: \(error)")
|
Log.err("Unable to copy file: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +0,0 @@
|
|||||||
//
|
|
||||||
// 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."))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
21
phpmon-tests/Versions/AppUpdaterCheckTest.swift
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// AppUpdaterCheckTest.swift
|
||||||
|
// phpmon-tests
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 10/05/2022.
|
||||||
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class AppUpdaterCheckTest: XCTestCase {
|
||||||
|
|
||||||
|
func testCanRetrieveVersionFromCask() {
|
||||||
|
let caskVersion = AppUpdateChecker.retrieveVersionFromCask()
|
||||||
|
|
||||||
|
let version = VersionExtractor.from(caskVersion)
|
||||||
|
|
||||||
|
XCTAssertNotNil(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
62
phpmon-tests/Versions/AppVersionTest.swift
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
//
|
||||||
|
// AppVersionTest.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 10/05/2022.
|
||||||
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class AppVersionTest: XCTestCase {
|
||||||
|
|
||||||
|
func testCanRetrieveInternalAppVersion() {
|
||||||
|
XCTAssertNotNil(AppVersion.fromCurrentVersion())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCanParseNormalVersionString() {
|
||||||
|
let version = AppVersion.from("1.0.0")
|
||||||
|
|
||||||
|
XCTAssertNotNil(version)
|
||||||
|
XCTAssertEqual("1.0.0", version?.version)
|
||||||
|
XCTAssertEqual(nil, version?.build)
|
||||||
|
XCTAssertEqual(nil, version?.suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCanParseCaskVersionString() {
|
||||||
|
let version = AppVersion.from("1.0.0_600")
|
||||||
|
|
||||||
|
XCTAssertNotNil(version)
|
||||||
|
XCTAssertEqual("1.0.0", version?.version)
|
||||||
|
XCTAssertEqual("600", version?.build)
|
||||||
|
XCTAssertEqual(nil, version?.suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCanParseDevVersionStringWithoutBuildNumber() {
|
||||||
|
let version = AppVersion.from("1.0.0-dev")
|
||||||
|
|
||||||
|
XCTAssertNotNil(version)
|
||||||
|
XCTAssertEqual("1.0.0", version?.version)
|
||||||
|
XCTAssertEqual(nil, version?.build)
|
||||||
|
XCTAssertEqual("dev", version?.suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCanParseDevVersionStringWithBuildNumber() {
|
||||||
|
let version = AppVersion.from("1.0.0-dev,870")
|
||||||
|
|
||||||
|
XCTAssertNotNil(version)
|
||||||
|
XCTAssertEqual("1.0.0", version?.version)
|
||||||
|
XCTAssertEqual("870", version?.build)
|
||||||
|
XCTAssertEqual("dev", version?.suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCanParseUnderscoresAsBuildSeparatorToo() {
|
||||||
|
let version = AppVersion.from("1.0.0-dev_870")
|
||||||
|
|
||||||
|
XCTAssertNotNil(version)
|
||||||
|
XCTAssertEqual("1.0.0", version?.version)
|
||||||
|
XCTAssertEqual("870", version?.build)
|
||||||
|
XCTAssertEqual("dev", version?.suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -3,7 +3,7 @@
|
|||||||
// phpmon-tests
|
// phpmon-tests
|
||||||
//
|
//
|
||||||
// Created by Nico Verbruggen on 01/04/2021.
|
// Created by Nico Verbruggen on 01/04/2021.
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import XCTest
|
import XCTest
|
||||||
@@ -22,8 +22,8 @@ class PhpVersionDetectionTest: XCTestCase {
|
|||||||
"unrelatedphp@1.0", // should be omitted, invalid
|
"unrelatedphp@1.0", // should be omitted, invalid
|
||||||
"php@5.6",
|
"php@5.6",
|
||||||
"php@5.4" // should be omitted, not supported
|
"php@5.4" // should be omitted, not supported
|
||||||
], checkBinaries: false)
|
], checkBinaries: false, generateHelpers: false)
|
||||||
|
|
||||||
XCTAssertEqual(outcome, ["8.0", "7.0", "5.6"])
|
XCTAssertEqual(outcome, ["8.0", "7.0"])
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -11,20 +11,24 @@ import XCTest
|
|||||||
class PhpVersionNumberTest: XCTestCase {
|
class PhpVersionNumberTest: XCTestCase {
|
||||||
|
|
||||||
func testCanDeconstructPhpVersion() throws {
|
func testCanDeconstructPhpVersion() throws {
|
||||||
|
XCTAssertEqual(
|
||||||
|
try! PhpVersionNumber.parse("PHP 8.2.0-dev"),
|
||||||
|
PhpVersionNumber(major: 8, minor: 2, patch: 0)
|
||||||
|
)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
try! PhpVersionNumber.parse("PHP 8.1.0RC5-dev"),
|
try! PhpVersionNumber.parse("PHP 8.1.0RC5-dev"),
|
||||||
PhpVersionNumber(major: 8, minor: 1, patch: 0)
|
PhpVersionNumber(major: 8, minor: 1, patch: 0)
|
||||||
)
|
)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumber.make(from: "8.0.11"),
|
try! PhpVersionNumber.parse("8.0.11"),
|
||||||
PhpVersionNumber(major: 8, minor: 0, patch: 11)
|
PhpVersionNumber(major: 8, minor: 0, patch: 11)
|
||||||
)
|
)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumber.make(from: "7.4.2"),
|
try! PhpVersionNumber.parse("7.4.2"),
|
||||||
PhpVersionNumber(major: 7, minor: 4, patch: 2)
|
PhpVersionNumber(major: 7, minor: 4, patch: 2)
|
||||||
)
|
)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumber.make(from: "7.4"),
|
try! PhpVersionNumber.parse("7.4"),
|
||||||
PhpVersionNumber(major: 7, minor: 4, patch: nil)
|
PhpVersionNumber(major: 7, minor: 4, patch: nil)
|
||||||
)
|
)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
@@ -32,13 +36,13 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
nil
|
nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPhpVersionNumberParse() throws {
|
func testPhpVersionNumberParse() throws {
|
||||||
XCTAssertThrowsError(try PhpVersionNumber.parse("OOF")) { error in
|
XCTAssertThrowsError(try PhpVersionNumber.parse("OOF")) { error in
|
||||||
XCTAssertTrue(error is VersionParseError)
|
XCTAssertTrue(error is VersionParseError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCanCheckFixedConstraints() throws {
|
func testCanCheckFixedConstraints() throws {
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
@@ -47,7 +51,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.0"]).all
|
.make(from: ["7.0"]).all
|
||||||
)
|
)
|
||||||
|
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4.3", "7.3.3", "7.2.3", "7.1.3", "7.0.3"])
|
.make(from: ["7.4.3", "7.3.3", "7.2.3", "7.1.3", "7.0.3"])
|
||||||
@@ -55,7 +59,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.0.3"]).all
|
.make(from: ["7.0.3"]).all
|
||||||
)
|
)
|
||||||
|
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||||
@@ -63,7 +67,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.0"]).all
|
.make(from: ["7.0"]).all
|
||||||
)
|
)
|
||||||
|
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||||
@@ -72,7 +76,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
.make(from: []).all
|
.make(from: []).all
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCanCheckCaretConstraints() throws {
|
func testCanCheckCaretConstraints() throws {
|
||||||
// 1. Imprecise checks
|
// 1. Imprecise checks
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
@@ -82,7 +86,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]).all
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]).all
|
||||||
)
|
)
|
||||||
|
|
||||||
// 2. Imprecise check with precise constraint (lenient AKA not strict)
|
// 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.
|
// These versions are interpreted as 7.4.999, 7.3.999, 7.2.999, etc.
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
@@ -92,7 +96,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]).all
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]).all
|
||||||
)
|
)
|
||||||
|
|
||||||
// 3. Imprecise check with precise constraint (strict mode)
|
// 3. Imprecise check with precise constraint (strict mode)
|
||||||
// These versions are interpreted as 7.4.0, 7.3.0, 7.2.0, etc.
|
// These versions are interpreted as 7.4.0, 7.3.0, 7.2.0, etc.
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
@@ -102,7 +106,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4", "7.3", "7.2", "7.1"]).all
|
.make(from: ["7.4", "7.3", "7.2", "7.1"]).all
|
||||||
)
|
)
|
||||||
|
|
||||||
// 4. Precise members and constraint all around
|
// 4. Precise members and constraint all around
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
@@ -111,7 +115,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all
|
.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)
|
// 5. Precise members but imprecise constraint (strict mode)
|
||||||
// In strict mode the constraint's patch version is assumed to be 0
|
// In strict mode the constraint's patch version is assumed to be 0
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
@@ -121,7 +125,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all
|
.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)
|
// 6. Precise members but imprecise constraint (lenient mode)
|
||||||
// In lenient mode the constraint's patch version is assumed to be equal
|
// In lenient mode the constraint's patch version is assumed to be equal
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
@@ -132,7 +136,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all
|
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCanCheckTildeConstraints() throws {
|
func testCanCheckTildeConstraints() throws {
|
||||||
// 1. Imprecise checks
|
// 1. Imprecise checks
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
@@ -142,7 +146,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]).all
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]).all
|
||||||
)
|
)
|
||||||
|
|
||||||
// 2. Imprecise check with precise constraint (lenient AKA not strict)
|
// 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.
|
// These versions are interpreted as 7.4.999, 7.3.999, 7.2.999, etc.
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
@@ -155,7 +159,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.0"]).all
|
.make(from: ["7.0"]).all
|
||||||
)
|
)
|
||||||
|
|
||||||
// 3. Imprecise check with precise constraint (strict mode)
|
// 3. Imprecise check with precise constraint (strict mode)
|
||||||
// These versions are interpreted as 7.4.0, 7.3.0, 7.2.0, etc.
|
// These versions are interpreted as 7.4.0, 7.3.0, 7.2.0, etc.
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
@@ -168,7 +172,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: []).all
|
.make(from: []).all
|
||||||
)
|
)
|
||||||
|
|
||||||
// 4. Precise members and constraint all around
|
// 4. Precise members and constraint all around
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
@@ -179,7 +183,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.0.10"]).all
|
.make(from: ["7.0.10"]).all
|
||||||
)
|
)
|
||||||
|
|
||||||
// 5. Precise members but imprecise constraint (strict mode)
|
// 5. Precise members but imprecise constraint (strict mode)
|
||||||
// In strict mode the constraint's patch version is assumed to be 0.
|
// In strict mode the constraint's patch version is assumed to be 0.
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
@@ -189,7 +193,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all
|
.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)
|
// 6. Precise members but imprecise constraint (lenient mode)
|
||||||
// In lenient mode the constraint's patch version is assumed to be equal.
|
// 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.)
|
// (Strictness does not make any difference here, but both should be tested.)
|
||||||
@@ -201,7 +205,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all
|
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCanCheckGreaterThanOrEqualConstraints() throws {
|
func testCanCheckGreaterThanOrEqualConstraints() throws {
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
@@ -210,7 +214,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]).all
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]).all
|
||||||
)
|
)
|
||||||
|
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||||
@@ -218,7 +222,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]).all
|
.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)
|
// Strict check (>7.2.5 is too new for 7.2 which resolves to 7.2.0)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
@@ -227,7 +231,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4", "7.3"]).all
|
.make(from: ["7.4", "7.3"]).all
|
||||||
)
|
)
|
||||||
|
|
||||||
// Non-strict check (ignoring patch, 7.2 resolves to 7.2.999)
|
// Non-strict check (ignoring patch, 7.2 resolves to 7.2.999)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
@@ -237,7 +241,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
.make(from: ["7.4", "7.3", "7.2"]).all
|
.make(from: ["7.4", "7.3", "7.2"]).all
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCanCheckGreaterThanConstraints() throws {
|
func testCanCheckGreaterThanConstraints() throws {
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
@@ -246,7 +250,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4", "7.3", "7.2", "7.1"]).all
|
.make(from: ["7.4", "7.3", "7.2", "7.1"]).all
|
||||||
)
|
)
|
||||||
|
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||||
@@ -255,7 +259,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4", "7.3", "7.2"]).all
|
.make(from: ["7.4", "7.3", "7.2"]).all
|
||||||
)
|
)
|
||||||
|
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||||
@@ -264,7 +268,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.4", "7.3"]).all
|
.make(from: ["7.4", "7.3"]).all
|
||||||
)
|
)
|
||||||
|
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.3.1", "7.2.9", "7.2.8", "7.2.6", "7.2.5", "7.2"])
|
.make(from: ["7.3.1", "7.2.9", "7.2.8", "7.2.6", "7.2.5", "7.2"])
|
||||||
@@ -273,7 +277,7 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.3.1", "7.2.9", "7.2"]).all
|
.make(from: ["7.3.1", "7.2.9", "7.2"]).all
|
||||||
)
|
)
|
||||||
|
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
PhpVersionNumberCollection
|
PhpVersionNumberCollection
|
||||||
.make(from: ["7.3.1", "7.2.9", "7.2.8", "7.2.6", "7.2.5", "7.2"])
|
.make(from: ["7.3.1", "7.2.9", "7.2.8", "7.2.6", "7.2.5", "7.2"])
|
18
phpmon-tests/Versions/ValetVersionExtractorTest.swift
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// ValetTest.swift
|
||||||
|
// phpmon-tests
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 29/11/2021.
|
||||||
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class ValetVersionExtractorTest: XCTestCase {
|
||||||
|
|
||||||
|
func testDetermineValetVersion() {
|
||||||
|
let version = valet("--version", sudo: false)
|
||||||
|
XCTAssert(version.contains("Laravel Valet 2") || version.contains("Laravel Valet 3"))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -3,7 +3,7 @@
|
|||||||
// phpmon-tests
|
// phpmon-tests
|
||||||
//
|
//
|
||||||
// Created by Nico Verbruggen on 16/12/2021.
|
// Created by Nico Verbruggen on 16/12/2021.
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import XCTest
|
import XCTest
|
||||||
@@ -14,12 +14,12 @@ class VersionExtractorTest: XCTestCase {
|
|||||||
XCTAssertEqual(VersionExtractor.from("Laravel Valet 2.17.1"), "2.17.1")
|
XCTAssertEqual(VersionExtractor.from("Laravel Valet 2.17.1"), "2.17.1")
|
||||||
XCTAssertEqual(VersionExtractor.from("Laravel Valet 2.0"), "2.0")
|
XCTAssertEqual(VersionExtractor.from("Laravel Valet 2.0"), "2.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testVersionComparison() {
|
func testVersionComparison() {
|
||||||
XCTAssertEqual("2.0".versionCompare("2.1"), .orderedAscending)
|
XCTAssertEqual("2.0".versionCompare("2.1"), .orderedAscending)
|
||||||
XCTAssertEqual("2.1".versionCompare("2.0"), .orderedDescending)
|
XCTAssertEqual("2.1".versionCompare("2.0"), .orderedDescending)
|
||||||
XCTAssertEqual("2.0".versionCompare("2.0"), .orderedSame)
|
XCTAssertEqual("2.0".versionCompare("2.0"), .orderedSame)
|
||||||
XCTAssertEqual("2.17.0".versionCompare("2.17.1"), .orderedAscending)
|
XCTAssertEqual("2.17.0".versionCompare("2.17.1"), .orderedAscending)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
38
phpmon/Assets.xcassets/AppColor.colorset/Contents.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.988",
|
||||||
|
"green" : "0.580",
|
||||||
|
"red" : "0.277"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.988",
|
||||||
|
"green" : "0.723",
|
||||||
|
"red" : "0.277"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 585 B |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 134 KiB |
BIN
phpmon/Assets.xcassets/AppIconDev.appiconset/icon_128x128.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
phpmon/Assets.xcassets/AppIconDev.appiconset/icon_128x128@2x.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
phpmon/Assets.xcassets/AppIconDev.appiconset/icon_16x16.png
Normal file
After Width: | Height: | Size: 632 B |
BIN
phpmon/Assets.xcassets/AppIconDev.appiconset/icon_16x16@2x.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
phpmon/Assets.xcassets/AppIconDev.appiconset/icon_256x256.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
phpmon/Assets.xcassets/AppIconDev.appiconset/icon_256x256@2x.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
phpmon/Assets.xcassets/AppIconDev.appiconset/icon_32x32.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
phpmon/Assets.xcassets/AppIconDev.appiconset/icon_32x32@2x.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
phpmon/Assets.xcassets/AppIconDev.appiconset/icon_512x512.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
phpmon/Assets.xcassets/AppIconDev.appiconset/icon_512x512@2x.png
Normal file
After Width: | Height: | Size: 163 KiB |
38
phpmon/Assets.xcassets/AppSecondary.colorset/Contents.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.250",
|
||||||
|
"green" : "0.250",
|
||||||
|
"red" : "0.250"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.750",
|
||||||
|
"green" : "0.750",
|
||||||
|
"red" : "0.750"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "ServiceOn.png",
|
"filename" : "Default.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "ServiceOn@2x.png",
|
"filename" : "Default@2x.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
BIN
phpmon/Assets.xcassets/IconDefault.imageset/Default.png
vendored
Normal file
After Width: | Height: | Size: 861 B |
BIN
phpmon/Assets.xcassets/IconDefault.imageset/Default@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.2 KiB |
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "ServiceOff.png",
|
"filename" : "Proxy.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "ServiceOff@2x.png",
|
"filename" : "Proxy@2x.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
BIN
phpmon/Assets.xcassets/IconProxy.imageset/Proxy.png
vendored
Normal file
After Width: | Height: | Size: 935 B |
BIN
phpmon/Assets.xcassets/IconProxy.imageset/Proxy@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.4 KiB |
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "ServiceLoading.png",
|
"filename" : "Isolated.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "ServiceLoading@2x.png",
|
"filename" : "Isolated@2x.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
BIN
phpmon/Assets.xcassets/Isolated.imageset/Isolated.png
vendored
Normal file
After Width: | Height: | Size: 690 B |
BIN
phpmon/Assets.xcassets/Isolated.imageset/Isolated@2x.png
vendored
Normal file
After Width: | Height: | Size: 827 B |
Before Width: | Height: | Size: 854 B |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 826 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 819 B |
Before Width: | Height: | Size: 1.2 KiB |
@@ -2,145 +2,153 @@
|
|||||||
// Services.swift
|
// Services.swift
|
||||||
// PHP Monitor
|
// PHP Monitor
|
||||||
//
|
//
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
class Actions {
|
class Actions {
|
||||||
|
|
||||||
// MARK: - Services
|
// MARK: - Services
|
||||||
|
|
||||||
public static func restartPhpFpm()
|
public static func restartPhpFpm() {
|
||||||
{
|
|
||||||
brew("services restart \(PhpEnv.phpInstall.formula)", sudo: true)
|
brew("services restart \(PhpEnv.phpInstall.formula)", sudo: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func restartNginx()
|
public static func restartNginx() {
|
||||||
{
|
|
||||||
brew("services restart nginx", sudo: true)
|
brew("services restart nginx", sudo: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func restartDnsMasq()
|
public static func restartDnsMasq() {
|
||||||
{
|
|
||||||
brew("services restart dnsmasq", sudo: true)
|
brew("services restart dnsmasq", sudo: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func stopAllServices()
|
public static func stopValetServices() {
|
||||||
{
|
|
||||||
brew("services stop \(PhpEnv.phpInstall.formula)", sudo: true)
|
brew("services stop \(PhpEnv.phpInstall.formula)", sudo: true)
|
||||||
brew("services stop nginx", sudo: true)
|
brew("services stop nginx", sudo: true)
|
||||||
brew("services stop dnsmasq", sudo: true)
|
brew("services stop dnsmasq", sudo: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func fixHomebrewPermissions() throws
|
public static func fixHomebrewPermissions() throws {
|
||||||
{
|
|
||||||
var servicesCommands = [
|
var servicesCommands = [
|
||||||
"\(Paths.brew) services stop nginx",
|
"\(Paths.brew) services stop nginx",
|
||||||
"\(Paths.brew) services stop dnsmasq",
|
"\(Paths.brew) services stop dnsmasq"
|
||||||
]
|
]
|
||||||
var cellarCommands = [
|
var cellarCommands = [
|
||||||
"chown -R \(Paths.whoami):staff \(Paths.cellarPath)/nginx",
|
"chown -R \(Paths.whoami):admin \(Paths.cellarPath)/nginx",
|
||||||
"chown -R \(Paths.whoami):staff \(Paths.cellarPath)/dnsmasq"
|
"chown -R \(Paths.whoami):admin \(Paths.cellarPath)/dnsmasq"
|
||||||
]
|
]
|
||||||
|
|
||||||
PhpEnv.shared.availablePhpVersions.forEach { version in
|
PhpEnv.shared.availablePhpVersions.forEach { version in
|
||||||
let formula = version == PhpEnv.brewPhpVersion
|
let formula = version == PhpEnv.brewPhpVersion
|
||||||
? "php"
|
? "php"
|
||||||
: "php@\(version)"
|
: "php@\(version)"
|
||||||
servicesCommands.append("\(Paths.brew) services stop \(formula)")
|
servicesCommands.append("\(Paths.brew) services stop \(formula)")
|
||||||
cellarCommands.append("chown -R \(Paths.whoami):staff \(Paths.cellarPath)/\(formula)")
|
cellarCommands.append("chown -R \(Paths.whoami):admin \(Paths.cellarPath)/\(formula)")
|
||||||
}
|
}
|
||||||
|
|
||||||
let script =
|
let script =
|
||||||
servicesCommands.joined(separator: " && ")
|
servicesCommands.joined(separator: " && ")
|
||||||
+ " && "
|
+ " && "
|
||||||
+ cellarCommands.joined(separator: " && ")
|
+ cellarCommands.joined(separator: " && ")
|
||||||
|
|
||||||
let appleScript = NSAppleScript(
|
let appleScript = NSAppleScript(
|
||||||
source: "do shell script \"\(script)\" with administrator privileges"
|
source: "do shell script \"\(script)\" with administrator privileges"
|
||||||
)
|
)
|
||||||
|
|
||||||
let eventResult: NSAppleEventDescriptor? = appleScript?.executeAndReturnError(nil)
|
let eventResult: NSAppleEventDescriptor? = appleScript?.executeAndReturnError(nil)
|
||||||
|
|
||||||
if (eventResult == nil) {
|
if eventResult == nil {
|
||||||
throw HomebrewPermissionError(kind: .applescriptNilError)
|
throw HomebrewPermissionError(kind: .applescriptNilError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Third Party Services
|
||||||
|
public static func stopService(name: String, completion: @escaping () -> Void) {
|
||||||
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
|
brew("services stop \(name)", sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name })
|
||||||
|
ServicesManager.loadHomebrewServices(completed: {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func startService(name: String, completion: @escaping () -> Void) {
|
||||||
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
|
brew("services start \(name)", sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name })
|
||||||
|
ServicesManager.loadHomebrewServices(completed: {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Finding Config Files
|
// MARK: - Finding Config Files
|
||||||
|
|
||||||
public static func openGenericPhpConfigFolder()
|
public static func openGenericPhpConfigFolder() {
|
||||||
{
|
let files = [NSURL(fileURLWithPath: "\(Paths.etcPath)/php")]
|
||||||
let files = [NSURL(fileURLWithPath: "\(Paths.etcPath)/php")];
|
|
||||||
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
|
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func openGlobalComposerFolder()
|
public static func openGlobalComposerFolder() {
|
||||||
{
|
|
||||||
let file = FileManager.default.homeDirectoryForCurrentUser
|
let file = FileManager.default.homeDirectoryForCurrentUser
|
||||||
.appendingPathComponent(".composer/composer.json")
|
.appendingPathComponent(".composer/composer.json")
|
||||||
NSWorkspace.shared.activateFileViewerSelecting([file] as [URL])
|
NSWorkspace.shared.activateFileViewerSelecting([file] as [URL])
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func openPhpConfigFolder(version: String)
|
public static func openPhpConfigFolder(version: String) {
|
||||||
{
|
let files = [NSURL(fileURLWithPath: "\(Paths.etcPath)/php/\(version)/php.ini")]
|
||||||
let files = [NSURL(fileURLWithPath: "\(Paths.etcPath)/php/\(version)/php.ini")];
|
|
||||||
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
|
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func openValetConfigFolder()
|
public static func openValetConfigFolder() {
|
||||||
{
|
|
||||||
let file = FileManager.default.homeDirectoryForCurrentUser
|
let file = FileManager.default.homeDirectoryForCurrentUser
|
||||||
.appendingPathComponent(".config/valet")
|
.appendingPathComponent(".config/valet")
|
||||||
NSWorkspace.shared.activateFileViewerSelecting([file] as [URL])
|
NSWorkspace.shared.activateFileViewerSelecting([file] as [URL])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func openPhpMonitorConfigFile() {
|
||||||
|
let file = FileManager.default.homeDirectoryForCurrentUser
|
||||||
|
.appendingPathComponent(".config/phpmon")
|
||||||
|
NSWorkspace.shared.activateFileViewerSelecting([file] as [URL])
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Other Actions
|
// MARK: - Other Actions
|
||||||
|
|
||||||
public static func createTempPhpInfoFile() -> URL
|
public static func createTempPhpInfoFile() -> URL {
|
||||||
{
|
|
||||||
// Write a file called `phpmon_phpinfo.php` to /tmp
|
// Write a file called `phpmon_phpinfo.php` to /tmp
|
||||||
try! "<?php phpinfo();".write(toFile: "/tmp/phpmon_phpinfo.php", atomically: true, encoding: .utf8)
|
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
|
// 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")
|
Shell.run("\(Paths.binPath)/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html")
|
||||||
|
|
||||||
return URL(string: "file:///private/tmp/phpmon_phpinfo.html")!
|
return URL(string: "file:///private/tmp/phpmon_phpinfo.html")!
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Fix My Valet
|
// MARK: - Fix My Valet
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Detects all currently available PHP versions,
|
Detects all currently available PHP versions,
|
||||||
and unlinks each and every one of them.
|
and unlinks each and every one of them.
|
||||||
|
|
||||||
|
This all happens in sequence, nothing runs in parallel.
|
||||||
|
|
||||||
After this, the brew services are also stopped,
|
After this, the brew services are also stopped,
|
||||||
the latest PHP version is linked, and php + nginx are restarted.
|
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
|
If this does not solve the issue, the user may need to install additional
|
||||||
extensions and/or run `composer global update`.
|
extensions and/or run `composer global update`.
|
||||||
*/
|
*/
|
||||||
public static func fixMyValet()
|
public static func fixMyValet(completed: @escaping () -> Void) {
|
||||||
{
|
InternalSwitcher().performSwitch(to: PhpEnv.brewPhpVersion, completion: {
|
||||||
brew("services restart dnsmasq", sudo: true)
|
brew("services restart dnsmasq", sudo: true)
|
||||||
|
brew("services restart php", sudo: true)
|
||||||
PhpEnv.shared.detectPhpVersions().forEach { (version) in
|
brew("services restart nginx", sudo: true)
|
||||||
let formula = (version == PhpEnv.brewPhpVersion) ? "php" : "php@\(version)"
|
completed()
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,13 +2,13 @@
|
|||||||
// Command.swift
|
// Command.swift
|
||||||
// PHP Monitor
|
// PHP Monitor
|
||||||
//
|
//
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
public class Command {
|
public class Command {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Immediately executes a command.
|
Immediately executes a command.
|
||||||
|
|
||||||
@@ -20,21 +20,21 @@ public class Command {
|
|||||||
let task = Process()
|
let task = Process()
|
||||||
task.launchPath = path
|
task.launchPath = path
|
||||||
task.arguments = arguments
|
task.arguments = arguments
|
||||||
|
|
||||||
let pipe = Pipe()
|
let pipe = Pipe()
|
||||||
task.standardOutput = pipe
|
task.standardOutput = pipe
|
||||||
task.launch()
|
task.launch()
|
||||||
|
|
||||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||||
let output: String = String.init(data: data, encoding: String.Encoding.utf8)!
|
let output: String = String.init(data: data, encoding: String.Encoding.utf8)!
|
||||||
|
|
||||||
if (trimNewlines) {
|
if trimNewlines {
|
||||||
return output.components(separatedBy: .newlines)
|
return output.components(separatedBy: .newlines)
|
||||||
.filter({ !$0.isEmpty })
|
.filter({ !$0.isEmpty })
|
||||||
.joined(separator: "\n")
|
.joined(separator: "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,19 +2,19 @@
|
|||||||
// Constants.swift
|
// Constants.swift
|
||||||
// PHP Monitor
|
// PHP Monitor
|
||||||
//
|
//
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class Constants {
|
struct Constants {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The latest PHP version that is considered to be stable at the time of release.
|
* 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).
|
* This version number is currently not used (only as a default fallback).
|
||||||
*/
|
*/
|
||||||
static let LatestStablePhpVersion = "8.1"
|
static let LatestStablePhpVersion = "8.1"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The minimum version of Valet that is recommended.
|
The minimum version of Valet that is recommended.
|
||||||
If the installed version is older, a notification will be shown
|
If the installed version is older, a notification will be shown
|
||||||
@@ -24,7 +24,7 @@ class Constants {
|
|||||||
See also: https://github.com/laravel/valet/releases/tag/v2.16.2
|
See also: https://github.com/laravel/valet/releases/tag/v2.16.2
|
||||||
*/
|
*/
|
||||||
static let MinimumRecommendedValetVersion = "2.16.2"
|
static let MinimumRecommendedValetVersion = "2.16.2"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The PHP versions supported by this application.
|
* The PHP versions supported by this application.
|
||||||
* Versions that do not appear in this array are omitted from the list.
|
* Versions that do not appear in this array are omitted from the list.
|
||||||
@@ -34,7 +34,7 @@ class Constants {
|
|||||||
// STABLE RELEASES
|
// STABLE RELEASES
|
||||||
// ====================
|
// ====================
|
||||||
// Versions of PHP that are stable and are supported.
|
// Versions of PHP that are stable and are supported.
|
||||||
"5.6",
|
"5.6", // only supported when Valet 2.x is active
|
||||||
"7.0",
|
"7.0",
|
||||||
"7.1",
|
"7.1",
|
||||||
"7.2",
|
"7.2",
|
||||||
@@ -42,7 +42,7 @@ class Constants {
|
|||||||
"7.4",
|
"7.4",
|
||||||
"8.0",
|
"8.0",
|
||||||
"8.1",
|
"8.1",
|
||||||
|
|
||||||
// ====================
|
// ====================
|
||||||
// EXPERIMENTAL SUPPORT
|
// EXPERIMENTAL SUPPORT
|
||||||
// ====================
|
// ====================
|
||||||
@@ -51,9 +51,32 @@ class Constants {
|
|||||||
"8.2"
|
"8.2"
|
||||||
]
|
]
|
||||||
|
|
||||||
/**
|
struct Urls {
|
||||||
The URL that people can visit if they wish to help support the project.
|
|
||||||
*/
|
static let DonationPayment = URL(
|
||||||
static let DonationUrl = URL(string: "https://nicoverbruggen.be/sponsor#pay-now")!
|
string: "https://nicoverbruggen.be/sponsor#pay-now"
|
||||||
|
)!
|
||||||
|
|
||||||
|
static let DonationPage = URL(
|
||||||
|
string: "https://nicoverbruggen.be/sponsor"
|
||||||
|
)!
|
||||||
|
|
||||||
|
static let FrequentlyAskedQuestions = URL(
|
||||||
|
string: "https://github.com/nicoverbruggen/phpmon#%EF%B8%8F-faq--troubleshooting"
|
||||||
|
)!
|
||||||
|
|
||||||
|
static let GitHubReleases = URL(
|
||||||
|
string: "https://github.com/nicoverbruggen/phpmon/releases"
|
||||||
|
)!
|
||||||
|
|
||||||
|
static let StableBuildCaskFile = URL(
|
||||||
|
string: "https://raw.githubusercontent.com/nicoverbruggen/homebrew-cask/master/Casks/phpmon.rb"
|
||||||
|
)!
|
||||||
|
|
||||||
|
static let DevBuildCaskFile = URL(
|
||||||
|
string: "https://raw.githubusercontent.com/nicoverbruggen/homebrew-cask/master/Casks/phpmon-dev.rb"
|
||||||
|
)!
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class Events {
|
class Events {
|
||||||
|
|
||||||
static let ServicesUpdated = Notification.Name("ServicesUpdated")
|
static let ServicesUpdated = Notification.Name("ServicesUpdated")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,39 +3,36 @@
|
|||||||
// PHP Monitor
|
// PHP Monitor
|
||||||
//
|
//
|
||||||
// Created by Nico Verbruggen on 24/12/2021.
|
// Created by Nico Verbruggen on 24/12/2021.
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
// MARK: Common Shell Commands
|
// MARK: Common Shell Commands
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Runs a `valet` command.
|
Runs a `valet` command. Defaults to running as superuser.
|
||||||
*/
|
*/
|
||||||
func valet(_ command: String) -> String
|
func valet(_ command: String, sudo: Bool = true) -> String {
|
||||||
{
|
return Shell.pipe("\(sudo ? "sudo " : "")" + "\(Paths.valet) \(command)", requiresPath: true)
|
||||||
return Shell.pipe("sudo \(Paths.valet) \(command)", requiresPath: true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Runs a `brew` command. Can run as superuser.
|
Runs a `brew` command. Can run as superuser.
|
||||||
*/
|
*/
|
||||||
func brew(_ command: String, sudo: Bool = false)
|
func brew(_ command: String, sudo: Bool = false) {
|
||||||
{
|
|
||||||
Shell.run("\(sudo ? "sudo " : "")" + "\(Paths.brew) \(command)")
|
Shell.run("\(sudo ? "sudo " : "")" + "\(Paths.brew) \(command)")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Runs `sed` in order to replace all occurrences of a string in a specific file with another.
|
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)
|
func sed(file: String, original: String, replacement: String) {
|
||||||
{
|
|
||||||
// Escape slashes (or `sed` won't work)
|
// Escape slashes (or `sed` won't work)
|
||||||
let e_original = original.replacingOccurrences(of: "/", with: "\\/")
|
let e_original = original.replacingOccurrences(of: "/", with: "\\/")
|
||||||
let e_replacement = replacement.replacingOccurrences(of: "/", with: "\\/")
|
let e_replacement = replacement.replacingOccurrences(of: "/", with: "\\/")
|
||||||
|
|
||||||
// Check if gsed exists; it is able to follow symlinks,
|
// Check if gsed exists; it is able to follow symlinks,
|
||||||
// which we want to do to toggle the extension
|
// which we want to do to toggle the extension
|
||||||
if Shell.fileExists("\(Paths.binPath)/gsed") {
|
if Filesystem.fileExists("\(Paths.binPath)/gsed") {
|
||||||
Shell.run("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)")
|
Shell.run("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)")
|
||||||
} else {
|
} else {
|
||||||
Shell.run("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)")
|
Shell.run("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)")
|
||||||
@@ -45,8 +42,7 @@ func sed(file: String, original: String, replacement: String)
|
|||||||
/**
|
/**
|
||||||
Uses `grep` to determine whether a particular query string can be found in a particular file.
|
Uses `grep` to determine whether a particular query string can be found in a particular file.
|
||||||
*/
|
*/
|
||||||
func grepContains(file: String, query: String) -> Bool
|
func grepContains(file: String, query: String) -> Bool {
|
||||||
{
|
|
||||||
return Shell.pipe("""
|
return Shell.pipe("""
|
||||||
grep -q '\(query)' \(file); [ $? -eq 0 ] && echo "YES" || echo "NO"
|
grep -q '\(query)' \(file); [ $? -eq 0 ] && echo "YES" || echo "NO"
|
||||||
""")
|
""")
|
||||||
|
@@ -3,50 +3,56 @@
|
|||||||
// PHP Monitor
|
// PHP Monitor
|
||||||
//
|
//
|
||||||
// Created by Nico Verbruggen on 21/12/2021.
|
// Created by Nico Verbruggen on 21/12/2021.
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class Log {
|
class Log {
|
||||||
|
|
||||||
static var shared = Log()
|
static var shared = Log()
|
||||||
|
|
||||||
enum Verbosity: Int {
|
enum Verbosity: Int {
|
||||||
case error = 1,
|
case error = 1,
|
||||||
warning = 2,
|
warning = 2,
|
||||||
info = 3,
|
info = 3,
|
||||||
performance = 4
|
performance = 4
|
||||||
|
|
||||||
public func isApplicable() -> Bool {
|
public func isApplicable() -> Bool {
|
||||||
return Log.shared.verbosity.rawValue >= self.rawValue
|
return Log.shared.verbosity.rawValue >= self.rawValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var verbosity: Verbosity = .warning
|
var verbosity: Verbosity = .warning
|
||||||
|
|
||||||
static func err(_ item: Any) {
|
static func err(_ item: Any) {
|
||||||
if Verbosity.error.isApplicable() {
|
if Verbosity.error.isApplicable() {
|
||||||
print("[ERR] \(item)")
|
print("[E] \(item)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func warn(_ item: Any) {
|
static func warn(_ item: Any) {
|
||||||
if Verbosity.warning.isApplicable() {
|
if Verbosity.warning.isApplicable() {
|
||||||
print("[WRN] \(item)")
|
print("[W] \(item)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func info(_ item: Any) {
|
static func info(_ item: Any) {
|
||||||
if Verbosity.info.isApplicable() {
|
if Verbosity.info.isApplicable() {
|
||||||
print(item)
|
print("\(item)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func perf(_ item: Any) {
|
static func perf(_ item: Any) {
|
||||||
if Verbosity.performance.isApplicable() {
|
if Verbosity.performance.isApplicable() {
|
||||||
print(item)
|
print("[P] \(item)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func separator(as verbosity: Verbosity = .info) {
|
||||||
|
if verbosity.isApplicable() {
|
||||||
|
print("==================================")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
// Paths.swift
|
// Paths.swift
|
||||||
// PHP Monitor
|
// PHP Monitor
|
||||||
//
|
//
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@@ -12,63 +12,87 @@ import Foundation
|
|||||||
The path to the Homebrew directory and the user's name are fetched only once, at boot.
|
The path to the Homebrew directory and the user's name are fetched only once, at boot.
|
||||||
*/
|
*/
|
||||||
public class Paths {
|
public class Paths {
|
||||||
|
|
||||||
public static let shared = Paths()
|
public static let shared = Paths()
|
||||||
|
|
||||||
private var baseDir : Paths.HomebrewDir
|
internal var baseDir: Paths.HomebrewDir
|
||||||
|
|
||||||
private var userName : String
|
private var userName: String
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
baseDir = FileManager.default.fileExists(atPath: "\(HomebrewDir.opt.rawValue)/bin/brew") ? .opt : .usr
|
baseDir = App.architecture != "x86_64" ? .opt : .usr
|
||||||
userName = String(Shell.pipe("whoami").split(separator: "\n")[0])
|
userName = String(Shell.pipe("whoami").split(separator: "\n")[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func detectBinaryPaths() {
|
||||||
|
detectComposerBinary()
|
||||||
|
}
|
||||||
|
|
||||||
// - MARK: Binaries
|
// - MARK: Binaries
|
||||||
|
|
||||||
public static var valet: String {
|
public static var valet: String {
|
||||||
return "\(binPath)/valet"
|
return "\(binPath)/valet"
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var brew: String {
|
public static var brew: String {
|
||||||
return "\(binPath)/brew"
|
return "\(binPath)/brew"
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var php: String {
|
public static var php: String {
|
||||||
return "\(binPath)/php"
|
return "\(binPath)/php"
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var phpConfig: String {
|
public static var phpConfig: String {
|
||||||
return "\(binPath)/php-config"
|
return "\(binPath)/php-config"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// - MARK: Detected Binaries
|
||||||
|
|
||||||
|
/** The path to the Composer binary. Can be in multiple locations, so is detected instead. */
|
||||||
|
public static var composer: String?
|
||||||
|
|
||||||
// - MARK: Paths
|
// - MARK: Paths
|
||||||
|
|
||||||
public static var whoami: String {
|
public static var whoami: String {
|
||||||
return shared.userName
|
return shared.userName
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var cellarPath: String {
|
public static var cellarPath: String {
|
||||||
return "\(shared.baseDir.rawValue)/Cellar"
|
return "\(shared.baseDir.rawValue)/Cellar"
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var binPath: String {
|
public static var binPath: String {
|
||||||
return "\(shared.baseDir.rawValue)/bin"
|
return "\(shared.baseDir.rawValue)/bin"
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var optPath: String {
|
public static var optPath: String {
|
||||||
return "\(shared.baseDir.rawValue)/opt"
|
return "\(shared.baseDir.rawValue)/opt"
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var etcPath: String {
|
public static var etcPath: String {
|
||||||
return "\(shared.baseDir.rawValue)/etc"
|
return "\(shared.baseDir.rawValue)/etc"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Flexible Binaries
|
||||||
|
// (these can be in multiple locations, so we scan common places because)
|
||||||
|
// (PHP Monitor will not use the user's own PATH)
|
||||||
|
|
||||||
|
private func detectComposerBinary() {
|
||||||
|
if Filesystem.fileExists("/usr/local/bin/composer") {
|
||||||
|
Paths.composer = "/usr/local/bin/composer"
|
||||||
|
} else if Filesystem.fileExists("/opt/homebrew/bin/composer") {
|
||||||
|
Paths.composer = "/opt/homebrew/bin/composer"
|
||||||
|
} else {
|
||||||
|
Paths.composer = nil
|
||||||
|
Log.warn("Composer was not found.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Enum
|
// MARK: - Enum
|
||||||
|
|
||||||
public enum HomebrewDir: String {
|
public enum HomebrewDir: String {
|
||||||
case opt = "/opt/homebrew"
|
case opt = "/opt/homebrew"
|
||||||
case usr = "/usr/local"
|
case usr = "/usr/local"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
62
phpmon/Common/Core/Process.swift
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
//
|
||||||
|
// Process.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 23/02/2022.
|
||||||
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Process {
|
||||||
|
|
||||||
|
/**
|
||||||
|
When a process is running in the background, it can send content to standard
|
||||||
|
output or standard error, just like it would in a terminal. Using `listen`
|
||||||
|
allows us to react whenever data is received by running a particular closure,
|
||||||
|
depending on which type of data is received.
|
||||||
|
*/
|
||||||
|
public func listen(
|
||||||
|
didReceiveStandardOutputData: @escaping (String) -> Void,
|
||||||
|
didReceiveStandardErrorData: @escaping (String) -> Void
|
||||||
|
) {
|
||||||
|
let outputPipe = Pipe()
|
||||||
|
let errorPipe = Pipe()
|
||||||
|
|
||||||
|
self.standardOutput = outputPipe
|
||||||
|
self.standardError = errorPipe
|
||||||
|
|
||||||
|
[
|
||||||
|
(outputPipe, didReceiveStandardOutputData),
|
||||||
|
(errorPipe, didReceiveStandardErrorData)
|
||||||
|
].forEach { (pipe: Pipe, callback: @escaping (String) -> Void) in
|
||||||
|
pipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
forName: NSNotification.Name.NSFileHandleDataAvailable,
|
||||||
|
object: pipe.fileHandleForReading,
|
||||||
|
queue: nil
|
||||||
|
) { _ in
|
||||||
|
if let outputString = String(
|
||||||
|
data: pipe.fileHandleForReading.availableData,
|
||||||
|
encoding: String.Encoding.utf8
|
||||||
|
) {
|
||||||
|
callback(outputString)
|
||||||
|
}
|
||||||
|
pipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
After the process is done running, you'll want to stop listening.
|
||||||
|
*/
|
||||||
|
public func haltListening() {
|
||||||
|
if let pipe = self.standardOutput as? Pipe {
|
||||||
|
NotificationCenter.default.removeObserver(pipe.fileHandleForReading)
|
||||||
|
}
|
||||||
|
if let pipe = self.standardError as? Pipe {
|
||||||
|
NotificationCenter.default.removeObserver(pipe.fileHandleForReading)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -2,41 +2,41 @@
|
|||||||
// Shell.swift
|
// Shell.swift
|
||||||
// PHP Monitor
|
// PHP Monitor
|
||||||
//
|
//
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
public class Shell {
|
public class Shell {
|
||||||
|
|
||||||
// MARK: - Invoke static functions
|
// MARK: - Invoke static functions
|
||||||
|
|
||||||
public static func run(
|
public static func run(
|
||||||
_ command: String,
|
_ command: String,
|
||||||
requiresPath: Bool = false
|
requiresPath: Bool = false
|
||||||
) {
|
) {
|
||||||
Shell.user.run(command, requiresPath: requiresPath)
|
Shell.user.run(command, requiresPath: requiresPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func pipe(
|
public static func pipe(
|
||||||
_ command: String,
|
_ command: String,
|
||||||
requiresPath: Bool = false
|
requiresPath: Bool = false
|
||||||
) -> String {
|
) -> String {
|
||||||
return Shell.user.pipe(command, requiresPath: requiresPath)
|
return Shell.user.pipe(command, requiresPath: requiresPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Singleton
|
// MARK: - Singleton
|
||||||
|
|
||||||
/**
|
/**
|
||||||
We now require macOS 11, so no need to detect which terminal to use.
|
We now require macOS 11, so no need to detect which terminal to use.
|
||||||
*/
|
*/
|
||||||
public var shell: String = "/bin/sh"
|
public var shell: String = "/bin/sh"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Singleton to access a user shell (with --login)
|
Singleton to access a user shell (with --login)
|
||||||
*/
|
*/
|
||||||
public static let user = Shell()
|
public static let user = Shell()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Runs a shell command without using the output.
|
Runs a shell command without using the output.
|
||||||
Uses the default shell.
|
Uses the default shell.
|
||||||
@@ -51,7 +51,7 @@ public class Shell {
|
|||||||
// Equivalent of piping to /dev/null; don't do anything with the string
|
// Equivalent of piping to /dev/null; don't do anything with the string
|
||||||
_ = Shell.pipe(command, requiresPath: requiresPath)
|
_ = Shell.pipe(command, requiresPath: requiresPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Runs a shell command and returns the output.
|
Runs a shell command and returns the output.
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ public class Shell {
|
|||||||
)
|
)
|
||||||
return !hasError ? shellOutput.standardOutput : shellOutput.errorOutput
|
return !hasError ? shellOutput.standardOutput : shellOutput.errorOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Runs the command and returns a `ShellOutput` object, which contains info about the process.
|
Runs the command and returns a `ShellOutput` object, which contains info about the process.
|
||||||
|
|
||||||
@@ -81,17 +81,17 @@ public class Shell {
|
|||||||
_ command: String,
|
_ command: String,
|
||||||
requiresPath: Bool = false
|
requiresPath: Bool = false
|
||||||
) -> Shell.Output {
|
) -> Shell.Output {
|
||||||
|
|
||||||
let outputPipe = Pipe()
|
let outputPipe = Pipe()
|
||||||
let errorPipe = Pipe()
|
let errorPipe = Pipe()
|
||||||
|
|
||||||
let task = self.createTask(for: command, requiresPath: requiresPath)
|
let task = self.createTask(for: command, requiresPath: requiresPath)
|
||||||
task.standardOutput = outputPipe
|
task.standardOutput = outputPipe
|
||||||
task.standardError = errorPipe
|
task.standardError = errorPipe
|
||||||
task.launch()
|
task.launch()
|
||||||
task.waitUntilExit()
|
task.waitUntilExit()
|
||||||
|
|
||||||
return Shell.Output(
|
let output = Shell.Output(
|
||||||
standardOutput: String(
|
standardOutput: String(
|
||||||
data: outputPipe.fileHandleForReading.readDataToEndOfFile(),
|
data: outputPipe.fileHandleForReading.readDataToEndOfFile(),
|
||||||
encoding: .utf8
|
encoding: .utf8
|
||||||
@@ -102,17 +102,14 @@ public class Shell {
|
|||||||
)!,
|
)!,
|
||||||
task: task
|
task: task
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if CommandLine.arguments.contains("--v") {
|
||||||
|
log(task: task, output: output)
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
Checks if a file exists at a certain path.
|
|
||||||
Used to be done with a shell command, now uses the native FileManager class instead.
|
|
||||||
*/
|
|
||||||
public static func fileExists(_ path: String) -> Bool {
|
|
||||||
let fullPath = path.replacingOccurrences(of: "~", with: "/Users/\(Paths.whoami)")
|
|
||||||
return FileManager.default.fileExists(atPath: fullPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Creates a new process with the correct PATH and shell.
|
Creates a new process with the correct PATH and shell.
|
||||||
*/
|
*/
|
||||||
@@ -120,55 +117,36 @@ public class Shell {
|
|||||||
let tailoredCommand = requiresPath
|
let tailoredCommand = requiresPath
|
||||||
? "export PATH=\(Paths.binPath):$PATH && \(command)"
|
? "export PATH=\(Paths.binPath):$PATH && \(command)"
|
||||||
: command
|
: command
|
||||||
|
|
||||||
let task = Process()
|
let task = Process()
|
||||||
task.launchPath = self.shell
|
task.launchPath = self.shell
|
||||||
task.arguments = ["--noprofile", "-norc", "--login", "-c", tailoredCommand]
|
task.arguments = ["--noprofile", "-norc", "--login", "-c", tailoredCommand]
|
||||||
|
|
||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func captureOutput(
|
/**
|
||||||
_ task: Process,
|
Verbose logging for PHP Monitor's synchronous shell output.
|
||||||
didReceiveStdOutData: @escaping (String) -> Void,
|
*/
|
||||||
didReceiveStdErrData: @escaping (String) -> Void
|
private func log(task: Process, output: Output) {
|
||||||
) {
|
Log.info("")
|
||||||
let outputPipe = Pipe()
|
Log.info("==== COMMAND ====")
|
||||||
let errorPipe = Pipe()
|
Log.info("")
|
||||||
|
Log.info("\(self.shell) \(task.arguments?.joined(separator: " ") ?? "")")
|
||||||
task.standardOutput = outputPipe
|
Log.info("")
|
||||||
task.standardError = errorPipe
|
Log.info("==== OUTPUT ====")
|
||||||
|
Log.info("")
|
||||||
[(outputPipe, didReceiveStdOutData), (errorPipe, didReceiveStdErrData)].forEach {
|
dump(output)
|
||||||
(pipe: Pipe, callback: @escaping (String) -> Void) in
|
Log.info("")
|
||||||
pipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
|
Log.info("==== END OUTPUT ====")
|
||||||
NotificationCenter.default.addObserver(
|
Log.info("")
|
||||||
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 class Output {
|
||||||
public let standardOutput: String
|
public let standardOutput: String
|
||||||
public let errorOutput: String
|
public let errorOutput: String
|
||||||
public let task: Process
|
public let task: Process
|
||||||
|
|
||||||
init(standardOutput: String,
|
init(standardOutput: String,
|
||||||
errorOutput: String,
|
errorOutput: String,
|
||||||
task: Process) {
|
task: Process) {
|
||||||
|
@@ -15,9 +15,9 @@ struct HomebrewPermissionError: Error, AlertableError {
|
|||||||
enum Kind: String {
|
enum Kind: String {
|
||||||
case applescriptNilError = "homebrew_permissions.applescript_returned_nil"
|
case applescriptNilError = "homebrew_permissions.applescript_returned_nil"
|
||||||
}
|
}
|
||||||
|
|
||||||
let kind: Kind
|
let kind: Kind
|
||||||
|
|
||||||
func getErrorMessageKey() -> String {
|
func getErrorMessageKey() -> String {
|
||||||
return "alert.errors.\(self.kind.rawValue)"
|
return "alert.errors.\(self.kind.rawValue)"
|
||||||
}
|
}
|
||||||
|
24
phpmon/Common/Extensions/ArrayExtension.swift
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// ArrayExtension.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 11/06/2022.
|
||||||
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Array {
|
||||||
|
/**
|
||||||
|
Sourced from Stack Overflow
|
||||||
|
https://stackoverflow.com/a/33540708
|
||||||
|
*/
|
||||||
|
func chunked(by distance: Int) -> [[Element]] {
|
||||||
|
let indicesSequence = stride(from: startIndex, to: endIndex, by: distance)
|
||||||
|
let array: [[Element]] = indicesSequence.map {
|
||||||
|
let newIndex = $0.advanced(by: distance) > endIndex ? endIndex : $0.advanced(by: distance)
|
||||||
|
return Array(self[$0 ..< newIndex])
|
||||||
|
}
|
||||||
|
return array
|
||||||
|
}
|
||||||
|
}
|
@@ -2,17 +2,17 @@
|
|||||||
// Date.swift
|
// Date.swift
|
||||||
// PHP Monitor
|
// PHP Monitor
|
||||||
//
|
//
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
extension Date {
|
extension Date {
|
||||||
|
|
||||||
func toString() -> String {
|
func toString() -> String {
|
||||||
let dateFormatter = DateFormatter()
|
let dateFormatter = DateFormatter()
|
||||||
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||||
return dateFormatter.string(from: self)
|
return dateFormatter.string(from: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,27 +3,49 @@
|
|||||||
// PHP Monitor
|
// PHP Monitor
|
||||||
//
|
//
|
||||||
// Created by Nico Verbruggen on 14/04/2021.
|
// Created by Nico Verbruggen on 14/04/2021.
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
extension NSMenu {
|
extension NSMenu {
|
||||||
|
|
||||||
open func addItem(_ newItem: NSMenuItem, withKeyModifier modifier: NSEvent.ModifierFlags) {
|
open func addItem(_ newItem: NSMenuItem, withKeyModifier modifier: NSEvent.ModifierFlags) {
|
||||||
newItem.keyEquivalentModifierMask = modifier
|
newItem.keyEquivalentModifierMask = modifier
|
||||||
self.addItem(newItem)
|
self.addItem(newItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBDesignable class LocalizedMenuItem: NSMenuItem {
|
@IBDesignable class LocalizedMenuItem: NSMenuItem {
|
||||||
|
|
||||||
@IBInspectable
|
@IBInspectable
|
||||||
var localizationKey: String? {
|
var localizationKey: String? {
|
||||||
didSet {
|
didSet {
|
||||||
self.title = localizationKey?.localized ?? self.title
|
self.title = localizationKey?.localized ?? self.title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - NSMenuItem subclasses
|
||||||
|
|
||||||
|
class PhpMenuItem: NSMenuItem {
|
||||||
|
var version: String = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
class XdebugMenuItem: NSMenuItem {
|
||||||
|
var mode: String = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExtensionMenuItem: NSMenuItem {
|
||||||
|
var phpExtension: PhpExtension?
|
||||||
|
}
|
||||||
|
|
||||||
|
class EditorMenuItem: NSMenuItem {
|
||||||
|
var editor: Application?
|
||||||
|
}
|
||||||
|
|
||||||
|
class PresetMenuItem: NSMenuItem {
|
||||||
|
var preset: Preset?
|
||||||
}
|
}
|
||||||
|
38
phpmon/Common/Extensions/NSWindowExtension.swift
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// NSWindowExtension.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 17/02/2022.
|
||||||
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
extension NSWindow {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Shakes a window. Inspired by: http://blog.ericd.net/2016/09/30/shaking-a-macos-window/
|
||||||
|
*/
|
||||||
|
func shake() {
|
||||||
|
let numberOfShakes = 3, durationOfShake = 0.2, vigourOfShake: CGFloat = 0.03
|
||||||
|
|
||||||
|
let frame: CGRect = self.frame
|
||||||
|
let shakeAnimation: CAKeyframeAnimation = CAKeyframeAnimation()
|
||||||
|
|
||||||
|
let shakePath = CGMutablePath()
|
||||||
|
shakePath.move( to: CGPoint(x: frame.minX, y: frame.minY))
|
||||||
|
|
||||||
|
for _ in 0...numberOfShakes-1 {
|
||||||
|
shakePath.addLine(to: CGPoint(x: frame.minX - frame.size.width * vigourOfShake, y: frame.minY))
|
||||||
|
shakePath.addLine(to: CGPoint(x: frame.minX + frame.size.width * vigourOfShake, y: frame.minY))
|
||||||
|
}
|
||||||
|
|
||||||
|
shakePath.closeSubpath()
|
||||||
|
shakeAnimation.path = shakePath
|
||||||
|
shakeAnimation.duration = durationOfShake
|
||||||
|
|
||||||
|
self.animations = ["frameOrigin": shakeAnimation]
|
||||||
|
self.animator().setFrameOrigin(self.frame.origin)
|
||||||
|
}
|
||||||
|
}
|
@@ -2,42 +2,47 @@
|
|||||||
// StringExtension.swift
|
// StringExtension.swift
|
||||||
// PHP Monitor
|
// PHP Monitor
|
||||||
//
|
//
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension String {
|
extension String {
|
||||||
|
|
||||||
var localized: String {
|
var localized: String {
|
||||||
|
if #available(macOS 13, *) {
|
||||||
|
return NSLocalizedString(
|
||||||
|
self, tableName: nil, bundle: Bundle.main, value: "", comment: ""
|
||||||
|
).replacingOccurrences(of: "Preferences", with: "Settings")
|
||||||
|
}
|
||||||
|
|
||||||
return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
|
return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func localized(_ args: CVarArg...) -> String {
|
func localized(_ args: CVarArg...) -> String {
|
||||||
String(format: self.localized, arguments: args)
|
String(format: self.localized, arguments: args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func countInstances(of stringToFind: String) -> Int {
|
func countInstances(of stringToFind: String) -> Int {
|
||||||
if (stringToFind.isEmpty) {
|
if stringToFind.isEmpty {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var count = 0
|
var count = 0
|
||||||
var searchRange: Range<String.Index>?
|
var searchRange: Range<String.Index>?
|
||||||
|
|
||||||
while let foundRange = range(of: stringToFind, options: [], range: searchRange) {
|
while let foundRange = range(of: stringToFind, options: [], range: searchRange) {
|
||||||
count += 1
|
count += 1
|
||||||
searchRange = Range(uncheckedBounds: (lower: foundRange.upperBound, upper: endIndex))
|
searchRange = Range(uncheckedBounds: (lower: foundRange.upperBound, upper: endIndex))
|
||||||
}
|
}
|
||||||
|
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
subscript (r: Range<String.Index>) -> String {
|
subscript(r: Range<String.Index>) -> String {
|
||||||
let start = r.lowerBound
|
let start = r.lowerBound
|
||||||
let end = r.upperBound
|
let end = r.upperBound
|
||||||
return String(self[start ..< end])
|
return String(self[start ..< end])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code taken from: https://sarunw.com/posts/how-to-compare-two-app-version-strings-in-swift/
|
// Code taken from: https://sarunw.com/posts/how-to-compare-two-app-version-strings-in-swift/
|
||||||
/*
|
/*
|
||||||
<1> We split the version by period (.).
|
<1> We split the version by period (.).
|
||||||
@@ -50,12 +55,12 @@ extension String {
|
|||||||
*/
|
*/
|
||||||
func versionCompare(_ otherVersion: String) -> ComparisonResult {
|
func versionCompare(_ otherVersion: String) -> ComparisonResult {
|
||||||
let versionDelimiter = "."
|
let versionDelimiter = "."
|
||||||
|
|
||||||
var versionComponents = self.components(separatedBy: versionDelimiter) // <1>
|
var versionComponents = self.components(separatedBy: versionDelimiter) // <1>
|
||||||
var otherVersionComponents = otherVersion.components(separatedBy: versionDelimiter)
|
var otherVersionComponents = otherVersion.components(separatedBy: versionDelimiter)
|
||||||
|
|
||||||
let zeroDiff = versionComponents.count - otherVersionComponents.count // <2>
|
let zeroDiff = versionComponents.count - otherVersionComponents.count // <2>
|
||||||
|
|
||||||
if zeroDiff == 0 { // <3>
|
if zeroDiff == 0 { // <3>
|
||||||
// Same format, compare normally
|
// Same format, compare normally
|
||||||
return self.compare(otherVersion, options: .numeric)
|
return self.compare(otherVersion, options: .numeric)
|
||||||
@@ -70,5 +75,23 @@ extension String {
|
|||||||
.compare(otherVersionComponents.joined(separator: versionDelimiter), options: .numeric) // <6>
|
.compare(otherVersionComponents.joined(separator: versionDelimiter), options: .numeric) // <6>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var stripped: String {
|
||||||
|
do {
|
||||||
|
guard let data = self.data(using: .unicode) else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
let attributed = try NSAttributedString(
|
||||||
|
data: data,
|
||||||
|
options: [
|
||||||
|
.documentType: NSAttributedString.DocumentType.html,
|
||||||
|
.characterEncoding: String.Encoding.utf8.rawValue],
|
||||||
|
documentAttributes: nil
|
||||||
|
)
|
||||||
|
return attributed.string
|
||||||
|
} catch {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
// PHP Monitor
|
// PHP Monitor
|
||||||
//
|
//
|
||||||
// Created by Nico Verbruggen on 04/02/2021.
|
// Created by Nico Verbruggen on 04/02/2021.
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@@ -12,25 +12,25 @@ import Cocoa
|
|||||||
// Adapted from: https://stackoverflow.com/a/46268778
|
// Adapted from: https://stackoverflow.com/a/46268778
|
||||||
|
|
||||||
protocol XibLoadable {
|
protocol XibLoadable {
|
||||||
|
|
||||||
static var xibName: String? { get }
|
static var xibName: String? { get }
|
||||||
static func createFromXib(in bundle: Bundle) -> Self?
|
static func createFromXib(in bundle: Bundle) -> Self?
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension XibLoadable where Self: NSView {
|
extension XibLoadable where Self: NSView {
|
||||||
|
|
||||||
static var xibName: String? {
|
static var xibName: String? {
|
||||||
return String(describing: Self.self)
|
return String(describing: Self.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func createFromXib(in bundle: Bundle = Bundle.main) -> Self? {
|
static func createFromXib(in bundle: Bundle = Bundle.main) -> Self? {
|
||||||
guard let xibName = xibName else { return nil }
|
guard let xibName = xibName else { return nil }
|
||||||
var topLevelArray: NSArray? = nil
|
var topLevelArray: NSArray?
|
||||||
bundle.loadNibNamed(NSNib.Name(xibName), owner: self, topLevelObjects: &topLevelArray)
|
bundle.loadNibNamed(NSNib.Name(xibName), owner: self, topLevelObjects: &topLevelArray)
|
||||||
guard let results = topLevelArray else { return nil }
|
guard let results = topLevelArray else { return nil }
|
||||||
let views = Array<Any>(results).filter { $0 is Self }
|
let views = [Any](results).filter { $0 is Self }
|
||||||
return views.last as? Self
|
return views.last as? Self
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,31 +2,13 @@
|
|||||||
// Alert.swift
|
// Alert.swift
|
||||||
// PHP Monitor
|
// PHP Monitor
|
||||||
//
|
//
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class Alert {
|
class Alert {
|
||||||
|
|
||||||
public static func present(
|
|
||||||
messageText: String,
|
|
||||||
informativeText: String,
|
|
||||||
buttonTitle: String = "OK",
|
|
||||||
secondButtonTitle: String = "",
|
|
||||||
style: NSAlert.Style = .informational
|
|
||||||
) -> Bool {
|
|
||||||
let alert = NSAlert.init()
|
|
||||||
alert.alertStyle = style
|
|
||||||
alert.messageText = messageText
|
|
||||||
alert.informativeText = informativeText
|
|
||||||
alert.addButton(withTitle: buttonTitle)
|
|
||||||
if (!secondButtonTitle.isEmpty) {
|
|
||||||
alert.addButton(withTitle: secondButtonTitle)
|
|
||||||
}
|
|
||||||
return alert.runModal() == .alertFirstButtonReturn
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func confirm(
|
public static func confirm(
|
||||||
onWindow window: NSWindow,
|
onWindow window: NSWindow,
|
||||||
messageText: String,
|
messageText: String,
|
||||||
@@ -36,12 +18,16 @@ class Alert {
|
|||||||
style: NSAlert.Style = .warning,
|
style: NSAlert.Style = .warning,
|
||||||
onFirstButtonPressed: @escaping (() -> Void)
|
onFirstButtonPressed: @escaping (() -> Void)
|
||||||
) {
|
) {
|
||||||
|
if !Thread.isMainThread {
|
||||||
|
fatalError("You should always present alerts on the main thread!")
|
||||||
|
}
|
||||||
|
|
||||||
let alert = NSAlert.init()
|
let alert = NSAlert.init()
|
||||||
alert.alertStyle = style
|
alert.alertStyle = style
|
||||||
alert.messageText = messageText
|
alert.messageText = messageText
|
||||||
alert.informativeText = informativeText
|
alert.informativeText = informativeText
|
||||||
alert.addButton(withTitle: buttonTitle)
|
alert.addButton(withTitle: buttonTitle)
|
||||||
if (!secondButtonTitle.isEmpty) {
|
if !secondButtonTitle.isEmpty {
|
||||||
alert.addButton(withTitle: secondButtonTitle)
|
alert.addButton(withTitle: secondButtonTitle)
|
||||||
}
|
}
|
||||||
alert.beginSheetModal(for: window) { response in
|
alert.beginSheetModal(for: window) { response in
|
||||||
@@ -50,32 +36,5 @@ class Alert {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
Notify the user about something by showing an alert.
|
|
||||||
*/
|
|
||||||
public static func notify(message: String, info: String, style: NSAlert.Style = .informational) {
|
|
||||||
_ = present(
|
|
||||||
messageText: message,
|
|
||||||
informativeText: info,
|
|
||||||
buttonTitle: "OK",
|
|
||||||
secondButtonTitle: "",
|
|
||||||
style: style
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Notify the user about a particular error (which must be `Alertable`)
|
|
||||||
by showing an alert.
|
|
||||||
*/
|
|
||||||
public static func notify(about error: Error & AlertableError) {
|
|
||||||
let key = error.getErrorMessageKey()
|
|
||||||
_ = present(
|
|
||||||
messageText: "\(key).title".localized,
|
|
||||||
informativeText: "\(key).description".localized,
|
|
||||||
buttonTitle: "OK",
|
|
||||||
secondButtonTitle: "",
|
|
||||||
style: .critical
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
// PHP Monitor
|
// PHP Monitor
|
||||||
//
|
//
|
||||||
// Created by Nico Verbruggen on 07/12/2021.
|
// Created by Nico Verbruggen on 07/12/2021.
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@@ -12,23 +12,23 @@ import Foundation
|
|||||||
/// In most cases this is going to be a code editor, but it could also be another application
|
/// In most cases this is going to be a code editor, but it could also be another application
|
||||||
/// that supports opening those directories, like a visual Git client or a terminal app.
|
/// that supports opening those directories, like a visual Git client or a terminal app.
|
||||||
class Application {
|
class Application {
|
||||||
|
|
||||||
enum AppType {
|
enum AppType {
|
||||||
case editor, browser, git_gui, terminal, user_supplied
|
case editor, browser, git_gui, terminal, user_supplied
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Name of the app. Used for display purposes and to determine `name.app` exists.
|
/// Name of the app. Used for display purposes and to determine `name.app` exists.
|
||||||
let name: String
|
let name: String
|
||||||
|
|
||||||
/// Application type. Depending on the type, a different action might occur.
|
/// Application type. Depending on the type, a different action might occur.
|
||||||
let type: AppType
|
let type: AppType
|
||||||
|
|
||||||
/// Initializer. Used to detect a specific app of a specific type.
|
/// Initializer. Used to detect a specific app of a specific type.
|
||||||
init(_ name: String, _ type: AppType) {
|
init(_ name: String, _ type: AppType) {
|
||||||
self.name = name
|
self.name = name
|
||||||
self.type = type
|
self.type = type
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Attempt to open a specific directory in the app of choice.
|
Attempt to open a specific directory in the app of choice.
|
||||||
(This will open the app if it isn't open yet.)
|
(This will open the app if it isn't open yet.)
|
||||||
@@ -36,7 +36,7 @@ class Application {
|
|||||||
@objc public func openDirectory(file: String) {
|
@objc public func openDirectory(file: String) {
|
||||||
return Shell.run("/usr/bin/open -a \"\(name)\" \"\(file)\"")
|
return Shell.run("/usr/bin/open -a \"\(name)\" \"\(file)\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Checks if the app is installed. */
|
/** Checks if the app is installed. */
|
||||||
func isInstalled() -> Bool {
|
func isInstalled() -> Bool {
|
||||||
// If this script does not complain, the app exists!
|
// If this script does not complain, the app exists!
|
||||||
@@ -45,7 +45,7 @@ class Application {
|
|||||||
requiresPath: false
|
requiresPath: false
|
||||||
).task.terminationStatus == 0
|
).task.terminationStatus == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Detect which apps are available to open a specific directory.
|
Detect which apps are available to open a specific directory.
|
||||||
*/
|
*/
|
||||||
|
@@ -1,29 +0,0 @@
|
|||||||
//
|
|
||||||
// Async.swift
|
|
||||||
// PHP Monitor
|
|
||||||
//
|
|
||||||
// Created by Nico Verbruggen on 23/01/2022.
|
|
||||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
/**
|
|
||||||
This generic async helper is something I'd like to use in more places.
|
|
||||||
|
|
||||||
The `DispatchQueue.global` into `DispatchQueue.main.async` logic is common in the app.
|
|
||||||
|
|
||||||
I could also use try `async` support which was introduced in Swift but that would
|
|
||||||
require too much refactoring at this time to consider. I also need to read up on async
|
|
||||||
in order to properly grasp all the gotchas. Looking into that later at some point.
|
|
||||||
*/
|
|
||||||
public func runAsync(_ execute: @escaping () -> Void, completion: @escaping () -> Void = {})
|
|
||||||
{
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
|
||||||
execute()
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -3,13 +3,13 @@
|
|||||||
// PHP Monitor
|
// PHP Monitor
|
||||||
//
|
//
|
||||||
// Created by Nico Verbruggen on 07/12/2021.
|
// Created by Nico Verbruggen on 07/12/2021.
|
||||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class Filesystem {
|
class Filesystem {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Checks if a file exists at the provided path.
|
Checks if a file exists at the provided path.
|
||||||
Uses `FileManager`.
|
Uses `FileManager`.
|
||||||
@@ -19,5 +19,5 @@ class Filesystem {
|
|||||||
atPath: path.replacingOccurrences(of: "~", with: "/Users/\(Paths.whoami)")
|
atPath: path.replacingOccurrences(of: "~", with: "/Users/\(Paths.whoami)")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|