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

♻️ Reworked helper scripts

- Add 'Welcome Tour' to First Aid menu
- Updated 'Welcome Tour'
- Helpers are now always written to ~/.config/phpmon/bin
- Updated helpers (now symlinked)
- Updated checks for when to symlink helpers
This commit is contained in:
2022-08-14 23:57:46 +02:00
parent 237185995d
commit bbbdce6b44
10 changed files with 124 additions and 43 deletions

View File

@ -1677,7 +1677,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 = 920; CURRENT_PROJECT_VERSION = 950;
DEBUG = YES; DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787; DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@ -1704,7 +1704,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 = 920; CURRENT_PROJECT_VERSION = 950;
DEBUG = NO; DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787; DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;

View File

@ -19,9 +19,12 @@ public class Paths {
private var userName: String private var userName: String
private var PATH: String
init() { init() {
baseDir = App.architecture != "x86_64" ? .opt : .usr baseDir = App.architecture != "x86_64" ? .opt : .usr
userName = String(Shell.pipe("whoami").split(separator: "\n")[0]) userName = String(Shell.pipe("whoami").split(separator: "\n")[0])
PATH = String(Shell.pipe("echo $PATH")).trimmingCharacters(in: .whitespacesAndNewlines)
} }
public func detectBinaryPaths() { public func detectBinaryPaths() {
@ -57,6 +60,10 @@ public class Paths {
return shared.userName return shared.userName
} }
public static var PATH: String {
return shared.PATH
}
public static var cellarPath: String { public static var cellarPath: String {
return "\(shared.baseDir.rawValue)/Cellar" return "\(shared.baseDir.rawValue)/Cellar"
} }

View File

@ -33,6 +33,15 @@ class Filesystem {
return exists && !isDirectory.boolValue return exists && !isDirectory.boolValue
} }
public static func fileIsSymlink(_ path: String) -> Bool {
do {
let attribs = try FileManager.default.attributesOfItem(atPath: path)
return attribs[.type] as! FileAttributeType == FileAttributeType.typeSymbolicLink
} catch {
return false
}
}
/** /**
Checks if a directory exists at the provided path. Checks if a directory exists at the provided path.
*/ */

View File

@ -16,8 +16,18 @@ class PhpHelper {
// Take the PHP version (e.g. "7.2") and generate a dotless version // Take the PHP version (e.g. "7.2") and generate a dotless version
let dotless = version.replacingOccurrences(of: ".", with: "") let dotless = version.replacingOccurrences(of: ".", with: "")
// Determine the dotless name for this PHP version
let destination = "/Users/\(Paths.whoami)/.config/phpmon/bin/pm\(dotless)"
// Check if the ~/.config/phpmon/bin directory is in the PATH
let inPath = Paths.PATH.contains("/Users/\(Paths.whoami)/.config/phpmon/bin")
// Check if we can create symlinks (`/usr/local/bin` must be writable)
let canWriteSymlinks = FileManager.default.isWritableFile(atPath: "/usr/local/bin/")
do { do {
let destination = "/usr/local/bin/pm\(dotless)" Shell.run("mkdir -p ~/.config/phpmon/bin")
if FileManager.default.fileExists(atPath: destination) { if FileManager.default.fileExists(atPath: destination) {
let contents = try String(contentsOfFile: destination) let contents = try String(contentsOfFile: destination)
if !contents.contains(keyPhrase) { if !contents.contains(keyPhrase) {
@ -52,10 +62,40 @@ class PhpHelper {
// Make sure the file is executable // Make sure the file is executable
Shell.run("chmod +x \(destination)") Shell.run("chmod +x \(destination)")
// Create a symlink if the folder is not in the PATH
if !inPath {
// First, check if we can create symlinks at all
if !canWriteSymlinks {
Log.err("PHP Monitor does not have permission to symlink `/usr/local/bin/\(dotless)`.")
return
}
// Write the symlink
self.createSymlink(dotless)
}
} catch { } catch {
print(error) print(error)
Log.err("Could not write PHP Monitor helper for PHP \(version) to /usr/local/bin/pm\(dotless)") Log.err("Could not write PHP Monitor helper for PHP \(version) to \(destination))")
} }
} }
private static func createSymlink(_ dotless: String) {
let source = "/Users/\(Paths.whoami)/.config/phpmon/bin/pm\(dotless)"
let destination = "/usr/local/bin/pm\(dotless)"
if !Filesystem.fileExists(destination) {
Log.info("Creating new symlink: \(destination)")
Shell.run("ln -s \(source) \(destination)")
return
}
if !Filesystem.fileIsSymlink(destination) {
Log.info("Overwriting existing file with new symlink: \(destination)")
Shell.run("ln -fs \(source) \(destination)")
return
}
Log.info("Symlink in \(destination) already exists, OK.")
}
} }

View File

@ -57,6 +57,9 @@ extension MainMenu {
let installation = PhpEnv.phpInstall let installation = PhpEnv.phpInstall
installation.notifyAboutBrokenPhpFpm() installation.notifyAboutBrokenPhpFpm()
// Check for other problems
WarningManager.shared.evaluateWarnings()
// Set up the config watchers on launch (updated automatically when switching) // Set up the config watchers on launch (updated automatically when switching)
Log.info("Setting up watchers...") Log.info("Setting up watchers...")
App.shared.handlePhpConfigWatcher() App.shared.handlePhpConfigWatcher()
@ -85,15 +88,11 @@ extension MainMenu {
// Start the background refresh timer // Start the background refresh timer
startSharedTimer() startSharedTimer()
// Check warnings
WarningManager.shared.evaluateWarnings()
// Update the stats // Update the stats
Stats.incrementSuccessfulLaunchCount() Stats.incrementSuccessfulLaunchCount()
Stats.evaluateSponsorMessageShouldBeDisplayed() Stats.evaluateSponsorMessageShouldBeDisplayed()
// Present first launch screen if needed // Present first launch screen if needed
#warning("You should definitely tweak this view again")
if Stats.successfulLaunchCount == 0 && !isRunningSwiftUIPreview { if Stats.successfulLaunchCount == 0 && !isRunningSwiftUIPreview {
Log.info("Should present the first launch screen!") Log.info("Should present the first launch screen!")
DispatchQueue.main.async { DispatchQueue.main.async {

View File

@ -126,6 +126,16 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
ServicesManager.shared.loadData() ServicesManager.shared.loadData()
} }
/**
Shows the Welcome Tour screen, again.
Did this need a comment? No, probably not.
*/
@objc func showWelcomeTour() {
DispatchQueue.main.async {
OnboardingWindowController.show()
}
}
/** Reloads the menu in the background, using `asyncExecution`. */ /** Reloads the menu in the background, using `asyncExecution`. */
@objc func reloadPhpMonitorMenuInBackground() { @objc func reloadPhpMonitorMenuInBackground() {
asyncExecution({ asyncExecution({

View File

@ -199,6 +199,10 @@ extension StatusMenu {
let servicesMenu = NSMenu() let servicesMenu = NSMenu()
servicesMenu.addItem(HeaderView.asMenuItem(text: "mi_first_aid".localized)) servicesMenu.addItem(HeaderView.asMenuItem(text: "mi_first_aid".localized))
servicesMenu.addItem(NSMenuItem(title: "mi_view_onboarding".localized,
action: #selector(MainMenu.showWelcomeTour), keyEquivalent: ""))
let fixMyValetMenuItem = NSMenuItem( let fixMyValetMenuItem = NSMenuItem(
title: "mi_fix_my_valet".localized(PhpEnv.brewPhpVersion), title: "mi_fix_my_valet".localized(PhpEnv.brewPhpVersion),
action: #selector(MainMenu.fixMyValet), keyEquivalent: "" action: #selector(MainMenu.fixMyValet), keyEquivalent: ""
@ -216,22 +220,15 @@ extension StatusMenu {
servicesMenu.addItem(NSMenuItem.separator()) servicesMenu.addItem(NSMenuItem.separator())
servicesMenu.addItem(HeaderView.asMenuItem(text: "mi_services".localized)) servicesMenu.addItem(HeaderView.asMenuItem(text: "mi_services".localized))
servicesMenu.addItem( servicesMenu.addItem(NSMenuItem(title: "mi_restart_dnsmasq".localized,
NSMenuItem(title: "mi_restart_dnsmasq".localized, action: #selector(MainMenu.restartDnsMasq), keyEquivalent: "d"))
action: #selector(MainMenu.restartDnsMasq), keyEquivalent: "d") servicesMenu.addItem(NSMenuItem(title: "mi_restart_php_fpm".localized,
) action: #selector(MainMenu.restartPhpFpm), keyEquivalent: "p"))
servicesMenu.addItem(
NSMenuItem(title: "mi_restart_php_fpm".localized, servicesMenu.addItem(NSMenuItem(title: "mi_restart_nginx".localized,
action: #selector(MainMenu.restartPhpFpm), keyEquivalent: "p") action: #selector(MainMenu.restartNginx), keyEquivalent: "n"))
) servicesMenu.addItem(NSMenuItem(title: "mi_restart_valet_services".localized,
servicesMenu.addItem( action: #selector(MainMenu.restartValetServices), keyEquivalent: "s"))
NSMenuItem(title: "mi_restart_nginx".localized,
action: #selector(MainMenu.restartNginx), keyEquivalent: "n")
)
servicesMenu.addItem(
NSMenuItem(title: "mi_restart_valet_services".localized,
action: #selector(MainMenu.restartValetServices), keyEquivalent: "s")
)
servicesMenu.addItem( servicesMenu.addItem(
NSMenuItem(title: "mi_stop_valet_services".localized, NSMenuItem(title: "mi_stop_valet_services".localized,
action: #selector(MainMenu.stopValetServices), keyEquivalent: "s"), action: #selector(MainMenu.stopValetServices), keyEquivalent: "s"),

View File

@ -25,18 +25,17 @@ struct OnboardingTextItem: View {
Text(title.localizedForSwiftUI) Text(title.localizedForSwiftUI)
.font(.system(size: 14)) .font(.system(size: 14))
.lineLimit(3) .lineLimit(3)
HStack { Text(description.localizedForSwiftUI)
Text(description.localizedForSwiftUI) .foregroundColor(Color.secondary)
.foregroundColor(Color.secondary) .font(.system(size: 13))
.font(.system(size: 13)) .lineLimit(6)
.lineLimit(3) .fixedSize(horizontal: false, vertical: true)
.fixedSize(horizontal: false, vertical: true) .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
}
} }
} }
.padding() .padding()
.overlay(RoundedRectangle(cornerRadius: 5).stroke(Color.gray.opacity(0.3), lineWidth: 1)) .overlay(RoundedRectangle(cornerRadius: 5)
.stroke(Color.gray.opacity(0.3), lineWidth: 1))
} }
} }
@ -57,9 +56,13 @@ struct OnboardingView: View {
.padding(.bottom, 5) .padding(.bottom, 5)
Text("onboarding.explore".localized) Text("onboarding.explore".localized)
.padding(.bottom) .padding(.bottom)
.padding(.trailing)
} }
.padding(.top, 10) .padding(.top, 10)
} }
.padding(.leading)
.padding(.trailing)
VStack { VStack {
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 10) {
OnboardingTextItem( OnboardingTextItem(
@ -67,6 +70,11 @@ struct OnboardingView: View {
title: "onboarding.tour.menu_bar.title", title: "onboarding.tour.menu_bar.title",
description: "onboarding.tour.menu_bar" description: "onboarding.tour.menu_bar"
) )
OnboardingTextItem(
icon: "checkmark.circle.fill",
title: "onboarding.tour.services.title",
description: "onboarding.tour.services"
)
OnboardingTextItem( OnboardingTextItem(
icon: "list.bullet.circle.fill", icon: "list.bullet.circle.fill",
title: "onboarding.tour.domains.title", title: "onboarding.tour.domains.title",
@ -79,6 +87,7 @@ struct OnboardingView: View {
) )
} }
}.padding() }.padding()
VStack(spacing: 20) { VStack(spacing: 20) {
HStack { HStack {
Image(systemName: "questionmark.circle.fill") Image(systemName: "questionmark.circle.fill")

View File

@ -32,11 +32,16 @@ class WarningManager {
), ),
Warning( Warning(
command: { command: {
!Paths.PATH.contains("/Users/\(Paths.whoami)/.config/phpmon/bin") &&
!FileManager.default.isWritableFile(atPath: "/usr/local/bin/") !FileManager.default.isWritableFile(atPath: "/usr/local/bin/")
}, },
name: "`/usr/local/bin` not writable", name: "Helpers cannot be symlinked and not in PATH",
title: "warnings.helper_permissions.title", title: "warnings.helper_permissions.title",
paragraphs: ["warnings.helper_permissions.description", "warnings.helper_permissions.unavailable"], paragraphs: [
"warnings.helper_permissions.description",
"warnings.helper_permissions.unavailable",
"warnings.helper_permissions.symlink"
],
url: "https://github.com/nicoverbruggen/phpmon/wiki/PHP-Monitor-helper-binaries" url: "https://github.com/nicoverbruggen/phpmon/wiki/PHP-Monitor-helper-binaries"
) )
] ]
@ -59,7 +64,7 @@ class WarningManager {
for check in self.evaluations { for check in self.evaluations {
if await check.applies() { if await check.applies() {
Log.info("[WARNING] \(check.name)") Log.info("[DOCTOR] \(check.name) (!)")
self.warnings.append(check) self.warnings.append(check)
continue continue
} }

View File

@ -74,6 +74,8 @@
"mi_no_presets" = "No presets available."; "mi_no_presets" = "No presets available.";
"mi_set_up_presets" = "Learn more about presets..."; "mi_set_up_presets" = "Learn more about presets...";
"mi_view_onboarding" = "Show Welcome Tour...";
"mi_xdebug_available_modes" = "Available Modes"; "mi_xdebug_available_modes" = "Available Modes";
"mi_xdebug_actions" = "Actions"; "mi_xdebug_actions" = "Actions";
"mi_xdebug_disable_all" = "Disable All Modes"; "mi_xdebug_disable_all" = "Disable All Modes";
@ -518,7 +520,8 @@ If you are seeing this message but are confused why this folder has gone missing
"warnings.helper_permissions.title" = "PHP Monitors helpers are currently unavailable."; "warnings.helper_permissions.title" = "PHP Monitors helpers are currently unavailable.";
"warnings.helper_permissions.description" = "PHP Monitor comes with various helper binaries. Using these binaries allows you to easily invoke a specific version of PHP without switching the linked PHP version."; "warnings.helper_permissions.description" = "PHP Monitor comes with various helper binaries. Using these binaries allows you to easily invoke a specific version of PHP without switching the linked PHP version.";
"warnings.helper_permissions.unavailable" = "However, these helpers are currently *unavailable* because PHP Monitor could not create the required symlinks (alternatively, you could add PHP Monitor's helper directory to your `PATH` variable to make this warning go away as well)."; "warnings.helper_permissions.unavailable" = "However, these helpers are potentially *unavailable* because PHP Monitor cannot currently create or update the required symlinks.";
"warnings.helper_permissions.symlink" = "If you do not wish to make `/usr/local/bin` writable, you can add PHP Monitor's helper directory to your `PATH` variable to make this warning go away. (Click on ”Learn More” to find out how to fix this issue.)";
"warnings.arm_compatibility.title" = "You are running PHP Monitor using Rosetta on Apple Silicon, which means your PHP environment is also running via Rosetta."; "warnings.arm_compatibility.title" = "You are running PHP Monitor using Rosetta on Apple Silicon, which means your PHP environment is also running via Rosetta.";
"warnings.arm_compatibility.description" = "You appear to be running an ARM-compatible version of macOS, but you are currently running PHP Monitor using Rosetta. While this will work correctly, it is recommended that you use the native version of Homebrew."; "warnings.arm_compatibility.description" = "You appear to be running an ARM-compatible version of macOS, but you are currently running PHP Monitor using Rosetta. While this will work correctly, it is recommended that you use the native version of Homebrew.";
@ -527,13 +530,15 @@ If you are seeing this message but are confused why this folder has gone missing
"onboarding.title" = "Welcome Tour"; "onboarding.title" = "Welcome Tour";
"onboarding.welcome" = "Welcome to PHP Monitor!"; "onboarding.welcome" = "Welcome to PHP Monitor!";
"onboarding.explore" = "Learn more about some of the features that PHP Monitor has to offer."; "onboarding.explore" = "Learn more about some of the features that PHP Monitor has to offer. You can find a more comprehensive list of features on GitHub.";
"onboarding.tour.menu_bar.title" = "Get Started"; "onboarding.tour.menu_bar.title" = "Get Started";
"onboarding.tour.menu_bar" = "PHP Monitor lives in your menu bar. From here, you can switch the globally linked PHP version, start or stop services, locate config files, and more."; "onboarding.tour.menu_bar" = "PHP Monitor lives in your menu bar. From here, you can switch the globally linked PHP version, start or stop services, locate config files, and more.";
"onboarding.tour.faq_hint" = "I recommend that you check out the [README](https://github.com/nicoverbruggen/phpmon/blob/main/README.md) on GitHub: it contains a comprehensive FAQ with various tips and common questions and answers."; "onboarding.tour.faq_hint" = "I recommend that you check out the [README](https://github.com/nicoverbruggen/phpmon/blob/main/README.md) on GitHub: it contains a comprehensive FAQ with various tips and common questions and answers.";
"onboarding.tour.domains.title" = "Domains"; "onboarding.tour.services.title" = "Manage Services";
"onboarding.tour.domains" = "By opening the Domains window via the Menu Bar item, you can view which domains are linked and parked."; "onboarding.tour.services" = "Once you click on the menu bar item, you can see at a glance based on the checkmarks or crosses if all of the Homebrew services are up and running. You can also click on a service to quickly toggle it. You can also add your own!";
"onboarding.tour.isolation.title" = "Isolation"; "onboarding.tour.domains.title" = "Manage Domains";
"onboarding.tour.isolation" = "If you have Valet 3 installed, you can even use domain isolation by right-clicking on a given domain in the Domains window. This allows you to pick a specific version of PHP to use for that domain!"; "onboarding.tour.domains" = "By opening the Domains window via the menu bar item, you can view which domains are linked and parked, as well as active nginx proxies.";
"onboarding.tour.once" = "You will only see the Welcome Tour once. You can re-open the Welcome Tour later via the menu bar icon."; "onboarding.tour.isolation.title" = "Isolate Domains";
"onboarding.tour.isolation" = "If you have Valet 3 installed, you can even use domain isolation by right-clicking on a given domain in the Domains window. This allows you to pick a specific version of PHP to use for that domain, and that domain only!";
"onboarding.tour.once" = "You will only see the Welcome Tour once. You can re-open the Welcome Tour later via the menu bar icon (under First Aid & Services).";
"onboarding.tour.close" = "Close Tour"; "onboarding.tour.close" = "Close Tour";