From c6f2167c92f6fa5b546ae204d0ff0edadc275df2 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 17 Jan 2023 18:57:21 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Added=20environment=20check=20group?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 16 +- phpmon/Common/Core/Logger.swift | 6 + phpmon/Common/Helpers/FSNotifier.swift | 2 +- phpmon/Domain/App/EnvironmentCheck.swift | 6 + phpmon/Domain/App/Startup.swift | 395 ++++++++++--------- phpmon/Domain/Integrations/Valet/Valet.swift | 6 +- 6 files changed, 229 insertions(+), 202 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 8b3622e..c92efae 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2809,7 +2809,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1020; + CURRENT_PROJECT_VERSION = 1100; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2821,7 +2821,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.7; + MARKETING_VERSION = 6.0; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2838,7 +2838,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1020; + CURRENT_PROJECT_VERSION = 1100; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2850,7 +2850,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.7; + MARKETING_VERSION = 6.0; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3066,7 +3066,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1020; + CURRENT_PROJECT_VERSION = 1100; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3077,7 +3077,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.7; + MARKETING_VERSION = 6.0; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev; PRODUCT_NAME = "$(TARGET_NAME) DEV"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3176,7 +3176,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1020; + CURRENT_PROJECT_VERSION = 1100; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3187,7 +3187,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.7; + MARKETING_VERSION = 6.0; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/phpmon/Common/Core/Logger.swift b/phpmon/Common/Core/Logger.swift index 7ab6592..f8ab817 100644 --- a/phpmon/Common/Core/Logger.swift +++ b/phpmon/Common/Core/Logger.swift @@ -55,4 +55,10 @@ class Log { } } + static func line(as verbosity: Verbosity = .info) { + if verbosity.isApplicable() { + print("----------------------------------") + } + } + } diff --git a/phpmon/Common/Helpers/FSNotifier.swift b/phpmon/Common/Helpers/FSNotifier.swift index 16294cc..ca4a678 100644 --- a/phpmon/Common/Helpers/FSNotifier.swift +++ b/phpmon/Common/Helpers/FSNotifier.swift @@ -29,7 +29,7 @@ class FSNotifier { self.url = url self.linked = FileSystem.fileExists(Paths.php) - print("Initial PHP linked state: \(linked)") + Log.info("[FSN] Initial PHP linked state: \(linked ? "linked" : "unlinked")") fileDescriptor = open(url.path, O_EVTONLY) diff --git a/phpmon/Domain/App/EnvironmentCheck.swift b/phpmon/Domain/App/EnvironmentCheck.swift index a044ddf..551d2a5 100644 --- a/phpmon/Domain/App/EnvironmentCheck.swift +++ b/phpmon/Domain/App/EnvironmentCheck.swift @@ -43,3 +43,9 @@ struct EnvironmentCheck { return await !self.command() } } + +struct EnvironmentCheckGroup { + let name: String + let condition: () -> Bool + let checks: [EnvironmentCheck] +} diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index 7699a5d..bc87e4c 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -21,16 +21,27 @@ class Startup { // Do the important system setup checks Log.info("[ARCH] The user is running PHP Monitor with the architecture: \(App.architecture)") - for check in self.checks { - if await check.succeeds() { - Log.info("[OK] \(check.name)") - continue - } + for group in self.groups { + if group.condition() { + Log.line() + Log.info("Running \(group.name) checks!") + Log.line() + for check in group.checks { + if await check.succeeds() { + Log.info("[OK] \(check.name)") + continue + } - // If we get here, something's gone wrong and the check has failed... - Log.info("[FAIL] \(check.name)") - await showAlert(for: check) - return false + // If we get here, something's gone wrong and the check has failed... + Log.info("[FAIL] \(check.name)") + await showAlert(for: check) + return false + } + } else { + Log.line() + Log.info("Skipping \(group.name) checks!") + Log.line() + } } // If we get here, nothing has gone wrong. That's what we want! @@ -81,192 +92,192 @@ class Startup { // MARK: - Check (List) - public var checks: [EnvironmentCheck] = [ - // ================================================================================= - // The Homebrew binary must exist. - // ================================================================================= - EnvironmentCheck( - command: { return !FileSystem.fileExists(Paths.brew) }, - name: "`\(Paths.brew)` exists", - titleText: "alert.homebrew_missing.title".localized, - subtitleText: "alert.homebrew_missing.subtitle".localized, - descriptionText: "alert.homebrew_missing.info".localized( - App.architecture - .replacingOccurrences(of: "x86_64", with: "Intel") - .replacingOccurrences(of: "arm64", with: "Apple Silicon"), - Paths.brew + public var groups: [EnvironmentCheckGroup] = [ + EnvironmentCheckGroup(name: "core", condition: { return true }, checks: [ + // ================================================================================= + // The Homebrew binary must exist. + // ================================================================================= + EnvironmentCheck( + command: { return !FileSystem.fileExists(Paths.brew) }, + name: "`\(Paths.brew)` exists", + titleText: "alert.homebrew_missing.title".localized, + subtitleText: "alert.homebrew_missing.subtitle".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 ), - buttonText: "alert.homebrew_missing.quit".localized, - requiresAppRestart: true - ), - /* - // ================================================================================= - // The PHP binary must exist. - // ================================================================================= - EnvironmentCheck( - command: { return !FileSystem.fileExists(Paths.php) }, - name: "`\(Paths.php)` exists", - titleText: "startup.errors.php_binary.title".localized, - subtitleText: "startup.errors.php_binary.subtitle".localized, - descriptionText: "startup.errors.php_binary.desc".localized(Paths.php) - ), - */ - // ================================================================================= - // Make sure we can detect one or more PHP installations. - // ================================================================================= - EnvironmentCheck( - command: { - return await !Shell.pipe("ls \(Paths.optPath) | grep php").out.contains("php") - }, - name: "`ls \(Paths.optPath) | grep php` returned php result", - titleText: "startup.errors.php_opt.title".localized, - subtitleText: "startup.errors.php_opt.subtitle".localized( - Paths.optPath + // ================================================================================= + // The PHP binary must exist. + // ================================================================================= + EnvironmentCheck( + command: { return !FileSystem.fileExists(Paths.php) }, + name: "`\(Paths.php)` exists", + titleText: "startup.errors.php_binary.title".localized, + subtitleText: "startup.errors.php_binary.subtitle".localized, + descriptionText: "startup.errors.php_binary.desc".localized(Paths.php) ), - descriptionText: "startup.errors.php_opt.desc".localized - ), - // ================================================================================= - // The Valet binary must exist. - // ================================================================================= - EnvironmentCheck( - command: { - return !(FileSystem.fileExists(Paths.valet) || FileSystem.fileExists("~/.composer/vendor/bin/valet")) - }, - name: "`valet` binary exists", - titleText: "startup.errors.valet_executable.title".localized, - subtitleText: "startup.errors.valet_executable.subtitle".localized, - descriptionText: "startup.errors.valet_executable.desc".localized( - Paths.valet - ) - ), - // ================================================================================= - // Check if Valet and Homebrew need manual password intervention. If they do, then - // PHP Monitor will be unable to run these commands, which prevents PHP Monitor from - // functioning correctly. Let the user know that they need to run `valet trust`. - // ================================================================================= - EnvironmentCheck( - command: { return await !Shell.pipe("cat /private/etc/sudoers.d/brew").out.contains(Paths.brew) }, - name: "`/private/etc/sudoers.d/brew` contains brew", - titleText: "startup.errors.sudoers_brew.title".localized, - subtitleText: "startup.errors.sudoers_brew.subtitle".localized, - descriptionText: "startup.errors.sudoers_brew.desc".localized - ), - EnvironmentCheck( - command: { return await !Shell.pipe("cat /private/etc/sudoers.d/valet").out.contains(Paths.valet) }, - name: "`/private/etc/sudoers.d/valet` contains valet", - titleText: "startup.errors.sudoers_valet.title".localized, - subtitleText: "startup.errors.sudoers_valet.subtitle".localized, - descriptionText: "startup.errors.sudoers_valet.desc".localized - ), - // ================================================================================= - // Verify if the Homebrew services are running (as root). - // ================================================================================= - EnvironmentCheck( - command: { - await HomebrewDiagnostics.loadInstalledTaps() - return await HomebrewDiagnostics.cannotLoadService("dnsmasq") - }, - name: "`sudo \(Paths.brew) services info` JSON loaded", - titleText: "startup.errors.services_json_error.title".localized, - subtitleText: "startup.errors.services_json_error.subtitle".localized, - descriptionText: "startup.errors.services_json_error.desc".localized - ), - // ================================================================================= - // Determine that Valet is installed - // ================================================================================= - EnvironmentCheck( - command: { - return !FileSystem.directoryExists("~/.config/valet") - }, - name: "`.config/valet` not empty (Valet installed)", - titleText: "startup.errors.valet_not_installed.title".localized, - subtitleText: "startup.errors.valet_not_installed.subtitle".localized, - descriptionText: "startup.errors.valet_not_installed.desc".localized - ), - // ================================================================================= - // Determine that the Valet configuration JSON file is valid. - // ================================================================================= - EnvironmentCheck( - command: { - // Detect additional binaries (e.g. Composer) - Paths.shared.detectBinaryPaths() - // Load the configuration file (config.json) - Valet.shared.loadConfiguration() - // This check fails when the config is nil - return Valet.shared.config == nil - }, - name: "`config.json` was valid", - titleText: "startup.errors.valet_json_invalid.title".localized, - subtitleText: "startup.errors.valet_json_invalid.subtitle".localized, - descriptionText: "startup.errors.valet_json_invalid.desc".localized - ), - // ================================================================================= - // Check for `which` alias issue - // ================================================================================= - EnvironmentCheck( - command: { - let nodePath = await Shell.pipe("which node").out - return App.architecture == "x86_64" + // ================================================================================= + // Make sure we can detect one or more PHP installations. + // ================================================================================= + EnvironmentCheck( + command: { + return await !Shell.pipe("ls \(Paths.optPath) | grep php").out.contains("php") + }, + name: "`ls \(Paths.optPath) | grep php` returned php result", + titleText: "startup.errors.php_opt.title".localized, + subtitleText: "startup.errors.php_opt.subtitle".localized( + Paths.optPath + ), + descriptionText: "startup.errors.php_opt.desc".localized + ), + ]), + EnvironmentCheckGroup(name: "valet", condition: { return Valet.installed() }, checks: [ + // ================================================================================= + // The Valet binary must exist. + // ================================================================================= + EnvironmentCheck( + command: { + return !(FileSystem.fileExists(Paths.valet) || FileSystem.fileExists("~/.composer/vendor/bin/valet")) + }, + name: "`valet` binary exists", + titleText: "startup.errors.valet_executable.title".localized, + subtitleText: "startup.errors.valet_executable.subtitle".localized, + descriptionText: "startup.errors.valet_executable.desc".localized( + Paths.valet + ) + ), + // ================================================================================= + // Check if Valet and Homebrew need manual password intervention. If they do, then + // PHP Monitor will be unable to run these commands, which prevents PHP Monitor from + // functioning correctly. Let the user know that they need to run `valet trust`. + // ================================================================================= + EnvironmentCheck( + command: { return await !Shell.pipe("cat /private/etc/sudoers.d/brew").out.contains(Paths.brew) }, + name: "`/private/etc/sudoers.d/brew` contains brew", + titleText: "startup.errors.sudoers_brew.title".localized, + subtitleText: "startup.errors.sudoers_brew.subtitle".localized, + descriptionText: "startup.errors.sudoers_brew.desc".localized + ), + EnvironmentCheck( + command: { return await !Shell.pipe("cat /private/etc/sudoers.d/valet").out.contains(Paths.valet) }, + name: "`/private/etc/sudoers.d/valet` contains valet", + titleText: "startup.errors.sudoers_valet.title".localized, + subtitleText: "startup.errors.sudoers_valet.subtitle".localized, + descriptionText: "startup.errors.sudoers_valet.desc".localized + ), + // ================================================================================= + // Determine that Valet is installed + // ================================================================================= + EnvironmentCheck( + command: { + return !FileSystem.directoryExists("~/.config/valet") + }, + name: "`.config/valet` not empty (Valet installed)", + titleText: "startup.errors.valet_not_installed.title".localized, + subtitleText: "startup.errors.valet_not_installed.subtitle".localized, + descriptionText: "startup.errors.valet_not_installed.desc".localized + ), + // ================================================================================= + // Determine that the Valet configuration JSON file is valid. + // ================================================================================= + EnvironmentCheck( + command: { + // Detect additional binaries (e.g. Composer) + Paths.shared.detectBinaryPaths() + // Load the configuration file (config.json) + Valet.shared.loadConfiguration() + // This check fails when the config is nil + return Valet.shared.config == nil + }, + name: "`config.json` was valid", + titleText: "startup.errors.valet_json_invalid.title".localized, + subtitleText: "startup.errors.valet_json_invalid.subtitle".localized, + descriptionText: "startup.errors.valet_json_invalid.desc".localized + ), + // ================================================================================= + // Verify if the Homebrew services are running (as root). + // ================================================================================= + EnvironmentCheck( + command: { + await HomebrewDiagnostics.loadInstalledTaps() + return await HomebrewDiagnostics.cannotLoadService("dnsmasq") + }, + name: "`sudo \(Paths.brew) services info` JSON loaded", + titleText: "startup.errors.services_json_error.title".localized, + subtitleText: "startup.errors.services_json_error.subtitle".localized, + descriptionText: "startup.errors.services_json_error.desc".localized + ), + // ================================================================================= + // Check for `which` alias issue + // ================================================================================= + EnvironmentCheck( + command: { + let nodePath = await Shell.pipe("which node").out + return App.architecture == "x86_64" && FileSystem.fileExists("/usr/local/bin/which") && nodePath.contains("env: node: No such file or directory") - }, - name: "`env: node` issue does not apply", - titleText: "startup.errors.which_alias_issue.title".localized, - subtitleText: "startup.errors.which_alias_issue.subtitle".localized, - descriptionText: "startup.errors.which_alias_issue.desc".localized - ), - // ================================================================================= - // Determine that Valet works correctly (no issues in platform detected) - // ================================================================================= - /* - EnvironmentCheck( - command: { - return await Shell.pipe("valet --version").out - .contains("Composer detected issues in your platform") - }, - name: "`no global composer issues", - titleText: "startup.errors.global_composer_platform_issues.title".localized, - subtitleText: "startup.errors.global_composer_platform_issues.subtitle".localized, - descriptionText: "startup.errors.global_composer_platform_issues.desc".localized - ), - // ================================================================================= - // Determine the Valet version and ensure it isn't unknown. - // ================================================================================= - EnvironmentCheck( - command: { - let output = await Shell.pipe("valet --version").out - // Failure condition #1: does not contain Laravel Valet - if !output.contains("Laravel Valet") { - return true - } - // Failure condition #2: version cannot be parsed - let versionString = output - .trimmingCharacters(in: .whitespacesAndNewlines) - .components(separatedBy: "Laravel Valet")[1] - .trimmingCharacters(in: .whitespaces) - // Extract the version number - Valet.shared.version = try! VersionNumber.parse(VersionExtractor.from(output)!) - // Get the actual version - return Valet.shared.version == nil - }, - name: "`valet --version` was loaded", - titleText: "startup.errors.valet_version_unknown.title".localized, - subtitleText: "startup.errors.valet_version_unknown.subtitle".localized, - descriptionText: "startup.errors.valet_version_unknown.desc".localized - ), - // ================================================================================= - // Ensure the Valet version is supported. - // ================================================================================= - EnvironmentCheck( - command: { - // We currently support Valet 2, 3 or 4. Any other version should get an alert. - return ![2, 3, 4].contains(Valet.shared.version.major) - }, - name: "valet version is supported", - titleText: "startup.errors.valet_version_not_supported.title".localized, - subtitleText: "startup.errors.valet_version_not_supported.subtitle".localized, - descriptionText: "startup.errors.valet_version_not_supported.desc".localized - ) - */ + }, + name: "`env: node` issue does not apply", + titleText: "startup.errors.which_alias_issue.title".localized, + subtitleText: "startup.errors.which_alias_issue.subtitle".localized, + descriptionText: "startup.errors.which_alias_issue.desc".localized + ), + // ================================================================================= + // Determine that Valet works correctly (no issues in platform detected) + // ================================================================================= + EnvironmentCheck( + command: { + return await Shell.pipe("valet --version").out + .contains("Composer detected issues in your platform") + }, + name: "`no global composer issues", + titleText: "startup.errors.global_composer_platform_issues.title".localized, + subtitleText: "startup.errors.global_composer_platform_issues.subtitle".localized, + descriptionText: "startup.errors.global_composer_platform_issues.desc".localized + ), + // ================================================================================= + // Determine the Valet version and ensure it isn't unknown. + // ================================================================================= + EnvironmentCheck( + command: { + let output = await Shell.pipe("valet --version").out + // Failure condition #1: does not contain Laravel Valet + if !output.contains("Laravel Valet") { + return true + } + // Failure condition #2: version cannot be parsed + let versionString = output + .trimmingCharacters(in: .whitespacesAndNewlines) + .components(separatedBy: "Laravel Valet")[1] + .trimmingCharacters(in: .whitespaces) + // Extract the version number + Valet.shared.version = try! VersionNumber.parse(VersionExtractor.from(output)!) + // Get the actual version + return Valet.shared.version == nil + }, + name: "`valet --version` was loaded", + titleText: "startup.errors.valet_version_unknown.title".localized, + subtitleText: "startup.errors.valet_version_unknown.subtitle".localized, + descriptionText: "startup.errors.valet_version_unknown.desc".localized + ), + // ================================================================================= + // Ensure the Valet version is supported. + // ================================================================================= + EnvironmentCheck( + command: { + // We currently support Valet 2, 3 or 4. Any other version should get an alert. + return ![2, 3, 4].contains(Valet.shared.version?.major) + }, + name: "valet version is supported", + titleText: "startup.errors.valet_version_not_supported.title".localized, + subtitleText: "startup.errors.valet_version_not_supported.subtitle".localized, + descriptionText: "startup.errors.valet_version_not_supported.desc".localized + ) + ]) ] } diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 0e9e6e9..788d088 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -22,7 +22,7 @@ class Valet { static let shared = Valet() /// The version of Valet that was detected. - var version: VersionNumber? = nil + var version: VersionNumber? /// The Valet configuration file. var config: Valet.Configuration! @@ -57,6 +57,10 @@ class Valet { } } + public static func installed() -> Bool { + return FileSystem.fileExists(Paths.binPath.appending("/valet")) + } + /** Check if a particular feature is enabled. */