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

Added environment check groups

This commit is contained in:
2023-01-17 18:57:21 +01:00
parent e8d705e228
commit c6f2167c92
6 changed files with 229 additions and 202 deletions

View File

@ -2809,7 +2809,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1020; CURRENT_PROJECT_VERSION = 1100;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEBUG = YES; DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787; DEVELOPMENT_TEAM = 8M54J5J787;
@ -2821,7 +2821,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 11.0; MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 5.7; MARKETING_VERSION = 6.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -2838,7 +2838,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1020; CURRENT_PROJECT_VERSION = 1100;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEBUG = NO; DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787; DEVELOPMENT_TEAM = 8M54J5J787;
@ -2850,7 +2850,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 11.0; MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 5.7; MARKETING_VERSION = 6.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -3066,7 +3066,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1020; CURRENT_PROJECT_VERSION = 1100;
DEBUG = NO; DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787; DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@ -3077,7 +3077,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 11.0; MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 5.7; MARKETING_VERSION = 6.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev;
PRODUCT_NAME = "$(TARGET_NAME) DEV"; PRODUCT_NAME = "$(TARGET_NAME) DEV";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -3176,7 +3176,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1020; CURRENT_PROJECT_VERSION = 1100;
DEBUG = YES; DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787; DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@ -3187,7 +3187,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 11.0; MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 5.7; MARKETING_VERSION = 6.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -55,4 +55,10 @@ class Log {
} }
} }
static func line(as verbosity: Verbosity = .info) {
if verbosity.isApplicable() {
print("----------------------------------")
}
}
} }

View File

@ -29,7 +29,7 @@ class FSNotifier {
self.url = url self.url = url
self.linked = FileSystem.fileExists(Paths.php) 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) fileDescriptor = open(url.path, O_EVTONLY)

View File

@ -43,3 +43,9 @@ struct EnvironmentCheck {
return await !self.command() return await !self.command()
} }
} }
struct EnvironmentCheckGroup {
let name: String
let condition: () -> Bool
let checks: [EnvironmentCheck]
}

View File

