mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2025-11-09 13:10:24 +01:00
✅ Added linting
This commit is contained in:
@@ -9,42 +9,37 @@ import Foundation
|
||||
import AppKit
|
||||
|
||||
class Actions {
|
||||
|
||||
|
||||
// MARK: - Services
|
||||
|
||||
public static func restartPhpFpm()
|
||||
{
|
||||
|
||||
public static func restartPhpFpm() {
|
||||
brew("services restart \(PhpEnv.phpInstall.formula)", sudo: true)
|
||||
}
|
||||
|
||||
public static func restartNginx()
|
||||
{
|
||||
|
||||
public static func restartNginx() {
|
||||
brew("services restart nginx", sudo: true)
|
||||
}
|
||||
|
||||
public static func restartDnsMasq()
|
||||
{
|
||||
|
||||
public static func restartDnsMasq() {
|
||||
brew("services restart dnsmasq", sudo: true)
|
||||
}
|
||||
|
||||
public static func stopAllServices()
|
||||
{
|
||||
|
||||
public static func stopAllServices() {
|
||||
brew("services stop \(PhpEnv.phpInstall.formula)", sudo: true)
|
||||
brew("services stop nginx", sudo: true)
|
||||
brew("services stop dnsmasq", sudo: true)
|
||||
}
|
||||
|
||||
public static func fixHomebrewPermissions() throws
|
||||
{
|
||||
|
||||
public static func fixHomebrewPermissions() throws {
|
||||
var servicesCommands = [
|
||||
"\(Paths.brew) services stop nginx",
|
||||
"\(Paths.brew) services stop dnsmasq",
|
||||
"\(Paths.brew) services stop dnsmasq"
|
||||
]
|
||||
var cellarCommands = [
|
||||
"chown -R \(Paths.whoami):admin \(Paths.cellarPath)/nginx",
|
||||
"chown -R \(Paths.whoami):admin \(Paths.cellarPath)/dnsmasq"
|
||||
]
|
||||
|
||||
|
||||
PhpEnv.shared.availablePhpVersions.forEach { version in
|
||||
let formula = version == PhpEnv.brewPhpVersion
|
||||
? "php"
|
||||
@@ -52,66 +47,61 @@ class Actions {
|
||||
servicesCommands.append("\(Paths.brew) services stop \(formula)")
|
||||
cellarCommands.append("chown -R \(Paths.whoami):admin \(Paths.cellarPath)/\(formula)")
|
||||
}
|
||||
|
||||
|
||||
let script =
|
||||
servicesCommands.joined(separator: " && ")
|
||||
+ " && "
|
||||
+ cellarCommands.joined(separator: " && ")
|
||||
|
||||
|
||||
let appleScript = NSAppleScript(
|
||||
source: "do shell script \"\(script)\" with administrator privileges"
|
||||
)
|
||||
|
||||
|
||||
let eventResult: NSAppleEventDescriptor? = appleScript?.executeAndReturnError(nil)
|
||||
|
||||
if (eventResult == nil) {
|
||||
|
||||
if eventResult == nil {
|
||||
throw HomebrewPermissionError(kind: .applescriptNilError)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Finding Config Files
|
||||
|
||||
public static func openGenericPhpConfigFolder()
|
||||
{
|
||||
let files = [NSURL(fileURLWithPath: "\(Paths.etcPath)/php")];
|
||||
|
||||
public static func openGenericPhpConfigFolder() {
|
||||
let files = [NSURL(fileURLWithPath: "\(Paths.etcPath)/php")]
|
||||
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
|
||||
}
|
||||
|
||||
public static func openGlobalComposerFolder()
|
||||
{
|
||||
|
||||
public static func openGlobalComposerFolder() {
|
||||
let file = FileManager.default.homeDirectoryForCurrentUser
|
||||
.appendingPathComponent(".composer/composer.json")
|
||||
NSWorkspace.shared.activateFileViewerSelecting([file] as [URL])
|
||||
}
|
||||
|
||||
public static func openPhpConfigFolder(version: String)
|
||||
{
|
||||
let files = [NSURL(fileURLWithPath: "\(Paths.etcPath)/php/\(version)/php.ini")];
|
||||
|
||||
public static func openPhpConfigFolder(version: String) {
|
||||
let files = [NSURL(fileURLWithPath: "\(Paths.etcPath)/php/\(version)/php.ini")]
|
||||
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
|
||||
}
|
||||
|
||||
public static func openValetConfigFolder()
|
||||
{
|
||||
|
||||
public static func openValetConfigFolder() {
|
||||
let file = FileManager.default.homeDirectoryForCurrentUser
|
||||
.appendingPathComponent(".config/valet")
|
||||
NSWorkspace.shared.activateFileViewerSelecting([file] as [URL])
|
||||
}
|
||||
|
||||
// MARK: - Other Actions
|
||||
|
||||
public static func createTempPhpInfoFile() -> URL
|
||||
{
|
||||
|
||||
public static func createTempPhpInfoFile() -> URL {
|
||||
// Write a file called `phpmon_phpinfo.php` to /tmp
|
||||
try! "<?php phpinfo();".write(toFile: "/tmp/phpmon_phpinfo.php", atomically: true, encoding: .utf8)
|
||||
|
||||
|
||||
// Tell php-cgi to run the PHP and output as an .html file
|
||||
Shell.run("\(Paths.binPath)/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html")
|
||||
|
||||
|
||||
return URL(string: "file:///private/tmp/phpmon_phpinfo.html")!
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Fix My Valet
|
||||
|
||||
|
||||
/**
|
||||
Detects all currently available PHP versions,
|
||||
and unlinks each and every one of them.
|
||||
@@ -124,8 +114,7 @@ class Actions {
|
||||
If this does not solve the issue, the user may need to install additional
|
||||
extensions and/or run `composer global update`.
|
||||
*/
|
||||
public static func fixMyValet(completed: @escaping () -> Void)
|
||||
{
|
||||
public static func fixMyValet(completed: @escaping () -> Void) {
|
||||
InternalSwitcher().performSwitch(to: PhpEnv.brewPhpVersion, completion: {
|
||||
brew("services restart dnsmasq", sudo: true)
|
||||
brew("services restart php", sudo: true)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import Cocoa
|
||||
|
||||
public class Command {
|
||||
|
||||
|
||||
/**
|
||||
Immediately executes a command.
|
||||
|
||||
@@ -20,21 +20,21 @@ public class Command {
|
||||
let task = Process()
|
||||
task.launchPath = path
|
||||
task.arguments = arguments
|
||||
|
||||
|
||||
let pipe = Pipe()
|
||||
task.standardOutput = pipe
|
||||
task.launch()
|
||||
|
||||
|
||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||
let output: String = String.init(data: data, encoding: String.Encoding.utf8)!
|
||||
|
||||
if (trimNewlines) {
|
||||
|
||||
if trimNewlines {
|
||||
return output.components(separatedBy: .newlines)
|
||||
.filter({ !$0.isEmpty })
|
||||
.joined(separator: "\n")
|
||||
}
|
||||
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
import Cocoa
|
||||
|
||||
struct Constants {
|
||||
|
||||
|
||||
/**
|
||||
* The latest PHP version that is considered to be stable at the time of release.
|
||||
* This version number is currently not used (only as a default fallback).
|
||||
*/
|
||||
static let LatestStablePhpVersion = "8.1"
|
||||
|
||||
|
||||
/**
|
||||
The minimum version of Valet that is recommended.
|
||||
If the installed version is older, a notification will be shown
|
||||
@@ -24,7 +24,7 @@ struct Constants {
|
||||
See also: https://github.com/laravel/valet/releases/tag/v2.16.2
|
||||
*/
|
||||
static let MinimumRecommendedValetVersion = "2.16.2"
|
||||
|
||||
|
||||
/**
|
||||
* The PHP versions supported by this application.
|
||||
* Versions that do not appear in this array are omitted from the list.
|
||||
@@ -42,7 +42,7 @@ struct Constants {
|
||||
"7.4",
|
||||
"8.0",
|
||||
"8.1",
|
||||
|
||||
|
||||
// ====================
|
||||
// EXPERIMENTAL SUPPORT
|
||||
// ====================
|
||||
@@ -50,9 +50,9 @@ struct Constants {
|
||||
// dev release. In this case, that means that the version below is detected.
|
||||
"8.2"
|
||||
]
|
||||
|
||||
|
||||
struct Urls {
|
||||
|
||||
|
||||
static let DonationPayment = URL(
|
||||
string: "https://nicoverbruggen.be/sponsor#pay-now"
|
||||
)!
|
||||
@@ -62,7 +62,7 @@ struct Constants {
|
||||
static let FrequentlyAskedQuestions = URL(
|
||||
string: "https://github.com/nicoverbruggen/phpmon#%EF%B8%8F-faq--troubleshooting"
|
||||
)!
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
|
||||
class Events {
|
||||
|
||||
|
||||
static let ServicesUpdated = Notification.Name("ServicesUpdated")
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -11,28 +11,25 @@
|
||||
/**
|
||||
Runs a `valet` command. Defaults to running as superuser.
|
||||
*/
|
||||
func valet(_ command: String, sudo: Bool = true) -> String
|
||||
{
|
||||
func valet(_ command: String, sudo: Bool = true) -> String {
|
||||
return Shell.pipe("\(sudo ? "sudo " : "")" + "\(Paths.valet) \(command)", requiresPath: true)
|
||||
}
|
||||
|
||||
/**
|
||||
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)")
|
||||
}
|
||||
|
||||
/**
|
||||
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)
|
||||
let e_original = original.replacingOccurrences(of: "/", with: "\\/")
|
||||
let e_replacement = replacement.replacingOccurrences(of: "/", with: "\\/")
|
||||
|
||||
|
||||
// Check if gsed exists; it is able to follow symlinks,
|
||||
// which we want to do to toggle the extension
|
||||
if Filesystem.fileExists("\(Paths.binPath)/gsed") {
|
||||
@@ -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.
|
||||
*/
|
||||
func grepContains(file: String, query: String) -> Bool
|
||||
{
|
||||
func grepContains(file: String, query: String) -> Bool {
|
||||
return Shell.pipe("""
|
||||
grep -q '\(query)' \(file); [ $? -eq 0 ] && echo "YES" || echo "NO"
|
||||
""")
|
||||
|
||||
@@ -9,50 +9,50 @@
|
||||
import Foundation
|
||||
|
||||
class Log {
|
||||
|
||||
|
||||
static var shared = Log()
|
||||
|
||||
|
||||
enum Verbosity: Int {
|
||||
case error = 1,
|
||||
warning = 2,
|
||||
info = 3,
|
||||
performance = 4
|
||||
|
||||
|
||||
public func isApplicable() -> Bool {
|
||||
return Log.shared.verbosity.rawValue >= self.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var verbosity: Verbosity = .warning
|
||||
|
||||
|
||||
static func err(_ item: Any) {
|
||||
if Verbosity.error.isApplicable() {
|
||||
print("[E] \(item)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static func warn(_ item: Any) {
|
||||
if Verbosity.warning.isApplicable() {
|
||||
print("[W] \(item)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static func info(_ item: Any) {
|
||||
if Verbosity.info.isApplicable() {
|
||||
print("\(item)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static func perf(_ item: Any) {
|
||||
if Verbosity.performance.isApplicable() {
|
||||
print("[P] \(item)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static func separator(as verbosity: Verbosity = .info) {
|
||||
if verbosity.isApplicable() {
|
||||
print("==================================")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -12,71 +12,71 @@ import Foundation
|
||||
The path to the Homebrew directory and the user's name are fetched only once, at boot.
|
||||
*/
|
||||
public class Paths {
|
||||
|
||||
|
||||
public static let shared = Paths()
|
||||
|
||||
|
||||
internal var baseDir: Paths.HomebrewDir
|
||||
|
||||
|
||||
private var userName: String
|
||||
|
||||
|
||||
init() {
|
||||
baseDir = App.architecture != "x86_64" ? .opt : .usr
|
||||
userName = String(Shell.pipe("whoami").split(separator: "\n")[0])
|
||||
}
|
||||
|
||||
|
||||
public func detectBinaryPaths() {
|
||||
detectComposerBinary()
|
||||
}
|
||||
|
||||
// - MARK: Binaries
|
||||
|
||||
|
||||
public static var valet: String {
|
||||
return "\(binPath)/valet"
|
||||
}
|
||||
|
||||
|
||||
public static var brew: String {
|
||||
return "\(binPath)/brew"
|
||||
}
|
||||
|
||||
|
||||
public static var php: String {
|
||||
return "\(binPath)/php"
|
||||
}
|
||||
|
||||
|
||||
public static var phpConfig: String {
|
||||
return "\(binPath)/php-config"
|
||||
}
|
||||
|
||||
|
||||
// - MARK: Detected Binaries
|
||||
|
||||
|
||||
/** The path to the Composer binary. Can be in multiple locations, so is detected instead. */
|
||||
public static var composer: String? = nil
|
||||
|
||||
public static var composer: String?
|
||||
|
||||
// - MARK: Paths
|
||||
|
||||
|
||||
public static var whoami: String {
|
||||
return shared.userName
|
||||
}
|
||||
|
||||
|
||||
public static var cellarPath: String {
|
||||
return "\(shared.baseDir.rawValue)/Cellar"
|
||||
}
|
||||
|
||||
|
||||
public static var binPath: String {
|
||||
return "\(shared.baseDir.rawValue)/bin"
|
||||
}
|
||||
|
||||
|
||||
public static var optPath: String {
|
||||
return "\(shared.baseDir.rawValue)/opt"
|
||||
}
|
||||
|
||||
|
||||
public static var etcPath: String {
|
||||
return "\(shared.baseDir.rawValue)/etc"
|
||||
}
|
||||
|
||||
|
||||
// MARK: - 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"
|
||||
@@ -87,12 +87,12 @@ public class Paths {
|
||||
Log.warn("Composer was not found.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Enum
|
||||
|
||||
|
||||
public enum HomebrewDir: String {
|
||||
case opt = "/opt/homebrew"
|
||||
case usr = "/usr/local"
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
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`
|
||||
@@ -22,10 +22,10 @@ extension Process {
|
||||
) {
|
||||
let outputPipe = Pipe()
|
||||
let errorPipe = Pipe()
|
||||
|
||||
|
||||
self.standardOutput = outputPipe
|
||||
self.standardError = errorPipe
|
||||
|
||||
|
||||
[
|
||||
(outputPipe, didReceiveStandardOutputData),
|
||||
(errorPipe, didReceiveStandardErrorData)
|
||||
@@ -35,15 +35,18 @@ extension Process {
|
||||
forName: NSNotification.Name.NSFileHandleDataAvailable,
|
||||
object: pipe.fileHandleForReading,
|
||||
queue: nil
|
||||
) { notification in
|
||||
if let outputString = String(data: pipe.fileHandleForReading.availableData, encoding: String.Encoding.utf8) {
|
||||
) { _ 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.
|
||||
*/
|
||||
@@ -55,5 +58,5 @@ extension Process {
|
||||
NotificationCenter.default.removeObserver(pipe.fileHandleForReading)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -8,35 +8,35 @@
|
||||
import Cocoa
|
||||
|
||||
public class Shell {
|
||||
|
||||
|
||||
// MARK: - Invoke static functions
|
||||
|
||||
|
||||
public static func run(
|
||||
_ command: String,
|
||||
requiresPath: Bool = false
|
||||
) {
|
||||
Shell.user.run(command, requiresPath: requiresPath)
|
||||
}
|
||||
|
||||
|
||||
public static func pipe(
|
||||
_ command: String,
|
||||
requiresPath: Bool = false
|
||||
) -> String {
|
||||
return Shell.user.pipe(command, requiresPath: requiresPath)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Singleton
|
||||
|
||||
|
||||
/**
|
||||
We now require macOS 11, so no need to detect which terminal to use.
|
||||
*/
|
||||
public var shell: String = "/bin/sh"
|
||||
|
||||
|
||||
/**
|
||||
Singleton to access a user shell (with --login)
|
||||
*/
|
||||
public static let user = Shell()
|
||||
|
||||
|
||||
/**
|
||||
Runs a shell command without using the output.
|
||||
Uses the default shell.
|
||||
@@ -51,7 +51,7 @@ public class Shell {
|
||||
// Equivalent of piping to /dev/null; don't do anything with the string
|
||||
_ = Shell.pipe(command, requiresPath: requiresPath)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Runs a shell command and returns the output.
|
||||
|
||||
@@ -69,7 +69,7 @@ public class Shell {
|
||||
)
|
||||
return !hasError ? shellOutput.standardOutput : shellOutput.errorOutput
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Runs the command and returns a `ShellOutput` object, which contains info about the process.
|
||||
|
||||
@@ -81,16 +81,16 @@ public class Shell {
|
||||
_ command: String,
|
||||
requiresPath: Bool = false
|
||||
) -> Shell.Output {
|
||||
|
||||
|
||||
let outputPipe = Pipe()
|
||||
let errorPipe = Pipe()
|
||||
|
||||
|
||||
let task = self.createTask(for: command, requiresPath: requiresPath)
|
||||
task.standardOutput = outputPipe
|
||||
task.standardError = errorPipe
|
||||
task.launch()
|
||||
task.waitUntilExit()
|
||||
|
||||
|
||||
return Shell.Output(
|
||||
standardOutput: String(
|
||||
data: outputPipe.fileHandleForReading.readDataToEndOfFile(),
|
||||
@@ -103,7 +103,7 @@ public class Shell {
|
||||
task: task
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Creates a new process with the correct PATH and shell.
|
||||
*/
|
||||
@@ -111,19 +111,19 @@ public class Shell {
|
||||
let tailoredCommand = requiresPath
|
||||
? "export PATH=\(Paths.binPath):$PATH && \(command)"
|
||||
: command
|
||||
|
||||
|
||||
let task = Process()
|
||||
task.launchPath = self.shell
|
||||
task.arguments = ["--noprofile", "-norc", "--login", "-c", tailoredCommand]
|
||||
|
||||
|
||||
return task
|
||||
}
|
||||
|
||||
|
||||
public class Output {
|
||||
public let standardOutput: String
|
||||
public let errorOutput: String
|
||||
public let task: Process
|
||||
|
||||
|
||||
init(standardOutput: String,
|
||||
errorOutput: String,
|
||||
task: Process) {
|
||||
|
||||
@@ -15,9 +15,9 @@ struct HomebrewPermissionError: Error, AlertableError {
|
||||
enum Kind: String {
|
||||
case applescriptNilError = "homebrew_permissions.applescript_returned_nil"
|
||||
}
|
||||
|
||||
|
||||
let kind: Kind
|
||||
|
||||
|
||||
func getErrorMessageKey() -> String {
|
||||
return "alert.errors.\(self.kind.rawValue)"
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
import Cocoa
|
||||
|
||||
extension Date {
|
||||
|
||||
|
||||
func toString() -> String {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
return dateFormatter.string(from: self)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -9,21 +9,21 @@
|
||||
import Cocoa
|
||||
|
||||
extension NSMenu {
|
||||
|
||||
|
||||
open func addItem(_ newItem: NSMenuItem, withKeyModifier modifier: NSEvent.ModifierFlags) {
|
||||
newItem.keyEquivalentModifierMask = modifier
|
||||
self.addItem(newItem)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@IBDesignable class LocalizedMenuItem: NSMenuItem {
|
||||
|
||||
|
||||
@IBInspectable
|
||||
var localizationKey: String? {
|
||||
didSet {
|
||||
self.title = localizationKey?.localized ?? self.title
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -10,29 +10,29 @@ import Foundation
|
||||
import Cocoa
|
||||
|
||||
extension NSWindow {
|
||||
|
||||
|
||||
/**
|
||||
Shakes a window. Inspired by: http://blog.ericd.net/2016/09/30/shaking-a-macos-window/
|
||||
*/
|
||||
func shake(){
|
||||
func shake() {
|
||||
let numberOfShakes = 3, durationOfShake = 0.2, vigourOfShake: CGFloat = 0.03
|
||||
|
||||
|
||||
let frame: CGRect = self.frame
|
||||
let shakeAnimation :CAKeyframeAnimation = CAKeyframeAnimation()
|
||||
|
||||
let shakeAnimation: CAKeyframeAnimation = CAKeyframeAnimation()
|
||||
|
||||
let shakePath = CGMutablePath()
|
||||
shakePath.move( to: CGPoint(x:NSMinX(frame), y:NSMinY(frame)))
|
||||
|
||||
shakePath.move( to: CGPoint(x: frame.minX, y: frame.minY))
|
||||
|
||||
for _ in 0...numberOfShakes-1 {
|
||||
shakePath.addLine(to: CGPoint(x:NSMinX(frame) - frame.size.width * vigourOfShake, y:NSMinY(frame)))
|
||||
shakePath.addLine(to: CGPoint(x:NSMinX(frame) + frame.size.width * vigourOfShake, y:NSMinY(frame)))
|
||||
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.animations = ["frameOrigin": shakeAnimation]
|
||||
self.animator().setFrameOrigin(self.frame.origin)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,28 +7,28 @@
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
|
||||
|
||||
var localized: String {
|
||||
return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
|
||||
}
|
||||
|
||||
|
||||
func localized(_ args: CVarArg...) -> String {
|
||||
String(format: self.localized, arguments: args)
|
||||
}
|
||||
|
||||
|
||||
func countInstances(of stringToFind: String) -> Int {
|
||||
if (stringToFind.isEmpty) {
|
||||
if stringToFind.isEmpty {
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
var count = 0
|
||||
var searchRange: Range<String.Index>?
|
||||
|
||||
|
||||
while let foundRange = range(of: stringToFind, options: [], range: searchRange) {
|
||||
count += 1
|
||||
searchRange = Range(uncheckedBounds: (lower: foundRange.upperBound, upper: endIndex))
|
||||
}
|
||||
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ extension String {
|
||||
let end = r.upperBound
|
||||
return String(self[start ..< end])
|
||||
}
|
||||
|
||||
|
||||
// Code taken from: https://sarunw.com/posts/how-to-compare-two-app-version-strings-in-swift/
|
||||
/*
|
||||
<1> We split the version by period (.).
|
||||
@@ -50,12 +50,12 @@ extension String {
|
||||
*/
|
||||
func versionCompare(_ otherVersion: String) -> ComparisonResult {
|
||||
let versionDelimiter = "."
|
||||
|
||||
|
||||
var versionComponents = self.components(separatedBy: versionDelimiter) // <1>
|
||||
var otherVersionComponents = otherVersion.components(separatedBy: versionDelimiter)
|
||||
|
||||
|
||||
let zeroDiff = versionComponents.count - otherVersionComponents.count // <2>
|
||||
|
||||
|
||||
if zeroDiff == 0 { // <3>
|
||||
// Same format, compare normally
|
||||
return self.compare(otherVersion, options: .numeric)
|
||||
@@ -70,5 +70,5 @@ extension String {
|
||||
.compare(otherVersionComponents.joined(separator: versionDelimiter), options: .numeric) // <6>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -12,25 +12,25 @@ import Cocoa
|
||||
// Adapted from: https://stackoverflow.com/a/46268778
|
||||
|
||||
protocol XibLoadable {
|
||||
|
||||
|
||||
static var xibName: String? { get }
|
||||
static func createFromXib(in bundle: Bundle) -> Self?
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension XibLoadable where Self: NSView {
|
||||
|
||||
|
||||
static var xibName: String? {
|
||||
return String(describing: Self.self)
|
||||
}
|
||||
|
||||
|
||||
static func createFromXib(in bundle: Bundle = Bundle.main) -> Self? {
|
||||
guard let xibName = xibName else { return nil }
|
||||
var topLevelArray: NSArray? = nil
|
||||
var topLevelArray: NSArray?
|
||||
bundle.loadNibNamed(NSNib.Name(xibName), owner: self, topLevelObjects: &topLevelArray)
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import Cocoa
|
||||
|
||||
class Alert {
|
||||
|
||||
|
||||
public static func confirm(
|
||||
onWindow window: NSWindow,
|
||||
messageText: String,
|
||||
@@ -21,13 +21,13 @@ class Alert {
|
||||
if !Thread.isMainThread {
|
||||
fatalError("You should always present alerts on the main thread!")
|
||||
}
|
||||
|
||||
|
||||
let alert = NSAlert.init()
|
||||
alert.alertStyle = style
|
||||
alert.messageText = messageText
|
||||
alert.informativeText = informativeText
|
||||
alert.addButton(withTitle: buttonTitle)
|
||||
if (!secondButtonTitle.isEmpty) {
|
||||
if !secondButtonTitle.isEmpty {
|
||||
alert.addButton(withTitle: secondButtonTitle)
|
||||
}
|
||||
alert.beginSheetModal(for: window) { response in
|
||||
@@ -36,5 +36,5 @@ class Alert {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -12,23 +12,23 @@ import Foundation
|
||||
/// 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.
|
||||
class Application {
|
||||
|
||||
|
||||
enum AppType {
|
||||
case editor, browser, git_gui, terminal, user_supplied
|
||||
}
|
||||
|
||||
|
||||
/// Name of the app. Used for display purposes and to determine `name.app` exists.
|
||||
let name: String
|
||||
|
||||
|
||||
/// Application type. Depending on the type, a different action might occur.
|
||||
let type: AppType
|
||||
|
||||
|
||||
/// Initializer. Used to detect a specific app of a specific type.
|
||||
init(_ name: String, _ type: AppType) {
|
||||
self.name = name
|
||||
self.type = type
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Attempt to open a specific directory in the app of choice.
|
||||
(This will open the app if it isn't open yet.)
|
||||
@@ -36,7 +36,7 @@ class Application {
|
||||
@objc public func openDirectory(file: String) {
|
||||
return Shell.run("/usr/bin/open -a \"\(name)\" \"\(file)\"")
|
||||
}
|
||||
|
||||
|
||||
/** Checks if the app is installed. */
|
||||
func isInstalled() -> Bool {
|
||||
// If this script does not complain, the app exists!
|
||||
@@ -45,7 +45,7 @@ class Application {
|
||||
requiresPath: false
|
||||
).task.terminationStatus == 0
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Detect which apps are available to open a specific directory.
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Cocoa
|
||||
|
||||
class Filesystem {
|
||||
|
||||
|
||||
/**
|
||||
Checks if a file exists at the provided path.
|
||||
Uses `FileManager`.
|
||||
@@ -19,5 +19,5 @@ class Filesystem {
|
||||
atPath: path.replacingOccurrences(of: "~", with: "/Users/\(Paths.whoami)")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -9,19 +9,19 @@ import Foundation
|
||||
import UserNotifications
|
||||
|
||||
class LocalNotification {
|
||||
|
||||
|
||||
public static func send(title: String, subtitle: String) {
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = title
|
||||
content.body = subtitle
|
||||
|
||||
|
||||
let uuidString = UUID().uuidString
|
||||
let request = UNNotificationRequest(
|
||||
identifier: uuidString,
|
||||
content: content,
|
||||
trigger: nil
|
||||
)
|
||||
|
||||
|
||||
let notificationCenter = UNUserNotificationCenter.current()
|
||||
notificationCenter.add(request) { (error) in
|
||||
if error != nil {
|
||||
@@ -29,5 +29,5 @@ class LocalNotification {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -8,40 +8,40 @@
|
||||
import Cocoa
|
||||
|
||||
class MenuBarImageGenerator {
|
||||
|
||||
|
||||
/**
|
||||
Takes a string and converts it to an image that can be displayed in the menu bar.
|
||||
The width of the NSImage depends on the length of the text.
|
||||
*/
|
||||
public static func textToImage(text: String) -> NSImage {
|
||||
|
||||
|
||||
let font = NSFont.systemFont(ofSize: 14, weight: .medium)
|
||||
|
||||
|
||||
let textStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
|
||||
let textFontAttributes = [
|
||||
NSAttributedString.Key.font: font,
|
||||
NSAttributedString.Key.foregroundColor: NSColor.black,
|
||||
NSAttributedString.Key.paragraphStyle: textStyle
|
||||
]
|
||||
|
||||
let padding : CGFloat = 2.0;
|
||||
|
||||
|
||||
let padding: CGFloat = 2.0
|
||||
|
||||
// Create an attributed string so we'll know how wide the item will need to be
|
||||
let attributedString = NSAttributedString(string: text, attributes: textFontAttributes)
|
||||
let textSize = attributedString.size()
|
||||
|
||||
|
||||
// Add padding to the width of the menu bar item
|
||||
let size = NSSize(width: textSize.width + (2 * padding), height: textSize.height)
|
||||
let image = NSImage(size: size)
|
||||
|
||||
|
||||
// Set the image rect with the appropriate dimensions
|
||||
let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
|
||||
|
||||
|
||||
// Position the text inside the image rect
|
||||
let textRect = CGRect(x: padding, y: 0.5, width: image.size.width, height: image.size.height)
|
||||
|
||||
|
||||
let targetImage: NSImage = NSImage(size: image.size)
|
||||
|
||||
|
||||
let representation: NSBitmapImageRep = NSBitmapImageRep(
|
||||
bitmapDataPlanes: nil,
|
||||
pixelsWide: Int(image.size.width),
|
||||
@@ -54,40 +54,40 @@ class MenuBarImageGenerator {
|
||||
bytesPerRow: 0,
|
||||
bitsPerPixel: 0
|
||||
)!
|
||||
|
||||
|
||||
targetImage.addRepresentation(representation)
|
||||
targetImage.lockFocus()
|
||||
|
||||
image.draw(in: imageRect)
|
||||
text.draw(in: textRect, withAttributes: textFontAttributes)
|
||||
|
||||
|
||||
targetImage.unlockFocus()
|
||||
return targetImage
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
The same as before, but also attempts to add an icon to the left.
|
||||
*/
|
||||
public static func textToImageWithIcon(text: String) -> NSImage {
|
||||
|
||||
|
||||
// We'll start out with the image containing the text
|
||||
let textImage = self.textToImage(text: text)
|
||||
|
||||
|
||||
// Then we'll fetch the image we want on the left
|
||||
var iconType = Preferences.preferences[.iconTypeToDisplay] as? String
|
||||
if iconType == nil {
|
||||
Log.warn("Invalid icon type found, using the default")
|
||||
iconType = MenuBarIcon.iconPhp.rawValue
|
||||
}
|
||||
|
||||
|
||||
let iconImage = NSImage(named: "MenuBar_\(iconType!)")!
|
||||
|
||||
|
||||
// We'll need to reference the width of the icon a bunch of times
|
||||
let iconWidthSize = iconImage.size.width
|
||||
|
||||
|
||||
// There will also be an additional divider between the image and the text (image)
|
||||
let divider: CGFloat = 3
|
||||
|
||||
|
||||
// Use a fixed size for the height of the menu bar (18pt)
|
||||
let imageRect = CGRect(
|
||||
x: 0,
|
||||
@@ -95,14 +95,14 @@ class MenuBarImageGenerator {
|
||||
width: textImage.size.width + iconWidthSize + divider,
|
||||
height: 18
|
||||
)
|
||||
|
||||
|
||||
// Create a new image, we'll draw the text and our icon in there
|
||||
let image: NSImage = NSImage(size: imageRect.size)
|
||||
image.lockFocus()
|
||||
|
||||
// Calculate the offset between the image and the text
|
||||
let offset = imageRect.size.width - textImage.size.width
|
||||
|
||||
|
||||
// Draw the text with a negative x offset (so there is room on the left for the icon)
|
||||
textImage.draw(
|
||||
in: imageRect,
|
||||
@@ -115,7 +115,7 @@ class MenuBarImageGenerator {
|
||||
operation: .overlay,
|
||||
fraction: 1
|
||||
)
|
||||
|
||||
|
||||
// Draw the icon directly in the left of the imageRect (where we left space)
|
||||
iconImage.draw(
|
||||
in: imageRect,
|
||||
@@ -128,11 +128,11 @@ class MenuBarImageGenerator {
|
||||
operation: .overlay,
|
||||
fraction: 1
|
||||
)
|
||||
|
||||
|
||||
// We're done with this image
|
||||
image.unlockFocus()
|
||||
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -15,32 +15,32 @@ import Cocoa
|
||||
- Note: This class does make a simple assumption: each window controller corresponds to a single view.
|
||||
*/
|
||||
class PMWindowController: NSWindowController, NSWindowDelegate {
|
||||
|
||||
|
||||
public var windowName: String {
|
||||
fatalError("Please specify a window name")
|
||||
}
|
||||
|
||||
|
||||
override func showWindow(_ sender: Any?) {
|
||||
super.showWindow(sender)
|
||||
App.shared.register(window: windowName)
|
||||
}
|
||||
|
||||
|
||||
func windowWillClose(_ notification: Notification) {
|
||||
App.shared.remove(window: windowName)
|
||||
}
|
||||
|
||||
|
||||
deinit {
|
||||
Log.perf("Window controller '\(windowName)' was deinitialized")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension NSWindowController {
|
||||
|
||||
|
||||
public func positionWindowInTopLeftCorner() {
|
||||
guard let frame = NSScreen.main?.frame else { return }
|
||||
guard let window = self.window else { return }
|
||||
|
||||
|
||||
window.setFrame(NSRect(
|
||||
x: frame.size.width - window.frame.size.width - 20,
|
||||
y: frame.size.height - window.frame.size.height - 40,
|
||||
@@ -48,5 +48,5 @@ extension NSWindowController {
|
||||
height: window.frame.height
|
||||
), display: true)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
|
||||
class VersionExtractor {
|
||||
|
||||
|
||||
/**
|
||||
This attempts to extract the version number from any given string.
|
||||
*/
|
||||
@@ -19,26 +19,26 @@ class VersionExtractor {
|
||||
pattern: #"(?<version>(\d+)(.)(\d+)((.)(\d+))?)"#,
|
||||
options: []
|
||||
)
|
||||
|
||||
|
||||
let match = regex.matches(
|
||||
in: string,
|
||||
options: [],
|
||||
range: NSMakeRange(0, string.count)
|
||||
range: NSRange(location: 0, length: string.count)
|
||||
).first
|
||||
|
||||
|
||||
guard let match = match else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
let range = Range(
|
||||
match.range(withName: "version"),
|
||||
in: string
|
||||
)!
|
||||
|
||||
|
||||
return String(string[range])
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -21,78 +21,78 @@ class ActivePhpInstallation {
|
||||
var version: Version!
|
||||
var limits: Limits!
|
||||
var extensions: [PhpExtension]!
|
||||
|
||||
|
||||
// MARK: - Computed
|
||||
|
||||
|
||||
var formula: String {
|
||||
return (version.short == PhpEnv.brewPhpVersion) ? "php" : "php@\(version.short)"
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Initializer
|
||||
|
||||
init() {
|
||||
// Show information about the current version
|
||||
getVersion()
|
||||
|
||||
|
||||
// If an error occurred, exit early
|
||||
if (version.error) {
|
||||
if version.error {
|
||||
limits = Limits()
|
||||
extensions = []
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Load extension information
|
||||
let path = URL(fileURLWithPath: "\(Paths.etcPath)/php/\(version.short)/php.ini")
|
||||
extensions = PhpExtension.load(from: path)
|
||||
|
||||
|
||||
// Get configuration values
|
||||
limits = Limits(
|
||||
memory_limit: getByteCount(key: "memory_limit"),
|
||||
upload_max_filesize: getByteCount(key: "upload_max_filesize"),
|
||||
post_max_size: getByteCount(key: "post_max_size")
|
||||
)
|
||||
|
||||
|
||||
// Return a list of .ini files parsed after php.ini
|
||||
let paths = Command.execute(path: Paths.php, arguments: ["-r", "echo php_ini_scanned_files();"])
|
||||
.replacingOccurrences(of: "\n", with: "")
|
||||
.split(separator: ",")
|
||||
.map { String($0) }
|
||||
|
||||
|
||||
// See if any extensions are present in said .ini files
|
||||
paths.forEach { (iniFilePath) in
|
||||
let exts = PhpExtension.load(from: URL(fileURLWithPath: iniFilePath))
|
||||
if exts.count > 0 {
|
||||
extensions.append(contentsOf: exts)
|
||||
let loadedExtensions = PhpExtension.load(from: URL(fileURLWithPath: iniFilePath))
|
||||
if loadedExtensions.isEmpty {
|
||||
extensions.append(contentsOf: loadedExtensions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
When the app tries to retrieve the version, the installation is considered broken if the output is nothing,
|
||||
_or_ if the output contains the word "Warning" or "Error". In normal situations this should not be the case.
|
||||
*/
|
||||
private func getVersion() -> Void {
|
||||
private func getVersion() {
|
||||
self.version = Version()
|
||||
|
||||
|
||||
let version = Command.execute(path: Paths.phpConfig, arguments: ["--version"], trimNewlines: true)
|
||||
|
||||
if (version == "" || version.contains("Warning") || version.contains("Error")) {
|
||||
|
||||
if version == "" || version.contains("Warning") || version.contains("Error") {
|
||||
self.version.short = "💩 BROKEN"
|
||||
self.version.long = ""
|
||||
self.version.error = true
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// That's the long version
|
||||
self.version.long = version
|
||||
|
||||
|
||||
// Next up, let's strip away the minor version number
|
||||
let segments = self.version.long.components(separatedBy: ".")
|
||||
|
||||
|
||||
// Get the first two elements
|
||||
self.version.short = segments[0...1].joined(separator: ".")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Retrieves the display value for a specific key in the `.ini` file.
|
||||
|
||||
@@ -110,18 +110,18 @@ class ActivePhpInstallation {
|
||||
*/
|
||||
private func getByteCount(key: String) -> String {
|
||||
let value = Command.execute(path: Paths.php, arguments: ["-r", "echo ini_get('\(key)');"])
|
||||
|
||||
|
||||
// Check if the value is unlimited
|
||||
if (value == "-1") {
|
||||
if value == "-1" {
|
||||
return "∞"
|
||||
}
|
||||
|
||||
|
||||
// Check if the syntax is valid otherwise
|
||||
let regex = try! NSRegularExpression(pattern: #"^([0-9]*)(K|M|G|)$"#, options: [])
|
||||
let match = regex.matches(in: value, options: [], range: NSMakeRange(0, value.count)).first
|
||||
let match = regex.matches(in: value, options: [], range: NSRange(location: 0, length: value.count)).first
|
||||
return (match == nil) ? "⚠️" : "\(value)B"
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Determine if PHP-FPM is configured correctly.
|
||||
|
||||
@@ -135,11 +135,11 @@ class ActivePhpInstallation {
|
||||
let fileName = "\(Paths.etcPath)/php/5.6/php-fpm.conf"
|
||||
return Shell.pipe("cat \(fileName)").contains("valet.sock")
|
||||
}
|
||||
|
||||
|
||||
// Make sure to check if valet-fpm.conf exists. If it does, we should be fine :)
|
||||
return Filesystem.fileExists("\(Paths.etcPath)/php/\(self.version.short)/php-fpm.d/valet-fpm.conf")
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Structs
|
||||
|
||||
/**
|
||||
@@ -153,7 +153,7 @@ class ActivePhpInstallation {
|
||||
var long = "???"
|
||||
var error = false
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Struct containing information about the limits of the current PHP installation.
|
||||
Includes: memory limit, max upload size and max post size.
|
||||
@@ -163,5 +163,5 @@ class ActivePhpInstallation {
|
||||
var upload_max_filesize = "???"
|
||||
var post_max_size = "???"
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
import Foundation
|
||||
|
||||
class Xdebug {
|
||||
|
||||
|
||||
public static var enabled: Bool {
|
||||
return !self.mode.isEmpty
|
||||
}
|
||||
|
||||
|
||||
public static var mode: String {
|
||||
return Command.execute(path: Paths.php, arguments: ["-r", "echo ini_get('xdebug.mode');"])
|
||||
}
|
||||
|
||||
|
||||
public static var modes: [String] {
|
||||
return [
|
||||
"off",
|
||||
@@ -26,8 +26,8 @@ class Xdebug {
|
||||
"debug",
|
||||
"gcstats",
|
||||
"profile",
|
||||
"trace",
|
||||
"trace"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -8,18 +8,18 @@
|
||||
import Foundation
|
||||
|
||||
struct HomebrewPackage: Decodable {
|
||||
|
||||
|
||||
let name: String
|
||||
let full_name: String
|
||||
let aliases: [String]
|
||||
let installed: [HomebrewInstalled]
|
||||
let linked_keg: String?
|
||||
|
||||
|
||||
public var version: String {
|
||||
return aliases.first!
|
||||
.replacingOccurrences(of: "php@", with: "")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
struct HomebrewInstalled: Decodable {
|
||||
|
||||
@@ -18,7 +18,7 @@ struct HomebrewService: Decodable, Equatable {
|
||||
let status: String?
|
||||
let log_path: String?
|
||||
let error_log_path: String?
|
||||
|
||||
|
||||
public static func loadAll(
|
||||
filter: [String] = [PhpEnv.phpInstall.formula, "nginx", "dnsmasq"],
|
||||
completion: @escaping ([HomebrewService]) -> Void
|
||||
@@ -27,11 +27,11 @@ struct HomebrewService: Decodable, Equatable {
|
||||
let data = Shell
|
||||
.pipe("sudo \(Paths.brew) services info --all --json", requiresPath: true)
|
||||
.data(using: .utf8)!
|
||||
|
||||
|
||||
let services = try! JSONDecoder()
|
||||
.decode([HomebrewService].self, from: data)
|
||||
.filter({ return filter.contains($0.name) })
|
||||
|
||||
|
||||
completion(services)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,42 +9,42 @@
|
||||
import Foundation
|
||||
|
||||
class PhpEnv {
|
||||
|
||||
|
||||
// MARK: - Initializer
|
||||
|
||||
|
||||
init() {
|
||||
self.currentInstall = ActivePhpInstallation()
|
||||
|
||||
let brewPhpAlias = Shell.pipe("\(Paths.brew) info php --json");
|
||||
|
||||
|
||||
let brewPhpAlias = Shell.pipe("\(Paths.brew) info php --json")
|
||||
|
||||
self.homebrewPackage = try! JSONDecoder().decode(
|
||||
[HomebrewPackage].self,
|
||||
from: brewPhpAlias.data(using: .utf8)!
|
||||
).first!
|
||||
|
||||
|
||||
Log.info("When on your system, the `php` formula means version \(homebrewPackage.version)!")
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
|
||||
/** The delegate that is informed of updates. */
|
||||
weak var delegate: PhpSwitcherDelegate?
|
||||
|
||||
/** The static app instance. Accessible at any time. */
|
||||
static let shared = PhpEnv()
|
||||
|
||||
|
||||
/** Whether the switcher is busy performing any actions. */
|
||||
var isBusy: Bool = false
|
||||
|
||||
|
||||
/** All available versions of PHP. */
|
||||
var availablePhpVersions: [String] = []
|
||||
|
||||
|
||||
/** Cached information about the PHP installations. */
|
||||
var cachedPhpInstallations: [String: PhpInstallation] = [:]
|
||||
|
||||
|
||||
/** Information about the currently linked PHP installation. */
|
||||
var currentInstall: ActivePhpInstallation
|
||||
|
||||
|
||||
/**
|
||||
The version that the `php` formula via Brew is aliased to on the current system.
|
||||
|
||||
@@ -57,63 +57,62 @@ class PhpEnv {
|
||||
static var brewPhpVersion: String {
|
||||
return Self.shared.homebrewPackage.version
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
The currently linked and active PHP installation.
|
||||
*/
|
||||
static var phpInstall: ActivePhpInstallation {
|
||||
return Self.shared.currentInstall
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Information we were able to discern from the Homebrew info command.
|
||||
*/
|
||||
var homebrewPackage: HomebrewPackage! = nil
|
||||
|
||||
|
||||
// MARK: - Methods
|
||||
|
||||
|
||||
public static var switcher: PhpSwitcher {
|
||||
return InternalSwitcher()
|
||||
}
|
||||
|
||||
public static func detectPhpVersions() -> Void {
|
||||
|
||||
public static func detectPhpVersions() {
|
||||
_ = Self.shared.detectPhpVersions()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Detects which versions of PHP are installed.
|
||||
*/
|
||||
public func detectPhpVersions() -> [String]
|
||||
{
|
||||
public func detectPhpVersions() -> [String] {
|
||||
let files = Shell.pipe("ls \(Paths.optPath) | grep php@")
|
||||
|
||||
|
||||
var versionsOnly = extractPhpVersions(from: files.components(separatedBy: "\n"))
|
||||
|
||||
|
||||
// Make sure the aliased version is detected
|
||||
// The user may have `php` installed, but not e.g. `php@8.0`
|
||||
// We should also detect that as a version that is installed
|
||||
let phpAlias = homebrewPackage.version
|
||||
|
||||
|
||||
// Avoid inserting a duplicate
|
||||
if (!versionsOnly.contains(phpAlias) && Filesystem.fileExists("\(Paths.optPath)/php/bin/php")) {
|
||||
if !versionsOnly.contains(phpAlias) && Filesystem.fileExists("\(Paths.optPath)/php/bin/php") {
|
||||
versionsOnly.append(phpAlias)
|
||||
}
|
||||
|
||||
|
||||
Log.info("The PHP versions that were detected are: \(versionsOnly)")
|
||||
|
||||
|
||||
availablePhpVersions = versionsOnly
|
||||
|
||||
|
||||
var mappedVersions: [String: PhpInstallation] = [:]
|
||||
|
||||
|
||||
availablePhpVersions.forEach { version in
|
||||
mappedVersions[version] = PhpInstallation(version)
|
||||
}
|
||||
|
||||
|
||||
cachedPhpInstallations = mappedVersions
|
||||
|
||||
|
||||
return versionsOnly
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Extracts valid PHP versions from an array of strings.
|
||||
This array of strings is usually retrieved from `grep`.
|
||||
@@ -126,14 +125,14 @@ class PhpEnv {
|
||||
checkBinaries: Bool = true,
|
||||
generateHelpers: Bool = true
|
||||
) -> [String] {
|
||||
var output : [String] = []
|
||||
|
||||
var output: [String] = []
|
||||
|
||||
var supported = Constants.SupportedPhpVersions
|
||||
|
||||
|
||||
if !Valet.enabled(feature: .supportForPhp56) {
|
||||
supported.removeAll { $0 == "5.6" }
|
||||
}
|
||||
|
||||
|
||||
versions.filter { (version) -> Bool in
|
||||
// Omit everything that doesn't start with php@
|
||||
// (e.g. something-php@8.0 won't be detected)
|
||||
@@ -144,19 +143,18 @@ class PhpEnv {
|
||||
// is supported and where the binary exists (avoids broken installs)
|
||||
if !output.contains(version)
|
||||
&& supported.contains(version)
|
||||
&& (checkBinaries ? Filesystem.fileExists("\(Paths.optPath)/php@\(version)/bin/php") : true)
|
||||
{
|
||||
&& (checkBinaries ? Filesystem.fileExists("\(Paths.optPath)/php@\(version)/bin/php") : true) {
|
||||
output.append(version)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if generateHelpers {
|
||||
output.forEach { PhpHelper.generate(for: $0) }
|
||||
}
|
||||
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
|
||||
public func validVersions(for constraint: String) -> [PhpVersionNumber] {
|
||||
constraint.split(separator: "|").flatMap {
|
||||
return PhpVersionNumberCollection
|
||||
@@ -164,7 +162,7 @@ class PhpEnv {
|
||||
.matching(constraint: $0.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Validates whether the currently running version matches the provided version.
|
||||
*/
|
||||
@@ -173,7 +171,7 @@ class PhpEnv {
|
||||
Log.info("Switching to version \(version) seems to have succeeded. Validation passed.")
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,27 +9,28 @@
|
||||
import Foundation
|
||||
|
||||
class PhpHelper {
|
||||
|
||||
|
||||
static let keyPhrase = "This file was automatically generated by PHP Monitor."
|
||||
|
||||
|
||||
public static func generate(for version: String) {
|
||||
// Take the PHP version (e.g. "7.2") and generate a dotless version
|
||||
let dotless = version.replacingOccurrences(of: ".", with: "")
|
||||
|
||||
|
||||
do {
|
||||
let destination = "/usr/local/bin/pm\(dotless)"
|
||||
if FileManager.default.fileExists(atPath: destination) {
|
||||
let contents = try String(contentsOfFile: destination)
|
||||
if !contents.contains(keyPhrase) {
|
||||
Log.info("The file at '\(destination)' already exists and was not generated by PHP Monitor (or is unreadable). Not updating this file.")
|
||||
Log.info("The file at '\(destination)' already exists and was not generated by PHP Monitor "
|
||||
+ "(or is unreadable). Not updating this file.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Let's follow the symlink to the PHP binary folder
|
||||
let path = URL(fileURLWithPath: "\(Paths.optPath)/php@\(version)/bin")
|
||||
.resolvingSymlinksInPath().path
|
||||
|
||||
|
||||
// The contents of the script!
|
||||
let script = """
|
||||
#!/bin/zsh
|
||||
@@ -41,14 +42,14 @@ class PhpHelper {
|
||||
|| echo "You must run '. pm\(dotless)' (or 'source pm\(dotless)') instead!";
|
||||
export PATH=\(path):$PATH
|
||||
"""
|
||||
|
||||
|
||||
// Write to the destination
|
||||
try script.write(
|
||||
to: URL(fileURLWithPath: destination),
|
||||
atomically: true,
|
||||
encoding: String.Encoding.utf8
|
||||
)
|
||||
|
||||
|
||||
// Make sure the file is executable
|
||||
Shell.run("chmod +x \(destination)")
|
||||
} catch {
|
||||
@@ -56,5 +57,5 @@ class PhpHelper {
|
||||
Log.err("Could not write PHP Monitor helper for PHP \(version) to /usr/local/bin/pm\(dotless)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -10,21 +10,21 @@ import Foundation
|
||||
|
||||
public struct PhpVersionNumberCollection: Equatable {
|
||||
let versions: [PhpVersionNumber]
|
||||
|
||||
|
||||
public static func make(from versions: [String]) -> Self {
|
||||
return PhpVersionNumberCollection(
|
||||
versions: versions.map { try! PhpVersionNumber.parse($0) }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
public var first: PhpVersionNumber? {
|
||||
return self.versions.first
|
||||
}
|
||||
|
||||
|
||||
public var all: [PhpVersionNumber] {
|
||||
return self.versions
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Checks if any versions of PHP are valid for the constraint provided.
|
||||
Due to the complexity of evaluating these, a important test is maintained.
|
||||
@@ -61,13 +61,13 @@ public struct PhpVersionNumberCollection: Equatable {
|
||||
// Strict constraint (e.g. "7.0") -> returns specific version
|
||||
return self.versions.filter { $0.isSameAs(version, strict) }
|
||||
}
|
||||
|
||||
|
||||
if let version = PhpVersionNumber.make(from: constraint, type: .caretVersionRange) {
|
||||
// Caret range means that the major version is never higher but minor version can be higher
|
||||
// ^7.2 will be compatible with all versions between 7.2 and 8.0
|
||||
return self.versions.filter { $0.hasNewerMinorVersionOrPatch(version, strict) }
|
||||
}
|
||||
|
||||
|
||||
if let version = PhpVersionNumber.make(from: constraint, type: .tildeVersionRange) {
|
||||
// Tilde range means that most specific digit is used as the basis.
|
||||
return self.versions.filter {
|
||||
@@ -78,11 +78,11 @@ public struct PhpVersionNumberCollection: Equatable {
|
||||
: $0.hasSameMajorButNewerOrSameMinor(version, strict)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let version = PhpVersionNumber.make(from: constraint, type: .greaterThanOrEqual) {
|
||||
return self.versions.filter { $0.isSameAs(version, strict) || $0.isNewerThan(version, strict) }
|
||||
}
|
||||
|
||||
|
||||
if let version = PhpVersionNumber.make(from: constraint, type: .greaterThan) {
|
||||
return self.versions.filter { $0.isNewerThan(version, strict) }
|
||||
}
|
||||
@@ -95,47 +95,52 @@ public struct PhpVersionNumber: Equatable {
|
||||
let major: Int
|
||||
let minor: Int
|
||||
let patch: Int?
|
||||
|
||||
|
||||
public func toString() -> String {
|
||||
return self.patch == nil
|
||||
? "\(major).\(minor)"
|
||||
: "\(major).\(minor).\(patch!)"
|
||||
}
|
||||
|
||||
|
||||
public func patch(_ strictFallback: Bool = true, _ constraint: PhpVersionNumber? = nil) -> Int {
|
||||
return patch ?? (strictFallback ? 0 : constraint?.patch ?? 999)
|
||||
}
|
||||
|
||||
|
||||
public var homebrewVersion: String {
|
||||
return "\(major).\(minor)"
|
||||
}
|
||||
|
||||
|
||||
public enum MatchType: String {
|
||||
case versionOnly = #"^(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||
case caretVersionRange = #"^\^(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||
case tildeVersionRange = #"^~(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||
case greaterThanOrEqual = #"^>=(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||
case greaterThan = #"^>(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||
|
||||
|
||||
// TODO: (6.0) Handle these cases (even though I suspect these are uncommon)
|
||||
/*
|
||||
case smallerThanOrEqual = #"^<=(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||
case smallerThan = #"^<(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
public static func parse(_ text: String) throws -> Self {
|
||||
guard let versionText = VersionExtractor.from(text) else {
|
||||
throw VersionParseError()
|
||||
}
|
||||
|
||||
|
||||
return Self.make(from: versionText)!
|
||||
}
|
||||
|
||||
|
||||
public static func make(from versionString: String, type: MatchType = .versionOnly) -> Self? {
|
||||
let regex = try! NSRegularExpression(pattern: type.rawValue, options: [])
|
||||
let match = regex.matches(in: versionString, options: [], range: NSMakeRange(0, versionString.count)).first
|
||||
|
||||
|
||||
let match = regex.matches(
|
||||
in: versionString,
|
||||
options: [],
|
||||
range: NSRange(location: 0, length: versionString.count)
|
||||
).first
|
||||
|
||||
if match != nil {
|
||||
let major = Int(
|
||||
versionString[Range(match!.range(withName: "major"), in: versionString)!]
|
||||
@@ -143,24 +148,24 @@ public struct PhpVersionNumber: Equatable {
|
||||
let minor = Int(
|
||||
versionString[Range(match!.range(withName: "minor"), in: versionString)!]
|
||||
)!
|
||||
var patch: Int? = nil
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// MARK: Comparison Logic
|
||||
|
||||
|
||||
internal func isSameAs(_ version: PhpVersionNumber, _ strict: Bool) -> Bool {
|
||||
return self.major == version.major
|
||||
&& self.minor == version.minor
|
||||
&& (strict ? self.patch(strict, version) == version.patch(strict) : true)
|
||||
}
|
||||
|
||||
|
||||
internal func isNewerThan(_ version: PhpVersionNumber, _ strict: Bool) -> Bool {
|
||||
return (
|
||||
self.major > version.major ||
|
||||
@@ -169,7 +174,7 @@ public struct PhpVersionNumber: Equatable {
|
||||
&& self.patch(strict) > version.patch(strict)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
internal func hasNewerMinorVersionOrPatch(_ version: PhpVersionNumber, _ strict: Bool) -> Bool {
|
||||
return self.major == version.major &&
|
||||
(
|
||||
@@ -177,12 +182,12 @@ public struct PhpVersionNumber: Equatable {
|
||||
|| self.minor > version.minor
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
internal func hasSameMajorAndMinorButNewerOrSamePatch(_ version: PhpVersionNumber, _ strict: Bool) -> Bool {
|
||||
return self.major == version.major && self.minor == version.minor
|
||||
&& self.patch(strict, version) >= version.patch(strict)
|
||||
}
|
||||
|
||||
|
||||
internal func hasSameMajorButNewerOrSameMinor(_ version: PhpVersionNumber, _ strict: Bool) -> Bool {
|
||||
return self.major == version.major
|
||||
&& self.minor >= version.minor
|
||||
|
||||
@@ -16,24 +16,26 @@ import Foundation
|
||||
instances. You can find more information here: https://nshipster.com/swift-regular-expressions/
|
||||
*/
|
||||
class PhpExtension {
|
||||
|
||||
|
||||
/// The file where this extension was located.
|
||||
var file: String
|
||||
|
||||
|
||||
/// The original string that was used to determine this extension is active.
|
||||
var line: String
|
||||
|
||||
/// The name of the extension. This is always identical to the name found in the original string. If you want to display this name, capitalize this.
|
||||
|
||||
/// The name of the extension. This is always identical to the name found in the original string.
|
||||
/// If you want to display this name, capitalize this.
|
||||
var name: String
|
||||
|
||||
|
||||
/// Whether the extension has been enabled.
|
||||
var enabled: Bool
|
||||
|
||||
|
||||
/// The file where this extension was located, but only the filename, not the full path to the .ini file.
|
||||
var fileNameOnly: String {
|
||||
return String(file.split(separator: "/").last ?? "php.ini")
|
||||
}
|
||||
|
||||
|
||||
// swiftlint:disable line_length
|
||||
/**
|
||||
This regular expression will allow us to identify lines which activate an extension.
|
||||
|
||||
@@ -47,29 +49,31 @@ class PhpExtension {
|
||||
- Note: Extensions that are disabled in a different way will not be detected. This is intentional.
|
||||
*/
|
||||
static let extensionRegex = #"^(extension|zend_extension|;(\s?)extension|;(\s?)zend_extension)(\s?)(=)(\s?)(?<name>["]?(?:\/?.\/?)+(?:\.so)"?)$"#
|
||||
|
||||
// swiftlint:enable line_length
|
||||
|
||||
/**
|
||||
When registering an extension, we do that based on the line found inside the .ini file.
|
||||
*/
|
||||
init(_ line: String, file: String) {
|
||||
let regex = try! NSRegularExpression(pattern: Self.extensionRegex, options: [])
|
||||
let match = regex.matches(in: line, options: [], range: NSMakeRange(0, line.count)).first
|
||||
let match = regex.matches(in: line, options: [], range: NSRange(location: 0, length: line.count)).first
|
||||
let range = Range(match!.range(withName: "name"), in: line)!
|
||||
|
||||
|
||||
self.line = line
|
||||
|
||||
|
||||
let fullPath = String(line[range])
|
||||
.replacingOccurrences(of: "\"", with: "") // replace excess "
|
||||
.replacingOccurrences(of: ".so", with: "") // replace excess .so
|
||||
|
||||
|
||||
self.name = String(fullPath.split(separator: "/").last!) // take last segment
|
||||
|
||||
|
||||
self.enabled = !line.contains(";")
|
||||
self.file = file
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
This simply toggles the extension in the .ini file. You may need to restart the other services in order for this change to apply.
|
||||
This simply toggles the extension in the .ini file.
|
||||
You may need to restart the other services in order for this change to apply.
|
||||
*/
|
||||
func toggle() {
|
||||
let newLine = enabled
|
||||
@@ -77,25 +81,25 @@ class PhpExtension {
|
||||
? "; \(line)"
|
||||
// ENABLED: Line where the comment delimiter (;) is removed
|
||||
: line.replacingOccurrences(of: "; ", with: "")
|
||||
|
||||
|
||||
sed(file: file, original: line, replacement: newLine)
|
||||
|
||||
|
||||
enabled.toggle()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Static Methods
|
||||
|
||||
|
||||
/**
|
||||
This method will attempt to identify all extensions in the .ini file at a certain URL.
|
||||
*/
|
||||
static func load(from path: URL) -> [PhpExtension] {
|
||||
let file = try? String(contentsOf: path, encoding: .utf8)
|
||||
|
||||
if (file == nil) {
|
||||
|
||||
if file == nil {
|
||||
Log.err("There was an issue reading the file. Assuming no extensions were found.")
|
||||
return []
|
||||
}
|
||||
|
||||
|
||||
return file!.components(separatedBy: "\n")
|
||||
.filter {
|
||||
return $0.range(of: Self.extensionRegex, options: .regularExpression) != nil
|
||||
@@ -104,5 +108,5 @@ class PhpExtension {
|
||||
return PhpExtension($0, file: path.path)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -9,28 +9,28 @@
|
||||
import Foundation
|
||||
|
||||
class PhpInstallation {
|
||||
|
||||
|
||||
var versionNumber: PhpVersionNumber
|
||||
|
||||
|
||||
/**
|
||||
In order to determine details about a PHP installation, we’ll simply run `php-config --version`
|
||||
in the relevant directory.
|
||||
*/
|
||||
init(_ version: String) {
|
||||
|
||||
|
||||
let phpConfigExecutablePath = "\(Paths.optPath)/php@\(version)/bin/php-config"
|
||||
self.versionNumber = PhpVersionNumber.make(from: version)!
|
||||
|
||||
|
||||
if Filesystem.fileExists(phpConfigExecutablePath) {
|
||||
let longVersionString = Command.execute(
|
||||
path: phpConfigExecutablePath,
|
||||
arguments: ["--version"]
|
||||
).trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
|
||||
// The parser should always work, or the string has to be very unusual.
|
||||
// If so, the app SHOULD crash, so that the users report what's up.
|
||||
self.versionNumber = try! PhpVersionNumber.parse(longVersionString)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
|
||||
class InternalSwitcher: PhpSwitcher {
|
||||
|
||||
|
||||
/**
|
||||
Switching to a new PHP version involves:
|
||||
- unlinking the current version
|
||||
@@ -20,50 +20,49 @@ class InternalSwitcher: PhpSwitcher {
|
||||
the version that is switched to may or may not be identical to `php`
|
||||
(without @version).
|
||||
*/
|
||||
func performSwitch(to version: String, completion: @escaping () -> Void)
|
||||
{
|
||||
func performSwitch(to version: String, completion: @escaping () -> Void) {
|
||||
Log.info("Switching to \(version), unlinking all versions...")
|
||||
|
||||
|
||||
let isolated = Valet.shared.sites.filter { site in
|
||||
site.isolatedPhpVersion != nil
|
||||
}.map { site in
|
||||
return site.isolatedPhpVersion!.versionNumber.homebrewVersion
|
||||
}
|
||||
|
||||
|
||||
var versions: Set<String> = [version]
|
||||
|
||||
if (Valet.enabled(feature: .isolatedSites)) {
|
||||
|
||||
if Valet.enabled(feature: .isolatedSites) {
|
||||
versions = versions.union(isolated)
|
||||
}
|
||||
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
|
||||
PhpEnv.shared.availablePhpVersions.forEach { (available) in
|
||||
group.enter()
|
||||
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
self.disableDefaultPhpFpmPool(available)
|
||||
self.stopPhpVersion(available)
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
group.notify(queue: .global(qos: .userInitiated)) {
|
||||
Log.info("All versions have been unlinked!")
|
||||
Log.info("Linking the new version!")
|
||||
|
||||
|
||||
for formula in versions {
|
||||
self.startPhpVersion(formula, primary: (version == formula))
|
||||
}
|
||||
|
||||
|
||||
Log.info("Restarting nginx, just to be sure!")
|
||||
brew("services restart nginx", sudo: true)
|
||||
|
||||
|
||||
Log.info("The new version(s) have been linked!")
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func disableDefaultPhpFpmPool(_ version: String) {
|
||||
let pool = "\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf"
|
||||
if FileManager.default.fileExists(atPath: pool) {
|
||||
@@ -71,8 +70,9 @@ class InternalSwitcher: PhpSwitcher {
|
||||
let existing = URL(string: "file://\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf")!
|
||||
let new = URL(string: "file://\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf.disabled-by-phpmon")!
|
||||
do {
|
||||
if (FileManager.default.fileExists(atPath: new.path)) {
|
||||
Log.info("A moved `www.conf.disabled-by-phpmon` file was found for PHP \(version), cleaning up so the newer `www.conf` can be moved again.")
|
||||
if FileManager.default.fileExists(atPath: new.path) {
|
||||
Log.info("A moved `www.conf.disabled-by-phpmon` file was found for PHP \(version), "
|
||||
+ "cleaning up so the newer `www.conf` can be moved again.")
|
||||
try FileManager.default.removeItem(at: new)
|
||||
}
|
||||
try FileManager.default.moveItem(at: existing, to: new)
|
||||
@@ -82,26 +82,26 @@ class InternalSwitcher: PhpSwitcher {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func stopPhpVersion(_ version: String) {
|
||||
let formula = (version == PhpEnv.brewPhpVersion) ? "php" : "php@\(version)"
|
||||
brew("unlink \(formula)")
|
||||
brew("services stop \(formula)", sudo: true)
|
||||
Log.info("Unlinked and stopped services for \(formula)")
|
||||
}
|
||||
|
||||
|
||||
private func startPhpVersion(_ version: String, primary: Bool) {
|
||||
let formula = (version == PhpEnv.brewPhpVersion) ? "php" : "php@\(version)"
|
||||
|
||||
if (primary) {
|
||||
|
||||
if primary {
|
||||
Log.info("\(formula) is the primary formula, linking and starting services...")
|
||||
brew("link \(formula) --overwrite --force")
|
||||
} else {
|
||||
Log.info("\(formula) is an isolated PHP version, starting services only...")
|
||||
}
|
||||
|
||||
|
||||
brew("services start \(formula)", sudo: true)
|
||||
|
||||
|
||||
if Valet.enabled(feature: .isolatedSites) && primary {
|
||||
let socketVersion = version.replacingOccurrences(of: ".", with: "")
|
||||
Shell.run("ln -sF ~/.config/valet/valet\(socketVersion).sock ~/.config/valet/valet.sock")
|
||||
@@ -109,5 +109,5 @@ class InternalSwitcher: PhpSwitcher {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
import Foundation
|
||||
|
||||
protocol PhpSwitcherDelegate: AnyObject {
|
||||
|
||||
|
||||
func switcherDidStartSwitching(to version: String)
|
||||
|
||||
|
||||
func switcherDidCompleteSwitch(to version: String)
|
||||
|
||||
|
||||
}
|
||||
|
||||
protocol PhpSwitcher {
|
||||
|
||||
|
||||
func performSwitch(to version: String, completion: @escaping () -> Void)
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user