From a9f9c38e0dc5c8155f3de1b0e3aaa45485d7d5b4 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 15 Aug 2022 01:47:55 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Rework=20how=20the=20user'?= =?UTF-8?q?s=20PATH=20is=20loaded?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 6 + phpmon/Common/Core/Paths.swift | 7 - phpmon/Common/Core/Shell+PATH.swift | 27 ++++ phpmon/Common/PHP/PHP Version/PhpHelper.swift | 2 +- phpmon/Credits.html | 6 +- phpmon/Domain/Menu/MainMenu.swift | 1 - phpmon/Domain/Menu/StatusMenu+Items.swift | 1 + phpmon/Domain/Menu/StatusMenu.swift | 23 ++- .../SwiftUI/Onboarding/OnboardingView.swift | 143 +++++++++--------- .../SwiftUI/Warning/WarningListView.swift | 18 ++- phpmon/Domain/Warnings/WarningManager.swift | 6 +- phpmon/Localizable.strings | 16 +- 12 files changed, 154 insertions(+), 102 deletions(-) create mode 100644 phpmon/Common/Core/Shell+PATH.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index bd55d39..cbdae3f 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -98,6 +98,8 @@ C42CFB1627DFDE7900862737 /* nginx-site.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1527DFDE7900862737 /* nginx-site.test */; }; C42CFB1827DFDFDC00862737 /* nginx-site-isolated.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */; }; C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */; }; + C42E3BF428A9BF5100AFECFC /* Shell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */; }; + C42E3BF528A9BF5100AFECFC /* Shell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */; }; C42F26732805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; C42F26762805FEE200938AC7 /* nginx-secure-proxy.test in Resources */ = {isa = PBXBuildFile; fileRef = C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */; }; @@ -356,6 +358,7 @@ C42CFB1527DFDE7900862737 /* nginx-site.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site.test"; sourceTree = ""; }; C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site-isolated.test"; sourceTree = ""; }; C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationTest.swift; sourceTree = ""; }; + C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Shell+PATH.swift"; sourceTree = ""; }; C42F26722805B4B400938AC7 /* DomainListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListable.swift; sourceTree = ""; }; C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-secure-proxy.test"; sourceTree = ""; }; C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Notifications.swift"; sourceTree = ""; }; @@ -608,6 +611,7 @@ C4B5853D2770FE3900DA4FBE /* Command.swift */, C4B5853B2770FE3900DA4FBE /* Paths.swift */, C4B5853C2770FE3900DA4FBE /* Shell.swift */, + C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */, C4C1019A27C65C6F001FACC2 /* Process.swift */, C40C7F2F27722E8D00DDDCDC /* Logger.swift */, C417DC73277614690015E6EE /* Helpers.swift */, @@ -1386,6 +1390,7 @@ C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */, C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */, C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */, + C42E3BF428A9BF5100AFECFC /* Shell+PATH.swift in Sources */, C42337A3281F19F000459A48 /* Xdebug.swift in Sources */, C4B97B75275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */, C41C02A627E60D7A009F26CB /* SiteScanner.swift in Sources */, @@ -1430,6 +1435,7 @@ C449B4F027EE7FB800C47E8A /* DomainListTLSCell.swift in Sources */, C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */, C43A8A2425D9D20D00591B77 /* HomebrewPackageTest.swift in Sources */, + C42E3BF528A9BF5100AFECFC /* Shell+PATH.swift in Sources */, C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */, C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */, C4F319C927B034A500AFF46F /* Stats.swift in Sources */, diff --git a/phpmon/Common/Core/Paths.swift b/phpmon/Common/Core/Paths.swift index 6a0a4ba..465951b 100644 --- a/phpmon/Common/Core/Paths.swift +++ b/phpmon/Common/Core/Paths.swift @@ -19,12 +19,9 @@ public class Paths { private var userName: String - private var PATH: String - init() { baseDir = App.architecture != "x86_64" ? .opt : .usr userName = String(Shell.pipe("whoami").split(separator: "\n")[0]) - PATH = String(Shell.pipe("echo $PATH")).trimmingCharacters(in: .whitespacesAndNewlines) } public func detectBinaryPaths() { @@ -60,10 +57,6 @@ public class Paths { return shared.userName } - public static var PATH: String { - return shared.PATH - } - public static var cellarPath: String { return "\(shared.baseDir.rawValue)/Cellar" } diff --git a/phpmon/Common/Core/Shell+PATH.swift b/phpmon/Common/Core/Shell+PATH.swift new file mode 100644 index 0000000..cd29071 --- /dev/null +++ b/phpmon/Common/Core/Shell+PATH.swift @@ -0,0 +1,27 @@ +// +// Shell+PATH.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 15/08/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +extension Shell { + + var PATH: String { + let task = Process() + task.launchPath = "/bin/zsh" + + // We need an interactive shell so the user's PATH is loaded in correctly + task.arguments = ["--login", "-ilc", "echo $PATH"] + + let pipe = Pipe() + task.standardOutput = pipe + task.launch() + let data = pipe.fileHandleForReading.readDataToEndOfFile() + + return String(data: data, encoding: String.Encoding.utf8) ?? "" + } +} diff --git a/phpmon/Common/PHP/PHP Version/PhpHelper.swift b/phpmon/Common/PHP/PHP Version/PhpHelper.swift index cc5c9bb..9224635 100644 --- a/phpmon/Common/PHP/PHP Version/PhpHelper.swift +++ b/phpmon/Common/PHP/PHP Version/PhpHelper.swift @@ -20,7 +20,7 @@ class PhpHelper { 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") + let inPath = Shell.user.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/") diff --git a/phpmon/Credits.html b/phpmon/Credits.html index 40bcc83..d2e9a84 100644 --- a/phpmon/Credits.html +++ b/phpmon/Credits.html @@ -13,9 +13,9 @@
-

