1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-08-08 04:20:07 +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; isa = PBXGroup;
children = ( children = (
C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */, C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */,
C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */,
C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */, C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */,
C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */,
C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */, C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */,
C4CE3BB927B31F670086CA49 /* MainMenu+Composer.swift */, C4CE3BB927B31F670086CA49 /* MainMenu+Composer.swift */,
C47331A1247093B7009A0597 /* StatusMenu.swift */, C47331A1247093B7009A0597 /* StatusMenu.swift */,
@ -587,8 +587,8 @@
C4811D2322D70A4700B5F6B3 /* App.swift */, C4811D2322D70A4700B5F6B3 /* App.swift */,
C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */, C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */,
C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */, C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */,
C4D8016522B1584700C6DA1B /* Startup.swift */,
C4EED88827A48778006D7272 /* InterAppHandler.swift */, C4EED88827A48778006D7272 /* InterAppHandler.swift */,
C4D8016522B1584700C6DA1B /* Startup.swift */,
); );
path = App; path = App;
sourceTree = "<group>"; sourceTree = "<group>";

View File

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

View File

@ -9,11 +9,11 @@
// MARK: Common Shell Commands // MARK: Common Shell Commands
/** /**
Runs a `valet` command. Runs a `valet` command. Defaults to running as superuser.
*/ */
func valet(_ command: String) -> String func valet(_ command: String, sudo: Bool = true) -> String
{ {
return Shell.pipe("sudo \(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. 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( _ = present(
messageText: message, messageText: message,
informativeText: info, informativeText: info,
buttonTitle: "OK", buttonTitle: button,
secondButtonTitle: "", secondButtonTitle: "",
style: style style: style
) )

View File

@ -10,8 +10,71 @@ import AppKit
class Startup { 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 failed: Bool = false
public var failureCallback = {}
/** /**
Checks the user's environment and checks if PHP Monitor can be used properly. Checks the user's environment and checks if PHP Monitor can be used properly.
@ -20,66 +83,46 @@ class Startup {
- Parameter success: Callback that is fired if the application can proceed with launch - 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 - 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( for check in self.checks {
!Shell.fileExists("\(Paths.binPath)/php"), let failureCondition = check.command()
messageText: "startup.errors.php_binary.title".localized,
informativeText: "startup.errors.php_binary.desc".localized if !failureCondition {
continue
}
failed = true
if check.requiresAppRestart {
Alert.notify(
message: check.titleText,
info: check.descriptionText,
button: check.buttonText,
style: .critical
) )
exit(1)
}
performEnvironmentCheck( Alert.notify(
!Shell.pipe("ls \(Paths.optPath) | grep php").contains("php"), message: check.titleText,
messageText: "startup.errors.php_opt.title".localized, info: check.descriptionText,
informativeText: "startup.errors.php_opt.desc".localized style: .critical
) )
}
performEnvironmentCheck( if failed {
// Check for Valet; it can be symlinked or in .composer/vendor/bin failure()
!(Shell.fileExists("\(Paths.binPath))/valet") return
|| 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() initializeSwitcher()
Log.info("PHP Monitor has determined the application has successfully passed all checks.") Log.info("PHP Monitor has determined the application has successfully passed all checks.")
success() success()
} }
}
/** /**
Because the Switcher requires various environment guarantees, the switcher is only Because the Switcher requires various environment guarantees, the switcher is only
@ -92,30 +135,31 @@ class Startup {
} }
} }
// MARK: - EnvironmentCheck struct
/** /**
Perform an environment check. 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.
- 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
*/ */
private func performEnvironmentCheck( struct EnvironmentCheck {
_ condition: Bool, let command: () -> Bool
messageText: String, let titleText: String
informativeText: String let descriptionText: String
let buttonText: String
let requiresAppRestart: Bool
init(
command: @escaping () -> Bool,
titleText: String,
descriptionText: String,
buttonText: String = "OK",
requiresAppRestart: Bool = false
) { ) {
if (!condition) { return } self.command = command
self.titleText = titleText
DispatchQueue.main.async { [self] in self.descriptionText = descriptionText
// Present the information to the user self.buttonText = buttonText
Alert.notify( self.requiresAppRestart = requiresAppRestart
message: messageText,
info: informativeText,
style: .critical
)
// Only breaking issues will throw the extra retry modal
failureCallback()
} }
} }
} }

View File

@ -16,29 +16,11 @@ extension MainMenu {
// Start with the icon // Start with the icon
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!) 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 // Perform environment boot checks
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in DispatchQueue.main.async {
Startup().checkEnvironment(success: { onEnvironmentPass() }, Startup().checkEnvironment(
failure: { onEnvironmentFail() } success: { self.onEnvironmentPass() },
failure: { self.onEnvironmentFail() }
) )
} }
} }

View File

@ -181,12 +181,6 @@
"notification.services_restarted" = "Valet services restarted"; "notification.services_restarted" = "Valet services restarted";
"notification.services_restarted_desc" = "All services have been successfully 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 // Composer Update
"alert.composer_missing.title" = "Composer not found!"; "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`."; "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 // 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 /// 1. PHP binary not found
"startup.errors.php_binary.title" = "PHP is not correctly installed"; "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 /// 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.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 /// 3a. Valet not installed
"startup.errors.valet_executable.title" = "Laravel Valet is not correctly 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] /// 3b. Valet configuration file missing [currently not enabled]
"startup.errors.valet_config.title" = "Laravel Valet configuration file missing"; "startup.errors.valet_config.title" = "Laravel Valet configuration file missing";