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

Compare commits

...

18 Commits

Author SHA1 Message Date
cb504cc3f0 🔧 Bump build 2023-02-24 19:37:41 +01:00
7c08a2fdfe 👌 Improve PHP Doctor w/ async code
- This fixes an issue with PHP Doctor's "Scan Again" button not working.
- This also adds a new check which verifies if "php.ini", "php-fpm.conf"
  and "php-fpm.d/valet-fpm.conf" exist (all required files).
2023-02-24 19:36:12 +01:00
2f47610c85 🔧 Use macOS 12.4 deployment target 2023-02-21 02:27:03 +01:00
cc6324b692 ♻️ PHP Guard changes 2023-02-20 18:15:12 +01:00
15d75a7f98 🔧 Bump build 2023-02-17 17:21:13 +01:00
c7c5311ff9 🐛 Ensure checkbox shows correct initial state 2023-02-17 17:19:56 +01:00
c93f047909 🔧 Bump build 2023-02-15 20:32:56 +01:00
f82a2120f7 🔧 Add display name 2023-02-15 20:32:37 +01:00
857cba9f45 On macOS 13 and newer, add "Start at login" (#210) 2023-02-15 20:24:01 +01:00
ec49257bcc 🔧 Bump build 2023-02-13 17:43:52 +01:00
9c6a21008a 🐛 Adjusted for new Homebrew JSON output (#235) 2023-02-13 17:38:09 +01:00
5dffbf57d1 👌 Do not load identity asynchronously 2023-02-11 20:46:05 +01:00
21a1d6576e 🔧 Bump build 2023-02-10 19:36:42 +01:00
b08912ce11 Add support for wildcard constraints (#224) 2023-02-10 19:31:07 +01:00
7285d24ef3 👌 Improve first launch onboarding experience 2023-02-07 22:02:34 +01:00
ac60c66bb9 🐛 Add missing strings for update 2023-02-06 19:33:41 +01:00
9a7575790a 🔧 Bump build 2023-02-06 19:12:34 +01:00
cd5cbccb04 🐛 Fix generated script (#231) 2023-02-06 19:10:10 +01:00
31 changed files with 520 additions and 188 deletions

View File

@ -112,6 +112,7 @@
C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A1925D9CD1000591B77 /* Utility.swift */; };
C43A8A2025D9D1D700591B77 /* brew-formula.json in Resources */ = {isa = PBXBuildFile; fileRef = C43A8A1F25D9D1D700591B77 /* brew-formula.json */; };
C43A8A2425D9D20D00591B77 /* HomebrewPackageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A2325D9D20D00591B77 /* HomebrewPackageTest.swift */; };
C43FDBE929A932B0003D85EC /* PhpConfigChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43FDBE829A932B0003D85EC /* PhpConfigChecker.swift */; };
C44067F527E2582B0045BD4E /* DomainListNameCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F427E2582B0045BD4E /* DomainListNameCell.swift */; };
C44067F727E258410045BD4E /* DomainListPhpCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F627E258410045BD4E /* DomainListPhpCell.swift */; };
C44067F927E2585E0045BD4E /* DomainListTypeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F827E2585E0045BD4E /* DomainListTypeCell.swift */; };
@ -481,6 +482,10 @@
C47699EF28A2F2A30060FEB8 /* WarningManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699EE28A2F2A30060FEB8 /* WarningManager.swift */; };
C47699F128A2F3150060FEB8 /* Warning.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699F028A2F3150060FEB8 /* Warning.swift */; };
C476FF9822B0DD830098105B /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; };
C47DF1AF299D5A3B0007055D /* LoginItemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DF1AE299D5A3B0007055D /* LoginItemManager.swift */; };
C47DF1B0299D5A3B0007055D /* LoginItemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DF1AE299D5A3B0007055D /* LoginItemManager.swift */; };
C47DF1B1299D5A3B0007055D /* LoginItemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DF1AE299D5A3B0007055D /* LoginItemManager.swift */; };
C47DF1B2299D5A3B0007055D /* LoginItemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DF1AE299D5A3B0007055D /* LoginItemManager.swift */; };
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; };
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; };
C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; };
@ -807,6 +812,7 @@
C43A8A1925D9CD1000591B77 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = "<group>"; };
C43A8A1F25D9D1D700591B77 /* brew-formula.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "brew-formula.json"; sourceTree = "<group>"; };
C43A8A2325D9D20D00591B77 /* HomebrewPackageTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackageTest.swift; sourceTree = "<group>"; };
C43FDBE829A932B0003D85EC /* PhpConfigChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigChecker.swift; sourceTree = "<group>"; };
C44067F427E2582B0045BD4E /* DomainListNameCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListNameCell.swift; sourceTree = "<group>"; };
C44067F627E258410045BD4E /* DomainListPhpCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListPhpCell.swift; sourceTree = "<group>"; };
C44067F827E2585E0045BD4E /* DomainListTypeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListTypeCell.swift; sourceTree = "<group>"; };
@ -857,6 +863,7 @@
C47699EE28A2F2A30060FEB8 /* WarningManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningManager.swift; sourceTree = "<group>"; };
C47699F028A2F3150060FEB8 /* Warning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Warning.swift; sourceTree = "<group>"; };
C476FF9722B0DD830098105B /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = "<group>"; };
C47DF1AE299D5A3B0007055D /* LoginItemManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginItemManager.swift; sourceTree = "<group>"; };
C4811D2322D70A4700B5F6B3 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = "<group>"; };
C48D0C9225CC804200CC7490 /* XibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XibLoadable.swift; sourceTree = "<group>"; };
@ -1214,6 +1221,7 @@
C422DDAB28A2DAA100CEAC97 /* Warnings */ = {
isa = PBXGroup;
children = (
C43FDBE729A9329A003D85EC /* Services */,
C47699F028A2F3150060FEB8 /* Warning.swift */,
C47699EE28A2F2A30060FEB8 /* WarningManager.swift */,
C422DDAC28A2DAC600CEAC97 /* WarningsWindowController.swift */,
@ -1239,6 +1247,14 @@
path = Warning;
sourceTree = "<group>";
};
C43FDBE729A9329A003D85EC /* Services */ = {
isa = PBXGroup;
children = (
C43FDBE829A932B0003D85EC /* PhpConfigChecker.swift */,
);
path = Services;
sourceTree = "<group>";
};
C44067F327E256560045BD4E /* Cells */ = {
isa = PBXGroup;
children = (
@ -1440,6 +1456,7 @@
C4B5635D276AB09000F12CCB /* VersionExtractor.swift */,
C4D3660A29113F20006BD146 /* System.swift */,
C4D36614291160A1006BD146 /* WIP.swift */,
C47DF1AE299D5A3B0007055D /* LoginItemManager.swift */,
);
path = Helpers;
sourceTree = "<group>";
@ -2016,6 +2033,7 @@
files = (
C47699EF28A2F2A30060FEB8 /* WarningManager.swift in Sources */,
C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */,
C47DF1AF299D5A3B0007055D /* LoginItemManager.swift in Sources */,
C4D3661A291173EA006BD146 /* DictionaryExtension.swift in Sources */,
C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */,
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */,
@ -2148,6 +2166,7 @@
C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */,
C4FACE83288F1F9700FC478F /* OnboardingWindowController.swift in Sources */,
C4080FFA27BD956700BF2C6B /* BetterAlertVC.swift in Sources */,
C43FDBE929A932B0003D85EC /* PhpConfigChecker.swift in Sources */,
C4BF56AB2949381100379603 /* FakeValetInteractor.swift in Sources */,
C4B5635E276AB09000F12CCB /* VersionExtractor.swift in Sources */,
C451AFF62969E40F0078E617 /* HelpButton.swift in Sources */,
@ -2224,6 +2243,7 @@
C4D36617291160A1006BD146 /* WIP.swift in Sources */,
C471E85728F9BB650021E251 /* DomainListTLSCell.swift in Sources */,
C471E85828F9BB650021E251 /* DomainListNameCell.swift in Sources */,
C47DF1B1299D5A3B0007055D /* LoginItemManager.swift in Sources */,
C471E85928F9BB650021E251 /* DomainListPhpCell.swift in Sources */,
C471E85A28F9BB650021E251 /* DomainListTypeCell.swift in Sources */,
C471E85B28F9BB650021E251 /* DomainListKindCell.swift in Sources */,
@ -2355,6 +2375,7 @@
C471E89528F9BB8F0021E251 /* MenuBarImageGenerator.swift in Sources */,
C471E89628F9BB8F0021E251 /* PMWindowController.swift in Sources */,
C471E89728F9BB8F0021E251 /* VersionExtractor.swift in Sources */,
C47DF1B2299D5A3B0007055D /* LoginItemManager.swift in Sources */,
C4E2E86728FC2F1B003B070C /* XCPMApplication.swift in Sources */,
C471E89828F9BB8F0021E251 /* ValetProxy.swift in Sources */,
C471E89A28F9BB8F0021E251 /* DomainScanner.swift in Sources */,
@ -2565,6 +2586,7 @@
C4AF9F7B2754499000D44ED0 /* Valet.swift in Sources */,
C4C1019C27C65C6F001FACC2 /* Process.swift in Sources */,
C451AFF72969E40F0078E617 /* HelpButton.swift in Sources */,
C47DF1B0299D5A3B0007055D /* LoginItemManager.swift in Sources */,
C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */,
C45B914A295607F400F4EC78 /* Service.swift in Sources */,
C4C0E8E327F88B13002D32A9 /* ValetDomainScanner.swift in Sources */,
@ -2782,7 +2804,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@ -2839,7 +2861,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
@ -2857,18 +2879,19 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1060;
CURRENT_PROJECT_VERSION = 1074;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 5.8;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -2886,18 +2909,19 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1060;
CURRENT_PROJECT_VERSION = 1074;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 5.8;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -3096,7 +3120,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
@ -3114,17 +3138,18 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1060;
CURRENT_PROJECT_VERSION = 1074;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor DEV";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 5.8;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev;
PRODUCT_NAME = "$(TARGET_NAME) DEV";
@ -3205,7 +3230,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@ -3224,17 +3249,18 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1060;
CURRENT_PROJECT_VERSION = 1074;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor DEV";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 5.8;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@ -24,7 +24,7 @@ You can also add new domains as links, isolate sites, manage various services, a
PHP Monitor is a universal application that runs natively on Apple Silicon **and** Intel-based Macs.
* Your user account can administer your computer (required for some functionality, e.g. certificate generation)
* macOS 11 Big Sur or later
* macOS 12.4 or later (Monterey and Ventura are supported)
* Homebrew is installed in `/usr/local/homebrew` or `/opt/homebrew`
* Homebrew `php` formula is installed
* Laravel Valet (works with Valet v2, v3 and v4)

View File

@ -6,7 +6,7 @@ Generally speaking, only the latest version of **PHP Monitor** is supported, exc
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Recommended Valet Version |
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
| 5.7 | ✅ 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)<br/>PHP 7.1-PHP 8.2 (w/ Valet 4.x*) | 3.0 or higher recommended<br/> 2.16.2 minimum |
| 5.8 | ✅ Universal binary | ✅ Yes | Monterey (12.4+)<br/>Ventura (13.0+) | macOS 12.4 | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.2 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.2 (w/ Valet 4.x*) | 3.0 or higher recommended<br/> 2.16.2 minimum |
(*) Preliminary listing. Valet 4 hasn't been released yet and the versions of PHP Valet can work with might still change.
@ -16,6 +16,7 @@ These versions of PHP Monitor are no longer supported, but if youre using an
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version |
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
| 5.7 | ✅ Universal binary | ❌ | 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 |
| 5.6 | ✅ Universal binary | ❌ | 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 |
| 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)<br/>Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 | 2.13 |

View File

@ -13,21 +13,21 @@ class Actions {
// MARK: - Services
public static func restartPhpFpm() async {
await brew("services restart \(Homebrew.Formulae.php.name)", sudo: Homebrew.Formulae.php.elevated)
await brew("services restart \(Homebrew.Formulae.php)", sudo: Homebrew.Formulae.php.elevated)
}
public static func restartNginx() async {
await brew("services restart \(Homebrew.Formulae.nginx.name)", sudo: Homebrew.Formulae.nginx.elevated)
await brew("services restart \(Homebrew.Formulae.nginx)", sudo: Homebrew.Formulae.nginx.elevated)
}
public static func restartDnsMasq() async {
await brew("services restart \(Homebrew.Formulae.dnsmasq.name)", sudo: Homebrew.Formulae.dnsmasq.elevated)
await brew("services restart \(Homebrew.Formulae.dnsmasq)", sudo: Homebrew.Formulae.dnsmasq.elevated)
}
public static func stopValetServices() async {
await brew("services stop \(Homebrew.Formulae.php.name)", sudo: Homebrew.Formulae.php.elevated)
await brew("services stop \(Homebrew.Formulae.nginx.name)", sudo: Homebrew.Formulae.nginx.elevated)
await brew("services stop \(Homebrew.Formulae.dnsmasq.name)", sudo: Homebrew.Formulae.dnsmasq.elevated)
await brew("services stop \(Homebrew.Formulae.php)", sudo: Homebrew.Formulae.php.elevated)
await brew("services stop \(Homebrew.Formulae.nginx)", sudo: Homebrew.Formulae.nginx.elevated)
await brew("services stop \(Homebrew.Formulae.dnsmasq)", sudo: Homebrew.Formulae.dnsmasq.elevated)
}
public static func fixHomebrewPermissions() throws {
@ -54,9 +54,10 @@ class Actions {
+ " && "
+ cellarCommands.joined(separator: " && ")
let appleScript = NSAppleScript(
source: "do shell script \"\(script)\" with administrator privileges"
)
let source = "do shell script \"\(script)\" with administrator privileges"
Log.perf(source)
let appleScript = NSAppleScript(source: source)
let eventResult: NSAppleEventDescriptor? = appleScript?.executeAndReturnError(nil)

View File

@ -36,10 +36,14 @@ class Homebrew {
}
}
class HomebrewFormula: Equatable, Hashable {
class HomebrewFormula: Equatable, Hashable, CustomStringConvertible {
let name: String
let elevated: Bool
var description: String {
return name
}
init(_ name: String, elevated: Bool = true) {
self.name = name
self.elevated = elevated

View File

@ -16,16 +16,12 @@ public class Paths {
public static let shared = Paths()
internal var baseDir: Paths.HomebrewDir
private var userName: String! = nil
private var userName: String
init() {
baseDir = App.architecture != "x86_64" ? .opt : .usr
}
public func loadUser() async {
let output = await Shell.pipe("id -un").out
userName = String(output.split(separator: "\n")[0])
userName = identity()
Log.info("[ID] The current username is `\(userName)`.")
}
public func detectBinaryPaths() {

View File

@ -0,0 +1,25 @@
//
// LoginItemManager.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 15/02/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import AppKit
import ServiceManagement
@available(macOS 13.0, *)
class LoginItemManager {
func loginItemIsEnabled() -> Bool {
return SMAppService.mainApp.status == .enabled
}
func disableLoginItem() {
try? SMAppService.mainApp.unregister()
}
func enableLoginItem() {
try? SMAppService.mainApp.register()
}
}

View File

@ -27,6 +27,30 @@ public func system(_ command: String) -> String {
return output
}
/** Same as the `system` command, but does not return the output. */
public func system_quiet(_ command: String) {
_ = system(command)
}
/**
Retrieves the username for the currently signed in user via `/usr/bin/id`.
This cannot fail or the application will crash.
*/
public func identity() -> String {
let task = Process()
task.launchPath = "/usr/bin/id"
task.arguments = ["-un"]
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
guard let output = String(
data: pipe.fileHandleForReading.readDataToEndOfFile(),
encoding: String.Encoding.utf8
) else {
fatalError("Could not retrieve username via `id -un`!")
}
return output.trimmingCharacters(in: .whitespacesAndNewlines)
}

View File

@ -8,8 +8,6 @@
import Foundation
struct HomebrewPackage: Decodable {
let name: String
let full_name: String
let aliases: [String]
let installed: [HomebrewInstalled]

View File

@ -35,6 +35,7 @@ public struct PhpVersionNumberCollection: Equatable {
- Parameter strict: Whether the patch version check is strict. See more below.
The strict mode does not matter if a patch version is provided for all versions in the collection.
It also does not matter for certain comparisons (e.g. when dealing with wildcards).
Strict mode assumes that any PHP version lacking precise patch information, e.g. inferred
from Homebrew corresponds to the .0 patch version of that version. The default, which is imprecise,
@ -45,6 +46,7 @@ public struct PhpVersionNumberCollection: Equatable {
Given versions 8.0.? and 8.1.?, but the requirement is ^8.0.1, in strict mode only 8.1.? will
be considered valid (8.0 translates to 8.0.0 and as such is older than 8.0.1, 8.1.0 is OK).
When checking against actual PHP versions installed by the user (with patch precision), use
strict mode.
@ -52,11 +54,26 @@ public struct PhpVersionNumberCollection: Equatable {
Given versions 8.0.? and 8.1.?, but the requirement is ^8.0.1, in non-strict mode version 8.0
is assumed to be equal to version 8.0.999, which is actually fine if 8.0.1 is the required version.
In non-strict mode, the patch version is ignored for regular version checks (no caret / tilde).
If checking compatibility with general Homebrew versions of PHP, do NOT use strict mode, since
the patch version there is not used. (The formula php@8.0 suffices for ^8.0.1.)
*/
public func matching(constraint: String, strict: Bool = false) -> [VersionNumber] {
if constraint == "*" {
return self.versions
}
if let version = VersionNumber.make(from: constraint, type: .wildCardPatch) {
// Wildcard for patch (e.g. "7.4.*") must match major and minor (any patch)
return self.versions.filter { $0.hasSameMajorAndMinor(version) }
}
if let version = VersionNumber.make(from: constraint, type: .wildCardMinor) {
// Strict constraint (e.g. "7.*") -> must only match major (any patch, minor)
return self.versions.filter { $0.isSameMajorVersionAs(version) }
}
if let version = VersionNumber.make(from: constraint, type: .versionOnly) {
// Strict constraint (e.g. "7.0") -> returns specific version
return self.versions.filter { $0.isSameAs(version, strict) }

View File

@ -39,6 +39,8 @@ public struct VersionNumber: Equatable, Hashable {
public enum MatchType: String {
case versionOnly = #"^(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
case wildCardPatch = #"^(?<major>\d+).(?<minor>\d+).?(?<patch>\*)?\z"#
case wildCardMinor = #"^(?<major>\d+).(?<minor>\*)?\z"#
case caretVersionRange = #"^\^(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
case tildeVersionRange = #"^~(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
case greaterThanOrEqual = #"^>=(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
@ -64,21 +66,25 @@ public struct VersionNumber: Equatable, Hashable {
range: NSRange(location: 0, length: versionString.count)
).first
if match != nil {
let major = Int(
versionString[Range(match!.range(withName: "major"), in: versionString)!]
)!
let minor = Int(
versionString[Range(match!.range(withName: "minor"), in: versionString)!]
)!
var patch: Int?
if let minorRange = Range(match!.range(withName: "patch"), in: versionString) {
patch = Int(versionString[minorRange])
}
return Self(major: major, minor: minor, patch: patch)
guard let match else { return nil }
let major = Int(versionString[Range(match.range(withName: "major"), in: versionString)!])!
var minor: Int = 0
var patch: Int?
if let minorRange = Range(match.range(withName: "minor"), in: versionString) {
let value = versionString[minorRange] as String
// Zero is the fallback if a wildcard was used
minor = Int(value) ?? 0
}
return nil
if let patchRange = Range(match.range(withName: "patch"), in: versionString) {
let value = versionString[patchRange] as String
// nil is the fallback if a wildcard was used
patch = Int(value) ?? nil
}
return Self(major: major, minor: minor, patch: patch)
}
// MARK: Comparison Logic
@ -93,6 +99,10 @@ public struct VersionNumber: Equatable, Hashable {
&& (strict ? self.patch(strict, version) == version.patch(strict) : true)
}
internal func hasSameMajorAndMinor(_ version: VersionNumber) -> Bool {
return self.major == version.major && self.minor == version.minor
}
internal func isNewerThan(_ version: VersionNumber, _ strict: Bool) -> Bool {
return (
self.major > version.major ||

View File

@ -22,7 +22,6 @@ class InternalSwitcher: PhpSwitcher {
*/
func performSwitch(to version: String) async {
Log.info("Switching to \(version), unlinking all versions...")
let versions = getVersionsToBeHandled(version)
await withTaskGroup(of: String.self, body: { group in

View File

@ -111,7 +111,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
// Make sure notifications will work
setupNotifications()
Task { // Make sure the menu performs its initial checks
await paths.loadUser()
await menu.startup()
}
}

View File

@ -17,7 +17,7 @@ public class EnvironmentManager {
// Failure condition #1: does not contain Laravel Valet
if !output.contains("Laravel Valet") {
return true
return false
}
// Extract the version number
@ -25,7 +25,6 @@ public class EnvironmentManager {
// Get the actual version
return Valet.shared.version == nil
}() // returns true if none of the failure conditions are met
}
}

View File

@ -110,9 +110,9 @@ extension MainMenu {
Task { @MainActor in
OnboardingWindowController.show()
}
} else {
await AppUpdater().checkForUpdates(interactive: false)
}
await AppUpdater().checkForUpdates(interactive: false)
}
// Check if the linked version has changed between launches of phpmon

View File

@ -9,6 +9,7 @@
import Foundation
import Cocoa
@MainActor
class BetterAlert {
var windowController: NSWindowController!

View File

@ -42,4 +42,13 @@ class OnboardingWindowController: PMWindowController {
NSApp.activate(ignoringOtherApps: true)
}
override func close() {
super.close()
// Search for updates after closing the window
if Stats.successfulLaunchCount == 1 {
Task { await AppUpdater().checkForUpdates(interactive: false) }
}
}
}

View File

@ -243,6 +243,10 @@ class GeneralPreferencesVC: GenericPreferenceVC {
vc.getAutomaticUpdateCheckPV()
]
if #available(macOS 13, *) {
vc.views.append(CheckboxPreferenceView.makeLoginItemView())
}
return vc
}
}

View File

@ -142,42 +142,50 @@ class Stats {
}
public static func evaluateLastLinkedPhpVersion() {
let currentVersion = PhpEnv.phpInstall.version.short
let currentVersion = PhpEnv.phpInstall.version?.short ?? ""
let previousVersion = Stats.lastGlobalPhpVersion
// Save the PHP version that is currently in use (only if unknown)
if Stats.lastGlobalPhpVersion == "" {
if currentVersion == "" {
return Log.warn("<PG> PHP Guard is unable to determine the current PHP version!")
}
Log.info("<PG> The currently linked version of PHP is: \(currentVersion).")
if previousVersion == "" {
Stats.persistCurrentGlobalPhpVersion(version: currentVersion)
Log.info("Persisting the currently linked PHP version (first time only).")
} else {
Log.info("Previously, the globally linked PHP version was: \(previousVersion).")
if previousVersion != currentVersion {
Log.info("Currently, that version is: \(currentVersion). This is a mismatch.")
Task { @MainActor in
BetterAlert()
.withInformation(
title: "startup.version_mismatch.title".localized,
subtitle: "startup.version_mismatch.subtitle".localized(
currentVersion,
previousVersion
),
description: "startup.version_mismatch.desc".localized()
)
.withPrimary(text: "startup.version_mismatch.button_switch_back".localized(
previousVersion
), action: { alert in
alert.close(with: .OK)
Task { MainMenu.shared.switchToAnyPhpVersion(previousVersion) }
})
.withTertiary(text: "startup.version_mismatch.button_stay".localized(
currentVersion
), action: { alert in
Stats.persistCurrentGlobalPhpVersion(version: currentVersion)
alert.close(with: .OK)
})
.show()
}
}
return Log.warn("<PG> PHP Guard is saving the currently linked PHP version (first time only).")
}
Log.info("<PG> Previously, the globally linked PHP version was: \(previousVersion).")
if previousVersion == currentVersion {
return Log.info("<PG> PHP Guard did not notice any changes in the linked PHP version.")
}
// At this point, the version is *not* a match
Log.info("<PG> PHP Guard noticed a different PHP version. An alert will be displayed!")
Task { @MainActor in
BetterAlert()
.withInformation(
title: "startup.version_mismatch.title".localized,
subtitle: "startup.version_mismatch.subtitle".localized(
currentVersion,
previousVersion
),
description: "startup.version_mismatch.desc".localized()
)
.withPrimary(text: "startup.version_mismatch.button_switch_back".localized(
previousVersion
), action: { alert in
alert.close(with: .OK)
Task { MainMenu.shared.switchToAnyPhpVersion(previousVersion) }
})
.withTertiary(text: "startup.version_mismatch.button_stay".localized(
currentVersion
), action: { alert in
Stats.persistCurrentGlobalPhpVersion(version: currentVersion)
alert.close(with: .OK)
})
.show()
}
}
}

View File

@ -10,18 +10,12 @@ import Foundation
import Cocoa
class CheckboxPreferenceView: NSView, XibLoadable {
@IBOutlet weak var labelSection: NSTextField!
@IBOutlet weak var labelDescription: NSTextField!
@IBOutlet weak var buttonCheckbox: NSButton!
var action: (() -> Void)!
var preference: PreferenceName! {
didSet {
self.buttonCheckbox.state = Preferences.isEnabled(self.preference) ? .on : .off
}
}
var behavior: CheckboxPreferenceViewBehavior!
static func make(
sectionText: String,
@ -31,17 +25,75 @@ class CheckboxPreferenceView: NSView, XibLoadable {
action: @escaping () -> Void
) -> NSView {
let view = Self.createFromXib()!
view.behavior = CheckboxPreferenceBehavior(
button: view.buttonCheckbox,
preference: preference
)
view.labelSection.stringValue = sectionText
view.labelDescription.stringValue = descriptionText
view.buttonCheckbox.title = checkboxText
view.preference = preference
view.action = action
return view
}
@IBAction func toggled(_ sender: Any) {
Preferences.update(self.preference, value: buttonCheckbox.state == .on)
self.action()
@available(macOS 13.0, *)
static func makeLoginItemView() -> NSView {
let view = Self.createFromXib()!
view.behavior = CheckboxLaunchItemBehavior(button: view.buttonCheckbox)
view.labelSection.stringValue = "prefs.startup".localized
view.labelDescription.stringValue = "prefs.auto_start_desc".localized
view.buttonCheckbox.title = "prefs.auto_start_title".localized
view.action = {}
return view
}
@IBAction func toggled(_ sender: Any) {
self.behavior.toggled(checked: buttonCheckbox.state == .on)
self.action()
}
}
protocol CheckboxPreferenceViewBehavior {
func toggled(checked: Bool)
}
class CheckboxPreferenceBehavior: CheckboxPreferenceViewBehavior {
var button: NSButton
var preference: PreferenceName
init(button: NSButton, preference: PreferenceName) {
self.preference = preference
self.button = button
self.button.state = Preferences.isEnabled(self.preference) ? .on : .off
}
public func toggled(checked: Bool) {
Preferences.update(self.preference, value: checked)
}
}
@available(macOS 13.0, *)
class CheckboxLaunchItemBehavior: CheckboxPreferenceViewBehavior {
var manager = LoginItemManager()
var button: NSButton
init(button: NSButton) {
self.button = button
if manager.loginItemIsEnabled() {
self.button.state = .on
} else {
self.button.state = .off
}
}
public func toggled(checked: Bool) {
if checked {
self.manager.enableLoginItem()
} else {
self.manager.disableLoginItem()
}
self.button.state = self.manager.loginItemIsEnabled() ? .on : .off
}
}

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>

View File

@ -9,10 +9,14 @@
import SwiftUI
struct WarningListView: View {
@State var warnings: [Warning]
@ObservedObject var warningManager: WarningManager
init(empty: Bool = false) {
self.warnings = empty ? [] : WarningManager.shared.warnings
if empty {
WarningManager.shared.warnings = []
}
warningManager = WarningManager.shared
}
var body: some View {
@ -40,7 +44,6 @@ struct WarningListView: View {
Button("warnings.refresh.button".localizedForSwiftUI) {
Task { // Reload warnings
await WarningManager.shared.checkEnvironment()
self.warnings = WarningManager.shared.warnings
}
}
Text("warnings.refresh.button.description".localizedForSwiftUI)
@ -51,14 +54,14 @@ struct WarningListView: View {
List {
VStack(alignment: .leading, spacing: 0) {
if warnings.isEmpty {
if warningManager.warnings.isEmpty {
NoWarningsView()
} else {
ForEach(warnings) { warning in
ForEach(warningManager.warnings) { warning in
Group {
WarningView(
title: warning.title,
paragraphs: warning.paragraphs,
paragraphs: warning.paragraphs(),
documentationUrl: warning.url
)
.fixedSize(horizontal: false, vertical: true)
@ -67,7 +70,8 @@ struct WarningListView: View {
}.padding(5)
}
}
}.frame(minHeight: 0, maxHeight: .infinity).padding(5)
}
.frame(minHeight: 0, maxHeight: .infinity).padding(5)
}
.listRowInsets(EdgeInsets())
.listStyle(.plain)

View File

@ -26,7 +26,7 @@ struct WarningView: View {
Text(title.localizedForSwiftUI)
.fontWeight(.bold)
ForEach(paragraphs, id: \.self) { paragraph in
Text(paragraph.localizedForSwiftUI)
Text(paragraph)
.font(.system(size: 13))
}
}

View File

@ -0,0 +1,39 @@
//
// PhpConfigChecker.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 24/02/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
class PhpConfigChecker {
public static var shared = PhpConfigChecker()
var missing: [String] = []
public func check() {
missing = []
let shouldExist = [
"php.ini",
"php-fpm.conf",
"php-fpm.d/valet-fpm.conf"
]
for version in PhpEnv.shared.availablePhpVersions {
for file in shouldExist {
let fullFilePath = Paths.etcPath.appending("/php/\(version)/\(file)")
if !FileSystem.fileExists(fullFilePath) {
missing.append(fullFilePath)
}
}
}
if !missing.isEmpty {
Log.warn("The following config file(s) were missing: \(missing)")
}
}
}

View File

@ -8,19 +8,27 @@
import Foundation
struct Warning: Identifiable {
struct Warning: Identifiable, Hashable {
var id = UUID()
let command: () async -> Bool
let name: String
let title: String
let paragraphs: [String]
let paragraphs: () -> [String]
let url: String?
/**
- Parameters:
- command: The command that, if it returns true, means that a warning applies
- name: The internal name or description for this warning
- title: The title displayed for the user
- paragraphs: The main body of text displayed for the user
- url: The URL that one can navigate to for more information (if applicable)
*/
init(
command: @escaping () async -> Bool,
name: String,
title: String,
paragraphs: [String],
paragraphs: @escaping () -> [String],
url: String?
) {
self.command = command
@ -33,4 +41,12 @@ struct Warning: Identifiable {
public func applies() async -> Bool {
return await self.command()
}
public static func == (lhs: Warning, rhs: Warning) -> Bool {
return lhs.hashValue == rhs.hashValue
}
public func hash(into hasher: inout Hasher) {
hasher.combine(self.id)
}
}

View File

@ -9,9 +9,9 @@
import Foundation
import Cocoa
class WarningManager {
class WarningManager: ObservableObject {
static var shared = WarningManager()
static var shared: WarningManager = WarningManager()
init() {
if isRunningSwiftUIPreview {
@ -26,8 +26,8 @@ class WarningManager {
.trimmingCharacters(in: .whitespacesAndNewlines) == "1"
},
name: "Running PHP Monitor with Rosetta on M1",
title: "warnings.arm_compatibility.title",
paragraphs: ["warnings.arm_compatibility.description"],
title: "warnings.arm_compatibility.title".localized,
paragraphs: { return ["warnings.arm_compatibility.description".localized] },
url: "https://github.com/nicoverbruggen/phpmon/wiki/PHP-Monitor-and-Apple-Silicon"
),
Warning(
@ -36,13 +36,27 @@ class WarningManager {
!FileSystem.isWriteableFile("/usr/local/bin/")
},
name: "Helpers cannot be symlinked and not in PATH",
title: "warnings.helper_permissions.title",
paragraphs: [
"warnings.helper_permissions.description",
"warnings.helper_permissions.unavailable",
"warnings.helper_permissions.symlink"
],
title: "warnings.helper_permissions.title".localized,
paragraphs: { return [
"warnings.helper_permissions.description".localized,
"warnings.helper_permissions.unavailable".localized,
"warnings.helper_permissions.symlink".localized
] },
url: "https://github.com/nicoverbruggen/phpmon/wiki/PHP-Monitor-helper-binaries"
),
Warning(
command: {
PhpConfigChecker.shared.check()
return !PhpConfigChecker.shared.missing.isEmpty
},
name: "Your PHP installation is missing configuration files",
title: "warnings.files_missing.title".localized,
paragraphs: { return [
"warnings.files_missing.description".localized(
PhpConfigChecker.shared.missing.joined(separator: "\n")
)
] },
url: nil
)
]
@ -60,11 +74,11 @@ class WarningManager {
Checks the user's environment and checks if any special warnings apply.
*/
func checkEnvironment() async {
self.warnings = []
if ProcessInfo.processInfo.environment["EXTREME_DOCTOR_MODE"] != nil {
// For debugging purposes, we may wish to see all possible evaluations listed
self.warnings = self.evaluations
Task { @MainActor in
self.warnings = self.evaluations
}
} else {
// Otherwise, loop over the actual evaluations and list the warnings
await loopOverEvaluations()
@ -74,9 +88,14 @@ class WarningManager {
}
private func loopOverEvaluations() async {
Task { @MainActor in
self.warnings = []
}
for check in self.evaluations where await check.applies() {
Log.info("[DOCTOR] \(check.name) (!)")
self.warnings.append(check)
Task { @MainActor in
self.warnings.append(check)
}
continue
}
}

View File

@ -40,7 +40,7 @@
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019-2022 Nico Verbruggen. All rights reserved.</string>
<string>Copyright © 2019-2023 Nico Verbruggen. All rights reserved.</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
<key>NSPrincipalClass</key>

View File

@ -254,6 +254,10 @@ This has no effect on other terminals, only for the particular terminal session
"prefs.notifications" = "Notifications:";
"prefs.warnings" = "Warnings:";
"prefs.menu_contents" = "Features in Menu:";
"prefs.startup" = "Startup:";
"prefs.auto_start_desc" = "Automatically starts PHP Monitor when you log into your Mac.";
"prefs.auto_start_title" = "Start PHP Monitor at login";
"prefs.icon_options.php" = "Display PHP Icon";
"prefs.icon_options.elephant" = "Display Elephant Icon";
@ -355,6 +359,9 @@ This has no effect on other terminals, only for the particular terminal session
"notification.preset_reverted_title" = "Preset reverted";
"notification.preset_reverted_desc" = "The last preset you applied has been undone. Your previous configuration is now active.";
"notification.phpmon_updated.title" = "PHP Monitor has been updated!";
"notification.phpmon_updated.desc" = "You are now running PHP Monitor v%@.";
// Composer Update
"alert.composer_missing.title" = "Composer not found!";
"alert.composer_missing.subtitle" = "PHP Monitor could not find Composer. Make sure that Composer is installed and try again.";
@ -654,6 +661,13 @@ COMMON TROUBLESHOOTING TIPS
"warnings.arm_compatibility.title" = "You are running PHP Monitor using Rosetta on Apple Silicon, which means your PHP environment is also running via Rosetta.";
"warnings.arm_compatibility.description" = "You appear to be running an ARM-compatible version of macOS, but you are currently running PHP Monitor using Rosetta. While this will work correctly, it is recommended that you use the native version of Homebrew.";
"warnings.files_missing.title" = "Your PHP installation is lacking required configuration files";
"warnings.files_missing.description" = "The following files normally exist on a functional system:
• %@
When files like these are missing, it's recommended to reinstall the appropriate PHP version(s) via Homebrew again, which should restore the configuration files that are missing. Missing configuration files can be the reason why you get '502 Bad Gateway' errors, even after running Fix My Valet.";
"warnings.none" = "There are no recommendations available for you right now. You're all good!";
// ONBOARDING

View File

@ -23,11 +23,10 @@ class HomebrewPackageTest: XCTestCase {
[HomebrewPackage].self, from: json.data(using: .utf8)!
).first!
XCTAssertEqual(package.name, "php")
XCTAssertEqual(package.full_name, "php")
XCTAssertEqual(package.aliases.first!, "php@8.1")
XCTAssertEqual(package.aliases.first!, "php@8.2")
XCTAssertEqual(package.installed.contains(where: { installed in
installed.version.starts(with: "8.1")
installed.version.starts(with: "8.2")
}), true)
}

View File

@ -1,69 +1,77 @@
[
{
"name": "php",
"full_name": "php",
"tap": "homebrew/core",
"oldname": null,
"aliases": [
"php@8.1"
"php@8.2"
],
"versioned_formulae": [
"php@8.1",
"php@8.0",
"php@7.4",
"php@7.3",
"php@7.2"
"php@7.4"
],
"desc": "General-purpose scripting language",
"license": "PHP-3.01",
"homepage": "https://www.php.net/",
"versions": {
"stable": "8.1.10",
"stable": "8.2.2",
"head": "HEAD",
"bottle": true
},
"urls": {
"stable": {
"url": "https://www.php.net/distributions/php-8.1.10.tar.xz",
"url": "https://www.php.net/distributions/php-8.2.2.tar.xz",
"tag": null,
"revision": null
"revision": null,
"checksum": "bdc4aa38e652bac86039601840bae01c0c3653972eaa6f9f93d5f71953a7ee33"
},
"head": {
"url": "https://github.com/php/php-src.git",
"branch": "master"
}
},
"revision": 1,
"revision": 0,
"version_scheme": 0,
"bottle": {
"stable": {
"rebuild": 0,
"root_url": "https://ghcr.io/v2/homebrew/core",
"files": {
"arm64_ventura": {
"cellar": "/opt/homebrew/Cellar",
"url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:ad2e6a6f1cdc65c22b39bd607cbb7305958951cf58ee87d5060717be5a8b5a45",
"sha256": "ad2e6a6f1cdc65c22b39bd607cbb7305958951cf58ee87d5060717be5a8b5a45"
},
"arm64_monterey": {
"cellar": "/opt/homebrew/Cellar",
"url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:dcee33c9f445db3026a7e867805eb8f6d82e9e5599599b8c6cd8645475f7961c",
"sha256": "dcee33c9f445db3026a7e867805eb8f6d82e9e5599599b8c6cd8645475f7961c"
"url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:27069c973e63f38a3cb4fad1c7a2e17853bcffe318c8a957ff96a1026dff0cac",
"sha256": "27069c973e63f38a3cb4fad1c7a2e17853bcffe318c8a957ff96a1026dff0cac"
},
"arm64_big_sur": {
"cellar": "/opt/homebrew/Cellar",
"url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:e0590064cd32f2baa4102fa49c80056f3886a0a89aec0589d0134ecbf0e7923e",
"sha256": "e0590064cd32f2baa4102fa49c80056f3886a0a89aec0589d0134ecbf0e7923e"
"url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:ceef280bcd57e5f794ae59cc75e83d407c9704aa3d238b282bda52cbc644d0dd",
"sha256": "ceef280bcd57e5f794ae59cc75e83d407c9704aa3d238b282bda52cbc644d0dd"
},
"ventura": {
"cellar": "/usr/local/Cellar",
"url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:22f733b7b0b0ed95cd6b0a1534b9eca4cf63fe54647394c3f7e7ac019eb019ff",
"sha256": "22f733b7b0b0ed95cd6b0a1534b9eca4cf63fe54647394c3f7e7ac019eb019ff"
},
"monterey": {
"cellar": "/usr/local/Cellar",
"url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:62481320613b19c6ff310bf6ed50c7d2a2253cdbf403af12ec97bccd8a97a84c",
"sha256": "62481320613b19c6ff310bf6ed50c7d2a2253cdbf403af12ec97bccd8a97a84c"
"url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:9ff8f5e1df5e849567cdb2ddea6d3c2a2b9cae024842c9ac65b35a01657bfc37",
"sha256": "9ff8f5e1df5e849567cdb2ddea6d3c2a2b9cae024842c9ac65b35a01657bfc37"
},
"big_sur": {
"cellar": "/usr/local/Cellar",
"url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:b34d96f7aad3c580a7cbdaadb8054fb9b6872111a5eec8e1bcb4a529970c8e03",
"sha256": "b34d96f7aad3c580a7cbdaadb8054fb9b6872111a5eec8e1bcb4a529970c8e03"
},
"catalina": {
"cellar": "/usr/local/Cellar",
"url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:cc0b85dcfdd60e1d8d7fa74c9f53be5d249d068835dbc7a81edacb7a076b6c76",
"sha256": "cc0b85dcfdd60e1d8d7fa74c9f53be5d249d068835dbc7a81edacb7a076b6c76"
"url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:11fd1ea6da8ef728b7cacd4da8a51ed125069595abf4e37ae1552d418560c5fb",
"sha256": "11fd1ea6da8ef728b7cacd4da8a51ed125069595abf4e37ae1552d418560c5fb"
},
"x86_64_linux": {
"cellar": "/home/linuxbrew/.linuxbrew/Cellar",
"url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:b934a5a4ad2d29b629f83962b57f638a654801d1ba21ba659a42da2e5afe3fae",
"sha256": "b934a5a4ad2d29b629f83962b57f638a654801d1ba21ba659a42da2e5afe3fae"
"url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:baaa41e60f9e8125fe8f549d4813a8476a8947a1f10d7817a2ee36d8baa625f3",
"sha256": "baaa41e60f9e8125fe8f549d4813a8476a8947a1f10d7817a2ee36d8baa625f3"
}
}
}
@ -127,34 +135,35 @@
"conflicts_with": [
],
"caveats": "To enable PHP in Apache add the following to httpd.conf and restart Apache:\n LoadModule php_module $(brew --prefix)/opt/php/lib/httpd/modules/libphp.so\n\n <FilesMatch \\.php$>\n SetHandler application/x-httpd-php\n </FilesMatch>\n\nFinally, check DirectoryIndex includes index.php\n DirectoryIndex index.php index.html\n\nThe php.ini and php-fpm.ini file can be found in:\n $(brew --prefix)/etc/php/8.1/\n",
"caveats": "To enable PHP in Apache add the following to httpd.conf and restart Apache:\n LoadModule php_module $(brew --prefix)/opt/php/lib/httpd/modules/libphp.so\n\n <FilesMatch \\.php$>\n SetHandler application/x-httpd-php\n </FilesMatch>\n\nFinally, check DirectoryIndex includes index.php\n DirectoryIndex index.php index.html\n\nThe php.ini and php-fpm.ini file can be found in:\n $(brew --prefix)/etc/php/8.2/\n",
"installed": [
{
"version": "8.1.10_1",
"version": "8.2.2",
"used_options": [
],
"built_as_bottle": true,
"poured_from_bottle": true,
"time": 1675654665,
"runtime_dependencies": [
{
"full_name": "apr",
"version": "1.7.0",
"version": "1.7.2",
"declared_directly": true
},
{
"full_name": "ca-certificates",
"version": "2022-07-19",
"version": "2023-01-10",
"declared_directly": false
},
{
"full_name": "openssl@1.1",
"version": "1.1.1q",
"version": "1.1.1s",
"declared_directly": true
},
{
"full_name": "apr-util",
"version": "1.6.1",
"version": "1.6.3",
"declared_directly": true
},
{
@ -182,24 +191,24 @@
"version": "1.0.9",
"declared_directly": false
},
{
"full_name": "gettext",
"version": "0.21",
"declared_directly": true
},
{
"full_name": "libunistring",
"version": "1.0",
"version": "1.1",
"declared_directly": false
},
{
"full_name": "gettext",
"version": "0.21.1",
"declared_directly": true
},
{
"full_name": "libidn2",
"version": "2.3.3",
"version": "2.3.4",
"declared_directly": false
},
{
"full_name": "libnghttp2",
"version": "1.49.0",
"version": "1.51.0",
"declared_directly": false
},
{
@ -224,7 +233,7 @@
},
{
"full_name": "xz",
"version": "5.2.6",
"version": "5.4.1",
"declared_directly": false
},
{
@ -234,7 +243,7 @@
},
{
"full_name": "curl",
"version": "7.85.0",
"version": "7.87.0",
"declared_directly": true
},
{
@ -249,12 +258,12 @@
},
{
"full_name": "freetds",
"version": "1.3.13",
"version": "1.3.17",
"declared_directly": true
},
{
"full_name": "libpng",
"version": "1.6.37",
"version": "1.6.39",
"declared_directly": false
},
{
@ -264,12 +273,12 @@
},
{
"full_name": "fontconfig",
"version": "2.14.0",
"version": "2.14.2",
"declared_directly": false
},
{
"full_name": "jpeg-turbo",
"version": "2.1.4",
"version": "2.1.5",
"declared_directly": false
},
{
@ -278,13 +287,13 @@
"declared_directly": false
},
{
"full_name": "imath",
"version": "3.1.5",
"full_name": "highway",
"version": "1.0.3",
"declared_directly": false
},
{
"full_name": "openexr",
"version": "3.1.5",
"full_name": "imath",
"version": "3.1.6",
"declared_directly": false
},
{
@ -292,14 +301,24 @@
"version": "4.4.0",
"declared_directly": false
},
{
"full_name": "little-cms2",
"version": "2.14",
"declared_directly": false
},
{
"full_name": "openexr",
"version": "3.1.5",
"declared_directly": false
},
{
"full_name": "webp",
"version": "1.2.4",
"version": "1.3.0",
"declared_directly": false
},
{
"full_name": "jpeg-xl",
"version": "0.6.1",
"version": "0.8.1",
"declared_directly": false
},
{
@ -309,12 +328,12 @@
},
{
"full_name": "aom",
"version": "3.4.0",
"version": "3.5.0",
"declared_directly": false
},
{
"full_name": "libavif",
"version": "0.10.1",
"version": "0.11.1",
"declared_directly": false
},
{
@ -329,17 +348,17 @@
},
{
"full_name": "icu4c",
"version": "71.1",
"version": "72.1",
"declared_directly": true
},
{
"full_name": "krb5",
"version": "1.20",
"version": "1.20.1",
"declared_directly": true
},
{
"full_name": "libpq",
"version": "14.5",
"version": "15.1",
"declared_directly": true
},
{
@ -359,17 +378,17 @@
},
{
"full_name": "pcre2",
"version": "10.40",
"version": "10.42",
"declared_directly": true
},
{
"full_name": "readline",
"version": "8.1.2",
"version": "8.2.1",
"declared_directly": false
},
{
"full_name": "sqlite",
"version": "3.39.2",
"version": "3.40.1",
"declared_directly": true
},
{
@ -382,7 +401,7 @@
"installed_on_request": true
}
],
"linked_keg": "8.1.10_1",
"linked_keg": "8.2.2",
"pinned": false,
"outdated": false,
"deprecated": false,
@ -390,6 +409,8 @@
"deprecation_reason": null,
"disabled": false,
"disable_date": null,
"disable_reason": null
"disable_reason": null,
"tap_git_head": "0bbb89420e74756a5a5c145ed7efa4a32f7e7e7c"
}
]
]

View File

@ -44,6 +44,53 @@ class PhpVersionNumberTest: XCTestCase {
}
}
func test_can_parse_wildcard() throws {
let version = VersionNumber.make(from: "7.*", type: .wildCardMinor)
XCTAssertNotNil(version)
XCTAssertEqual(version!.major, 7)
XCTAssertEqual(version!.minor, 0)
}
func test_can_check_wildcard_version_constraint() throws {
// Wildcard for patch only
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4.10", "7.3.10", "7.3.9"])
.matching(constraint: "7.3.*", strict: false),
PhpVersionNumberCollection
.make(from: ["7.3.10", "7.3.9"]).all
)
// Wildcard for minor
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["8.0.0", "7.4.10", "7.3.10", "7.3.9"])
.matching(constraint: "7.*", strict: false),
PhpVersionNumberCollection
.make(from: ["7.4.10", "7.3.10", "7.3.9"]).all
)
// Full wildcard
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"])
.matching(constraint: "*", strict: false),
PhpVersionNumberCollection
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all
)
}
func test_can_check_any_version_constraint() throws {
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"])
.matching(constraint: "*", strict: false),
PhpVersionNumberCollection
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all
)
}
func test_can_check_fixed_constraints() throws {
XCTAssertEqual(
PhpVersionNumberCollection