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

♻️ Refactor startup procedure

This commit is contained in:
2022-02-11 21:52:46 +01:00
parent dc91d0e00c
commit dae47e3779
7 changed files with 145 additions and 119 deletions

View File

@ -511,8 +511,8 @@
isa = PBXGroup;
children = (
C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */,
C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */,
C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */,
C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */,
C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */,
C4CE3BB927B31F670086CA49 /* MainMenu+Composer.swift */,
C47331A1247093B7009A0597 /* StatusMenu.swift */,
@ -587,8 +587,8 @@
C4811D2322D70A4700B5F6B3 /* App.swift */,
C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */,
C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */,
C4D8016522B1584700C6DA1B /* Startup.swift */,
C4EED88827A48778006D7272 /* InterAppHandler.swift */,
C4D8016522B1584700C6DA1B /* Startup.swift */,
);
path = App;
sourceTree = "<group>";

View File

@ -11,7 +11,7 @@ import XCTest
class ValetTest: XCTestCase {
func testDetermineValetVersion() {
let version = valet("--version")
let version = valet("--version", sudo: false)
XCTAssert(version.contains("Laravel Valet 2."))
}

View File

@ -9,11 +9,11 @@
// MARK: Common Shell Commands
/**
Runs a `valet` command.
Runs a `valet` command. Defaults to running as superuser.
*/
func valet(_ command: String) -> String
func valet(_ command: String, sudo: Bool = true) -> String
{
return Shell.pipe("sudo \(Paths.valet) \(command)", requiresPath: true)
return Shell.pipe("\(sudo ? "sudo " : "")" + "\(Paths.valet) \(command)", requiresPath: true)
}
/**

View File

@ -54,11 +54,11 @@ class Alert {
/**
Notify the user about something by showing an alert.
*/
public static func notify(message: String, info: String, style: NSAlert.Style = .informational) {
public static func notify(message: String, info: String, button: String = "OK", style: NSAlert.Style = .informational) {
_ = present(
messageText: message,
informativeText: info,
buttonTitle: "OK",
buttonTitle: button,
secondButtonTitle: "",
style: style
)

View File

@ -10,8 +10,71 @@ import AppKit
class Startup {
public var checks: [EnvironmentCheck] = [
EnvironmentCheck(
command: { return !FileManager.default.fileExists(atPath: Paths.brew) },
titleText: "alert.homebrew_missing.title".localized,
descriptionText: "alert.homebrew_missing.info".localized(
App.architecture
.replacingOccurrences(of: "x86_64", with: "Intel")
.replacingOccurrences(of: "arm64", with: "Apple Silicon"),
Paths.brew
),
buttonText: "alert.homebrew_missing.quit".localized,
requiresAppRestart: true
),
EnvironmentCheck(
command: { return !Shell.fileExists(Paths.php) },
titleText: "startup.errors.php_binary.title".localized,
descriptionText: "startup.errors.php_binary.desc".localized(
Paths.php
)
),
EnvironmentCheck(
command: { return !Shell.pipe("ls \(Paths.optPath) | grep php").contains("php") },
titleText: "startup.errors.php_opt.title".localized,
descriptionText: "startup.errors.php_opt.desc".localized(
Paths.optPath
)
),
EnvironmentCheck(
command: {
return !(Shell.fileExists(Paths.valet)
|| Shell.fileExists("~/.composer/vendor/bin/valet"))
},
titleText: "startup.errors.valet_executable.title".localized,
descriptionText: "startup.errors.valet_executable.desc".localized(
Paths.valet
)
),
EnvironmentCheck(
command: { return HomebrewDiagnostics.cannotLoadService() },
titleText: "startup.errors.services_json_error.title".localized,
descriptionText: "startup.errors.services_json_error.desc".localized
),
EnvironmentCheck(
command: { return !Shell.pipe("cat /private/etc/sudoers.d/brew").contains(Paths.brew) },
titleText: "startup.errors.sudoers_brew.title".localized,
descriptionText: "startup.errors.sudoers_brew.desc".localized
),
EnvironmentCheck(
command: { return !Shell.pipe("cat /private/etc/sudoers.d/valet").contains(Paths.valet) },
titleText: "startup.errors.sudoers_valet.title".localized,
descriptionText: "startup.errors.sudoers_valet.desc".localized
),
EnvironmentCheck(
command: {
// Determine the Valet version only AFTER confirming the correct permission is in place
// or otherwise this command will never return a valid version number
Valet.shared.version = VersionExtractor.from(valet("--version", sudo: false))
return Valet.shared.version == nil
},
titleText: "startup.errors.valet_version_unknown.title".localized,
descriptionText: "startup.errors.valet_version_unknown.desc".localized
)
]
public var failed: Bool = false
public var failureCallback = {}
/**
Checks the user's environment and checks if PHP Monitor can be used properly.
@ -20,65 +83,45 @@ class Startup {
- Parameter success: Callback that is fired if the application can proceed with launch
- Parameter failure: Callback that is fired if the application must retry launch
*/
func checkEnvironment(success: () -> Void, failure: @escaping () -> Void)
func checkEnvironment(success: @escaping () -> Void, failure: @escaping () -> Void)
{
failureCallback = failure
// Do the important system setup checks
Log.info("The user is running PHP Monitor with the architecture: \(App.architecture)")
performEnvironmentCheck(
!Shell.fileExists("\(Paths.binPath)/php"),
messageText: "startup.errors.php_binary.title".localized,
informativeText: "startup.errors.php_binary.desc".localized
)
performEnvironmentCheck(
!Shell.pipe("ls \(Paths.optPath) | grep php").contains("php"),
messageText: "startup.errors.php_opt.title".localized,
informativeText: "startup.errors.php_opt.desc".localized
)
performEnvironmentCheck(
// Check for Valet; it can be symlinked or in .composer/vendor/bin
!(Shell.fileExists("\(Paths.binPath))/valet")
|| Shell.fileExists("~/.composer/vendor/bin/valet")
),
messageText: "startup.errors.valet_executable.title".localized,
informativeText: "startup.errors.valet_executable.desc".localized
)
performEnvironmentCheck(
HomebrewDiagnostics.cannotLoadService(),
messageText: "startup.errors.services_json_error.title".localized,
informativeText: "startup.errors.services_json_error.desc".localized
)
performEnvironmentCheck(
!Shell.pipe("cat /private/etc/sudoers.d/brew").contains("\(Paths.binPath)/brew"),
messageText: "startup.errors.sudoers_brew.title".localized,
informativeText: "startup.errors.sudoers_brew.desc".localized
)
performEnvironmentCheck(
// Check for Valet; it MUST be symlinked thanks to sudoers
!(Shell.pipe("cat /private/etc/sudoers.d/valet").contains("/usr/local/bin/valet")
|| Shell.pipe("cat /private/etc/sudoers.d/valet").contains("/opt/homebrew/bin/valet")
),
messageText: "startup.errors.sudoers_valet.title".localized,
informativeText: "startup.errors.sudoers_valet.desc".localized
)
// Determine the Valet version only AFTER confirming the correct permission is in place
Valet.shared.version = VersionExtractor.from(valet("--version"))
performEnvironmentCheck(
Valet.shared.version == nil,
messageText: "startup.errors.valet_version_unknown.title".localized,
informativeText: "startup.errors.valet_version_unknown.desc".localized
)
if (!failed) {
initializeSwitcher()
Log.info("PHP Monitor has determined the application has successfully passed all checks.")
success()
for check in self.checks {
let failureCondition = check.command()
if !failureCondition {
continue
}
failed = true
if check.requiresAppRestart {
Alert.notify(
message: check.titleText,
info: check.descriptionText,
button: check.buttonText,
style: .critical
)
exit(1)
}
Alert.notify(
message: check.titleText,
info: check.descriptionText,
style: .critical
)
}
if failed {
failure()
return
}
initializeSwitcher()
Log.info("PHP Monitor has determined the application has successfully passed all checks.")
success()
}
/**
@ -91,31 +134,32 @@ class Startup {
appDelegate.initializeSwitcher()
}
}
// MARK: - EnvironmentCheck struct
/**
Perform an environment check.
- Parameter condition: Fail condition to check for; if this returns `true`, the alert will be shown
- Parameter messageText: Short description of what is wrong
- Parameter informativeText: Expanded description of the environment check that failed
The `EnvironmentCheck` is used to defer the execution of all of these commands until necessary.
Checks that require an app restart will always lead to an alert and app termination shortly after.
*/
private func performEnvironmentCheck(
_ condition: Bool,
messageText: String,
informativeText: String
) {
if (!condition) { return }
DispatchQueue.main.async { [self] in
// Present the information to the user
Alert.notify(
message: messageText,
info: informativeText,
style: .critical
)
// Only breaking issues will throw the extra retry modal
failureCallback()
struct EnvironmentCheck {
let command: () -> Bool
let titleText: String
let descriptionText: String
let buttonText: String
let requiresAppRestart: Bool
init(
command: @escaping () -> Bool,
titleText: String,
descriptionText: String,
buttonText: String = "OK",
requiresAppRestart: Bool = false
) {
self.command = command
self.titleText = titleText
self.descriptionText = descriptionText
self.buttonText = buttonText
self.requiresAppRestart = requiresAppRestart
}
}
}

View File

@ -16,29 +16,11 @@ extension MainMenu {
// Start with the icon
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
// Do the important system setup checks
Log.info("The user is running PHP Monitor with the architecture: \(App.architecture)")
// Make sure Homebrew is installed
if !FileManager.default.fileExists(atPath: Paths.brew) {
_ = Alert.present(
messageText: "alert.homebrew_missing.title".localized,
informativeText: "alert.homebrew_missing.info".localized(
App.architecture
.replacingOccurrences(of: "x86_64", with: "Intel")
.replacingOccurrences(of: "arm64", with: "Apple Silicon"),
Paths.brew
),
buttonTitle: "alert.homebrew_missing.quit".localized,
style: .critical
)
exit(1)
}
// Perform environment boot checks
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
Startup().checkEnvironment(success: { onEnvironmentPass() },
failure: { onEnvironmentFail() }
DispatchQueue.main.async {
Startup().checkEnvironment(
success: { self.onEnvironmentPass() },
failure: { self.onEnvironmentFail() }
)
}
}

View File

@ -181,12 +181,6 @@
"notification.services_restarted" = "Valet services restarted";
"notification.services_restarted_desc" = "All services have been successfully restarted.";
// ALERTS
"alert.homebrew_missing.title" = "PHP Monitor cannot start";
"alert.homebrew_missing.info" = "You are running PHP Monitor with the following architecture: %@.\n\nA working Homebrew binary is expected in `%@`, but was not found, so PHP Monitor cannot work.\n\nIf you have not installed Homebrew yet, please do so. (If you are on Apple Silicon, make sure your Homebrew and PHP Monitor use the same architecture, by enabling or disabling Rosetta where needed.)\n\nPHP Monitor will now quit, please restart the app after fixing this issue.";
"alert.homebrew_missing.quit" = "Quit";
// Composer Update
"alert.composer_missing.title" = "Composer not found!";
"alert.composer_missing.info" = "Make sure you have Composer available in `/usr/local/bin/composer`. If Composer is located somewhere else, please create a symlink, like so (make sure to use the correct path):\n\n`ln -s /path/to/composer /usr/local/bin`.";
@ -258,17 +252,23 @@ You can do this by running `composer global update` in your terminal. After that
// STARTUP
/// 0. Architecture mismatch
"alert.homebrew_missing.title" = "PHP Monitor cannot start";
"alert.homebrew_missing.info" = "You are running PHP Monitor with the following architecture: %@.\n\nA working Homebrew binary is expected in `%@`, but was not found, so PHP Monitor cannot work.\n\nIf you have not installed Homebrew yet, please do so. (If you are on Apple Silicon, make sure your Homebrew and PHP Monitor use the same architecture, by enabling or disabling Rosetta where needed.)\n\nPHP Monitor will now quit, please restart the app after fixing this issue.";
"alert.homebrew_missing.quit" = "Quit";
/// 1. PHP binary not found
"startup.errors.php_binary.title" = "PHP is not correctly installed";
"startup.errors.php_binary.desc" = "You must install PHP via brew. Try running `which php` in Terminal, it should return `/usr/local/bin/php` (or `/opt/homebrew/bin/php`). The app will not work correctly until you resolve this issue. (Usually `brew link php` resolves this issue.)";
"startup.errors.php_binary.desc" = "You must install PHP via brew. Try running `which php` in Terminal, it should return `%@`. The app will not work correctly until you resolve this issue. (Usually `brew link php` resolves this issue.)";
/// 2. PHP not found in /usr/local/opt or /opt/homebrew/opt
"startup.errors.php_opt.title" = "PHP is not correctly installed";
"startup.errors.php_opt.desc" = "PHP alias was not found in `/usr/local/opt` or `/opt/homebrew/opt`. The app will not work correctly until you resolve this issue. If you already have the `php` formula installed, you may need to run `brew install php` in order for PHP Monitor to detect this installation.";
"startup.errors.php_opt.desc" = "PHP alias was not found in `%@`. The app will not work correctly until you resolve this issue. If you already have the `php` formula installed, you may need to run `brew install php` in order for PHP Monitor to detect this installation.";
/// 3a. Valet not installed
"startup.errors.valet_executable.title" = "Laravel Valet is not correctly installed";
"startup.errors.valet_executable.desc" = "You must install Valet with composer. Try running `which valet` in Terminal, it should return `/usr/local/bin/valet` or `/opt/homebrew/bin/valet`. The app will not work correctly until you resolve this issue. (PHP Monitor checks for the existence of `valet` in either of these paths.)";
"startup.errors.valet_executable.desc" = "You must install Valet with composer. Try running `which valet` in Terminal, it should return `%@`. The app will not work correctly until you resolve this issue. (PHP Monitor checks for the existence of `valet` in either of these paths.)";
/// 3b. Valet configuration file missing [currently not enabled]
"startup.errors.valet_config.title" = "Laravel Valet configuration file missing";