From b7de54dfa7a0b45c2e67263468524a51ba7a4ff3 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sun, 23 Apr 2023 12:01:30 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Rework=20folder=20ownersh?= =?UTF-8?q?ip=20operations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 30 +++-- .../Behaviors/BrewPermissionFixer.swift | 117 ++++++++++++++++++ .../Commands/InstallPhpVersionCommand.swift | 9 ++ .../Commands/RemovePhpVersionCommand.swift | 56 +-------- .../Commands/UpgradePhpVersionCommand.swift | 49 -------- .../{Commands => Fake}/FakeCommand.swift | 0 .../SwiftUI/PhpManager/PhpFormulaeView.swift | 3 + phpmon/Localizable.strings | 4 +- 8 files changed, 151 insertions(+), 117 deletions(-) create mode 100644 phpmon/Domain/Integrations/Homebrew/Behaviors/BrewPermissionFixer.swift delete mode 100644 phpmon/Domain/Integrations/Homebrew/Commands/UpgradePhpVersionCommand.swift rename phpmon/Domain/Integrations/Homebrew/{Commands => Fake}/FakeCommand.swift (100%) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index fa0f7ee..3f3f957 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -200,6 +200,10 @@ C45B91542956123A00F4EC78 /* FakeServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B91522956123A00F4EC78 /* FakeServicesManager.swift */; }; C45B91552956123A00F4EC78 /* FakeServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B91522956123A00F4EC78 /* FakeServicesManager.swift */; }; C45B91562956123A00F4EC78 /* FakeServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B91522956123A00F4EC78 /* FakeServicesManager.swift */; }; + C45D654C29F52F74004C28F9 /* BrewPermissionFixer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45D654B29F52F74004C28F9 /* BrewPermissionFixer.swift */; }; + C45D654D29F52F74004C28F9 /* BrewPermissionFixer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45D654B29F52F74004C28F9 /* BrewPermissionFixer.swift */; }; + C45D654E29F52F74004C28F9 /* BrewPermissionFixer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45D654B29F52F74004C28F9 /* BrewPermissionFixer.swift */; }; + C45D654F29F52F74004C28F9 /* BrewPermissionFixer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45D654B29F52F74004C28F9 /* BrewPermissionFixer.swift */; }; C45E2A7529199248005C7CFD /* InternalSwitcherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C471E7AF28F9B4940021E251 /* InternalSwitcherTest.swift */; }; C45E2A77291992DA005C7CFD /* FeatureTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E2A76291992DA005C7CFD /* FeatureTestCase.swift */; }; C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; }; @@ -515,10 +519,6 @@ C47699EF28A2F2A30060FEB8 /* WarningManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699EE28A2F2A30060FEB8 /* WarningManager.swift */; }; C47699F128A2F3150060FEB8 /* Warning.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699F028A2F3150060FEB8 /* Warning.swift */; }; C476FF9822B0DD830098105B /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; }; - C4777E0C29D71AFB007F0C67 /* UpgradePhpVersionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4777E0A29D71AF0007F0C67 /* UpgradePhpVersionCommand.swift */; }; - C4777E0D29D71AFD007F0C67 /* UpgradePhpVersionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4777E0A29D71AF0007F0C67 /* UpgradePhpVersionCommand.swift */; }; - C4777E0E29D71AFD007F0C67 /* UpgradePhpVersionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4777E0A29D71AF0007F0C67 /* UpgradePhpVersionCommand.swift */; }; - C4777E0F29D71AFD007F0C67 /* UpgradePhpVersionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4777E0A29D71AF0007F0C67 /* UpgradePhpVersionCommand.swift */; }; C47DF1AF299D5A3B0007055D /* LoginItemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DF1AE299D5A3B0007055D /* LoginItemManager.swift */; }; C47DF1B0299D5A3B0007055D /* LoginItemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DF1AE299D5A3B0007055D /* LoginItemManager.swift */; }; C47DF1B1299D5A3B0007055D /* LoginItemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DF1AE299D5A3B0007055D /* LoginItemManager.swift */; }; @@ -939,6 +939,7 @@ C45B9148295607F400F4EC78 /* Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Service.swift; sourceTree = ""; }; C45B914D295608E300F4EC78 /* ValetServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetServicesManager.swift; sourceTree = ""; }; C45B91522956123A00F4EC78 /* FakeServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeServicesManager.swift; sourceTree = ""; }; + C45D654B29F52F74004C28F9 /* BrewPermissionFixer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewPermissionFixer.swift; sourceTree = ""; }; C45E2A76291992DA005C7CFD /* FeatureTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureTestCase.swift; sourceTree = ""; }; C45E76132854A65300B4FE0C /* ServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesManager.swift; sourceTree = ""; }; C463E37F284930EE00422731 /* PresetHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetHelper.swift; sourceTree = ""; }; @@ -965,7 +966,6 @@ C47699EE28A2F2A30060FEB8 /* WarningManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningManager.swift; sourceTree = ""; }; C47699F028A2F3150060FEB8 /* Warning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Warning.swift; sourceTree = ""; }; C476FF9722B0DD830098105B /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = ""; }; - C4777E0A29D71AF0007F0C67 /* UpgradePhpVersionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradePhpVersionCommand.swift; sourceTree = ""; }; C47DF1AE299D5A3B0007055D /* LoginItemManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginItemManager.swift; sourceTree = ""; }; C4811D2322D70A4700B5F6B3 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = ""; }; @@ -1507,6 +1507,7 @@ C45B42C329C7C67400366A14 /* Fake */ = { isa = PBXGroup; children = ( + C4B79EC529CA474200A483EE /* FakeCommand.swift */, ); path = Fake; sourceTree = ""; @@ -1522,6 +1523,14 @@ path = Services; sourceTree = ""; }; + C45D654A29F52F5F004C28F9 /* Behaviors */ = { + isa = PBXGroup; + children = ( + C45D654B29F52F74004C28F9 /* BrewPermissionFixer.swift */, + ); + path = Behaviors; + sourceTree = ""; + }; C464ADAA275A7A25003FCD53 /* DomainList */ = { isa = PBXGroup; children = ( @@ -1670,6 +1679,7 @@ C4AF9F6C275445D900D44ED0 /* Homebrew */ = { isa = PBXGroup; children = ( + C45D654A29F52F5F004C28F9 /* Behaviors */, C4B79EBA29CA38D100A483EE /* Commands */, C45B42C329C7C67400366A14 /* Fake */, C43931C929C4C03F0069165B /* Brew.swift */, @@ -1753,9 +1763,7 @@ isa = PBXGroup; children = ( C4B79EBB29CA38DB00A483EE /* BrewCommand.swift */, - C4B79EC529CA474200A483EE /* FakeCommand.swift */, C4B79EC029CA473000A483EE /* InstallPhpVersionCommand.swift */, - C4777E0A29D71AF0007F0C67 /* UpgradePhpVersionCommand.swift */, C4B79ECA29CA475900A483EE /* RemovePhpVersionCommand.swift */, ); path = Commands; @@ -2275,7 +2283,6 @@ C43931CA29C4C03F0069165B /* Brew.swift in Sources */, C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */, C48D6C70279CD2AC00F26D7E /* VersionNumber.swift in Sources */, - C4777E0C29D71AFB007F0C67 /* UpgradePhpVersionCommand.swift in Sources */, C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */, C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */, C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */, @@ -2398,6 +2405,7 @@ C4B6091D2853AB9700C95265 /* ServicesView.swift in Sources */, C40508B128ADAB44008FAC1F /* NSMenuItemExtension.swift in Sources */, C4B97B7B275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */, + C45D654C29F52F74004C28F9 /* BrewPermissionFixer.swift in Sources */, C4D3660B29113F20006BD146 /* System.swift in Sources */, C4D36601291132B7006BD146 /* ValetScanners.swift in Sources */, C4EED88927A48778006D7272 /* InterAppHandler.swift in Sources */, @@ -2524,13 +2532,13 @@ C471E86828F9BB650021E251 /* PreferencesWindowController.swift in Sources */, C471E86928F9BB650021E251 /* PreferencesWindowController+Hotkey.swift in Sources */, C48DDD0F29C75C9E00D032D9 /* BlockingOverlayView.swift in Sources */, - C4777E0E29D71AFD007F0C67 /* UpgradePhpVersionCommand.swift in Sources */, C4AFC4B029C4F32F00BF4E0D /* BrewFormula.swift in Sources */, C471E86A28F9BB650021E251 /* PreferencesVC.swift in Sources */, C471E86B28F9BB650021E251 /* PreferenceName.swift in Sources */, C471E86C28F9BB650021E251 /* Preferences.swift in Sources */, C4D3660D29113F20006BD146 /* System.swift in Sources */, C471E86D28F9BB650021E251 /* CustomPrefs.swift in Sources */, + C45D654E29F52F74004C28F9 /* BrewPermissionFixer.swift in Sources */, C4E2E84C28FC1E70003B070C /* DataExtension.swift in Sources */, C471E86E28F9BB650021E251 /* MenuBarIcons.swift in Sources */, C471E86F28F9BB650021E251 /* Stats.swift in Sources */, @@ -2734,6 +2742,7 @@ C471E8E428F9BB8F0021E251 /* NoWarningsView.swift in Sources */, C471E8E528F9BB8F0021E251 /* OnboardingView.swift in Sources */, C4B79EBF29CA38DB00A483EE /* BrewCommand.swift in Sources */, + C45D654F29F52F74004C28F9 /* BrewPermissionFixer.swift in Sources */, C471E8E628F9BB8F0021E251 /* VersionPopoverView.swift in Sources */, C471E8E728F9BB8F0021E251 /* NoDomainResultsView.swift in Sources */, C471E8E828F9BB8F0021E251 /* ServicesView.swift in Sources */, @@ -2761,7 +2770,6 @@ C471E80E28F9BAE80021E251 /* DateExtension.swift in Sources */, C490E3BA29BCA368006D2DE6 /* App+BrewWatch.swift in Sources */, C471E7D028F9BA630021E251 /* FileSystemProtocol.swift in Sources */, - C4777E0F29D71AFD007F0C67 /* UpgradePhpVersionCommand.swift in Sources */, C471E81228F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */, C471E7DF28F9BAAB0021E251 /* RealCommand.swift in Sources */, C469E701294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */, @@ -2845,6 +2853,7 @@ 54B48B60275F66AE006D90C5 /* Application.swift in Sources */, C4FE011228084FC200D1DE6D /* SelectionVC.swift in Sources */, C4D3661B291173EA006BD146 /* DictionaryExtension.swift in Sources */, + C45D654D29F52F74004C28F9 /* BrewPermissionFixer.swift in Sources */, C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */, C493084B279F331F009C240B /* AddSiteVC.swift in Sources */, C44A874928905BB000498BC4 /* ProgressVC.swift in Sources */, @@ -2992,7 +3001,6 @@ C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */, C485707628BF455100539B36 /* SectionHeaderView.swift in Sources */, C46EBC4828DB9644007ACC74 /* RealShell.swift in Sources */, - C4777E0D29D71AFD007F0C67 /* UpgradePhpVersionCommand.swift in Sources */, C48DDD0E29C75C9E00D032D9 /* BlockingOverlayView.swift in Sources */, C4E4404727C56F4700D225E1 /* ValetSite.swift in Sources */, C44CCD4A27AFF3BC00CE40E5 /* MainMenu+Async.swift in Sources */, diff --git a/phpmon/Domain/Integrations/Homebrew/Behaviors/BrewPermissionFixer.swift b/phpmon/Domain/Integrations/Homebrew/Behaviors/BrewPermissionFixer.swift new file mode 100644 index 0000000..9f94c6c --- /dev/null +++ b/phpmon/Domain/Integrations/Homebrew/Behaviors/BrewPermissionFixer.swift @@ -0,0 +1,117 @@ +// +// BrewPermissionFixer.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 23/04/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class BrewPermissionFixer { + + var broken: [DueOwnershipFormula] = [] + + /** + Takes ownership of the /BREW_PATH/Cellar/php/x.y.z/bin folder, for all PHP versions. + + This might not be required if the user has only used that version of PHP + with site isolation, so this method checks if it's required first. + + This is a required operation for *all* PHP versions when PHP Version Manager is running + operations, since any installation or upgrade may prompt the installation or upgrade + of other PHP versions, in which case the permissions need to set correctly. + */ + public func fixPermissions() async throws { + await determineBrokenFormulae() + + if broken.isEmpty { + return + } + + let appleScript = NSAppleScript( + source: "do shell script \"\(buildBrokenFormulaeScript())\" with administrator privileges" + ) + + let eventResult: NSAppleEventDescriptor? = appleScript? + .executeAndReturnError(nil) + + if eventResult == nil { + throw HomebrewPermissionError( + kind: .applescriptNilError + ) + } + + Log.info("Ownership was taken of the folder(s) at: " + broken + .map({ $0.path }) + .joined(separator: ", ")) + } + + /** + Determines which formulae's permissions are broken. + + To do so, PHP Monitor resolves which directory needs to be checked and verifies + whether the Homebrew binary directory for the given PHP version is owned by root. + */ + private func determineBrokenFormulae() async { + let formulae = PhpEnv.shared.cachedPhpInstallations.keys + + for formula in formulae { + let realFormula = formula == PhpEnv.brewPhpAlias + ? "php" + : "php@\(formula)" + + let binaryPath = "\(Paths.optPath)/\(realFormula)/bin" + + if isOwnedByRoot(path: binaryPath) { + let borked = DueOwnershipFormula( + formula: realFormula, + path: binaryPath + ) + + Log.warn("\(formula) is owned by root") + + broken.append(borked) + } + } + } + + /** + Generates the appropriate AppleScript script required to restore permissions. + This script also stops the services prior to taking ownership, which is requirement. + */ + private func buildBrokenFormulaeScript() -> String { + return broken + .map { b in + return """ + \(Paths.brew) services stop \(b.formula) \ + && chown -R \(Paths.whoami):admin \(b.path) + """ + } + .joined( + separator: " && " + ) + } + + /** + Checks if the directory at the path is owned by the `root` user, + by checking the FS owner account name attribute. + */ + private func isOwnedByRoot(path: String) -> Bool { + do { + let attributes = try FileManager.default.attributesOfItem(atPath: path) + if let owner = attributes[.ownerAccountName] as? String { + return owner == "root" + } + } catch { + return true + } + + return true + } + + struct DueOwnershipFormula { + let formula: String + let path: String + } +} diff --git a/phpmon/Domain/Integrations/Homebrew/Commands/InstallPhpVersionCommand.swift b/phpmon/Domain/Integrations/Homebrew/Commands/InstallPhpVersionCommand.swift index a1bfcd0..8b717ad 100644 --- a/phpmon/Domain/Integrations/Homebrew/Commands/InstallPhpVersionCommand.swift +++ b/phpmon/Domain/Integrations/Homebrew/Commands/InstallPhpVersionCommand.swift @@ -38,6 +38,14 @@ class InstallPhpVersionCommand: BrewCommand { \(Paths.brew) install \(formula) --force """ + #error("Must keep track of the active PHP version (if applicable)") + + do { + try await BrewPermissionFixer().fixPermissions() + } catch { + return + } + let (process, _) = try! await Shell.attach( command, didReceiveOutput: { text, _ in @@ -56,6 +64,7 @@ class InstallPhpVersionCommand: BrewCommand { onProgress(.create(value: 0.95, title: progressTitle, description: "Reloading PHP versions...")) await PhpEnv.detectPhpVersions() await MainMenu.shared.refreshActiveInstallation() + #error("Must restore active PHP installation (if applicable)") onProgress(.create(value: 1, title: progressTitle, description: "The installation has succeeded.")) } else { throw BrewCommandError(error: "The command failed to run correctly.") diff --git a/phpmon/Domain/Integrations/Homebrew/Commands/RemovePhpVersionCommand.swift b/phpmon/Domain/Integrations/Homebrew/Commands/RemovePhpVersionCommand.swift index 36ab697..de043cc 100644 --- a/phpmon/Domain/Integrations/Homebrew/Commands/RemovePhpVersionCommand.swift +++ b/phpmon/Domain/Integrations/Homebrew/Commands/RemovePhpVersionCommand.swift @@ -35,7 +35,7 @@ class RemovePhpVersionCommand: BrewCommand { """ do { - try await self.fixPermissions(for: formula) + try await BrewPermissionFixer().fixPermissions() } catch { return } @@ -59,58 +59,4 @@ class RemovePhpVersionCommand: BrewCommand { throw BrewCommandError(error: "The command failed to run correctly.") } } - - /** - Takes ownership of the /BREW_PATH/Cellar/php/x.y.z/bin folder (if required). - - This might not be required if the user has only used that version of PHP - with site isolation, so this method checks if it's required first. - */ - private func fixPermissions(for formula: String) async throws { - // Omit the prefix - let path = formula.replacingOccurrences(of: "shivammathur/php/", with: "") - - // Binary path needs to be checked for ownership - let binaryPath = "\(Paths.optPath)/\(path)/bin" - - // Check if it's even necessary to perform the fix - if !isOwnedByRoot(path: binaryPath) { - return - } - - Log.info("Need to take ownership of `\(binaryPath)`...") - - let script = """ - \(Paths.brew) services stop \(formula) \ - && chown -R \(Paths.whoami):admin \(Paths.cellarPath)/\(path) - """ - - let appleScript = NSAppleScript( - source: "do shell script \"\(script)\" with administrator privileges" - ) - - let eventResult: NSAppleEventDescriptor? = appleScript?.executeAndReturnError(nil) - - if eventResult == nil { - throw HomebrewPermissionError(kind: .applescriptNilError) - } - - Log.info("Ownership was taken of the folder at `\(binaryPath)`.") - } - - /** - Checks if a given path is owned by root. If so, ownership might need to be taken. - */ - private func isOwnedByRoot(path: String) -> Bool { - do { - let attributes = try FileManager.default.attributesOfItem(atPath: path) - if let owner = attributes[.ownerAccountName] as? String { - return owner == "root" - } - } catch { - return true - } - - return true - } } diff --git a/phpmon/Domain/Integrations/Homebrew/Commands/UpgradePhpVersionCommand.swift b/phpmon/Domain/Integrations/Homebrew/Commands/UpgradePhpVersionCommand.swift deleted file mode 100644 index 171f9bd..0000000 --- a/phpmon/Domain/Integrations/Homebrew/Commands/UpgradePhpVersionCommand.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// InstallPhpVersionCommand.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 21/03/2023. -// Copyright © 2023 Nico Verbruggen. All rights reserved. -// - -import Foundation - -public typealias BrewDependent = String - -class UpgradePhpVersionCommand: BrewCommand { - let formula: String - let version: String - - init(formula: String) { - self.version = formula - .replacingOccurrences(of: "php@", with: "") - .replacingOccurrences(of: "shivammathur/php/", with: "") - self.formula = formula - } - - func execute() async throws -> [BrewDependent] { - let command = """ - export HOMEBREW_NO_INSTALL_UPGRADE=true; \ - export HOMEBREW_NO_INSTALL_CLEANUP=true; \ - \(Paths.brew) upgrade \(formula) -n - """ - - // Use this command to do a dry-run of the upgrade - // This will let us figure out the impact or failure modes - let (process, _) = try! await Shell.attach( - command, - didReceiveOutput: { text, _ in - if !text.isEmpty { - Log.perf(text) - } - }, - withTimeout: .minutes(5) - ) - - return [] - } - - func execute(onProgress: @escaping (BrewCommandProgress) -> Void) async throws { - // - } -} diff --git a/phpmon/Domain/Integrations/Homebrew/Commands/FakeCommand.swift b/phpmon/Domain/Integrations/Homebrew/Fake/FakeCommand.swift similarity index 100% rename from phpmon/Domain/Integrations/Homebrew/Commands/FakeCommand.swift rename to phpmon/Domain/Integrations/Homebrew/Fake/FakeCommand.swift diff --git a/phpmon/Domain/SwiftUI/PhpManager/PhpFormulaeView.swift b/phpmon/Domain/SwiftUI/PhpManager/PhpFormulaeView.swift index 12847a8..d20b7a7 100644 --- a/phpmon/Domain/SwiftUI/PhpManager/PhpFormulaeView.swift +++ b/phpmon/Domain/SwiftUI/PhpManager/PhpFormulaeView.swift @@ -148,11 +148,14 @@ struct PhpFormulaeView: View { Task { await self.install(formula) } } } + /* + // TODO: Remove this and add a "upgrade all" button instead? if formula.hasUpgrade { Button("phpman.buttons.update".localizedForSwiftUI) { Task { await self.install(formula) } } } + */ } .listRowBackground(index % 2 == 0 ? Color.gray.opacity(0) diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 22bae1c..a765f31 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -94,10 +94,10 @@ "phpman.busy.title" = "Checking for updates!"; "phpman.busy.description.outdated" = "Checking if any PHP version is outdated..."; -"phpman.version.available_for_installation" = "This version can be installed"; +"phpman.version.available_for_installation" = "This version can be installed."; "phpman.buttons.uninstall" = "Uninstall"; "phpman.buttons.install" = "Install"; -"phpman.buttons.update" = "Update"; +"phpman.buttons.update_all" = "Update All"; "phpman.title" = "PHP Version Manager"; "phpman.description" = "**PHP Version Manager** lets you install different PHP versions via Homebrew.";