Want to spread the love? Leave a star on GitHub!

-

Having issues? Consult the FAQ & Troubleshooting section.

-

Want to support me? You can financially support the continued development of this app.

+

Do you enjoy using the app? Leave a star on GitHub!

+

Having issues? Consult the FAQ section, I did my best to ensure everything is documented.

+

Want to support further development of PHP Monitor? You can financially support the continued development of this app.

Get the latest on Twitter Give me a follow on Twitter to learn about the latest and greatest updates of this app.


diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index 8e4cfff..7fe10b6 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -64,7 +64,6 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate menu.addRemainingMenuItems() menu.addItem(NSMenuItem.separator()) - menu.addWarningsMenuItem() menu.addCoreMenuItems() menu.items.forEach({ (item) in diff --git a/phpmon/Domain/Menu/StatusMenu+Items.swift b/phpmon/Domain/Menu/StatusMenu+Items.swift index 98aaf86..1259185 100644 --- a/phpmon/Domain/Menu/StatusMenu+Items.swift +++ b/phpmon/Domain/Menu/StatusMenu+Items.swift @@ -157,6 +157,7 @@ extension StatusMenu { return } + self.addItem(NSMenuItem.separator()) let xdebugSwitch = NSMenuItem( title: "mi_xdebug_mode".localized, action: nil, diff --git a/phpmon/Domain/Menu/StatusMenu.swift b/phpmon/Domain/Menu/StatusMenu.swift index 1c37355..8ece249 100644 --- a/phpmon/Domain/Menu/StatusMenu.swift +++ b/phpmon/Domain/Menu/StatusMenu.swift @@ -66,25 +66,34 @@ class StatusMenu: NSMenu { self.addExtensionsMenuItems() + self.addXdebugMenuItem() + + self.addPhpDoctorMenuItem() + self.addItem(NSMenuItem.separator()) - self.addXdebugMenuItem() self.addPresetsMenuItem() - self.addFirstAidAndServicesMenuItems() } - func addWarningsMenuItem() { + func addPhpDoctorMenuItem() { if !Preferences.isEnabled(.showPhpDoctorSuggestions) || !WarningManager.shared.hasWarnings() { return } self.addItem(NSMenuItem.separator()) - - let count = WarningManager.shared.warnings.count - self.addItem(NSMenuItem(title: "mi_warnings".localized(count), - action: #selector(MainMenu.openWarnings), keyEquivalent: "")) + self.addItem(HeaderView.asMenuItem(text: "mi_php_doctor".localized)) + self.addItem(NSMenuItem( + title: "mi_recommendations_count".localized(WarningManager.shared.warnings.count), + action: nil, + keyEquivalent: "" + )) + self.addItem(NSMenuItem( + title: "mi_view_recommendations".localized, + action: #selector(MainMenu.openWarnings), + keyEquivalent: "" + )) } func addCoreMenuItems() { diff --git a/phpmon/Domain/SwiftUI/Onboarding/OnboardingView.swift b/phpmon/Domain/SwiftUI/Onboarding/OnboardingView.swift index 34a2c0f..80df681 100644 --- a/phpmon/Domain/SwiftUI/Onboarding/OnboardingView.swift +++ b/phpmon/Domain/SwiftUI/Onboarding/OnboardingView.swift @@ -28,9 +28,9 @@ struct OnboardingTextItem: View { Text(description.localizedForSwiftUI) .foregroundColor(Color.secondary) .font(.system(size: 13)) - .lineLimit(6) + .lineLimit(4) .fixedSize(horizontal: false, vertical: true) - .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) + .frame(minWidth: 0, maxWidth: 800, alignment: .leading) } } .padding() @@ -41,80 +41,79 @@ struct OnboardingTextItem: View { struct OnboardingView: View { var body: some View { - VStack(spacing: 10) { - VStack(alignment: .center) { - HStack { - Image(nsImage: NSApp.applicationIconImage) - .resizable() - .frame(width: 80, height: 80) + VStack(alignment: .center, spacing: 5) { + HStack { + Image(nsImage: NSApp.applicationIconImage) + .resizable() + .frame(width: 80, height: 80) + .padding(.bottom, 5) + .padding(.trailing, 25) + VStack(alignment: .leading, spacing: 0) { + Text("onboarding.welcome".localized) + .font(.title) + .bold() .padding(.bottom, 5) - .padding(.trailing, 25) - VStack(alignment: .leading, spacing: 0) { - Text("onboarding.welcome".localized) - .font(.title) - .bold() - .padding(.bottom, 5) - Text("onboarding.explore".localized) - .padding(.bottom) - .padding(.trailing) - } - .padding(.top, 10) + Text("onboarding.explore".localized) + .padding(.bottom) + .padding(.trailing) } - .padding(.leading) - .padding(.trailing) - - VStack { - VStack(alignment: .leading, spacing: 10) { - OnboardingTextItem( - icon: "bolt.circle.fill", - title: "onboarding.tour.menu_bar.title", - description: "onboarding.tour.menu_bar" - ) - OnboardingTextItem( - icon: "checkmark.circle.fill", - title: "onboarding.tour.services.title", - description: "onboarding.tour.services" - ) - OnboardingTextItem( - icon: "list.bullet.circle.fill", - title: "onboarding.tour.domains.title", - description: "onboarding.tour.domains" - ) - OnboardingTextItem( - icon: "pin.circle.fill", - title: "onboarding.tour.isolation.title", - description: "onboarding.tour.isolation" - ) - } - }.padding() - - VStack(spacing: 20) { - HStack { - Image(systemName: "questionmark.circle.fill") - .resizable() - .frame(width: 24, height: 24) - .foregroundColor(Color.appSecondary) - .padding(.trailing, 10) - HStack { - Text("onboarding.tour.faq_hint".localizedForSwiftUI) - .lineLimit(5) - }.fixedSize(horizontal: false, vertical: true) - } - VStack { - Text("onboarding.tour.once".localized) - .font(.subheadline) - .foregroundColor(.gray) - .padding(.top, 5) - .padding(.bottom, 5) - .lineLimit(5) - Button("onboarding.tour.close".localized) { - App.shared.onboardingWindowController?.close() - } - } - }.padding() + .padding(.top, 10) } + .padding(.leading) + .padding(.trailing) + + VStack { + VStack(alignment: .leading, spacing: 10) { + OnboardingTextItem( + icon: "bolt.circle.fill", + title: "onboarding.tour.menu_bar.title", + description: "onboarding.tour.menu_bar" + ) + OnboardingTextItem( + icon: "checkmark.circle.fill", + title: "onboarding.tour.services.title", + description: "onboarding.tour.services" + ) + OnboardingTextItem( + icon: "list.bullet.circle.fill", + title: "onboarding.tour.domains.title", + description: "onboarding.tour.domains" + ) + OnboardingTextItem( + icon: "pin.circle.fill", + title: "onboarding.tour.isolation.title", + description: "onboarding.tour.isolation" + ) + } + }.padding() + + VStack(spacing: 20) { + HStack { + Image(systemName: "questionmark.circle.fill") + .resizable() + .frame(width: 24, height: 24) + .foregroundColor(Color.appSecondary) + .padding(.trailing, 10) + HStack { + Text("onboarding.tour.faq_hint".localizedForSwiftUI) + .lineLimit(5) + }.fixedSize(horizontal: false, vertical: true) + } + VStack { + Text("onboarding.tour.once".localized) + .font(.subheadline) + .foregroundColor(.gray) + .padding(.top, 5) + .padding(.bottom, 5) + .lineLimit(5) + Button("onboarding.tour.close".localized) { + App.shared.onboardingWindowController?.close() + } + } + } + .padding(.leading) + .padding(.trailing) } - .padding(.top, 8) } } diff --git a/phpmon/Domain/SwiftUI/Warning/WarningListView.swift b/phpmon/Domain/SwiftUI/Warning/WarningListView.swift index 7659c5a..0d29b57 100644 --- a/phpmon/Domain/SwiftUI/Warning/WarningListView.swift +++ b/phpmon/Domain/SwiftUI/Warning/WarningListView.swift @@ -11,23 +11,35 @@ import SwiftUI struct WarningListView: View { var body: some View { VStack { - HStack(spacing: 15) { + HStack(alignment: .center, spacing: 15) { Image(systemName: "stethoscope.circle.fill") .resizable() .frame(width: 40, height: 40) .foregroundColor(Color.red) .padding(12) - VStack(alignment: .trailing, spacing: 5) { + VStack(alignment: .leading, spacing: 5) { Text("warnings.description".localizedForSwiftUI) + .font(.system(size: 12)) .frame(maxWidth: .infinity, alignment: .leading) Text("warnings.disclaimer".localizedForSwiftUI) .font(.system(size: 12)) - .foregroundColor(.gray) .frame(maxWidth: .infinity, alignment: .leading) } } .padding(10) + Divider() + + HStack(alignment: .center, spacing: 15) { + Button("warnings.refresh.button".localizedForSwiftUI) { + WarningManager.shared.evaluateWarnings() + } + Text("warnings.refresh.button.description".localizedForSwiftUI) + .foregroundColor(.gray) + .font(.system(size: 11)) + } + .padding(10) + List { VStack(alignment: .leading, spacing: 0) { ForEach(WarningManager.shared.warnings) { warning in diff --git a/phpmon/Domain/Warnings/WarningManager.swift b/phpmon/Domain/Warnings/WarningManager.swift index 9f65c4a..5dd21de 100644 --- a/phpmon/Domain/Warnings/WarningManager.swift +++ b/phpmon/Domain/Warnings/WarningManager.swift @@ -32,8 +32,8 @@ class WarningManager { ), Warning( command: { - !Paths.PATH.contains("/Users/\(Paths.whoami)/.config/phpmon/bin") && - !FileManager.default.isWritableFile(atPath: "/usr/local/bin/") + return !Shell.user.PATH.contains("/Users/\(Paths.whoami)/.config/phpmon/bin") && + !FileManager.default.isWritableFile(atPath: "/usr/local/bin/") }, name: "Helpers cannot be symlinked and not in PATH", title: "warnings.helper_permissions.title", @@ -74,6 +74,8 @@ class WarningManager { if ProcessInfo.processInfo.environment["EXTREME_DOCTOR_MODE"] != nil { self.warnings = self.evaluations } + + MainMenu.shared.rebuild() } } diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index c090432..68fac0c 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -55,7 +55,9 @@ "mi_detected_extensions" = "Detected Extensions"; "mi_no_extensions_detected" = "No additional extensions detected."; -"mi_warnings" = "(%i) PHP Doctor..."; +"mi_php_doctor" = "PHP Doctor"; +"mi_recommendations_count" = "%i Issue(s) Detected!"; +"mi_view_recommendations" = "View Recommendations..."; "mi_valet" = "Laravel Valet"; "mi_domain_list" = "View Domains List..."; @@ -517,6 +519,8 @@ If you are seeing this message but are confused why this folder has gone missing "warnings.title" = "PHP Doctor"; "warnings.description" = "**PHP Doctor** will suggest improvements to your active system configuration."; "warnings.disclaimer" = "You may choose to hide all recommendations from the PHP Monitor menu in Preferences, but it is recommended that you deal with all actionable items."; +"warnings.refresh.button" = "Scan Again"; +"warnings.refresh.button.description" = "Press this button once you've fixed an issue. This will cause PHP Monitor to re-evaluate your environment. If it's really fixed, the recommendation should disappear."; "warnings.helper_permissions.title" = "PHP Monitor’s 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."; @@ -531,14 +535,14 @@ If you are seeing this message but are confused why this folder has gone missing "onboarding.title" = "Welcome Tour"; "onboarding.welcome" = "Welcome to PHP Monitor!"; "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" = "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.title" = "Power In Your Menu Bar"; +"onboarding.tour.menu_bar" = "PHP Monitor lives in your menu bar. From this menu, you can access most of PHP Monitor's key functionality, including switching the globally linked PHP version, locating config files, and much 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.services.title" = "Manage Services"; -"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.services.title" = "Manage Homebrew Services"; +"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."; "onboarding.tour.domains.title" = "Manage Domains"; "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.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.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";