mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2025-08-07 20:10:08 +02:00
🏗 Remove LegacyShell entirely
This commit is contained in:
@ -103,8 +103,6 @@
|
|||||||
C42CFB1627DFDE7900862737 /* nginx-site.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1527DFDE7900862737 /* nginx-site.test */; };
|
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 */; };
|
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 */; };
|
C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */; };
|
||||||
C42E3BF428A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */; };
|
|
||||||
C42E3BF528A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */; };
|
|
||||||
C42F26732805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; };
|
C42F26732805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; };
|
||||||
C42F26742805B4B400938AC7 /* 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 */; };
|
C42F26762805FEE200938AC7 /* nginx-secure-proxy.test in Resources */ = {isa = PBXBuildFile; fileRef = C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */; };
|
||||||
@ -224,8 +222,6 @@
|
|||||||
C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B56360276AB0A500F12CCB /* VersionExtractorTest.swift */; };
|
C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B56360276AB0A500F12CCB /* VersionExtractorTest.swift */; };
|
||||||
C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; };
|
C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; };
|
||||||
C4B5853F2770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; };
|
C4B5853F2770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; };
|
||||||
C4B585412770FE3900DA4FBE /* LegacyShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */; };
|
|
||||||
C4B585422770FE3900DA4FBE /* LegacyShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */; };
|
|
||||||
C4B585442770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; };
|
C4B585442770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; };
|
||||||
C4B585452770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; };
|
C4B585452770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; };
|
||||||
C4B6091A2853AAD300C95265 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B609192853AAD300C95265 /* SectionHeaderView.swift */; };
|
C4B6091A2853AAD300C95265 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B609192853AAD300C95265 /* SectionHeaderView.swift */; };
|
||||||
@ -406,7 +402,6 @@
|
|||||||
C42CFB1527DFDE7900862737 /* nginx-site.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site.test"; sourceTree = "<group>"; };
|
C42CFB1527DFDE7900862737 /* nginx-site.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site.test"; sourceTree = "<group>"; };
|
||||||
C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site-isolated.test"; sourceTree = "<group>"; };
|
C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site-isolated.test"; sourceTree = "<group>"; };
|
||||||
C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationTest.swift; sourceTree = "<group>"; };
|
C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationTest.swift; sourceTree = "<group>"; };
|
||||||
C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LegacyShell+PATH.swift"; sourceTree = "<group>"; };
|
|
||||||
C42F26722805B4B400938AC7 /* DomainListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListable.swift; sourceTree = "<group>"; };
|
C42F26722805B4B400938AC7 /* DomainListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListable.swift; sourceTree = "<group>"; };
|
||||||
C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-secure-proxy.test"; sourceTree = "<group>"; };
|
C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-secure-proxy.test"; sourceTree = "<group>"; };
|
||||||
C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Notifications.swift"; sourceTree = "<group>"; };
|
C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Notifications.swift"; sourceTree = "<group>"; };
|
||||||
@ -470,7 +465,6 @@
|
|||||||
C4B5635D276AB09000F12CCB /* VersionExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionExtractor.swift; sourceTree = "<group>"; };
|
C4B5635D276AB09000F12CCB /* VersionExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionExtractor.swift; sourceTree = "<group>"; };
|
||||||
C4B56360276AB0A500F12CCB /* VersionExtractorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionExtractorTest.swift; sourceTree = "<group>"; };
|
C4B56360276AB0A500F12CCB /* VersionExtractorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionExtractorTest.swift; sourceTree = "<group>"; };
|
||||||
C4B5853B2770FE3900DA4FBE /* Paths.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Paths.swift; sourceTree = "<group>"; };
|
C4B5853B2770FE3900DA4FBE /* Paths.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Paths.swift; sourceTree = "<group>"; };
|
||||||
C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyShell.swift; sourceTree = "<group>"; };
|
|
||||||
C4B5853D2770FE3900DA4FBE /* Command.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
|
C4B5853D2770FE3900DA4FBE /* Command.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
|
||||||
C4B609192853AAD300C95265 /* SectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderView.swift; sourceTree = "<group>"; };
|
C4B609192853AAD300C95265 /* SectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderView.swift; sourceTree = "<group>"; };
|
||||||
C4B6091C2853AB9700C95265 /* ServicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesView.swift; sourceTree = "<group>"; };
|
C4B6091C2853AB9700C95265 /* ServicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesView.swift; sourceTree = "<group>"; };
|
||||||
@ -897,15 +891,6 @@
|
|||||||
path = Next;
|
path = Next;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
C46EBC4C28DB9F43007ACC74 /* Pending Removal */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */,
|
|
||||||
C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */,
|
|
||||||
);
|
|
||||||
path = "Pending Removal";
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
C47331A0247093AC009A0597 /* Menu */ = {
|
C47331A0247093AC009A0597 /* Menu */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -1007,7 +992,6 @@
|
|||||||
C4B5853A2770FE2500DA4FBE /* Common */ = {
|
C4B5853A2770FE2500DA4FBE /* Common */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
C46EBC4C28DB9F43007ACC74 /* Pending Removal */,
|
|
||||||
C40C7F2127721F7300DDDCDC /* Core */,
|
C40C7F2127721F7300DDDCDC /* Core */,
|
||||||
54B20EDF263AA22C00D3250E /* PHP */,
|
54B20EDF263AA22C00D3250E /* PHP */,
|
||||||
C44CCD4327AFE93300CE40E5 /* Errors */,
|
C44CCD4327AFE93300CE40E5 /* Errors */,
|
||||||
@ -1383,7 +1367,6 @@
|
|||||||
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */,
|
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */,
|
||||||
C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */,
|
C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */,
|
||||||
C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */,
|
C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */,
|
||||||
C4B585412770FE3900DA4FBE /* LegacyShell.swift in Sources */,
|
|
||||||
C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */,
|
C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */,
|
||||||
C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */,
|
C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */,
|
||||||
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */,
|
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */,
|
||||||
@ -1509,7 +1492,6 @@
|
|||||||
C40508AF28ADA23D008FAC1F /* NoDomainResultsView.swift in Sources */,
|
C40508AF28ADA23D008FAC1F /* NoDomainResultsView.swift in Sources */,
|
||||||
C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */,
|
C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */,
|
||||||
C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */,
|
C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */,
|
||||||
C42E3BF428A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */,
|
|
||||||
C42337A3281F19F000459A48 /* Xdebug.swift in Sources */,
|
C42337A3281F19F000459A48 /* Xdebug.swift in Sources */,
|
||||||
C4B97B75275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */,
|
C4B97B75275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */,
|
||||||
C41C02A627E60D7A009F26CB /* SiteScanner.swift in Sources */,
|
C41C02A627E60D7A009F26CB /* SiteScanner.swift in Sources */,
|
||||||
@ -1563,7 +1545,6 @@
|
|||||||
C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */,
|
C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */,
|
||||||
C43A8A2425D9D20D00591B77 /* HomebrewPackageTest.swift in Sources */,
|
C43A8A2425D9D20D00591B77 /* HomebrewPackageTest.swift in Sources */,
|
||||||
C485707928BF456C00539B36 /* ArrayExtension.swift in Sources */,
|
C485707928BF456C00539B36 /* ArrayExtension.swift in Sources */,
|
||||||
C42E3BF528A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */,
|
|
||||||
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */,
|
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */,
|
||||||
C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */,
|
C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */,
|
||||||
C4F319C927B034A500AFF46F /* Stats.swift in Sources */,
|
C4F319C927B034A500AFF46F /* Stats.swift in Sources */,
|
||||||
@ -1668,7 +1649,6 @@
|
|||||||
C4A6957728D23EE300A14CF8 /* EnvironmentManager.swift in Sources */,
|
C4A6957728D23EE300A14CF8 /* EnvironmentManager.swift in Sources */,
|
||||||
C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */,
|
C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */,
|
||||||
C4A81CA528C67101008DD9D1 /* PMTableView.swift in Sources */,
|
C4A81CA528C67101008DD9D1 /* PMTableView.swift in Sources */,
|
||||||
C4B585422770FE3900DA4FBE /* LegacyShell.swift in Sources */,
|
|
||||||
C45E76152854A65300B4FE0C /* ServicesManager.swift in Sources */,
|
C45E76152854A65300B4FE0C /* ServicesManager.swift in Sources */,
|
||||||
C464ADAD275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */,
|
C464ADAD275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */,
|
||||||
C40C7F1F2772136000DDDCDC /* PhpEnv.swift in Sources */,
|
C40C7F1F2772136000DDDCDC /* PhpEnv.swift in Sources */,
|
||||||
|
@ -12,22 +12,22 @@ class Actions {
|
|||||||
|
|
||||||
// MARK: - Services
|
// MARK: - Services
|
||||||
|
|
||||||
public static func restartPhpFpm() {
|
public static func restartPhpFpm() async {
|
||||||
brew("services restart \(PhpEnv.phpInstall.formula)", sudo: true)
|
await brew("services restart \(PhpEnv.phpInstall.formula)", sudo: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func restartNginx() {
|
public static func restartNginx() async {
|
||||||
brew("services restart nginx", sudo: true)
|
await brew("services restart nginx", sudo: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func restartDnsMasq() {
|
public static func restartDnsMasq() async {
|
||||||
brew("services restart dnsmasq", sudo: true)
|
await brew("services restart dnsmasq", sudo: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func stopValetServices() {
|
public static func stopValetServices() async {
|
||||||
brew("services stop \(PhpEnv.phpInstall.formula)", sudo: true)
|
await brew("services stop \(PhpEnv.phpInstall.formula)", sudo: true)
|
||||||
brew("services stop nginx", sudo: true)
|
await brew("services stop nginx", sudo: true)
|
||||||
brew("services stop dnsmasq", sudo: true)
|
await brew("services stop dnsmasq", sudo: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func fixHomebrewPermissions() throws {
|
public static func fixHomebrewPermissions() throws {
|
||||||
@ -65,26 +65,20 @@ class Actions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Third Party Services
|
// MARK: - Third Party Services
|
||||||
public static func stopService(name: String, completion: @escaping () -> Void) {
|
public static func stopService(name: String) async {
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
await brew(
|
||||||
brew("services stop \(name)", sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name })
|
"services stop \(name)",
|
||||||
ServicesManager.loadHomebrewServices(completed: {
|
sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name }
|
||||||
DispatchQueue.main.async {
|
)
|
||||||
completion()
|
await ServicesManager.loadHomebrewServices()
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func startService(name: String, completion: @escaping () -> Void) {
|
public static func startService(name: String) async {
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
await brew(
|
||||||
brew("services start \(name)", sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name })
|
"services start \(name)",
|
||||||
ServicesManager.loadHomebrewServices(completed: {
|
sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name }
|
||||||
DispatchQueue.main.async {
|
)
|
||||||
completion()
|
await ServicesManager.loadHomebrewServices()
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Finding Config Files
|
// MARK: - Finding Config Files
|
||||||
@ -119,12 +113,12 @@ class Actions {
|
|||||||
|
|
||||||
// MARK: - Other Actions
|
// MARK: - Other Actions
|
||||||
|
|
||||||
public static func createTempPhpInfoFile() -> URL {
|
public static func createTempPhpInfoFile() async -> URL {
|
||||||
// Write a file called `phpmon_phpinfo.php` to /tmp
|
// Write a file called `phpmon_phpinfo.php` to /tmp
|
||||||
try! "<?php phpinfo();".write(toFile: "/tmp/phpmon_phpinfo.php", atomically: true, encoding: .utf8)
|
try! "<?php phpinfo();".write(toFile: "/tmp/phpmon_phpinfo.php", atomically: true, encoding: .utf8)
|
||||||
|
|
||||||
// Tell php-cgi to run the PHP and output as an .html file
|
// Tell php-cgi to run the PHP and output as an .html file
|
||||||
LegacyShell.run("\(Paths.binPath)/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html")
|
await Shell.quiet("\(Paths.binPath)/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html")
|
||||||
|
|
||||||
return URL(string: "file:///private/tmp/phpmon_phpinfo.html")!
|
return URL(string: "file:///private/tmp/phpmon_phpinfo.html")!
|
||||||
}
|
}
|
||||||
@ -145,10 +139,12 @@ class Actions {
|
|||||||
*/
|
*/
|
||||||
public static func fixMyValet(completed: @escaping () -> Void) {
|
public static func fixMyValet(completed: @escaping () -> Void) {
|
||||||
InternalSwitcher().performSwitch(to: PhpEnv.brewPhpAlias, completion: {
|
InternalSwitcher().performSwitch(to: PhpEnv.brewPhpAlias, completion: {
|
||||||
brew("services restart dnsmasq", sudo: true)
|
Task { // restart all services and fire callback upon completion
|
||||||
brew("services restart php", sudo: true)
|
await brew("services restart dnsmasq", sudo: true)
|
||||||
brew("services restart nginx", sudo: true)
|
await brew("services restart php", sudo: true)
|
||||||
completed()
|
await brew("services restart nginx", sudo: true)
|
||||||
|
completed()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,21 +11,21 @@
|
|||||||
/**
|
/**
|
||||||
Runs a `valet` command. Defaults to running as superuser.
|
Runs a `valet` command. Defaults to running as superuser.
|
||||||
*/
|
*/
|
||||||
func valet(_ command: String, sudo: Bool = true) -> String {
|
func valet(_ command: String, sudo: Bool = true) async -> String {
|
||||||
return LegacyShell.pipe("\(sudo ? "sudo " : "")" + "\(Paths.valet) \(command)", requiresPath: true)
|
return await Shell.pipe("\(sudo ? "sudo " : "")" + "\(Paths.valet) \(command)").out
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Runs a `brew` command. Can run as superuser.
|
Runs a `brew` command. Can run as superuser.
|
||||||
*/
|
*/
|
||||||
func brew(_ command: String, sudo: Bool = false) {
|
func brew(_ command: String, sudo: Bool = false) async {
|
||||||
LegacyShell.run("\(sudo ? "sudo " : "")" + "\(Paths.brew) \(command)")
|
await Shell.quiet("\(sudo ? "sudo " : "")" + "\(Paths.brew) \(command)")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Runs `sed` in order to replace all occurrences of a string in a specific file with another.
|
Runs `sed` in order to replace all occurrences of a string in a specific file with another.
|
||||||
*/
|
*/
|
||||||
func sed(file: String, original: String, replacement: String) {
|
func sed(file: String, original: String, replacement: String) async {
|
||||||
// Escape slashes (or `sed` won't work)
|
// Escape slashes (or `sed` won't work)
|
||||||
let e_original = original.replacingOccurrences(of: "/", with: "\\/")
|
let e_original = original.replacingOccurrences(of: "/", with: "\\/")
|
||||||
let e_replacement = replacement.replacingOccurrences(of: "/", with: "\\/")
|
let e_replacement = replacement.replacingOccurrences(of: "/", with: "\\/")
|
||||||
@ -33,19 +33,19 @@ func sed(file: String, original: String, replacement: String) {
|
|||||||
// Check if gsed exists; it is able to follow symlinks,
|
// Check if gsed exists; it is able to follow symlinks,
|
||||||
// which we want to do to toggle the extension
|
// which we want to do to toggle the extension
|
||||||
if Filesystem.fileExists("\(Paths.binPath)/gsed") {
|
if Filesystem.fileExists("\(Paths.binPath)/gsed") {
|
||||||
LegacyShell.run("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)")
|
await Shell.quiet("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)")
|
||||||
} else {
|
} else {
|
||||||
LegacyShell.run("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)")
|
await Shell.quiet("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Uses `grep` to determine whether a particular query string can be found in a particular file.
|
Uses `grep` to determine whether a particular query string can be found in a particular file.
|
||||||
*/
|
*/
|
||||||
func grepContains(file: String, query: String) -> Bool {
|
func grepContains(file: String, query: String) async -> Bool {
|
||||||
return LegacyShell.pipe("""
|
return await Shell.pipe("""
|
||||||
grep -q '\(query)' \(file); [ $? -eq 0 ] && echo "YES" || echo "NO"
|
grep -q '\(query)' \(file); [ $? -eq 0 ] && echo "YES" || echo "NO"
|
||||||
""")
|
""").out
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
.contains("YES")
|
.contains("YES")
|
||||||
}
|
}
|
||||||
|
@ -21,11 +21,11 @@ public class Paths {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
baseDir = App.architecture != "x86_64" ? .opt : .usr
|
baseDir = App.architecture != "x86_64" ? .opt : .usr
|
||||||
|
}
|
||||||
|
|
||||||
Task {
|
public func loadUser() async {
|
||||||
let output = await Shell.pipe("id -un").out
|
let output = await Shell.pipe("id -un").out
|
||||||
userName = String(output.split(separator: "\n")[0])
|
userName = String(output.split(separator: "\n")[0])
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func detectBinaryPaths() {
|
public func detectBinaryPaths() {
|
||||||
|
@ -125,11 +125,12 @@ class ActivePhpInstallation {
|
|||||||
versions of PHP, we can just check for the existence of the `valet-fpm.conf` file. If the check here fails,
|
versions of PHP, we can just check for the existence of the `valet-fpm.conf` file. If the check here fails,
|
||||||
that means that Valet won't work properly.
|
that means that Valet won't work properly.
|
||||||
*/
|
*/
|
||||||
func checkPhpFpmStatus() -> Bool {
|
func checkPhpFpmStatus() async -> Bool {
|
||||||
if self.version.short == "5.6" {
|
if self.version.short == "5.6" {
|
||||||
// The main PHP config file should contain `valet.sock` and then we're probably fine?
|
// The main PHP config file should contain `valet.sock` and then we're probably fine?
|
||||||
let fileName = "\(Paths.etcPath)/php/5.6/php-fpm.conf"
|
let fileName = "\(Paths.etcPath)/php/5.6/php-fpm.conf"
|
||||||
return LegacyShell.pipe("cat \(fileName)").contains("valet.sock")
|
return await Shell.pipe("cat \(fileName)").out
|
||||||
|
.contains("valet.sock")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure to check if valet-fpm.conf exists. If it does, we should be fine :)
|
// Make sure to check if valet-fpm.conf exists. If it does, we should be fine :)
|
||||||
|
@ -16,17 +16,15 @@ class PhpEnv {
|
|||||||
self.currentInstall = ActivePhpInstallation()
|
self.currentInstall = ActivePhpInstallation()
|
||||||
}
|
}
|
||||||
|
|
||||||
func determinePhpAlias() {
|
func determinePhpAlias() async {
|
||||||
Task {
|
let brewPhpAlias = await Shell.pipe("\(Paths.brew) info php --json").out
|
||||||
let brewPhpAlias = await Shell.pipe("\(Paths.brew) info php --json").out
|
|
||||||
|
|
||||||
self.homebrewPackage = try! JSONDecoder().decode(
|
self.homebrewPackage = try! JSONDecoder().decode(
|
||||||
[HomebrewPackage].self,
|
[HomebrewPackage].self,
|
||||||
from: brewPhpAlias.data(using: .utf8)!
|
from: brewPhpAlias.data(using: .utf8)!
|
||||||
).first!
|
).first!
|
||||||
|
|
||||||
Log.info("[BREW] On your system, the `php` formula means version \(homebrewPackage.version)!")
|
Log.info("[BREW] On your system, the `php` formula means version \(homebrewPackage.version)!")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
@ -80,8 +78,8 @@ class PhpEnv {
|
|||||||
return InternalSwitcher()
|
return InternalSwitcher()
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func detectPhpVersions() {
|
public static func detectPhpVersions() async {
|
||||||
Task { await Self.shared.detectPhpVersions() }
|
_ = await Self.shared.detectPhpVersions()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -90,7 +88,7 @@ class PhpEnv {
|
|||||||
public func detectPhpVersions() async -> [String] {
|
public func detectPhpVersions() async -> [String] {
|
||||||
let files = await Shell.pipe("ls \(Paths.optPath) | grep php@").out
|
let files = await Shell.pipe("ls \(Paths.optPath) | grep php@").out
|
||||||
|
|
||||||
var versionsOnly = extractPhpVersions(from: files.components(separatedBy: "\n"))
|
var versionsOnly = await extractPhpVersions(from: files.components(separatedBy: "\n"))
|
||||||
|
|
||||||
// Make sure the aliased version is detected
|
// Make sure the aliased version is detected
|
||||||
// The user may have `php` installed, but not e.g. `php@8.0`
|
// The user may have `php` installed, but not e.g. `php@8.0`
|
||||||
@ -128,7 +126,7 @@ class PhpEnv {
|
|||||||
from versions: [String],
|
from versions: [String],
|
||||||
checkBinaries: Bool = true,
|
checkBinaries: Bool = true,
|
||||||
generateHelpers: Bool = true
|
generateHelpers: Bool = true
|
||||||
) -> [String] {
|
) async -> [String] {
|
||||||
var output: [String] = []
|
var output: [String] = []
|
||||||
|
|
||||||
var supported = Constants.SupportedPhpVersions
|
var supported = Constants.SupportedPhpVersions
|
||||||
@ -153,7 +151,9 @@ class PhpEnv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if generateHelpers {
|
if generateHelpers {
|
||||||
output.forEach { PhpHelper.generate(for: $0) }
|
for item in output {
|
||||||
|
await PhpHelper.generate(for: item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
@ -12,7 +12,7 @@ class PhpHelper {
|
|||||||
|
|
||||||
static let keyPhrase = "This file was automatically generated by PHP Monitor."
|
static let keyPhrase = "This file was automatically generated by PHP Monitor."
|
||||||
|
|
||||||
public static func generate(for version: String) {
|
public static func generate(for version: String) async {
|
||||||
// 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: "")
|
||||||
|
|
||||||
@ -20,79 +20,81 @@ class PhpHelper {
|
|||||||
let destination = "\(Paths.homePath)/.config/phpmon/bin/pm\(dotless)"
|
let destination = "\(Paths.homePath)/.config/phpmon/bin/pm\(dotless)"
|
||||||
|
|
||||||
// Check if the ~/.config/phpmon/bin directory is in the PATH
|
// Check if the ~/.config/phpmon/bin directory is in the PATH
|
||||||
let inPath = LegacyShell.user.PATH.contains("\(Paths.homePath)/.config/phpmon/bin")
|
let inPath = Shell.PATH.contains("\(Paths.homePath)/.config/phpmon/bin")
|
||||||
|
|
||||||
// Check if we can create symlinks (`/usr/local/bin` must be writable)
|
// Check if we can create symlinks (`/usr/local/bin` must be writable)
|
||||||
let canWriteSymlinks = FileManager.default.isWritableFile(atPath: "/usr/local/bin/")
|
let canWriteSymlinks = FileManager.default.isWritableFile(atPath: "/usr/local/bin/")
|
||||||
|
|
||||||
do {
|
Task { // Create the appropriate folders and check if the files exist
|
||||||
Task { await Shell.quiet("mkdir -p ~/.config/phpmon/bin") }
|
do {
|
||||||
|
await Shell.quiet("mkdir -p ~/.config/phpmon/bin")
|
||||||
|
|
||||||
if Filesystem.fileExists(destination) {
|
if Filesystem.fileExists(destination) {
|
||||||
let contents = try String(contentsOfFile: destination)
|
let contents = try String(contentsOfFile: destination)
|
||||||
if !contents.contains(keyPhrase) {
|
if !contents.contains(keyPhrase) {
|
||||||
Log.info("The file at '\(destination)' already exists and was not generated by PHP Monitor "
|
Log.info("The file at '\(destination)' already exists and was not generated by PHP Monitor "
|
||||||
+ "(or is unreadable). Not updating this file.")
|
+ "(or is unreadable). Not updating this file.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Let's follow the symlink to the PHP binary folder
|
|
||||||
let path = URL(fileURLWithPath: "\(Paths.optPath)/php@\(version)/bin")
|
|
||||||
.resolvingSymlinksInPath().path
|
|
||||||
|
|
||||||
// The contents of the script!
|
|
||||||
let script = """
|
|
||||||
#!/bin/zsh
|
|
||||||
# \(keyPhrase)
|
|
||||||
# It reflects the location of PHP \(version)'s binaries on your system.
|
|
||||||
# Usage: . pm\(dotless)
|
|
||||||
[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] \\
|
|
||||||
&& echo "PHP Monitor has enabled this terminal to use PHP \(version)." \\
|
|
||||||
|| echo "You must run '. pm\(dotless)' (or 'source pm\(dotless)') instead!";
|
|
||||||
export PATH=\(path):$PATH
|
|
||||||
"""
|
|
||||||
|
|
||||||
// Write to the destination
|
|
||||||
try script.write(
|
|
||||||
to: URL(fileURLWithPath: destination),
|
|
||||||
atomically: true,
|
|
||||||
encoding: String.Encoding.utf8
|
|
||||||
)
|
|
||||||
|
|
||||||
// Make sure the file is executable
|
|
||||||
LegacyShell.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
|
// Let's follow the symlink to the PHP binary folder
|
||||||
self.createSymlink(dotless)
|
let path = URL(fileURLWithPath: "\(Paths.optPath)/php@\(version)/bin")
|
||||||
|
.resolvingSymlinksInPath().path
|
||||||
|
|
||||||
|
// The contents of the script!
|
||||||
|
let script = """
|
||||||
|
#!/bin/zsh
|
||||||
|
# \(keyPhrase)
|
||||||
|
# It reflects the location of PHP \(version)'s binaries on your system.
|
||||||
|
# Usage: . pm\(dotless)
|
||||||
|
[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] \\
|
||||||
|
&& echo "PHP Monitor has enabled this terminal to use PHP \(version)." \\
|
||||||
|
|| echo "You must run '. pm\(dotless)' (or 'source pm\(dotless)') instead!";
|
||||||
|
export PATH=\(path):$PATH
|
||||||
|
"""
|
||||||
|
|
||||||
|
// Write to the destination
|
||||||
|
try script.write(
|
||||||
|
to: URL(fileURLWithPath: destination),
|
||||||
|
atomically: true,
|
||||||
|
encoding: String.Encoding.utf8
|
||||||
|
)
|
||||||
|
|
||||||
|
// Make sure the file is executable
|
||||||
|
await Shell.quiet("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
|
||||||
|
await self.createSymlink(dotless)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print(error)
|
||||||
|
Log.err("Could not write PHP Monitor helper for PHP \(version) to \(destination))")
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
print(error)
|
|
||||||
Log.err("Could not write PHP Monitor helper for PHP \(version) to \(destination))")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func createSymlink(_ dotless: String) {
|
private static func createSymlink(_ dotless: String) async {
|
||||||
let source = "\(Paths.homePath)/.config/phpmon/bin/pm\(dotless)"
|
let source = "\(Paths.homePath)/.config/phpmon/bin/pm\(dotless)"
|
||||||
let destination = "/usr/local/bin/pm\(dotless)"
|
let destination = "/usr/local/bin/pm\(dotless)"
|
||||||
|
|
||||||
if !Filesystem.fileExists(destination) {
|
if !Filesystem.fileExists(destination) {
|
||||||
Log.info("Creating new symlink: \(destination)")
|
Log.info("Creating new symlink: \(destination)")
|
||||||
LegacyShell.run("ln -s \(source) \(destination)")
|
await Shell.quiet("ln -s \(source) \(destination)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !Filesystem.fileIsSymlink(destination) {
|
if !Filesystem.fileIsSymlink(destination) {
|
||||||
Log.info("Overwriting existing file with new symlink: \(destination)")
|
Log.info("Overwriting existing file with new symlink: \(destination)")
|
||||||
LegacyShell.run("ln -fs \(source) \(destination)")
|
await Shell.quiet("ln -fs \(source) \(destination)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,14 +75,14 @@ class PhpExtension {
|
|||||||
This simply toggles the extension in the .ini file.
|
This simply toggles the extension in the .ini file.
|
||||||
You may need to restart the other services in order for this change to apply.
|
You may need to restart the other services in order for this change to apply.
|
||||||
*/
|
*/
|
||||||
func toggle() {
|
func toggle() async {
|
||||||
let newLine = enabled
|
let newLine = enabled
|
||||||
// DISABLED: Commented out line
|
// DISABLED: Commented out line
|
||||||
? "; \(line)"
|
? "; \(line)"
|
||||||
// ENABLED: Line where the comment delimiter (;) is removed
|
// ENABLED: Line where the comment delimiter (;) is removed
|
||||||
: line.replacingOccurrences(of: "; ", with: "")
|
: line.replacingOccurrences(of: "; ", with: "")
|
||||||
|
|
||||||
sed(file: file, original: line, replacement: newLine)
|
await sed(file: file, original: line, replacement: newLine)
|
||||||
|
|
||||||
enabled.toggle()
|
enabled.toggle()
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,8 @@ class InternalSwitcher: PhpSwitcher {
|
|||||||
Please note that depending on which version is installed,
|
Please note that depending on which version is installed,
|
||||||
the version that is switched to may or may not be identical to `php`
|
the version that is switched to may or may not be identical to `php`
|
||||||
(without @version).
|
(without @version).
|
||||||
|
|
||||||
|
TODO: Use `async` and use structured concurrency: https://www.hackingwithswift.com/swift/5.5/structured-concurrency
|
||||||
*/
|
*/
|
||||||
func performSwitch(to version: String, completion: @escaping () -> Void) {
|
func performSwitch(to version: String, completion: @escaping () -> Void) {
|
||||||
Log.info("Switching to \(version), unlinking all versions...")
|
Log.info("Switching to \(version), unlinking all versions...")
|
||||||
@ -30,26 +32,28 @@ class InternalSwitcher: PhpSwitcher {
|
|||||||
PhpEnv.shared.availablePhpVersions.forEach { (available) in
|
PhpEnv.shared.availablePhpVersions.forEach { (available) in
|
||||||
group.enter()
|
group.enter()
|
||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
Task { // TODO: Use structured concurrency
|
||||||
self.disableDefaultPhpFpmPool(available)
|
await self.disableDefaultPhpFpmPool(available)
|
||||||
self.stopPhpVersion(available)
|
await self.stopPhpVersion(available)
|
||||||
group.leave()
|
group.leave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
group.notify(queue: .global(qos: .userInitiated)) {
|
group.notify(queue: .global(qos: .userInitiated)) {
|
||||||
Log.info("All versions have been unlinked!")
|
Task { // TODO: Use structured concurrency
|
||||||
Log.info("Linking the new version!")
|
Log.info("All versions have been unlinked!")
|
||||||
|
Log.info("Linking the new version!")
|
||||||
|
|
||||||
for formula in versions {
|
for formula in versions {
|
||||||
self.startPhpVersion(formula, primary: (version == formula))
|
await self.startPhpVersion(formula, primary: (version == formula))
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.info("Restarting nginx, just to be sure!")
|
||||||
|
await brew("services restart nginx", sudo: true)
|
||||||
|
|
||||||
|
Log.info("The new version(s) have been linked!")
|
||||||
|
completion()
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.info("Restarting nginx, just to be sure!")
|
|
||||||
brew("services restart nginx", sudo: true)
|
|
||||||
|
|
||||||
Log.info("The new version(s) have been linked!")
|
|
||||||
completion()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +78,7 @@ class InternalSwitcher: PhpSwitcher {
|
|||||||
return FileManager.default.fileExists(atPath: pool)
|
return FileManager.default.fileExists(atPath: pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
func disableDefaultPhpFpmPool(_ version: String) {
|
func disableDefaultPhpFpmPool(_ version: String) async {
|
||||||
let pool = "\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf"
|
let pool = "\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf"
|
||||||
if FileManager.default.fileExists(atPath: pool) {
|
if FileManager.default.fileExists(atPath: pool) {
|
||||||
Log.info("A default `www.conf` file was found in the php-fpm.d directory for PHP \(version).")
|
Log.info("A default `www.conf` file was found in the php-fpm.d directory for PHP \(version).")
|
||||||
@ -94,28 +98,28 @@ class InternalSwitcher: PhpSwitcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopPhpVersion(_ version: String) {
|
func stopPhpVersion(_ version: String) async {
|
||||||
let formula = (version == PhpEnv.brewPhpAlias) ? "php" : "php@\(version)"
|
let formula = (version == PhpEnv.brewPhpAlias) ? "php" : "php@\(version)"
|
||||||
brew("unlink \(formula)")
|
await brew("unlink \(formula)")
|
||||||
brew("services stop \(formula)", sudo: true)
|
await brew("services stop \(formula)", sudo: true)
|
||||||
Log.info("Unlinked and stopped services for \(formula)")
|
Log.info("Unlinked and stopped services for \(formula)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func startPhpVersion(_ version: String, primary: Bool) {
|
func startPhpVersion(_ version: String, primary: Bool) async {
|
||||||
let formula = (version == PhpEnv.brewPhpAlias) ? "php" : "php@\(version)"
|
let formula = (version == PhpEnv.brewPhpAlias) ? "php" : "php@\(version)"
|
||||||
|
|
||||||
if primary {
|
if primary {
|
||||||
Log.info("\(formula) is the primary formula, linking and starting services...")
|
Log.info("\(formula) is the primary formula, linking and starting services...")
|
||||||
brew("link \(formula) --overwrite --force")
|
await brew("link \(formula) --overwrite --force")
|
||||||
} else {
|
} else {
|
||||||
Log.info("\(formula) is an isolated PHP version, starting services only...")
|
Log.info("\(formula) is an isolated PHP version, starting services only...")
|
||||||
}
|
}
|
||||||
|
|
||||||
brew("services start \(formula)", sudo: true)
|
await brew("services start \(formula)", sudo: true)
|
||||||
|
|
||||||
if Valet.enabled(feature: .isolatedSites) && primary {
|
if Valet.enabled(feature: .isolatedSites) && primary {
|
||||||
let socketVersion = version.replacingOccurrences(of: ".", with: "")
|
let socketVersion = version.replacingOccurrences(of: ".", with: "")
|
||||||
LegacyShell.run("ln -sF ~/.config/valet/valet\(socketVersion).sock ~/.config/valet/valet.sock")
|
await Shell.quiet("ln -sF ~/.config/valet/valet\(socketVersion).sock ~/.config/valet/valet.sock")
|
||||||
Log.info("Symlinked new socket version (valet\(socketVersion).sock → valet.sock).")
|
Log.info("Symlinked new socket version (valet\(socketVersion).sock → valet.sock).")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
//
|
|
||||||
// Shell+PATH.swift
|
|
||||||
// PHP Monitor
|
|
||||||
//
|
|
||||||
// Created by Nico Verbruggen on 15/08/2022.
|
|
||||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
extension LegacyShell {
|
|
||||||
|
|
||||||
var PATH: String {
|
|
||||||
let task = Process()
|
|
||||||
task.launchPath = "/bin/zsh"
|
|
||||||
|
|
||||||
let command = Filesystem.fileExists("~/.zshrc")
|
|
||||||
// source the user's .zshrc file if it exists to complete $PATH
|
|
||||||
? ". ~/.zshrc && echo $PATH"
|
|
||||||
// otherwise, non-interactive mode is sufficient
|
|
||||||
: "echo $PATH"
|
|
||||||
|
|
||||||
task.arguments = ["--login", "-lc", command]
|
|
||||||
|
|
||||||
let pipe = Pipe()
|
|
||||||
task.standardOutput = pipe
|
|
||||||
task.launch()
|
|
||||||
|
|
||||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
|
||||||
|
|
||||||
return String(data: data, encoding: String.Encoding.utf8) ?? ""
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,175 +0,0 @@
|
|||||||
//
|
|
||||||
// Shell.swift
|
|
||||||
// PHP Monitor
|
|
||||||
//
|
|
||||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Cocoa
|
|
||||||
|
|
||||||
// TODO: Enable this to see where deprecations and replacements are needed.
|
|
||||||
@available(*, deprecated, message: "Use the new replacement `Shell` instead")
|
|
||||||
public class LegacyShell {
|
|
||||||
|
|
||||||
// MARK: - Invoke static functions
|
|
||||||
|
|
||||||
public static func run(
|
|
||||||
_ command: String,
|
|
||||||
requiresPath: Bool = false
|
|
||||||
) {
|
|
||||||
LegacyShell.user.run(command, requiresPath: requiresPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func pipe(
|
|
||||||
_ command: String,
|
|
||||||
requiresPath: Bool = false
|
|
||||||
) -> String {
|
|
||||||
return LegacyShell.user.pipe(command, requiresPath: requiresPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Singleton
|
|
||||||
|
|
||||||
/**
|
|
||||||
We now require macOS 11, so no need to detect which terminal to use.
|
|
||||||
*/
|
|
||||||
public var shell: String = "/bin/sh"
|
|
||||||
|
|
||||||
/** Additional exports that are sent if `requiresPath` is set to true. */
|
|
||||||
public var exports: String = ""
|
|
||||||
|
|
||||||
/**
|
|
||||||
Singleton to access a user shell (with --login)
|
|
||||||
*/
|
|
||||||
public static let user = LegacyShell()
|
|
||||||
|
|
||||||
/**
|
|
||||||
Runs a shell command without using the output.
|
|
||||||
Uses the default shell.
|
|
||||||
|
|
||||||
- Parameter command: The command to run
|
|
||||||
- Parameter requiresPath: By default, the PATH is not resolved but some binaries might require this
|
|
||||||
*/
|
|
||||||
private func run(
|
|
||||||
_ command: String,
|
|
||||||
requiresPath: Bool = false
|
|
||||||
) {
|
|
||||||
// Equivalent of piping to /dev/null; don't do anything with the string
|
|
||||||
_ = LegacyShell.pipe(command, requiresPath: requiresPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Runs a shell command and returns the output.
|
|
||||||
|
|
||||||
- Parameter command: The command to run
|
|
||||||
- Parameter requiresPath: By default, the PATH is not resolved but some binaries might require this
|
|
||||||
*/
|
|
||||||
private func pipe(
|
|
||||||
_ command: String,
|
|
||||||
requiresPath: Bool = false
|
|
||||||
) -> String {
|
|
||||||
let shellOutput = self.executeSynchronously(command, requiresPath: requiresPath)
|
|
||||||
let hasError = (
|
|
||||||
shellOutput.standardOutput == ""
|
|
||||||
&& shellOutput.errorOutput.lengthOfBytes(using: .utf8) > 0
|
|
||||||
)
|
|
||||||
return !hasError ? shellOutput.standardOutput : shellOutput.errorOutput
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Runs the command and returns a `ShellOutput` object, which contains info about the process.
|
|
||||||
|
|
||||||
- Parameter command: The command to run
|
|
||||||
- Parameter requiresPath: By default, the PATH is not resolved but some binaries might require this
|
|
||||||
- Parameter waitUntilExit: Waits for the command to complete before returning the `ShellOutput`
|
|
||||||
*/
|
|
||||||
public func executeSynchronously(
|
|
||||||
_ command: String,
|
|
||||||
requiresPath: Bool = false
|
|
||||||
) -> LegacyShell.Output {
|
|
||||||
|
|
||||||
let outputPipe = Pipe()
|
|
||||||
let errorPipe = Pipe()
|
|
||||||
|
|
||||||
let task = self.createTask(for: command, requiresPath: requiresPath)
|
|
||||||
task.standardOutput = outputPipe
|
|
||||||
task.standardError = errorPipe
|
|
||||||
task.launch()
|
|
||||||
task.waitUntilExit()
|
|
||||||
|
|
||||||
let output = LegacyShell.Output(
|
|
||||||
standardOutput: String(
|
|
||||||
data: outputPipe.fileHandleForReading.readDataToEndOfFile(),
|
|
||||||
encoding: .utf8
|
|
||||||
)!,
|
|
||||||
errorOutput: String(
|
|
||||||
data: errorPipe.fileHandleForReading.readDataToEndOfFile(),
|
|
||||||
encoding: .utf8
|
|
||||||
)!,
|
|
||||||
task: task
|
|
||||||
)
|
|
||||||
|
|
||||||
if CommandLine.arguments.contains("--v") {
|
|
||||||
log(task: task, output: output)
|
|
||||||
}
|
|
||||||
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Creates a new process with the correct PATH and shell.
|
|
||||||
*/
|
|
||||||
public func createTask(for command: String, requiresPath: Bool) -> Process {
|
|
||||||
var completeCommand = ""
|
|
||||||
|
|
||||||
Log.info("LEGACY COMMAND: \(command)")
|
|
||||||
|
|
||||||
if requiresPath {
|
|
||||||
// Basic export (PATH)
|
|
||||||
completeCommand += "export PATH=\(Paths.binPath):$PATH && "
|
|
||||||
|
|
||||||
// Put additional exports in between
|
|
||||||
if !self.exports.isEmpty {
|
|
||||||
completeCommand += "\(self.exports) && "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
completeCommand += command
|
|
||||||
|
|
||||||
let task = Process()
|
|
||||||
task.launchPath = self.shell
|
|
||||||
task.arguments = ["--noprofile", "-norc", "--login", "-c", completeCommand]
|
|
||||||
|
|
||||||
return task
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Verbose logging for PHP Monitor's synchronous shell output.
|
|
||||||
*/
|
|
||||||
private func log(task: Process, output: Output) {
|
|
||||||
Log.info("")
|
|
||||||
Log.info("==== COMMAND ====")
|
|
||||||
Log.info("")
|
|
||||||
Log.info("\(self.shell) \(task.arguments?.joined(separator: " ") ?? "")")
|
|
||||||
Log.info("")
|
|
||||||
Log.info("==== OUTPUT ====")
|
|
||||||
Log.info("")
|
|
||||||
dump(output)
|
|
||||||
Log.info("")
|
|
||||||
Log.info("==== END OUTPUT ====")
|
|
||||||
Log.info("")
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Output {
|
|
||||||
public let standardOutput: String
|
|
||||||
public let errorOutput: String
|
|
||||||
public let task: Process
|
|
||||||
|
|
||||||
init(standardOutput: String,
|
|
||||||
errorOutput: String,
|
|
||||||
task: Process) {
|
|
||||||
self.standardOutput = standardOutput
|
|
||||||
self.errorOutput = errorOutput
|
|
||||||
self.task = task
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -33,15 +33,17 @@ extension AppDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func reloadDomainListPressed(_ sender: Any) {
|
@IBAction func reloadDomainListPressed(_ sender: Any) {
|
||||||
let vc = App.shared.domainListWindowController?
|
Task { // Reload domains
|
||||||
.window?.contentViewController as? DomainListVC
|
let vc = App.shared.domainListWindowController?
|
||||||
|
.window?.contentViewController as? DomainListVC
|
||||||
|
|
||||||
if vc != nil {
|
if vc != nil {
|
||||||
// If the view exists, directly reload the list of sites.
|
// If the view exists, directly reload the list of sites.
|
||||||
vc!.reloadDomains()
|
await vc!.reloadDomains()
|
||||||
} else {
|
} else {
|
||||||
// If the view does not exist, reload the cached data that was populated when the app initially launched.
|
// If the view does not exist, reload the cached data that was populated when the app launched.
|
||||||
Valet.shared.reloadSites()
|
await Valet.shared.reloadSites()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,13 +13,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
|||||||
|
|
||||||
// MARK: - Variables
|
// MARK: - Variables
|
||||||
|
|
||||||
/**
|
|
||||||
The Shell singleton that keeps track of the history of all
|
|
||||||
(invoked by PHP Monitor) shell commands. It is used to
|
|
||||||
invoke all commands in this application.
|
|
||||||
*/
|
|
||||||
let sharedShell: LegacyShell
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The App singleton contains information about the state of
|
The App singleton contains information about the state of
|
||||||
the application and global variables.
|
the application and global variables.
|
||||||
@ -68,7 +61,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
|||||||
logger.verbosity = .performance
|
logger.verbosity = .performance
|
||||||
|
|
||||||
// TODO: Enable to fake broken setup during testing
|
// TODO: Enable to fake broken setup during testing
|
||||||
ActiveShell.useTestable(Testables.working.shellOutput)
|
// ActiveShell.useTestable(Testables.working.shellOutput)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
if CommandLine.arguments.contains("--v") {
|
if CommandLine.arguments.contains("--v") {
|
||||||
@ -81,7 +74,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
|||||||
Log.info("Version \(App.version)")
|
Log.info("Version \(App.version)")
|
||||||
Log.separator(as: .info)
|
Log.separator(as: .info)
|
||||||
|
|
||||||
self.sharedShell = LegacyShell.user
|
|
||||||
self.state = App.shared
|
self.state = App.shared
|
||||||
self.menu = MainMenu.shared
|
self.menu = MainMenu.shared
|
||||||
self.paths = Paths.shared
|
self.paths = Paths.shared
|
||||||
@ -103,8 +95,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
|||||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||||
// Make sure notifications will work
|
// Make sure notifications will work
|
||||||
setupNotifications()
|
setupNotifications()
|
||||||
// Make sure the menu performs its initial checks
|
Task { // Make sure the menu performs its initial checks
|
||||||
Task { await menu.startup() }
|
await paths.loadUser()
|
||||||
|
await menu.startup()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ class AppUpdateChecker {
|
|||||||
|
|
||||||
public static func retrieveVersionFromCask(
|
public static func retrieveVersionFromCask(
|
||||||
_ initiatedFromBackground: Bool = true
|
_ initiatedFromBackground: Bool = true
|
||||||
) -> String {
|
) async -> String {
|
||||||
let caskFile = App.version.contains("-dev")
|
let caskFile = App.version.contains("-dev")
|
||||||
? Constants.Urls.DevBuildCaskFile.absoluteString
|
? Constants.Urls.DevBuildCaskFile.absoluteString
|
||||||
: Constants.Urls.StableBuildCaskFile.absoluteString
|
: Constants.Urls.StableBuildCaskFile.absoluteString
|
||||||
@ -32,14 +32,14 @@ class AppUpdateChecker {
|
|||||||
command = "curl -s --max-time 5"
|
command = "curl -s --max-time 5"
|
||||||
}
|
}
|
||||||
|
|
||||||
return LegacyShell.pipe(
|
return await Shell.pipe(
|
||||||
"\(command) '\(caskFile)' | grep version"
|
"\(command) '\(caskFile)' | grep version"
|
||||||
)
|
).out
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func checkIfNewerVersionIsAvailable(
|
public static func checkIfNewerVersionIsAvailable(
|
||||||
initiatedFromBackground: Bool = true
|
initiatedFromBackground: Bool = true
|
||||||
) {
|
) async {
|
||||||
if initiatedFromBackground {
|
if initiatedFromBackground {
|
||||||
if !Preferences.isEnabled(.automaticBackgroundUpdateCheck) {
|
if !Preferences.isEnabled(.automaticBackgroundUpdateCheck) {
|
||||||
Log.info("Automatic updates are disabled. No check will be performed.")
|
Log.info("Automatic updates are disabled. No check will be performed.")
|
||||||
@ -49,7 +49,7 @@ class AppUpdateChecker {
|
|||||||
Log.info("Automatic updates are enabled, a check will be performed.")
|
Log.info("Automatic updates are enabled, a check will be performed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
let versionString = retrieveVersionFromCask(initiatedFromBackground)
|
let versionString = await retrieveVersionFromCask(initiatedFromBackground)
|
||||||
|
|
||||||
guard let onlineVersion = AppVersion.from(versionString) else {
|
guard let onlineVersion = AppVersion.from(versionString) else {
|
||||||
Log.err("We couldn't check for updates!")
|
Log.err("We couldn't check for updates!")
|
||||||
|
@ -26,10 +26,14 @@ class InterApp {
|
|||||||
DomainListVC.show()
|
DomainListVC.show()
|
||||||
}),
|
}),
|
||||||
InterApp.Action(command: "services/stop", action: { _ in
|
InterApp.Action(command: "services/stop", action: { _ in
|
||||||
MainMenu.shared.stopValetServices()
|
Task { // Stopping services as standalone task
|
||||||
|
await MainMenu.shared.stopValetServices()
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
InterApp.Action(command: "services/restart/all", action: { _ in
|
InterApp.Action(command: "services/restart/all", action: { _ in
|
||||||
MainMenu.shared.restartValetServices()
|
Task { // Restarting services as standalone task
|
||||||
|
await MainMenu.shared.restartValetServices()
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
InterApp.Action(command: "services/restart/nginx", action: { _ in
|
InterApp.Action(command: "services/restart/nginx", action: { _ in
|
||||||
MainMenu.shared.restartNginx()
|
MainMenu.shared.restartNginx()
|
||||||
|
@ -16,53 +16,44 @@ class ServicesManager: ObservableObject {
|
|||||||
@Published var rootServices: [String: HomebrewService] = [:]
|
@Published var rootServices: [String: HomebrewService] = [:]
|
||||||
@Published var userServices: [String: HomebrewService] = [:]
|
@Published var userServices: [String: HomebrewService] = [:]
|
||||||
|
|
||||||
public static func loadHomebrewServices(completed: (() -> Void)? = nil) {
|
public static func loadHomebrewServices() async {
|
||||||
let rootServiceNames = [
|
let rootServiceNames = [
|
||||||
PhpEnv.phpInstall.formula,
|
PhpEnv.phpInstall.formula,
|
||||||
"nginx",
|
"nginx",
|
||||||
"dnsmasq"
|
"dnsmasq"
|
||||||
]
|
]
|
||||||
|
|
||||||
DispatchQueue.global(qos: .background).async {
|
let normalJson = await Shell
|
||||||
let data = LegacyShell
|
.pipe("sudo \(Paths.brew) services info --all --json")
|
||||||
.pipe("sudo \(Paths.brew) services info --all --json", requiresPath: true)
|
.out
|
||||||
.data(using: .utf8)!
|
.data(using: .utf8)!
|
||||||
|
|
||||||
let services = try! JSONDecoder()
|
let normalServices = try! JSONDecoder()
|
||||||
.decode([HomebrewService].self, from: data)
|
.decode([HomebrewService].self, from: normalJson)
|
||||||
.filter({ return rootServiceNames.contains($0.name) })
|
.filter({ return rootServiceNames.contains($0.name) })
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
ServicesManager.shared.rootServices = Dictionary(
|
ServicesManager.shared.rootServices = Dictionary(
|
||||||
uniqueKeysWithValues: services.map { ($0.name, $0) }
|
uniqueKeysWithValues: normalServices.map { ($0.name, $0) }
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let userServiceNames = Preferences.custom.services else {
|
guard let userServiceNames = Preferences.custom.services else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.global(qos: .background).async {
|
let rootJson = await Shell
|
||||||
let data = LegacyShell
|
.pipe("\(Paths.brew) services info --all --json")
|
||||||
.pipe("\(Paths.brew) services info --all --json", requiresPath: true)
|
.out
|
||||||
.data(using: .utf8)!
|
.data(using: .utf8)!
|
||||||
|
|
||||||
let services = try! JSONDecoder()
|
let rootServices = try! JSONDecoder()
|
||||||
.decode([HomebrewService].self, from: data)
|
.decode([HomebrewService].self, from: rootJson)
|
||||||
.filter({ return userServiceNames.contains($0.name) })
|
.filter({ return userServiceNames.contains($0.name) })
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
ServicesManager.shared.userServices = Dictionary(
|
||||||
ServicesManager.shared.userServices = Dictionary(
|
uniqueKeysWithValues: rootServices.map { ($0.name, $0) }
|
||||||
uniqueKeysWithValues: services.map { ($0.name, $0) }
|
)
|
||||||
)
|
|
||||||
completed?()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadData() {
|
|
||||||
Self.loadHomebrewServices()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,9 +71,9 @@ class AddProxyVC: NSViewController, NSTextFieldDelegate {
|
|||||||
|
|
||||||
App.shared.domainListWindowController?.contentVC.setUIBusy()
|
App.shared.domainListWindowController?.contentVC.setUIBusy()
|
||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
Task {
|
||||||
LegacyShell.run("\(Paths.valet) proxy \(domain) \(proxyName)\(secure)", requiresPath: true)
|
await Shell.quiet("\(Paths.valet) proxy \(domain) \(proxyName)\(secure)")
|
||||||
Actions.restartNginx()
|
await Actions.restartNginx()
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
App.shared.domainListWindowController?.contentVC.setUINotBusy()
|
App.shared.domainListWindowController?.contentVC.setUINotBusy()
|
||||||
|
@ -51,7 +51,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate {
|
|||||||
|
|
||||||
// MARK: - Outlet Interactions
|
// MARK: - Outlet Interactions
|
||||||
|
|
||||||
@IBAction func pressedCreateLink(_ sender: Any) {
|
@IBAction func pressedCreateLink(_ sender: Any) async {
|
||||||
let path = pathControl.url!.path
|
let path = pathControl.url!.path
|
||||||
let name = inputDomainName.stringValue
|
let name = inputDomainName.stringValue
|
||||||
|
|
||||||
@ -71,7 +71,9 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate {
|
|||||||
|
|
||||||
// Adding `valet links` is a workaround for Valet malforming the config.json file
|
// Adding `valet links` is a workaround for Valet malforming the config.json file
|
||||||
// TODO: I will have to investigate and report this behaviour if possible
|
// TODO: I will have to investigate and report this behaviour if possible
|
||||||
LegacyShell.run("cd '\(path)' && \(Paths.valet) link '\(name)' && valet links", requiresPath: true)
|
Task {
|
||||||
|
await Shell.quiet("cd '\(path)' && \(Paths.valet) link '\(name)' && valet links")
|
||||||
|
}
|
||||||
|
|
||||||
dismissView(outcome: .OK)
|
dismissView(outcome: .OK)
|
||||||
|
|
||||||
@ -81,7 +83,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate {
|
|||||||
.searchField.stringValue = ""
|
.searchField.stringValue = ""
|
||||||
|
|
||||||
// Add the new item and scrolls to it
|
// Add the new item and scrolls to it
|
||||||
App.shared.domainListWindowController?
|
await App.shared.domainListWindowController?
|
||||||
.contentVC
|
.contentVC
|
||||||
.addedNewSite(
|
.addedNewSite(
|
||||||
name: name,
|
name: name,
|
||||||
|
@ -25,15 +25,14 @@ extension DomainListVC {
|
|||||||
|
|
||||||
self.waitAndExecute {
|
self.waitAndExecute {
|
||||||
// 1. Remove the original proxy
|
// 1. Remove the original proxy
|
||||||
LegacyShell.run("\(Paths.valet) unproxy \(selectedProxy.domain)", requiresPath: true)
|
await Shell.quiet("\(Paths.valet) unproxy \(selectedProxy.domain)")
|
||||||
|
|
||||||
// 2. Add a new proxy, which is either secured/unsecured
|
// 2. Add a new proxy, which is either secured/unsecured
|
||||||
let secure = originalSecureStatus ? "" : " --secure"
|
let secure = originalSecureStatus ? "" : " --secure"
|
||||||
LegacyShell.run("\(Paths.valet) proxy \(selectedProxy.domain) \(selectedProxy.target)\(secure)",
|
await Shell.quiet("\(Paths.valet) proxy \(selectedProxy.domain) \(selectedProxy.target)\(secure)")
|
||||||
requiresPath: true)
|
|
||||||
|
|
||||||
// 3. Restart nginx
|
// 3. Restart nginx
|
||||||
Actions.restartNginx()
|
await Actions.restartNginx()
|
||||||
|
|
||||||
// 4. Reload site list
|
// 4. Reload site list
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
@ -50,7 +49,7 @@ extension DomainListVC {
|
|||||||
let command = "cd '\(selectedSite.absolutePath)' && sudo \(Paths.valet) \(action) && exit;"
|
let command = "cd '\(selectedSite.absolutePath)' && sudo \(Paths.valet) \(action) && exit;"
|
||||||
|
|
||||||
waitAndExecute {
|
waitAndExecute {
|
||||||
LegacyShell.run(command, requiresPath: true)
|
await Shell.quiet(command)
|
||||||
} completion: { [self] in
|
} completion: { [self] in
|
||||||
selectedSite.determineSecured()
|
selectedSite.determineSecured()
|
||||||
if selectedSite.secured == originalSecureStatus {
|
if selectedSite.secured == originalSecureStatus {
|
||||||
@ -99,12 +98,12 @@ extension DomainListVC {
|
|||||||
NSWorkspace.shared.open(url)
|
NSWorkspace.shared.open(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func openInFinder() {
|
@objc func openInFinder() async {
|
||||||
LegacyShell.run("open '\(selectedSite!.absolutePath)'")
|
await Shell.quiet("open '\(selectedSite!.absolutePath)'")
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func openInTerminal() {
|
@objc func openInTerminal() async {
|
||||||
LegacyShell.run("open -b com.apple.terminal '\(selectedSite!.absolutePath)'")
|
await Shell.quiet("open -b com.apple.terminal '\(selectedSite!.absolutePath)'")
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func openWithEditor(sender: EditorMenuItem) async {
|
@objc func openWithEditor(sender: EditorMenuItem) async {
|
||||||
@ -157,9 +156,9 @@ extension DomainListVC {
|
|||||||
style: .critical,
|
style: .critical,
|
||||||
onFirstButtonPressed: {
|
onFirstButtonPressed: {
|
||||||
self.waitAndExecute {
|
self.waitAndExecute {
|
||||||
LegacyShell.run("valet unlink '\(site.name)'", requiresPath: true)
|
Task { await Shell.quiet("valet unlink '\(site.name)'") }
|
||||||
} completion: {
|
} completion: {
|
||||||
self.reloadDomains()
|
Task { await self.reloadDomains() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -179,9 +178,9 @@ extension DomainListVC {
|
|||||||
style: .critical,
|
style: .critical,
|
||||||
onFirstButtonPressed: {
|
onFirstButtonPressed: {
|
||||||
self.waitAndExecute {
|
self.waitAndExecute {
|
||||||
LegacyShell.run("valet unproxy '\(proxy.domain)'", requiresPath: true)
|
Task { await Shell.quiet("valet unproxy '\(proxy.domain)'") }
|
||||||
} completion: {
|
} completion: {
|
||||||
self.reloadDomains()
|
Task { await self.reloadDomains() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -191,7 +190,7 @@ extension DomainListVC {
|
|||||||
let rowToReload = tableView.selectedRow
|
let rowToReload = tableView.selectedRow
|
||||||
|
|
||||||
waitAndExecute {
|
waitAndExecute {
|
||||||
LegacyShell.run(command, requiresPath: true)
|
await Shell.quiet(command)
|
||||||
} completion: { [self] in
|
} completion: { [self] in
|
||||||
beforeCellReload()
|
beforeCellReload()
|
||||||
tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0, 1, 2, 3, 4])
|
tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0, 1, 2, 3, 4])
|
||||||
|
@ -97,7 +97,7 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
|
|||||||
domains = Valet.getDomainListable()
|
domains = Valet.getDomainListable()
|
||||||
searchedFor(text: lastSearchedFor)
|
searchedFor(text: lastSearchedFor)
|
||||||
} else {
|
} else {
|
||||||
reloadDomains()
|
Task { await reloadDomains() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
|
|||||||
Disables the UI so the user cannot interact with it.
|
Disables the UI so the user cannot interact with it.
|
||||||
Also shows a spinner to indicate that we're busy.
|
Also shows a spinner to indicate that we're busy.
|
||||||
*/
|
*/
|
||||||
public func setUIBusy() {
|
@MainActor public func setUIBusy() {
|
||||||
// If it takes more than 0.5s to set the UI to not busy, show a spinner
|
// If it takes more than 0.5s to set the UI to not busy, show a spinner
|
||||||
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: { _ in
|
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: { _ in
|
||||||
self.progressIndicator.startAnimation(true)
|
self.progressIndicator.startAnimation(true)
|
||||||
@ -121,7 +121,7 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
|
|||||||
/**
|
/**
|
||||||
Re-enables the UI so the user can interact with it.
|
Re-enables the UI so the user can interact with it.
|
||||||
*/
|
*/
|
||||||
public func setUINotBusy() {
|
@MainActor public func setUINotBusy() {
|
||||||
timer?.invalidate()
|
timer?.invalidate()
|
||||||
progressIndicator.stopAnimation(nil)
|
progressIndicator.stopAnimation(nil)
|
||||||
tableView.alphaValue = 1.0
|
tableView.alphaValue = 1.0
|
||||||
@ -136,12 +136,11 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
|
|||||||
- Parameter execute: Callback of the work that needs to happen.
|
- Parameter execute: Callback of the work that needs to happen.
|
||||||
- Parameter completion: Callback that is fired when the work is done.
|
- Parameter completion: Callback that is fired when the work is done.
|
||||||
*/
|
*/
|
||||||
internal func waitAndExecute(_ execute: @escaping () -> Void, completion: @escaping () -> Void = {}) {
|
internal func waitAndExecute(_ execute: @escaping () async -> Void, completion: @escaping () -> Void = {}) {
|
||||||
setUIBusy()
|
Task {
|
||||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
setUIBusy()
|
||||||
execute()
|
await execute()
|
||||||
|
|
||||||
// For a smoother animation, expect at least a 0.2 second delay
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [self] in
|
||||||
completion()
|
completion()
|
||||||
setUINotBusy()
|
setUINotBusy()
|
||||||
@ -151,9 +150,9 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
|
|||||||
|
|
||||||
// MARK: - Site Data Loading
|
// MARK: - Site Data Loading
|
||||||
|
|
||||||
func reloadDomains() {
|
func reloadDomains() async {
|
||||||
waitAndExecute {
|
waitAndExecute {
|
||||||
Valet.shared.reloadSites()
|
await Valet.shared.reloadSites()
|
||||||
} completion: { [self] in
|
} completion: { [self] in
|
||||||
domains = Valet.shared.sites
|
domains = Valet.shared.sites
|
||||||
searchedFor(text: lastSearchedFor)
|
searchedFor(text: lastSearchedFor)
|
||||||
@ -177,9 +176,9 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
|
|||||||
self.domains = descriptor.ascending ? sorted.reversed() : sorted
|
self.domains = descriptor.ascending ? sorted.reversed() : sorted
|
||||||
}
|
}
|
||||||
|
|
||||||
func addedNewSite(name: String, secure: Bool) {
|
func addedNewSite(name: String, secure: Bool) async {
|
||||||
waitAndExecute {
|
waitAndExecute {
|
||||||
Valet.shared.reloadSites()
|
await Valet.shared.reloadSites()
|
||||||
} completion: { [self] in
|
} completion: { [self] in
|
||||||
find(name, secure)
|
find(name, secure)
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,9 @@ class DomainListWindowController: PMWindowController, NSSearchFieldDelegate, NST
|
|||||||
// MARK: - Reload functionality
|
// MARK: - Reload functionality
|
||||||
|
|
||||||
@IBAction func pressedReload(_ sender: Any?) {
|
@IBAction func pressedReload(_ sender: Any?) {
|
||||||
contentVC.reloadDomains()
|
Task {
|
||||||
|
await contentVC.reloadDomains()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func pressedAddLink(_ sender: Any?) {
|
@IBAction func pressedAddLink(_ sender: Any?) {
|
||||||
|
@ -21,10 +21,9 @@ import Foundation
|
|||||||
self.completion = completion
|
self.completion = completion
|
||||||
|
|
||||||
Paths.shared.detectBinaryPaths()
|
Paths.shared.detectBinaryPaths()
|
||||||
|
|
||||||
if Paths.composer == nil {
|
if Paths.composer == nil {
|
||||||
DispatchQueue.main.async {
|
self.presentMissingAlert()
|
||||||
self.presentMissingAlert()
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +38,9 @@ import Foundation
|
|||||||
|
|
||||||
window?.setType(info: true)
|
window?.setType(info: true)
|
||||||
|
|
||||||
Task { await performComposerUpdate() }
|
Task { // Start the Composer global update as a separate task
|
||||||
|
await performComposerUpdate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func performComposerUpdate() async {
|
private func performComposerUpdate() async {
|
||||||
|
@ -42,7 +42,7 @@ class HomebrewDiagnostics {
|
|||||||
This check only needs to be performed if the `shivammathur/php` tap is active.
|
This check only needs to be performed if the `shivammathur/php` tap is active.
|
||||||
*/
|
*/
|
||||||
public static func checkForCaskConflict() {
|
public static func checkForCaskConflict() {
|
||||||
Task {
|
Task { // Check if there's a conflict
|
||||||
if await hasAliasConflict() {
|
if await hasAliasConflict() {
|
||||||
presentAlertAboutConflict()
|
presentAlertAboutConflict()
|
||||||
}
|
}
|
||||||
@ -72,9 +72,11 @@ class HomebrewDiagnostics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
versions.forEach { version in
|
versions.forEach { version in
|
||||||
switcher.disableDefaultPhpFpmPool(version)
|
Task { // Fix each pool concurrently (but perform the tasks sequentially)
|
||||||
switcher.stopPhpVersion(version)
|
await switcher.disableDefaultPhpFpmPool(version)
|
||||||
switcher.startPhpVersion(version, primary: version == primary)
|
await switcher.stopPhpVersion(version)
|
||||||
|
await switcher.startPhpVersion(version, primary: version == primary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,15 +116,15 @@ class Valet {
|
|||||||
Starts the preload of sites. In order to make sure PHP Monitor can correctly
|
Starts the preload of sites. In order to make sure PHP Monitor can correctly
|
||||||
handle all PHP versions including isolation, it needs to know about all sites.
|
handle all PHP versions including isolation, it needs to know about all sites.
|
||||||
*/
|
*/
|
||||||
public func startPreloadingSites() {
|
public func startPreloadingSites() async {
|
||||||
self.reloadSites()
|
await self.reloadSites()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Reloads the list of sites, assuming that the list isn't being reloaded at the time.
|
Reloads the list of sites, assuming that the list isn't being reloaded at the time.
|
||||||
(We don't want to do duplicate or parallel work!)
|
(We don't want to do duplicate or parallel work!)
|
||||||
*/
|
*/
|
||||||
public func reloadSites() {
|
public func reloadSites() async {
|
||||||
loadConfiguration()
|
loadConfiguration()
|
||||||
|
|
||||||
if isBusy {
|
if isBusy {
|
||||||
|
@ -42,17 +42,17 @@ extension MainMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func restartPhpFpm() {
|
@objc func restartPhpFpm() {
|
||||||
asyncExecution {
|
Task { // Simple restart service
|
||||||
Actions.restartPhpFpm()
|
await Actions.restartPhpFpm()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func restartValetServices() {
|
@MainActor @objc func restartValetServices() {
|
||||||
asyncExecution {
|
Task { // Restart services and show notification
|
||||||
Actions.restartDnsMasq()
|
await Actions.restartDnsMasq()
|
||||||
Actions.restartPhpFpm()
|
await Actions.restartPhpFpm()
|
||||||
Actions.restartNginx()
|
await Actions.restartNginx()
|
||||||
} success: {
|
|
||||||
LocalNotification.send(
|
LocalNotification.send(
|
||||||
title: "notification.services_restarted".localized,
|
title: "notification.services_restarted".localized,
|
||||||
subtitle: "notification.services_restarted_desc".localized,
|
subtitle: "notification.services_restarted_desc".localized,
|
||||||
@ -61,10 +61,10 @@ extension MainMenu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func stopValetServices() {
|
@MainActor @objc func stopValetServices() {
|
||||||
asyncExecution {
|
Task { // Stop services and show notification
|
||||||
Actions.stopValetServices()
|
await Actions.stopValetServices()
|
||||||
} success: {
|
|
||||||
LocalNotification.send(
|
LocalNotification.send(
|
||||||
title: "notification.services_stopped".localized,
|
title: "notification.services_stopped".localized,
|
||||||
subtitle: "notification.services_stopped_desc".localized,
|
subtitle: "notification.services_stopped_desc".localized,
|
||||||
@ -74,14 +74,14 @@ extension MainMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func restartNginx() {
|
@objc func restartNginx() {
|
||||||
asyncExecution {
|
Task {
|
||||||
Actions.restartNginx()
|
await Actions.restartNginx()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func restartDnsMasq() {
|
@objc func restartDnsMasq() {
|
||||||
asyncExecution {
|
Task {
|
||||||
Actions.restartDnsMasq()
|
await Actions.restartDnsMasq()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,18 +134,18 @@ extension MainMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func toggleExtension(sender: ExtensionMenuItem) {
|
@objc func toggleExtension(sender: ExtensionMenuItem) {
|
||||||
asyncExecution {
|
Task {
|
||||||
sender.phpExtension?.toggle()
|
await sender.phpExtension?.toggle()
|
||||||
|
|
||||||
if Preferences.isEnabled(.autoServiceRestartAfterExtensionToggle) {
|
if Preferences.isEnabled(.autoServiceRestartAfterExtensionToggle) {
|
||||||
Actions.restartPhpFpm()
|
await Actions.restartPhpFpm()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func performRollback() {
|
private func performRollback() {
|
||||||
asyncExecution {
|
Task {
|
||||||
PresetHelper.rollbackPreset?.apply()
|
await PresetHelper.rollbackPreset?.apply()
|
||||||
PresetHelper.rollbackPreset = nil
|
PresetHelper.rollbackPreset = nil
|
||||||
MainMenu.shared.rebuild()
|
MainMenu.shared.rebuild()
|
||||||
}
|
}
|
||||||
@ -171,8 +171,8 @@ extension MainMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func togglePreset(sender: PresetMenuItem) {
|
@objc func togglePreset(sender: PresetMenuItem) {
|
||||||
asyncExecution {
|
Task {
|
||||||
sender.preset?.apply()
|
await sender.preset?.apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,12 +191,11 @@ extension MainMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func openPhpInfo() {
|
@objc func openPhpInfo() {
|
||||||
var url: URL?
|
|
||||||
|
|
||||||
asyncWithBusyUI {
|
asyncWithBusyUI {
|
||||||
url = Actions.createTempPhpInfoFile()
|
Task {
|
||||||
} completion: {
|
let url = await Actions.createTempPhpInfoFile()
|
||||||
if url != nil { NSWorkspace.shared.open(url!) }
|
NSWorkspace.shared.open(url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ extension MainMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if behaviours.contains(.broadcastServicesUpdate) {
|
if behaviours.contains(.broadcastServicesUpdate) {
|
||||||
ServicesManager.shared.loadData()
|
Task { await ServicesManager.loadHomebrewServices() }
|
||||||
}
|
}
|
||||||
|
|
||||||
if error != nil {
|
if error != nil {
|
||||||
|
@ -21,18 +21,18 @@ extension MainMenu {
|
|||||||
await App.shared.environment.process()
|
await App.shared.environment.process()
|
||||||
|
|
||||||
if await Startup().checkEnvironment() {
|
if await Startup().checkEnvironment() {
|
||||||
self.onEnvironmentPass()
|
await self.onEnvironmentPass()
|
||||||
} else {
|
} else {
|
||||||
self.onEnvironmentFail()
|
await self.onEnvironmentFail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
When the environment is all clear and the app can run, let's go.
|
When the environment is all clear and the app can run, let's go.
|
||||||
*/
|
*/
|
||||||
private func onEnvironmentPass() {
|
private func onEnvironmentPass() async {
|
||||||
// Determine what the `php` formula is aliased to
|
// Determine what the `php` formula is aliased to
|
||||||
PhpEnv.shared.determinePhpAlias()
|
await PhpEnv.shared.determinePhpAlias()
|
||||||
|
|
||||||
// Determine install method
|
// Determine install method
|
||||||
Log.info(HomebrewDiagnostics.customCaskInstalled
|
Log.info(HomebrewDiagnostics.customCaskInstalled
|
||||||
@ -49,7 +49,7 @@ extension MainMenu {
|
|||||||
Valet.shared.validateVersion()
|
Valet.shared.validateVersion()
|
||||||
|
|
||||||
// Actually detect the PHP versions
|
// Actually detect the PHP versions
|
||||||
PhpEnv.detectPhpVersions()
|
await PhpEnv.detectPhpVersions()
|
||||||
|
|
||||||
// Check for an alias conflict
|
// Check for an alias conflict
|
||||||
HomebrewDiagnostics.checkForCaskConflict()
|
HomebrewDiagnostics.checkForCaskConflict()
|
||||||
@ -79,7 +79,7 @@ extension MainMenu {
|
|||||||
App.shared.loadGlobalHotkey()
|
App.shared.loadGlobalHotkey()
|
||||||
|
|
||||||
// Preload sites
|
// Preload sites
|
||||||
Valet.shared.startPreloadingSites()
|
await Valet.shared.startPreloadingSites()
|
||||||
|
|
||||||
// After preloading sites, check for PHP-FPM pool conflicts
|
// After preloading sites, check for PHP-FPM pool conflicts
|
||||||
HomebrewDiagnostics.checkForPhpFpmPoolConflicts()
|
HomebrewDiagnostics.checkForPhpFpmPoolConflicts()
|
||||||
@ -88,7 +88,7 @@ extension MainMenu {
|
|||||||
Valet.notifyAboutUnsupportedTLD()
|
Valet.notifyAboutUnsupportedTLD()
|
||||||
|
|
||||||
// Find out which services are active
|
// Find out which services are active
|
||||||
ServicesManager.shared.loadData()
|
await ServicesManager.loadHomebrewServices()
|
||||||
|
|
||||||
// Start the background refresh timer
|
// Start the background refresh timer
|
||||||
startSharedTimer()
|
startSharedTimer()
|
||||||
@ -111,9 +111,7 @@ extension MainMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for updates
|
// Check for updates
|
||||||
DispatchQueue.global(qos: .utility).async {
|
await AppUpdateChecker.checkIfNewerVersionIsAvailable()
|
||||||
AppUpdateChecker.checkIfNewerVersionIsAvailable()
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are ready!
|
// We are ready!
|
||||||
Log.info("PHP Monitor is ready to serve!")
|
Log.info("PHP Monitor is ready to serve!")
|
||||||
@ -122,9 +120,8 @@ extension MainMenu {
|
|||||||
/**
|
/**
|
||||||
When the environment is not OK, present an alert to inform the user.
|
When the environment is not OK, present an alert to inform the user.
|
||||||
*/
|
*/
|
||||||
private func onEnvironmentFail() {
|
private func onEnvironmentFail() async {
|
||||||
DispatchQueue.main.async { [self] in
|
DispatchQueue.main.async { [self] in
|
||||||
|
|
||||||
BetterAlert()
|
BetterAlert()
|
||||||
.withInformation(
|
.withInformation(
|
||||||
title: "alert.cannot_start.title".localized,
|
title: "alert.cannot_start.title".localized,
|
||||||
|
@ -19,36 +19,38 @@ extension MainMenu {
|
|||||||
PhpEnv.shared.isBusy = false
|
PhpEnv.shared.isBusy = false
|
||||||
|
|
||||||
// Reload the site list
|
// Reload the site list
|
||||||
self.reloadDomainListData()
|
Task {
|
||||||
|
await self.reloadDomainListData()
|
||||||
|
|
||||||
// Perform UI updates on main thread
|
// Perform UI updates on main thread
|
||||||
DispatchQueue.main.async { [self] in
|
DispatchQueue.main.async { [self] in
|
||||||
updatePhpVersionInStatusBar()
|
updatePhpVersionInStatusBar()
|
||||||
rebuild()
|
rebuild()
|
||||||
|
|
||||||
if !PhpEnv.shared.validate(version) {
|
if !PhpEnv.shared.validate(version) {
|
||||||
self.suggestFixMyValet(failed: version)
|
self.suggestFixMyValet(failed: version)
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run composer updates
|
||||||
|
if Preferences.isEnabled(.autoComposerGlobalUpdateAfterSwitch) {
|
||||||
|
ComposerWindow().updateGlobalDependencies(
|
||||||
|
notify: false,
|
||||||
|
completion: { _ in
|
||||||
|
self.notifyAboutVersionChange(to: version)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
self.notifyAboutVersionChange(to: version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if Valet still works correctly
|
||||||
|
self.checkForPlatformIssues()
|
||||||
|
|
||||||
|
// Update stats
|
||||||
|
Stats.incrementSuccessfulSwitchCount()
|
||||||
|
Stats.evaluateSponsorMessageShouldBeDisplayed()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run composer updates
|
|
||||||
if Preferences.isEnabled(.autoComposerGlobalUpdateAfterSwitch) {
|
|
||||||
ComposerWindow().updateGlobalDependencies(
|
|
||||||
notify: false,
|
|
||||||
completion: { _ in
|
|
||||||
self.notifyAboutVersionChange(to: version)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
self.notifyAboutVersionChange(to: version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if Valet still works correctly
|
|
||||||
self.checkForPlatformIssues()
|
|
||||||
|
|
||||||
// Update stats
|
|
||||||
Stats.incrementSuccessfulSwitchCount()
|
|
||||||
Stats.evaluateSponsorMessageShouldBeDisplayed()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,25 +104,21 @@ extension MainMenu {
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reloadDomainListData() {
|
private func reloadDomainListData() async {
|
||||||
if let window = App.shared.domainListWindowController {
|
if let window = App.shared.domainListWindowController {
|
||||||
DispatchQueue.main.async {
|
await window.contentVC.reloadDomains()
|
||||||
window.contentVC.reloadDomains()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Valet.shared.reloadSites()
|
await Valet.shared.reloadSites()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func notifyAboutVersionChange(to version: String) {
|
@MainActor private func notifyAboutVersionChange(to version: String) {
|
||||||
DispatchQueue.main.async {
|
LocalNotification.send(
|
||||||
LocalNotification.send(
|
title: String(format: "notification.version_changed_title".localized, version),
|
||||||
title: String(format: "notification.version_changed_title".localized, version),
|
subtitle: String(format: "notification.version_changed_desc".localized, version),
|
||||||
subtitle: String(format: "notification.version_changed_desc".localized, version),
|
preference: .notifyAboutVersionChange
|
||||||
preference: .notifyAboutVersionChange
|
)
|
||||||
)
|
|
||||||
|
|
||||||
PhpEnv.phpInstall.notifyAboutBrokenPhpFpm()
|
Task { await PhpEnv.phpInstall.notifyAboutBrokenPhpFpm() }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,11 +103,11 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
|
|||||||
Reloads the menu in the foreground.
|
Reloads the menu in the foreground.
|
||||||
This mimics the exact behaviours of `asyncExecution` as set in the method below.
|
This mimics the exact behaviours of `asyncExecution` as set in the method below.
|
||||||
*/
|
*/
|
||||||
@objc func reloadPhpMonitorMenuInForeground() {
|
@MainActor @objc func reloadPhpMonitorMenuInForeground() async {
|
||||||
refreshActiveInstallation()
|
refreshActiveInstallation()
|
||||||
refreshIcon()
|
refreshIcon()
|
||||||
rebuild(async: false)
|
rebuild(async: false)
|
||||||
ServicesManager.shared.loadData()
|
await ServicesManager.loadHomebrewServices()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -185,10 +185,8 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
|
|||||||
NSApplication.shared.terminate(nil)
|
NSApplication.shared.terminate(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func checkForUpdates() {
|
@objc func checkForUpdates() async {
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
await AppUpdateChecker.checkIfNewerVersionIsAvailable(initiatedFromBackground: false)
|
||||||
AppUpdateChecker.checkIfNewerVersionIsAvailable(initiatedFromBackground: false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Menu Delegate
|
// MARK: - Menu Delegate
|
||||||
@ -196,7 +194,9 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
|
|||||||
func menuWillOpen(_ menu: NSMenu) {
|
func menuWillOpen(_ menu: NSMenu) {
|
||||||
// Make sure the shortcut key does not trigger this when the menu is open
|
// Make sure the shortcut key does not trigger this when the menu is open
|
||||||
App.shared.shortcutHotkey?.isPaused = true
|
App.shared.shortcutHotkey?.isPaused = true
|
||||||
ServicesManager.shared.loadData()
|
Task {
|
||||||
|
await ServicesManager.loadHomebrewServices()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func menuDidClose(_ menu: NSMenu) {
|
func menuDidClose(_ menu: NSMenu) {
|
||||||
|
@ -74,5 +74,4 @@ class BetterAlertVC: NSViewController {
|
|||||||
self.view.window?.close()
|
self.view.window?.close()
|
||||||
NSApplication.shared.stopModal(withCode: code)
|
NSApplication.shared.stopModal(withCode: code)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,13 @@ extension ActivePhpInstallation {
|
|||||||
This method actively presents a modal if said checks fails, so don't call this method too many times.
|
This method actively presents a modal if said checks fails, so don't call this method too many times.
|
||||||
*/
|
*/
|
||||||
public func notifyAboutBrokenPhpFpm() {
|
public func notifyAboutBrokenPhpFpm() {
|
||||||
if !self.checkPhpFpmStatus() {
|
Task {
|
||||||
|
let fpmStatusConfiguredCorrectly = await self.checkPhpFpmStatus()
|
||||||
|
|
||||||
|
if fpmStatusConfiguredCorrectly {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
BetterAlert()
|
BetterAlert()
|
||||||
.withInformation(
|
.withInformation(
|
||||||
|
@ -26,7 +26,7 @@ struct CustomPrefs: Decodable {
|
|||||||
return self.environmentVariables != nil && !self.environmentVariables!.keys.isEmpty
|
return self.environmentVariables != nil && !self.environmentVariables!.keys.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, deprecated, message: "Use `setCustomEnvironmentVariables` instead")
|
// TODO: Rework this
|
||||||
public func getEnvironmentVariables() -> String {
|
public func getEnvironmentVariables() -> String {
|
||||||
return self.environmentVariables!.map { (key, value) in
|
return self.environmentVariables!.map { (key, value) in
|
||||||
return "export \(key)=\(value)"
|
return "export \(key)=\(value)"
|
||||||
@ -42,12 +42,12 @@ struct CustomPrefs: Decodable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension Preferences {
|
extension Preferences {
|
||||||
func loadCustomPreferences() {
|
func loadCustomPreferences() async {
|
||||||
// Ensure the configuration directory is created if missing
|
// Ensure the configuration directory is created if missing
|
||||||
LegacyShell.run("mkdir -p ~/.config/phpmon")
|
await Shell.quiet("mkdir -p ~/.config/phpmon")
|
||||||
|
|
||||||
// Move the legacy file
|
// Move the legacy file
|
||||||
moveOutdatedConfigurationFile()
|
await moveOutdatedConfigurationFile()
|
||||||
|
|
||||||
// Attempt to load the file if it exists
|
// Attempt to load the file if it exists
|
||||||
let url = URL(fileURLWithPath: "\(Paths.homePath)/.config/phpmon/config.json")
|
let url = URL(fileURLWithPath: "\(Paths.homePath)/.config/phpmon/config.json")
|
||||||
@ -60,10 +60,10 @@ extension Preferences {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func moveOutdatedConfigurationFile() {
|
func moveOutdatedConfigurationFile() async {
|
||||||
if Filesystem.fileExists("~/.phpmon.conf.json") && !Filesystem.fileExists("~/.config/phpmon/config.json") {
|
if Filesystem.fileExists("~/.phpmon.conf.json") && !Filesystem.fileExists("~/.config/phpmon/config.json") {
|
||||||
Log.info("An outdated configuration file was found. Moving it...")
|
Log.info("An outdated configuration file was found. Moving it...")
|
||||||
LegacyShell.run("cp ~/.phpmon.conf.json ~/.config/phpmon/config.json")
|
await Shell.quiet("cp ~/.phpmon.conf.json ~/.config/phpmon/config.json")
|
||||||
Log.info("The configuration file was copied successfully!")
|
Log.info("The configuration file was copied successfully!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,7 +87,9 @@ extension Preferences {
|
|||||||
|
|
||||||
if customPreferences.hasEnvironmentVariables() {
|
if customPreferences.hasEnvironmentVariables() {
|
||||||
Log.info("Configuring the additional exports...")
|
Log.info("Configuring the additional exports...")
|
||||||
LegacyShell.user.exports = customPreferences.getEnvironmentVariables()
|
if let shell = Shell as? SystemShell {
|
||||||
|
shell.exports = customPreferences.getEnvironmentVariables()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
Log.warn("The ~/.config/phpmon/config.json file seems to be missing or malformed.")
|
Log.warn("The ~/.config/phpmon/config.json file seems to be missing or malformed.")
|
||||||
|
@ -27,7 +27,10 @@ class Preferences {
|
|||||||
services: [],
|
services: [],
|
||||||
environmentVariables: [:]
|
environmentVariables: [:]
|
||||||
)
|
)
|
||||||
loadCustomPreferences()
|
|
||||||
|
Task {
|
||||||
|
await loadCustomPreferences()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - First Time Run
|
// MARK: - First Time Run
|
||||||
|
@ -67,19 +67,19 @@ struct Preset: Codable, Equatable {
|
|||||||
/**
|
/**
|
||||||
Applies a given preset.
|
Applies a given preset.
|
||||||
*/
|
*/
|
||||||
public func apply() {
|
public func apply() async {
|
||||||
Task {
|
Task {
|
||||||
// Was this a rollback?
|
// Was this a rollback?
|
||||||
let wasRollback = (self.name == "AutomaticRevertSnapshot")
|
let wasRollback = (self.name == "AutomaticRevertSnapshot")
|
||||||
|
|
||||||
// Save the preset that would revert this preset
|
// Save the preset that would revert this preset
|
||||||
self.persistRevert()
|
await self.persistRevert()
|
||||||
|
|
||||||
// Apply the PHP version if is considered a valid version
|
// Apply the PHP version if is considered a valid version
|
||||||
if self.version != nil {
|
if self.version != nil {
|
||||||
if await !switchToPhpVersionIfValid() {
|
if await !switchToPhpVersionIfValid() {
|
||||||
PresetHelper.rollbackPreset = nil
|
PresetHelper.rollbackPreset = nil
|
||||||
Actions.restartPhpFpm()
|
await Actions.restartPhpFpm()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,7 +94,7 @@ struct Preset: Codable, Equatable {
|
|||||||
for foundExt in PhpEnv.phpInstall.extensions
|
for foundExt in PhpEnv.phpInstall.extensions
|
||||||
where foundExt.name == ext.key && foundExt.enabled != ext.value {
|
where foundExt.name == ext.key && foundExt.enabled != ext.value {
|
||||||
Log.info("Toggling extension \(foundExt.name) in \(foundExt.file)")
|
Log.info("Toggling extension \(foundExt.name) in \(foundExt.file)")
|
||||||
foundExt.toggle()
|
await foundExt.toggle()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,7 +103,7 @@ struct Preset: Codable, Equatable {
|
|||||||
PresetHelper.loadRollbackPresetFromFile()
|
PresetHelper.loadRollbackPresetFromFile()
|
||||||
|
|
||||||
// Restart PHP FPM process (also reloads menu, which will show the preset rollback)
|
// Restart PHP FPM process (also reloads menu, which will show the preset rollback)
|
||||||
Actions.restartPhpFpm()
|
await Actions.restartPhpFpm()
|
||||||
|
|
||||||
// Show the correct notification
|
// Show the correct notification
|
||||||
if wasRollback {
|
if wasRollback {
|
||||||
@ -257,10 +257,10 @@ struct Preset: Codable, Equatable {
|
|||||||
/**
|
/**
|
||||||
Persists the revert as a JSON file, so it can be read from a file after restarting PHP Monitor.
|
Persists the revert as a JSON file, so it can be read from a file after restarting PHP Monitor.
|
||||||
*/
|
*/
|
||||||
private func persistRevert() {
|
private func persistRevert() async {
|
||||||
let data = try! JSONEncoder().encode(self.revertSnapshot)
|
let data = try! JSONEncoder().encode(self.revertSnapshot)
|
||||||
|
|
||||||
LegacyShell.run("mkdir -p ~/.config/phpmon")
|
await Shell.quiet("mkdir -p ~/.config/phpmon")
|
||||||
|
|
||||||
try! String(data: data, encoding: .utf8)!
|
try! String(data: data, encoding: .utf8)!
|
||||||
.write(
|
.write(
|
||||||
|
@ -91,15 +91,13 @@ struct CheckmarkView: View {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public func toggleService() {
|
public func toggleService() async {
|
||||||
if active()! {
|
if active()! {
|
||||||
Actions.stopService(name: serviceName, completion: {
|
await Actions.stopService(name: serviceName)
|
||||||
busy = false
|
busy = false
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
Actions.startService(name: serviceName, completion: {
|
await Actions.startService(name: serviceName)
|
||||||
busy = false
|
busy = false
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +119,7 @@ struct CheckmarkView: View {
|
|||||||
} else {
|
} else {
|
||||||
Button {
|
Button {
|
||||||
busy = true
|
busy = true
|
||||||
toggleService()
|
Task { await toggleService() }
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: active()! ? "checkmark" : "xmark")
|
Image(systemName: active()! ? "checkmark" : "xmark")
|
||||||
.resizable()
|
.resizable()
|
||||||
|
@ -25,7 +25,7 @@ class SystemShell: Shellable {
|
|||||||
Exports are additional environment variables set by the user via the custom configuration.
|
Exports are additional environment variables set by the user via the custom configuration.
|
||||||
These are populated when the configuration file is being loaded.
|
These are populated when the configuration file is being loaded.
|
||||||
*/
|
*/
|
||||||
private(set) var exports: String = ""
|
var exports: String = ""
|
||||||
|
|
||||||
/** Retrieves the user's PATH by opening an interactive shell and echoing $PATH. */
|
/** Retrieves the user's PATH by opening an interactive shell and echoing $PATH. */
|
||||||
private static func getPath() -> String {
|
private static func getPath() -> String {
|
||||||
|
@ -33,6 +33,8 @@ public class TestableShell: Shellable {
|
|||||||
didReceiveOutput: @escaping (String, ShellStream) -> Void,
|
didReceiveOutput: @escaping (String, ShellStream) -> Void,
|
||||||
withTimeout timeout: TimeInterval
|
withTimeout timeout: TimeInterval
|
||||||
) async throws -> (Process, ShellOutput) {
|
) async throws -> (Process, ShellOutput) {
|
||||||
|
assert(expectations.keys.contains(command), "No response declared for command: \(command)")
|
||||||
|
|
||||||
guard let expectation = expectations[command] else {
|
guard let expectation = expectations[command] else {
|
||||||
return (Process(), .err("No Expected Output"))
|
return (Process(), .err("No Expected Output"))
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user