@ -21,16 +21,27 @@ class Startup {
// Do the important system setup checks // Do the important system setup checks
Log.info("[ARCH] The user is running PHP Monitor with the architecture: \(App.architecture)") Log.info("[ARCH] The user is running PHP Monitor with the architecture: \(App.architecture)")
for check in self.checks { for group in self.groups {
if await check.succeeds() { if group.condition() {
Log.info("[OK] \(check.name)") Log.line()
continue 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... // If we get here, something's gone wrong and the check has failed...
Log.info("[FAIL] \(check.name)") Log.info("[FAIL] \(check.name)")
await showAlert(for: check) await showAlert(for: check)
return false 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! // If we get here, nothing has gone wrong. That's what we want!
@ -81,192 +92,192 @@ class Startup {
// MARK: - Check (List) // MARK: - Check (List)
public var checks: [EnvironmentCheck] = [ public var groups: [EnvironmentCheckGroup] = [
// ================================================================================= EnvironmentCheckGroup(name: "core", condition: { return true }, checks: [
// The Homebrew binary must exist. // =================================================================================
// ================================================================================= // The Homebrew binary must exist.
EnvironmentCheck( // =================================================================================
command: { return !FileSystem.fileExists(Paths.brew) }, EnvironmentCheck(
name: "`\(Paths.brew)` exists", command: { return !FileSystem.fileExists(Paths.brew) },
titleText: "alert.homebrew_missing.title".localized, name: "`\(Paths.brew)` exists",
subtitleText: "alert.homebrew_missing.subtitle".localized, titleText: "alert.homebrew_missing.title".localized,
descriptionText: "alert.homebrew_missing.info".localized( subtitleText: "alert.homebrew_missing.subtitle".localized,
App.architecture descriptionText: "alert.homebrew_missing.info".localized(
.replacingOccurrences(of: "x86_64", with: "Intel") App.architecture
.replacingOccurrences(of: "arm64", with: "Apple Silicon"), .replacingOccurrences(of: "x86_64", with: "Intel")
Paths.brew .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) },
// The PHP binary must exist. name: "`\(Paths.php)` exists",
// ================================================================================= titleText: "startup.errors.php_binary.title".localized,
EnvironmentCheck( subtitleText: "startup.errors.php_binary.subtitle".localized,
command: { return !FileSystem.fileExists(Paths.php) }, descriptionText: "startup.errors.php_binary.desc".localized(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
), ),
descriptionText: "startup.errors.php_opt.desc".localized // =================================================================================
), // Make sure we can detect one or more PHP installations.
// ================================================================================= // =================================================================================
// The Valet binary must exist. EnvironmentCheck(
// ================================================================================= command: {
EnvironmentCheck( return await !Shell.pipe("ls \(Paths.optPath) | grep php").out.contains("php")
command: { },
return !(FileSystem.fileExists(Paths.valet) || FileSystem.fileExists("~/.composer/vendor/bin/valet")) name: "`ls \(Paths.optPath) | grep php` returned php result",
}, titleText: "startup.errors.php_opt.title".localized,
name: "`valet` binary exists", subtitleText: "startup.errors.php_opt.subtitle".localized(
titleText: "startup.errors.valet_executable.title".localized, Paths.optPath
subtitleText: "startup.errors.valet_executable.subtitle".localized, ),
descriptionText: "startup.errors.valet_executable.desc".localized( descriptionText: "startup.errors.php_opt.desc".localized
Paths.valet ),
) ]),
), EnvironmentCheckGroup(name: "valet", condition: { return Valet.installed() }, checks: [
// ================================================================================= // =================================================================================
// Check if Valet and Homebrew need manual password intervention. If they do, then // The Valet binary must exist.
// 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: {
EnvironmentCheck( return !(FileSystem.fileExists(Paths.valet) || FileSystem.fileExists("~/.composer/vendor/bin/valet"))
command: { return await !Shell.pipe("cat /private/etc/sudoers.d/brew").out.contains(Paths.brew) }, },
name: "`/private/etc/sudoers.d/brew` contains brew", name: "`valet` binary exists",
titleText: "startup.errors.sudoers_brew.title".localized, titleText: "startup.errors.valet_executable.title".localized,
subtitleText: "startup.errors.sudoers_brew.subtitle".localized, subtitleText: "startup.errors.valet_executable.subtitle".localized,
descriptionText: "startup.errors.sudoers_brew.desc".localized descriptionText: "startup.errors.valet_executable.desc".localized(
), Paths.valet
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, // Check if Valet and Homebrew need manual password intervention. If they do, then
subtitleText: "startup.errors.sudoers_valet.subtitle".localized, // PHP Monitor will be unable to run these commands, which prevents PHP Monitor from
descriptionText: "startup.errors.sudoers_valet.desc".localized // functioning correctly. Let the user know that they need to run `valet trust`.
), // =================================================================================
// ================================================================================= EnvironmentCheck(
// Verify if the Homebrew services are running (as root). command: { return await !Shell.pipe("cat /private/etc/sudoers.d/brew").out.contains(Paths.brew) },
// ================================================================================= name: "`/private/etc/sudoers.d/brew` contains brew",
EnvironmentCheck( titleText: "startup.errors.sudoers_brew.title".localized,
command: { subtitleText: "startup.errors.sudoers_brew.subtitle".localized,
await HomebrewDiagnostics.loadInstalledTaps() descriptionText: "startup.errors.sudoers_brew.desc".localized
return await HomebrewDiagnostics.cannotLoadService("dnsmasq") ),
}, EnvironmentCheck(
name: "`sudo \(Paths.brew) services info` JSON loaded", command: { return await !Shell.pipe("cat /private/etc/sudoers.d/valet").out.contains(Paths.valet) },
titleText: "startup.errors.services_json_error.title".localized, name: "`/private/etc/sudoers.d/valet` contains valet",
subtitleText: "startup.errors.services_json_error.subtitle".localized, titleText: "startup.errors.sudoers_valet.title".localized,
descriptionText: "startup.errors.services_json_error.desc".localized subtitleText: "startup.errors.sudoers_valet.subtitle".localized,
), descriptionText: "startup.errors.sudoers_valet.desc".localized
// ================================================================================= ),
// Determine that Valet is installed // =================================================================================
// ================================================================================= // Determine that Valet is installed
EnvironmentCheck( // =================================================================================
command: { EnvironmentCheck(
return !FileSystem.directoryExists("~/.config/valet") command: {
}, return !FileSystem.directoryExists("~/.config/valet")
name: "`.config/valet` not empty (Valet installed)", },
titleText: "startup.errors.valet_not_installed.title".localized, name: "`.config/valet` not empty (Valet installed)",
subtitleText: "startup.errors.valet_not_installed.subtitle".localized, titleText: "startup.errors.valet_not_installed.title".localized,
descriptionText: "startup.errors.valet_not_installed.desc".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. // =================================================================================
// ================================================================================= // Determine that the Valet configuration JSON file is valid.
EnvironmentCheck( // =================================================================================
command: { EnvironmentCheck(
// Detect additional binaries (e.g. Composer) command: {
Paths.shared.detectBinaryPaths() // Detect additional binaries (e.g. Composer)
// Load the configuration file (config.json) Paths.shared.detectBinaryPaths()
Valet.shared.loadConfiguration() // Load the configuration file (config.json)
// This check fails when the config is nil Valet.shared.loadConfiguration()
return Valet.shared.config == nil // 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, name: "`config.json` was valid",
subtitleText: "startup.errors.valet_json_invalid.subtitle".localized, titleText: "startup.errors.valet_json_invalid.title".localized,
descriptionText: "startup.errors.valet_json_invalid.desc".localized subtitleText: "startup.errors.valet_json_invalid.subtitle".localized,
), descriptionText: "startup.errors.valet_json_invalid.desc".localized
// ================================================================================= ),
// Check for `which` alias issue // =================================================================================
// ================================================================================= // Verify if the Homebrew services are running (as root).
EnvironmentCheck( // =================================================================================
command: { EnvironmentCheck(
let nodePath = await Shell.pipe("which node").out command: {
return App.architecture == "x86_64" 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") && FileSystem.fileExists("/usr/local/bin/which")
&& nodePath.contains("env: node: No such file or directory") && nodePath.contains("env: node: No such file or directory")
}, },
name: "`env: node` issue does not apply", name: "`env: node` issue does not apply",
titleText: "startup.errors.which_alias_issue.title".localized, titleText: "startup.errors.which_alias_issue.title".localized,
subtitleText: "startup.errors.which_alias_issue.subtitle".localized, subtitleText: "startup.errors.which_alias_issue.subtitle".localized,
descriptionText: "startup.errors.which_alias_issue.desc".localized descriptionText: "startup.errors.which_alias_issue.desc".localized
), ),
// ================================================================================= // =================================================================================
// Determine that Valet works correctly (no issues in platform detected) // Determine that Valet works correctly (no issues in platform detected)
// ================================================================================= // =================================================================================
/* EnvironmentCheck(
EnvironmentCheck( command: {
command: { return await Shell.pipe("valet --version").out
return await Shell.pipe("valet --version").out .contains("Composer detected issues in your platform")
.contains("Composer detected issues in your platform") },
}, name: "`no global composer issues",
name: "`no global composer issues", titleText: "startup.errors.global_composer_platform_issues.title".localized,
titleText: "startup.errors.global_composer_platform_issues.title".localized, subtitleText: "startup.errors.global_composer_platform_issues.subtitle".localized,
subtitleText: "startup.errors.global_composer_platform_issues.subtitle".localized, descriptionText: "startup.errors.global_composer_platform_issues.desc".localized
descriptionText: "startup.errors.global_composer_platform_issues.desc".localized ),
), // =================================================================================
// ================================================================================= // Determine the Valet version and ensure it isn't unknown.
// Determine the Valet version and ensure it isn't unknown. // =================================================================================
// ================================================================================= EnvironmentCheck(
EnvironmentCheck( command: {
command: { let output = await Shell.pipe("valet --version").out
let output = await Shell.pipe("valet --version").out // Failure condition #1: does not contain Laravel Valet
// Failure condition #1: does not contain Laravel Valet if !output.contains("Laravel Valet") {
if !output.contains("Laravel Valet") { return true
return true }
} // Failure condition #2: version cannot be parsed
// Failure condition #2: version cannot be parsed let versionString = output
let versionString = output .trimmingCharacters(in: .whitespacesAndNewlines)
.trimmingCharacters(in: .whitespacesAndNewlines) .components(separatedBy: "Laravel Valet")[1]
.components(separatedBy: "Laravel Valet")[1] .trimmingCharacters(in: .whitespaces)
.trimmingCharacters(in: .whitespaces) // Extract the version number
// Extract the version number Valet.shared.version = try! VersionNumber.parse(VersionExtractor.from(output)!)
Valet.shared.version = try! VersionNumber.parse(VersionExtractor.from(output)!) // Get the actual version
// Get the actual version return Valet.shared.version == nil
return Valet.shared.version == nil },
}, name: "`valet --version` was loaded",
name: "`valet --version` was loaded", titleText: "startup.errors.valet_version_unknown.title".localized,
titleText: "startup.errors.valet_version_unknown.title".localized, subtitleText: "startup.errors.valet_version_unknown.subtitle".localized,
subtitleText: "startup.errors.valet_version_unknown.subtitle".localized, descriptionText: "startup.errors.valet_version_unknown.desc".localized
descriptionText: "startup.errors.valet_version_unknown.desc".localized ),
), // =================================================================================
// ================================================================================= // Ensure the Valet version is supported.
// Ensure the Valet version is supported. // =================================================================================
// ================================================================================= EnvironmentCheck(
EnvironmentCheck( command: {
command: { // We currently support Valet 2, 3 or 4. Any other version should get an alert.
// We currently support Valet 2, 3 or 4. Any other version should get an alert. return ![2, 3, 4].contains(Valet.shared.version?.major)
return ![2, 3, 4].contains(Valet.shared.version.major) },
}, name: "valet version is supported",
name: "valet version is supported", titleText: "startup.errors.valet_version_not_supported.title".localized,
titleText: "startup.errors.valet_version_not_supported.title".localized, subtitleText: "startup.errors.valet_version_not_supported.subtitle".localized,
subtitleText: "startup.errors.valet_version_not_supported.subtitle".localized, descriptionText: "startup.errors.valet_version_not_supported.desc".localized
descriptionText: "startup.errors.valet_version_not_supported.desc".localized )
) ])
*/
] ]
} }

View File

@ -22,7 +22,7 @@ class Valet {
static let shared = Valet() static let shared = Valet()
/// The version of Valet that was detected. /// The version of Valet that was detected.
var version: VersionNumber? = nil var version: VersionNumber?
/// The Valet configuration file. /// The Valet configuration file.
var config: Valet.Configuration! 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. Check if a particular feature is enabled.
*/ */