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

🚀 Version 7.1

This commit is contained in:
2024-11-26 15:24:18 +01:00
73 changed files with 955 additions and 873 deletions

View File

@ -14,6 +14,17 @@ It also automatically runs when you try to build the project. You'll get a warni
swiftlint --fix
```
## 📦 Swift Packages
Starting from PHP Monitor 7.1, the app now uses various first-party package dependencies.
The following package dependencies are in use:
* [`NVAppUpdater`](https://github.com/nicoverbruggen/NVAppUpdater)
* [`NVAlert`](https://github.com/nicoverbruggen/NVAlert)
You may need an internet connection to download these dependencies, or you can also clone the dependencies and include them manually.
## ⚙️ Preferences
You can find the persisted configuration file in `~/Library/Preferences/com.nicoverbruggen.phpmon.plist`
@ -40,6 +51,12 @@ Once you have downloaded this repository, open `PHP Monitor.xcodeproj`, and you
If you'd like to create a production build, choose "Any Mac" as the target and select Product > Archive.
## ✅ Testing
In order to properly test everything, you will want to use the _PHP Monitor DEV_ target. There are unit and UI tests both.
You may sporadically see failures in UI tests due to the following error: `Invalid parameter not satisfying: point.x != INFINITY && point.y != INFINITY`. This seems to be an issue with Xcode that Apple may need to resolve?
## 🚀 Release procedure
1. Merge into `main`

View File

@ -64,11 +64,6 @@
C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA927B0890D00544CD5 /* MenuBarIcons.swift */; };
C406A5F3298AD2CE00B5B85A /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C406A5F2298AD2CE00B5B85A /* main.swift */; };
C406A5F7298AD2CF00B5B85A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C406A5F6298AD2CF00B5B85A /* Assets.xcassets */; };
C406A602298AD50D00B5B85A /* Updater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C406A601298AD50D00B5B85A /* Updater.swift */; };
C4080FF627BD8C6400BF2C6B /* BetterAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */; };
C4080FF727BD8C6400BF2C6B /* BetterAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */; };
C4080FFA27BD956700BF2C6B /* BetterAlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */; };
C4080FFB27BD956700BF2C6B /* BetterAlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */; };
C409349D298EE8E900D25014 /* AppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C409349C298EE8E900D25014 /* AppUpdater.swift */; };
C409349E298EE8E900D25014 /* AppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C409349C298EE8E900D25014 /* AppUpdater.swift */; };
C409349F298EE8E900D25014 /* AppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C409349C298EE8E900D25014 /* AppUpdater.swift */; };
@ -295,6 +290,10 @@
C469E700294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */; };
C469E701294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */; };
C469E706294CFDF700A82AB2 /* DomainsListTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C469E702294CFDF700A82AB2 /* DomainsListTest.swift */; };
C46DC7A42C7B55DC00F19D17 /* Favorites.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46DC7A32C7B55DC00F19D17 /* Favorites.swift */; };
C46DC7A52C7B5BC900F19D17 /* Favorites.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46DC7A32C7B55DC00F19D17 /* Favorites.swift */; };
C46DC7A62C7B5BC900F19D17 /* Favorites.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46DC7A32C7B55DC00F19D17 /* Favorites.swift */; };
C46DC7A72C7B5BCA00F19D17 /* Favorites.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46DC7A32C7B55DC00F19D17 /* Favorites.swift */; };
C46EBC4428DB95F0007ACC74 /* ShellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */; };
C46EBC4528DB95F0007ACC74 /* ShellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */; };
C46EBC4728DB9644007ACC74 /* RealShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4628DB9644007ACC74 /* RealShell.swift */; };
@ -305,6 +304,15 @@
C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */; };
C46FA9892822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */; };
C46FA98C2822F08F00D78807 /* PhpConfigurationFileTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA98A2822F08F00D78807 /* PhpConfigurationFileTest.swift */; };
C47014FC2C46D31B0069AAE7 /* NVAppUpdater in Frameworks */ = {isa = PBXBuildFile; productRef = C47014FB2C46D31B0069AAE7 /* NVAppUpdater */; };
C47014FF2C46D57C0069AAE7 /* NVAlert in Frameworks */ = {isa = PBXBuildFile; productRef = C47014FE2C46D57C0069AAE7 /* NVAlert */; };
C47015022C46D6910069AAE7 /* NVAlertExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47015012C46D6910069AAE7 /* NVAlertExtension.swift */; };
C47015032C46D7F00069AAE7 /* NVAlertExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47015012C46D6910069AAE7 /* NVAlertExtension.swift */; };
C47015042C46D7F00069AAE7 /* NVAlertExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47015012C46D6910069AAE7 /* NVAlertExtension.swift */; };
C47015052C46D7F10069AAE7 /* NVAlertExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47015012C46D6910069AAE7 /* NVAlertExtension.swift */; };
C47015072C46D8180069AAE7 /* NVAlert in Frameworks */ = {isa = PBXBuildFile; productRef = C47015062C46D8180069AAE7 /* NVAlert */; };
C470150B2C46D81E0069AAE7 /* NVAlert in Frameworks */ = {isa = PBXBuildFile; productRef = C470150A2C46D81E0069AAE7 /* NVAlert */; };
C470150D2C46D83E0069AAE7 /* NVAlert in Frameworks */ = {isa = PBXBuildFile; productRef = C470150C2C46D83E0069AAE7 /* NVAlert */; };
C4709CA228524B3400088BB8 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4709CA128524B3400088BB8 /* StatsView.swift */; };
C471E79328F9B21F0021E251 /* ActiveFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */; };
C471E79428F9B23B0021E251 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; };
@ -392,10 +400,6 @@
C471E81828F9BAE80021E251 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; };
C471E81928F9BAE80021E251 /* NSMenuItemExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */; };
C471E81A28F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */; };
C471E81B28F9BB250021E251 /* BetterAlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */; };
C471E81C28F9BB250021E251 /* BetterAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */; };
C471E81D28F9BB260021E251 /* BetterAlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */; };
C471E81E28F9BB260021E251 /* BetterAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */; };
C471E81F28F9BB290021E251 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; };
C471E82028F9BB290021E251 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; };
C471E82128F9BB2E0021E251 /* ProjectTypeDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* ProjectTypeDetection.swift */; };
@ -720,8 +724,6 @@
C4C3643A28AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */; };
C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; };
C4C3ED4327834C5200AB15D8 /* CustomPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */; };
C4C75F5A298C2D5700DFD82E /* LaunchControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C75F59298C2D5700DFD82E /* LaunchControl.swift */; };
C4C75F5C298C31C000DFD82E /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C75F5B298C31C000DFD82E /* Utility.swift */; };
C4C8900328F0E28800CE5E97 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; };
C4C8900528F0E3D100CE5E97 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; };
C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */; };
@ -946,9 +948,6 @@
C406A5F2298AD2CE00B5B85A /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
C406A5F6298AD2CF00B5B85A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
C406A5FB298AD2CF00B5B85A /* phpmon-updater.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "phpmon-updater.entitlements"; sourceTree = "<group>"; };
C406A601298AD50D00B5B85A /* Updater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Updater.swift; sourceTree = "<group>"; };
C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BetterAlert.swift; sourceTree = "<group>"; };
C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BetterAlertVC.swift; sourceTree = "<group>"; };
C409349C298EE8E900D25014 /* AppUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdater.swift; sourceTree = "<group>"; };
C40934A1298EEB2C00D25014 /* CaskFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaskFile.swift; sourceTree = "<group>"; };
C40934A6298EEB8700D25014 /* phpmon-dev.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = "phpmon-dev.rb"; sourceTree = "<group>"; };
@ -1054,12 +1053,14 @@
C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListCellProtocol.swift; sourceTree = "<group>"; };
C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeValetProxy.swift; sourceTree = "<group>"; };
C469E702294CFDF700A82AB2 /* DomainsListTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainsListTest.swift; sourceTree = "<group>"; };
C46DC7A32C7B55DC00F19D17 /* Favorites.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Favorites.swift; sourceTree = "<group>"; };
C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellProtocol.swift; sourceTree = "<group>"; };
C46EBC4628DB9644007ACC74 /* RealShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealShell.swift; sourceTree = "<group>"; };
C46EBC4928DB966A007ACC74 /* TestableShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableShell.swift; sourceTree = "<group>"; };
C46FA23E246C358E00944F05 /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = "<group>"; };
C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigurationFile.swift; sourceTree = "<group>"; };
C46FA98A2822F08F00D78807 /* PhpConfigurationFileTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigurationFileTest.swift; sourceTree = "<group>"; };
C47015012C46D6910069AAE7 /* NVAlertExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NVAlertExtension.swift; sourceTree = "<group>"; };
C4709CA128524B3400088BB8 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; };
C471E79228F9B1D30021E251 /* PHP Monitor.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "PHP Monitor.xctestplan"; sourceTree = "<group>"; };
C471E7AD28F9B4940021E251 /* Feature Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Feature Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
@ -1120,8 +1121,6 @@
C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Items.swift"; sourceTree = "<group>"; };
C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Startup.swift"; sourceTree = "<group>"; };
C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPrefs.swift; sourceTree = "<group>"; };
C4C75F59298C2D5700DFD82E /* LaunchControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchControl.swift; sourceTree = "<group>"; };
C4C75F5B298C31C000DFD82E /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = "<group>"; };
C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystemProtocol.swift; sourceTree = "<group>"; };
C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealFileSystem.swift; sourceTree = "<group>"; };
C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveFileSystem.swift; sourceTree = "<group>"; };
@ -1198,6 +1197,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C47014FC2C46D31B0069AAE7 /* NVAppUpdater in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1205,6 +1205,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C47014FF2C46D57C0069AAE7 /* NVAlert in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1212,6 +1213,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C470150B2C46D81E0069AAE7 /* NVAlert in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1219,6 +1221,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C470150D2C46D83E0069AAE7 /* NVAlert in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1226,6 +1229,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C47015072C46D8180069AAE7 /* NVAlert in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1348,24 +1352,12 @@
isa = PBXGroup;
children = (
C406A5F2298AD2CE00B5B85A /* main.swift */,
C406A601298AD50D00B5B85A /* Updater.swift */,
C4C75F5B298C31C000DFD82E /* Utility.swift */,
C4C75F59298C2D5700DFD82E /* LaunchControl.swift */,
C406A5F6298AD2CF00B5B85A /* Assets.xcassets */,
C406A5FB298AD2CF00B5B85A /* phpmon-updater.entitlements */,
);
path = "phpmon-updater";
sourceTree = "<group>";
};
C4080FF827BD955900BF2C6B /* Notice */ = {
isa = PBXGroup;
children = (
C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */,
C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */,
);
path = Notice;
sourceTree = "<group>";
};
C40C5C9E2846A42D00E28255 /* Presets */ = {
isa = PBXGroup;
children = (
@ -1468,7 +1460,6 @@
C41E181722CB61EB0072CF09 /* Domain */ = {
isa = PBXGroup;
children = (
C4080FF827BD955900BF2C6B /* Notice */,
C4AF9F6B275445D300D44ED0 /* Integrations */,
C4B13B1D25C4915000548C3A /* App */,
C4D9ADBD27761084007277F4 /* PHP */,
@ -1789,6 +1780,7 @@
isa = PBXGroup;
children = (
C44DFA852A67090A00B98ED5 /* UI */,
C46DC7A32C7B55DC00F19D17 /* Favorites.swift */,
);
path = "Domain List";
sourceTree = "<group>";
@ -2235,6 +2227,7 @@
C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */,
C4E2E84928FC1E70003B070C /* DataExtension.swift */,
C4D36619291173EA006BD146 /* DictionaryExtension.swift */,
C47015012C46D6910069AAE7 /* NVAlertExtension.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -2255,6 +2248,9 @@
dependencies = (
);
name = "PHP Monitor Self-Updater";
packageProductDependencies = (
C47014FB2C46D31B0069AAE7 /* NVAppUpdater */,
);
productName = "PHP Monitor Updater";
productReference = C406A5F0298AD2CE00B5B85A /* PHP Monitor Self-Updater.app */;
productType = "com.apple.product-type.application";
@ -2275,6 +2271,7 @@
);
name = "PHP Monitor";
packageProductDependencies = (
C47014FE2C46D57C0069AAE7 /* NVAlert */,
);
productName = phpmon;
productReference = C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */;
@ -2294,6 +2291,9 @@
C471E7B228F9B4940021E251 /* PBXTargetDependency */,
);
name = "Feature Tests";
packageProductDependencies = (
C470150A2C46D81E0069AAE7 /* NVAlert */,
);
productName = "Feature Tests";
productReference = C471E7AD28F9B4940021E251 /* Feature Tests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
@ -2312,6 +2312,9 @@
C471E7C328F9B90F0021E251 /* PBXTargetDependency */,
);
name = "UI Tests";
packageProductDependencies = (
C470150C2C46D83E0069AAE7 /* NVAlert */,
);
productName = "UI Tests";
productReference = C471E7BC28F9B90F0021E251 /* UI Tests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
@ -2331,6 +2334,7 @@
);
name = "Unit Tests";
packageProductDependencies = (
C47015062C46D8180069AAE7 /* NVAlert */,
);
productName = "phpmon-tests";
productReference = C4F7807925D7F84B000DBC97 /* Unit Tests.xctest */;
@ -2344,7 +2348,7 @@
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1420;
LastUpgradeCheck = 1530;
LastUpgradeCheck = 1610;
ORGANIZATIONNAME = "Nico Verbruggen";
TargetAttributes = {
C406A5EF298AD2CE00B5B85A = {
@ -2381,6 +2385,8 @@
);
mainGroup = C41C1B2A22B0097F00E7CF16;
packageReferences = (
C47014FA2C46D31B0069AAE7 /* XCRemoteSwiftPackageReference "NVAppUpdater" */,
C47014FD2C46D57C0069AAE7 /* XCRemoteSwiftPackageReference "NVAlert" */,
);
productRefGroup = C41C1B3422B0097F00E7CF16 /* Products */;
projectDirPath = "";
@ -2508,10 +2514,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C4C75F5C298C31C000DFD82E /* Utility.swift in Sources */,
C490E3BC29BCA375006D2DE6 /* Measurements.swift in Sources */,
C406A602298AD50D00B5B85A /* Updater.swift in Sources */,
C4C75F5A298C2D5700DFD82E /* LaunchControl.swift in Sources */,
C41F3D08298AED0D0042ACBF /* System.swift in Sources */,
C406A5F3298AD2CE00B5B85A /* main.swift in Sources */,
);
@ -2552,7 +2555,6 @@
C48DDD0D29C75C9E00D032D9 /* BlockingOverlayView.swift in Sources */,
C45B91532956123A00F4EC78 /* FakeServicesManager.swift in Sources */,
C41C708D28AA7F7900E8D498 /* NoWarningsView.swift in Sources */,
C4080FF627BD8C6400BF2C6B /* BetterAlert.swift in Sources */,
0309E6672B0D4B2F002AC007 /* BrewExtensionsObservable.swift in Sources */,
C4E0F7ED27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */,
C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */,
@ -2686,11 +2688,11 @@
C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */,
C4FACE83288F1F9700FC478F /* OnboardingWindowController.swift in Sources */,
C4415E8D2B0287E90035F520 /* BrewFormulaeObservable.swift in Sources */,
C4080FFA27BD956700BF2C6B /* BetterAlertVC.swift in Sources */,
C43FDBE929A932B0003D85EC /* PhpConfigChecker.swift in Sources */,
C4BF56AB2949381100379603 /* FakeValetInteractor.swift in Sources */,
C4B5635E276AB09000F12CCB /* VersionExtractor.swift in Sources */,
C451AFF62969E40F0078E617 /* HelpButton.swift in Sources */,
C46DC7A42C7B55DC00F19D17 /* Favorites.swift in Sources */,
54D9E0B627E4F51E003B9AD9 /* HotKey.swift in Sources */,
C4AFC4AE29C4F32F00BF4E0D /* BrewPhpFormula.swift in Sources */,
C4D936C927E3EB6100BD69FE /* PhpHelper.swift in Sources */,
@ -2715,6 +2717,7 @@
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */,
C493084A279F331F009C240B /* AddSiteVC.swift in Sources */,
C4DEB7D427A5D60B00834718 /* Stats.swift in Sources */,
C47015022C46D6910069AAE7 /* NVAlertExtension.swift in Sources */,
C4E49DEA28F7643D0026AC4E /* CommandProtocol.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -2788,6 +2791,7 @@
C4E2E86628FC2F1B003B070C /* XCPMApplication.swift in Sources */,
C471E85F28F9BB650021E251 /* DomainListVC+Actions.swift in Sources */,
C4D5576629C77CC5001A44CD /* PhpVersionManagerWindowController.swift in Sources */,
C47015042C46D7F00069AAE7 /* NVAlertExtension.swift in Sources */,
C4ACE9E329F84EDD00110766 /* PhpGuard.swift in Sources */,
C471E86028F9BB650021E251 /* SelectionVC.swift in Sources */,
C471E86128F9BB650021E251 /* AddSiteVC.swift in Sources */,
@ -2855,6 +2859,7 @@
C4415E8F2B0287E90035F520 /* BrewFormulaeObservable.swift in Sources */,
C471E7D828F9BA8F0021E251 /* FileSystemProtocol.swift in Sources */,
C471E7F328F9BAC70021E251 /* PhpHelper.swift in Sources */,
C46DC7A62C7B5BC900F19D17 /* Favorites.swift in Sources */,
C471E7E728F9BAC20021E251 /* Constants.swift in Sources */,
C471E81628F9BAE80021E251 /* DateExtension.swift in Sources */,
C469E700294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */,
@ -2879,7 +2884,6 @@
C471E82A28F9BB330021E251 /* ValetListable.swift in Sources */,
031E2B6B2B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */,
C471E82728F9BB310021E251 /* BrewDiagnostics.swift in Sources */,
C471E81C28F9BB250021E251 /* BetterAlert.swift in Sources */,
C471E7DB28F9BA8F0021E251 /* RealShell.swift in Sources */,
C490E3B929BCA368006D2DE6 /* App+BrewWatch.swift in Sources */,
C471E7FF28F9BAD10021E251 /* Xdebug.swift in Sources */,
@ -2896,7 +2900,6 @@
C42106682AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */,
C4B79EC829CA474200A483EE /* FakeCommand.swift in Sources */,
C471E7DE28F9BAA30021E251 /* CommandProtocol.swift in Sources */,
C471E81B28F9BB250021E251 /* BetterAlertVC.swift in Sources */,
C471E82928F9BB330021E251 /* Valet.swift in Sources */,
C471E80728F9BAD40021E251 /* PhpConfigurationFile.swift in Sources */,
C471E7D528F9BA8F0021E251 /* TestableConfigurations.swift in Sources */,
@ -3004,6 +3007,7 @@
C471E8C628F9BB8F0021E251 /* PMTableView.swift in Sources */,
C471E8C728F9BB8F0021E251 /* Warning.swift in Sources */,
C471E8C828F9BB8F0021E251 /* WarningManager.swift in Sources */,
C46DC7A72C7B5BCA00F19D17 /* Favorites.swift in Sources */,
C471E8C928F9BB8F0021E251 /* PhpDoctorWindowController.swift in Sources */,
C41ADCEB2970CCC700120423 /* FSNotifier.swift in Sources */,
C471E8CA28F9BB8F0021E251 /* OnboardingWindowController.swift in Sources */,
@ -3052,6 +3056,7 @@
C456A0CE2AA6166F0080144F /* BytePhpPreference.swift in Sources */,
C4FD87A829AB9ABD0002D701 /* PhpConfigChecker.swift in Sources */,
C45B9151295608E300F4EC78 /* ValetServicesManager.swift in Sources */,
C47015032C46D7F00069AAE7 /* NVAlertExtension.swift in Sources */,
C471E8EC28F9BB8F0021E251 /* SwiftUIHelper.swift in Sources */,
C471E8EE28F9BB8F0021E251 /* HotKey.swift in Sources */,
C471E8EF28F9BB8F0021E251 /* HotKeysController.swift in Sources */,
@ -3092,7 +3097,6 @@
C4463FCF29804BCB007B93D5 /* RCFile.swift in Sources */,
C471E82C28F9BB340021E251 /* ValetListable.swift in Sources */,
C471E82828F9BB310021E251 /* BrewDiagnostics.swift in Sources */,
C471E81E28F9BB260021E251 /* BetterAlert.swift in Sources */,
C43BCD4729FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */,
C44E985F29B23EBF0059F773 /* UpdateCheckTest.swift in Sources */,
C4513F8E2B13E2E5001AD760 /* PhpExtensionManagerWindowController.swift in Sources */,
@ -3108,7 +3112,6 @@
C471E7DD28F9BAA30021E251 /* CommandProtocol.swift in Sources */,
C471E7D128F9BA630021E251 /* RealFileSystem.swift in Sources */,
033D459B2B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */,
C471E81D28F9BB260021E251 /* BetterAlertVC.swift in Sources */,
C471E82B28F9BB340021E251 /* Valet.swift in Sources */,
C471E80328F9BAD40021E251 /* PhpConfigurationFile.swift in Sources */,
C471E7C928F9BA2F0021E251 /* TestableConfigurations.swift in Sources */,
@ -3165,7 +3168,6 @@
C44A874928905BB000498BC4 /* ProgressVC.swift in Sources */,
C4D9ADC0277610E1007277F4 /* PhpSwitcher.swift in Sources */,
C485707528BF454F00539B36 /* StatsView.swift in Sources */,
C4080FFB27BD956700BF2C6B /* BetterAlertVC.swift in Sources */,
C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */,
54D9E0BB27E4F51E003B9AD9 /* ModifierFlagsExtension.swift in Sources */,
C485707328BF454300539B36 /* OnboardingView.swift in Sources */,
@ -3213,6 +3215,7 @@
C4F780AE25D80B37000DBC97 /* PhpExtensionTest.swift in Sources */,
C456A0C72AA614BD0080144F /* PhpPreference.swift in Sources */,
C42106672AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */,
C46DC7A52C7B5BC900F19D17 /* Favorites.swift in Sources */,
C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
C4FC21B128391F8E00D368BB /* MainMenu+Actions.swift in Sources */,
54D9E0B927E4F51E003B9AD9 /* KeyCombo.swift in Sources */,
@ -3258,7 +3261,6 @@
C40B24F427A310830018C7D2 /* StatusMenu.swift in Sources */,
C417DC75277614690015E6EE /* Helpers.swift in Sources */,
C469E6FF294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */,
C4080FF727BD8C6400BF2C6B /* BetterAlert.swift in Sources */,
C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */,
5489625928313231004F647A /* CreatedFromFile.swift in Sources */,
C4513F932B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */,
@ -3316,6 +3318,7 @@
C4F30B0A278E1A1A00755FCE /* ComposerJson.swift in Sources */,
C4C0E8E027F88AEB002D32A9 /* FakeDomainScanner.swift in Sources */,
C4463FCD29804BCB007B93D5 /* RCFile.swift in Sources */,
C47015052C46D7F10069AAE7 /* NVAlertExtension.swift in Sources */,
C409349E298EE8E900D25014 /* AppUpdater.swift in Sources */,
C4AF9F7D275454A900D44ED0 /* ValetVersionExtractorTest.swift in Sources */,
C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */,
@ -3432,14 +3435,14 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor Self-Updater";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023-2024 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -3467,14 +3470,14 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor Self-Updater";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023-2024 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -3502,14 +3505,14 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor Self-Updater";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023-2024 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -3537,14 +3540,14 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor Self-Updater";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023-2024 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -3685,7 +3688,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1492;
CURRENT_PROJECT_VERSION = 1525;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -3698,7 +3701,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0.6;
MARKETING_VERSION = 7.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -3716,7 +3719,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1492;
CURRENT_PROJECT_VERSION = 1525;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -3729,7 +3732,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0.6;
MARKETING_VERSION = 7.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -3957,7 +3960,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1492;
CURRENT_PROJECT_VERSION = 1525;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -3970,7 +3973,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0.6;
MARKETING_VERSION = 7.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) DEV";
@ -4074,7 +4077,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1492;
CURRENT_PROJECT_VERSION = 1525;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -4087,7 +4090,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0.6;
MARKETING_VERSION = 7.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) DEV";
@ -4191,7 +4194,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1492;
CURRENT_PROJECT_VERSION = 1525;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -4204,7 +4207,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0.6;
MARKETING_VERSION = 7.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.eap;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) EAP";
@ -4232,14 +4235,14 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor Self-Updater";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023-2024 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -4374,7 +4377,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1492;
CURRENT_PROJECT_VERSION = 1525;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -4387,7 +4390,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0.6;
MARKETING_VERSION = 7.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.eap;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) EAP";
@ -4415,14 +4418,14 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor Self-Updater";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023-2024 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -4614,6 +4617,53 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
C47014FA2C46D31B0069AAE7 /* XCRemoteSwiftPackageReference "NVAppUpdater" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/nicoverbruggen/NVAppUpdater";
requirement = {
branch = main;
kind = branch;
};
};
C47014FD2C46D57C0069AAE7 /* XCRemoteSwiftPackageReference "NVAlert" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/nicoverbruggen/NVAlert";
requirement = {
branch = main;
kind = branch;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
C47014FB2C46D31B0069AAE7 /* NVAppUpdater */ = {
isa = XCSwiftPackageProductDependency;
package = C47014FA2C46D31B0069AAE7 /* XCRemoteSwiftPackageReference "NVAppUpdater" */;
productName = NVAppUpdater;
};
C47014FE2C46D57C0069AAE7 /* NVAlert */ = {
isa = XCSwiftPackageProductDependency;
package = C47014FD2C46D57C0069AAE7 /* XCRemoteSwiftPackageReference "NVAlert" */;
productName = NVAlert;
};
C47015062C46D8180069AAE7 /* NVAlert */ = {
isa = XCSwiftPackageProductDependency;
package = C47014FD2C46D57C0069AAE7 /* XCRemoteSwiftPackageReference "NVAlert" */;
productName = NVAlert;
};
C470150A2C46D81E0069AAE7 /* NVAlert */ = {
isa = XCSwiftPackageProductDependency;
package = C47014FD2C46D57C0069AAE7 /* XCRemoteSwiftPackageReference "NVAlert" */;
productName = NVAlert;
};
C470150C2C46D83E0069AAE7 /* NVAlert */ = {
isa = XCSwiftPackageProductDependency;
package = C47014FD2C46D57C0069AAE7 /* XCRemoteSwiftPackageReference "NVAlert" */;
productName = NVAlert;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = C41C1B2B22B0097F00E7CF16 /* Project object */;
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1540"
LastUpgradeVersion = "1610"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1540"
LastUpgradeVersion = "1610"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1540"
LastUpgradeVersion = "1610"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1540"
LastUpgradeVersion = "1610"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1540"
LastUpgradeVersion = "1610"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -22,7 +22,7 @@ You can also add new domains as links, isolate sites, manage various services, a
PHP Monitor is a universal application that runs natively on Apple Silicon **and** Intel-based Macs.
* Your user account can administer your computer (required for some functionality, e.g. certificate generation)
* macOS 12.4 or later (Monterey, Ventura and Sonoma are supported)
* macOS 12.4 or later
* Homebrew is installed in the default location (`/usr/local/homebrew` or `/opt/homebrew`)
* Homebrew `php` formula is installed
* Optional but recommended: Laravel Valet

View File

@ -6,7 +6,7 @@ Generally speaking, only the latest version of **PHP Monitor** is supported, exc
| Version | Apple Silicon | Supported | Supported macOS | Minimum Deployment | Detected PHP Versions | Recommended Valet Version |
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
| 7.0 | ✅ Universal binary | ✅ Yes | Monterey (12.4+)<br/>Ventura (13.0+)<br/>Sonoma (14.0) | macOS 12.4+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.4 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.4 (w/ Valet 4.x)| 3.0 or higher recommended<br/> 2.16.2 minimum |
| 7.1 | ✅ Universal binary | ✅ Yes | Monterey (12.4+)<br/>Ventura (13.0+)<br/>Sonoma (14.0+)<br/>Sequoia (15.0+) | macOS 12.4+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.4 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.5 (w/ Valet 4.x)| 3.0 or higher recommended<br/> 2.16.2 minimum |
## Legacy versions
@ -14,6 +14,7 @@ These versions of PHP Monitor are no longer supported, but if youre using an
| Version | Apple Silicon | Supported | Supported macOS | Minimum Deployment | Detected PHP Versions | Minimum Required Valet Version |
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
| 7.0 | ✅ Universal binary | ❌ | Monterey (12.4+)<br/>Ventura (13.0+)<br/>Sonoma (14.0) | macOS 12.4+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.4 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.4 (w/ Valet 4.x)| 3.0 or higher recommended<br/> 2.16.2 minimum |
| 6.2 | ✅ Universal binary | ❌ | Monterey (12.4+)<br/>Ventura (13.0+)<br/>Sonoma (14.0) | macOS 12.4+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.4 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.4 (w/ Valet 4.x)| 3.0 or higher recommended<br/> 2.16.2 minimum |
| 6.1 | ✅ Universal binary | ❌ | Monterey (12.4+)<br/>Ventura (13.0+)<br/>Sonoma (14.0) | macOS 12.4+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.4 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.4 (w/ Valet 4.x)| 3.0 or higher recommended<br/> 2.16.2 minimum |
| 6.0 | ✅ Universal binary | ❌ | Monterey (12.4+)<br/>Ventura (13.0+) | macOS 12.4+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.2 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.2 (w/ Valet 4.x) | 3.0 or higher recommended<br/> 2.16.2 minimum |

View File

@ -1,46 +0,0 @@
//
// LaunchControl.swift
// PHP Monitor Self-Updater
//
// Created by Nico Verbruggen on 02/02/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
class LaunchControl {
public static func smartRestart(priority: [String]) async {
for appPath in priority {
if FileManager.default.fileExists(atPath: appPath) {
let app = await LaunchControl.startApplication(at: appPath)
if app != nil {
return
}
}
}
}
public static func terminateApplications(bundleIds: [String]) async {
let runningApplications = NSWorkspace.shared.runningApplications
// Terminate all instances found
for id in bundleIds {
if let phpmon = runningApplications.first(where: {
(application) in return application.bundleIdentifier == id
}) {
phpmon.terminate()
}
}
}
public static func startApplication(at path: String) async -> NSRunningApplication? {
await withCheckedContinuation { continuation in
let url = NSURL(fileURLWithPath: path, isDirectory: true) as URL
let configuration = NSWorkspace.OpenConfiguration()
NSWorkspace.shared.openApplication(at: url, configuration: configuration) { phpmon, error in
continuation.resume(returning: phpmon)
}
}
}
}

View File

@ -1,162 +0,0 @@
//
// Updater.swift
// PHP Monitor Updater
//
// Created by Nico Verbruggen on 01/02/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Cocoa
class Updater: NSObject, NSApplicationDelegate {
var updaterDirectory: String = ""
var manifestPath: String = ""
var manifest: ReleaseManifest! = nil
func applicationDidFinishLaunching(_ aNotification: Notification) {
Task { await self.installUpdate() }
}
func installUpdate() async {
print("PHP MONITOR SELF-UPDATER by Nico Verbruggen")
print("===========================================")
self.updaterDirectory = "~/.config/phpmon/updater"
.replacingOccurrences(of: "~", with: NSHomeDirectory())
print("Updater directory set to: \(self.updaterDirectory)")
self.manifestPath = "\(updaterDirectory)/update.json"
// Fetch the manifest on the local filesystem
let manifest = await parseManifest()!
// Download the latest file
let zipPath = await download(manifest)
// Terminate all instances of PHP Monitor first
await LaunchControl.terminateApplications(bundleIds: [
"com.nicoverbruggen.phpmon.eap",
"com.nicoverbruggen.phpmon.dev",
"com.nicoverbruggen.phpmon"
])
// Install the app based on the zip
let appPath = await extractAndInstall(zipPath: zipPath)
// Restart PHP Monitor, this will also close the updater
_ = await LaunchControl.startApplication(at: appPath)
exit(1)
}
func applicationWillTerminate(_ aNotification: Notification) {
exit(1)
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return false
}
private func parseManifest() async -> ReleaseManifest? {
// Read out the correct information from the manifest JSON
print("Checking manifest file at \(manifestPath)...")
do {
let manifestText = try String(contentsOfFile: manifestPath)
manifest = try JSONDecoder().decode(ReleaseManifest.self, from: manifestText.data(using: .utf8)!)
return manifest
} catch {
print("Parsing the manifest failed (or the manifest file doesn't exist)!")
await Alert.show(description: "The manifest file for a potential update was not found. Please try searching for updates again in PHP Monitor.")
}
return nil
}
private func download(_ manifest: ReleaseManifest) async -> String {
// Remove all zips
system_quiet("rm -rf \(updaterDirectory)/*.zip")
// Download the file (and follow redirects + no output on failure)
system_quiet("cd \"\(updaterDirectory)\" && curl \(manifest.url) -fLO --max-time 20")
// Identify the downloaded file
let filename = system("cd \"\(updaterDirectory)\" && ls | grep .zip")
.trimmingCharacters(in: .whitespacesAndNewlines)
// Ensure the zip exists
if filename.isEmpty {
print("The update has not been downloaded. Sadly, that means that PHP Monitor cannot not updated!")
await Alert.show(description: "The update could not be downloaded, or the file was not correctly written to disk. \n\nPlease try again. \n\n(Note that the download will time-out after 20 seconds, so for slow connections it is recommended to manually download the update.)")
}
// Calculate the checksum for the downloaded file
let checksum = system("openssl dgst -sha256 \"\(updaterDirectory)/\(filename)\" | awk '{print $NF}'")
.trimmingCharacters(in: .whitespacesAndNewlines)
// Compare the checksums
print("""
Comparing checksums...
Expected SHA256: \(manifest.sha256)
Actual SHA256: \(checksum)
""")
// Make sure the checksum matches before we do anything with the file
if checksum != manifest.sha256 {
print("The checksums failed to match. Cancelling!")
await Alert.show(description: "The downloaded update failed checksum validation. Please try again. If this issue persists, there may be an issue with the server and I do not recommend upgrading.")
}
// Return the path to the zip
return "\(updaterDirectory)/\(filename)"
}
private func extractAndInstall(zipPath: String) async -> String {
// Remove the directory that will contain the extracted update
system_quiet("rm -rf \"\(updaterDirectory)/extracted\"")
// Recreate the directory where we will unzip the .app file
system_quiet("mkdir -p \"\(updaterDirectory)/extracted\"")
// Make sure the updater directory exists
var isDirectory: ObjCBool = true
if !FileManager.default.fileExists(atPath: "\(updaterDirectory)/extracted", isDirectory: &isDirectory) {
await Alert.show(description: "The updater directory is missing. The automatic updater will quit. Make sure that ` ~/.config/phpmon/updater` is writeable.")
}
// Unzip the file
system_quiet("unzip \"\(zipPath)\" -d \"\(updaterDirectory)/extracted\"")
// Find the .app file
let app = system("ls \"\(updaterDirectory)/extracted\" | grep .app")
.trimmingCharacters(in: .whitespacesAndNewlines)
print("Finished extracting: \(updaterDirectory)/extracted/\(app)")
// Make sure the file was extracted
if app.isEmpty {
await Alert.show(description: "The downloaded file could not be extracted. The automatic updater will quit. Make sure that ` ~/.config/phpmon/updater` is writeable.")
}
// Remove the original app
print("Removing \(app) before replacing...")
system_quiet("rm -rf \"/Applications/\(app)\"")
// Move the new app in place
system_quiet("mv \"\(updaterDirectory)/extracted/\(app)\" \"/Applications/\(app)\"")
// Remove the zip
system_quiet("rm \"\(zipPath)\"")
// Remove the manifest
system_quiet("rm \"\(manifestPath)\"")
// Write a file that is only written when we upgraded successfully
system_quiet("touch \"\(updaterDirectory)/upgrade.success\"")
// Return the new location of the app
return "/Applications/\(app)"
}
}

View File

@ -1,34 +0,0 @@
//
// Utility.swift
// PHP Monitor Self-Updater
//
// Created by Nico Verbruggen on 02/02/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
class Alert {
public static func show(description: String, shouldExit: Bool = true) async {
await withUnsafeContinuation { continuation in
DispatchQueue.main.async {
let alert = NSAlert()
alert.messageText = "The app could not be updated."
alert.informativeText = description
alert.addButton(withTitle: "OK")
alert.alertStyle = .critical
alert.runModal()
if shouldExit {
exit(0)
}
continuation.resume()
}
}
}
}
public struct ReleaseManifest: Codable {
let url: String
let sha256: String
}

View File

@ -7,8 +7,17 @@
//
import Cocoa
import NVAppUpdater
let app = NSApplication.shared
let delegate = Updater()
app.delegate = delegate
let delegate = SelfUpdater(
appName: "PHP Monitor",
bundleIdentifiers: [
"com.nicoverbruggen.phpmon.eap",
"com.nicoverbruggen.phpmon.dev",
"com.nicoverbruggen.phpmon"
],
selfUpdaterPath: "~/.config/phpmon/updater"
)
NSApplication.shared.delegate = delegate
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)

View File

@ -24,13 +24,13 @@ struct Constants {
This hardcoded list will expire and will need to be modified when
the cutoff date occurs, which is when the `php` formula will
become PHP 8.4, and a new build will need to be made.
become PHP 8.5, and a new build will need to be made.
If users launch an older version of the app, then a warning
will be displayed to let them know that certain operations
will not work correctly and that they need to update their app.
*/
static let PhpFormulaeCutoffDate = "2024-11-30" // YYYY-MM-DD
static let PhpFormulaeCutoffDate = "2025-11-30" // YYYY-MM-DD
/**
* The PHP versions that are considered pre-release versions.
@ -39,6 +39,7 @@ struct Constants {
*/
static var ExperimentalPhpVersions: Set<String> {
let releaseDates = [
"8.5": Date.fromString(Self.PhpFormulaeCutoffDate),
"8.4": Date.fromString("2024-11-22")
]
@ -72,8 +73,8 @@ struct Constants {
static let DetectedPhpVersions: Set = [
"5.6",
"7.0", "7.1", "7.2", "7.3", "7.4",
"8.0", "8.1", "8.2", "8.3",
"8.4"
"8.0", "8.1", "8.2", "8.3", "8.4",
"8.5" // DEV
]
/**
@ -89,14 +90,13 @@ struct Constants {
3: // Valet v3 dropped support for v5.6
[
"7.0", "7.1", "7.2", "7.3", "7.4",
"8.0", "8.1", "8.2", "8.3",
"8.4" // dev
"8.0", "8.1", "8.2", "8.3", "8.4"
],
4: // Valet v4 dropped support for v7.0
[
"7.1", "7.2", "7.3", "7.4",
"8.0", "8.1", "8.2", "8.3",
"8.4" // dev
"8.0", "8.1", "8.2", "8.3", "8.4",
"8.5" // DEV
]
]
@ -112,6 +112,14 @@ struct Constants {
string: "https://phpmon.app/faq"
)!
static let WikiPhpUnavailable = URL(
string: "https://phpmon.app/php-unavailable"
)!
static let WikiPhpUpgrade = URL(
string: "https://phpmon.app/php-upgrade"
)!
static let DonationPayment = URL(
string: "https://phpmon.app/sponsor/now"
)!

View File

@ -45,7 +45,6 @@ func grepContains(file: String, query: String) async -> Bool {
/**
Attempts to introduce sleep for a particular duration. Use with caution.
Only intended for testing purposes.
*/
func delay(seconds: Double) async {
try! await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))

View File

@ -8,6 +8,6 @@
import Foundation
protocol AlertableError {
public protocol AlertableError {
func getErrorMessageKey() -> String
}

View File

@ -0,0 +1,23 @@
//
// NVAlertExtension.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 16/07/2024.
// Copyright © 2024 Nico Verbruggen. All rights reserved.
//
import Foundation
import NVAlert
extension NVAlert {
/**
Shows the modal for a particular error.
*/
@MainActor public static func show(for error: Error & AlertableError) {
let key = error.getErrorMessageKey()
return NVAlert().withInformation(
title: "\(key).title".localized,
subtitle: "\(key).description".localized
).withPrimary(text: "generic.ok".localized).show()
}
}

View File

@ -14,6 +14,8 @@ class PhpInstallation {
var iniFiles: [PhpConfigurationFile] = []
var isPreRelease: Bool = false
var isMissingBinary: Bool = false
var isHealthy: Bool = true
@ -59,6 +61,10 @@ class PhpInstallation {
trimNewlines: false
).trimmingCharacters(in: .whitespacesAndNewlines)
if longVersionString.contains("-dev") {
isPreRelease = true
}
// The parser should always work, or the string has to be very unusual.
// If so, the app SHOULD crash, so that the users report what's up.
versionNumber = try! VersionNumber.parse(longVersionString)

View File

@ -8,9 +8,6 @@
import Foundation
extension Process: @unchecked Sendable {}
extension Timer: @unchecked Sendable {}
class RealShell: ShellProtocol {
/**
The launch path of the terminal in question that is used.
@ -184,25 +181,26 @@ class RealShell: ShellProtocol {
}
return try await withCheckedThrowingContinuation({ continuation in
let timer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { _ in
let task = Task {
try await Task.sleep(nanoseconds: timeout.nanoseconds)
// Only terminate if the process is still running
if process.isRunning {
process.terminationHandler = nil
process.terminate()
return continuation.resume(throwing: ShellError.timedOut)
continuation.resume(throwing: ShellError.timedOut)
}
}
process.terminationHandler = { [timer, output] process in
timer.invalidate()
process.terminationHandler = { [output] process in
task.cancel()
process.haltListening()
if !output.err.isEmpty {
return continuation.resume(returning: (process, .err(output.err)))
continuation.resume(returning: (process, .err(output.err)))
} else {
continuation.resume(returning: (process, .out(output.out)))
}
return continuation.resume(returning: (process, .out(output.out)))
}
process.launch()
@ -210,3 +208,9 @@ class RealShell: ShellProtocol {
})
}
}
extension TimeInterval {
var nanoseconds: UInt64 {
return UInt64(self * 1_000_000_000)
}
}

View File

@ -18,11 +18,11 @@ class TestableFileSystem: FileSystemProtocol {
self.files = files
// Ensure that each of the ~ characters are replaced with the home directory path
for key in self.files.keys where key.contains("~") {
self.files.renameKey(
fromKey: key,
toKey: key.replacingOccurrences(of: "~", with: self.homeDirectory)
)
accessQueue.sync {
for (key, value) in files {
let adjustedKey = key.contains("~") ? key.replacingOccurrences(of: "~", with: self.homeDirectory) : key
self.files[adjustedKey] = value
}
}
// Ensure that intermediate directories are created
@ -46,38 +46,49 @@ class TestableFileSystem: FileSystemProtocol {
*/
private(set) var homeDirectory = "/Users/fake"
/**
Serial dispatch queue for ensuring thread-safe access to the `files` dictionary.
*/
private let accessQueue = DispatchQueue(label: "com.testablefilesystem.accessQueue")
// MARK: - Basics
func createDirectory(_ path: String, withIntermediateDirectories: Bool) throws {
let path = path.replacingTildeWithHomeDirectory
if files[path] != nil {
throw TestableFileSystemError.alreadyExists
try accessQueue.sync {
if files[path] != nil {
throw TestableFileSystemError.alreadyExists
}
self.createIntermediateDirectories(path)
self.files[path] = .fake(.directory)
}
self.createIntermediateDirectories(path)
self.files[path] = .fake(.directory)
}
func writeAtomicallyToFile(_ path: String, content: String) throws {
let path = path.replacingTildeWithHomeDirectory
if files[path] != nil {
throw TestableFileSystemError.alreadyExists
}
try accessQueue.sync {
if files[path] != nil {
throw TestableFileSystemError.alreadyExists
}
self.files[path] = .fake(.text, content)
self.files[path] = .fake(.text, content)
}
}
func getStringFromFile(_ path: String) throws -> String {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
return try accessQueue.sync {
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
return file.content ?? ""
return file.content ?? ""
}
}
func getShallowContentsOfDirectory(_ path: String) throws -> [String] {
@ -88,32 +99,36 @@ class TestableFileSystem: FileSystemProtocol {
seek = "\(seek)/"
}
return self.files.keys
.filter { $0.hasPrefix(seek) }
.map { $0.replacingOccurrences(of: seek, with: "") }
.filter { !$0.contains("/") }
return accessQueue.sync {
self.files.keys
.filter { $0.hasPrefix(seek) }
.map { $0.replacingOccurrences(of: seek, with: "") }
.filter { !$0.contains("/") }
}
}
func getDestinationOfSymlink(_ path: String) throws -> String {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
return try accessQueue.sync {
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
if file.type != .symlink {
throw TestableFileSystemError.notSymlink
}
if file.type != .symlink {
throw TestableFileSystemError.notSymlink
}
guard let pathToSymlink = file.content else {
throw TestableFileSystemError.invalidSymlink
}
guard let pathToSymlink = file.content else {
throw TestableFileSystemError.invalidSymlink
}
if !files.keys.contains(pathToSymlink) {
throw TestableFileSystemError.invalidSymlink
}
if !files.keys.contains(pathToSymlink) {
throw TestableFileSystemError.invalidSymlink
}
return pathToSymlink
return pathToSymlink
}
}
// MARK: - Move & Delete Files
@ -122,27 +137,31 @@ class TestableFileSystem: FileSystemProtocol {
let path = path.replacingTildeWithHomeDirectory
let newPath = newPath.replacingTildeWithHomeDirectory
self.files.keys.forEach { key in
if key.hasPrefix(path) {
self.files.renameKey(
fromKey: key,
toKey: key.replacingOccurrences(of: path, with: newPath)
)
accessQueue.sync {
self.files.keys.forEach { key in
if key.hasPrefix(path) {
self.files.renameKey(
fromKey: key,
toKey: key.replacingOccurrences(of: path, with: newPath)
)
}
}
}
self.files.renameKey(fromKey: path, toKey: newPath)
self.files.renameKey(fromKey: path, toKey: newPath)
}
}
func remove(_ path: String) throws {
// Remove recursively
self.files.keys.forEach { key in
if key.hasPrefix(path) {
self.files.removeValue(forKey: key)
accessQueue.sync {
// Remove recursively
self.files.keys.forEach { key in
if key.hasPrefix(path) {
self.files.removeValue(forKey: key)
}
}
}
self.files.removeValue(forKey: path)
self.files.removeValue(forKey: path)
}
}
// MARK: Attributes
@ -150,11 +169,13 @@ class TestableFileSystem: FileSystemProtocol {
func makeExecutable(_ path: String) throws {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
try accessQueue.sync {
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
file.type = .binary
file.type = .binary
}
}
// MARK: - Checks
@ -162,93 +183,107 @@ class TestableFileSystem: FileSystemProtocol {
func isExecutableFile(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path.replacingTildeWithHomeDirectory] else {
return false
}
return accessQueue.sync {
guard let file = files[path.replacingTildeWithHomeDirectory] else {
return false
}
return file.type == .binary
return file.type == .binary
}
}
func isWriteableFile(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path.replacingTildeWithHomeDirectory] else {
return false
}
return accessQueue.sync {
guard let file = files[path.replacingTildeWithHomeDirectory] else {
return false
}
return !file.readOnly
return !file.readOnly
}
}
func anyExists(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
return files.keys.contains(path)
return accessQueue.sync {
files.keys.contains(path)
}
}
func fileExists(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
return false
}
return accessQueue.sync {
guard let file = files[path] else {
return false
}
return [.binary, .symlink, .text].contains(file.type)
return [.binary, .symlink, .text].contains(file.type)
}
}
func directoryExists(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
return false
}
return accessQueue.sync {
guard let file = files[path] else {
return false
}
return [.directory].contains(file.type)
return [.directory].contains(file.type)
}
}
func isSymlink(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
return false
}
return accessQueue.sync {
guard let file = files[path] else {
return false
}
return file.type == .symlink
return file.type == .symlink
}
}
func isDirectory(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
return false
}
return accessQueue.sync {
guard let file = files[path] else {
return false
}
return file.type == .directory
return file.type == .directory
}
}
public func printContents() {
for key in self.files.keys.sorted() {
print("\(key) -> \(self.files[key]!.type)")
accessQueue.sync {
for key in self.files.keys.sorted() {
print("\(key) -> \(self.files[key]!.type)")
}
}
}
private func createIntermediateDirectories(_ path: String) {
let path = path.replacingTildeWithHomeDirectory
let items = path.components(separatedBy: "/")
var preceding = ""
var directoriesToCreate: [String] = []
for item in items {
let key = preceding == "/"
? "/\(item)"
: "\(preceding)/\(item)"
if !self.files.keys.contains(key) {
self.files[key] = .fake(.directory)
}
let key = preceding == "/" ? "/\(item)" : "\(preceding)/\(item)"
directoriesToCreate.append(key)
preceding = key
}
for key in directoriesToCreate where !self.files.keys.contains(key) {
self.files[key] = .fake(.directory)
}
}
}

View File

@ -89,6 +89,9 @@ class App {
/** List of detected (installed) applications that PHP Monitor can work with. */
var detectedApplications: [Application] = []
/** Favorites storage, which keeps track of favorited domains. */
var favorites = Favorites.shared
/** The warning manager, responsible for keeping track of warnings. */
var warnings = WarningManager.shared

View File

@ -8,6 +8,7 @@
import Foundation
import Cocoa
import NVAlert
class AppUpdater {
var caskFile: CaskFile!
@ -72,7 +73,7 @@ class AppUpdater {
: "brew upgrade phpmon"
Task { @MainActor in
BetterAlert().withInformation(
NVAlert().withInformation(
title: "updater.alerts.newer_version_available.title"
.localized(latestVersionOnline.humanReadable),
subtitle: "updater.alerts.newer_version_available.subtitle"
@ -112,7 +113,7 @@ class AppUpdater {
public func presentNoNewerVersionAvailableAlert() {
Task { @MainActor in
BetterAlert().withInformation(
NVAlert().withInformation(
title: "updater.alerts.is_latest_version.title".localized,
subtitle: "updater.alerts.is_latest_version.subtitle".localized(App.shortVersion),
description: ""
@ -124,7 +125,7 @@ class AppUpdater {
public func presentCouldNotRetrieveUpdate() {
Task { @MainActor in
BetterAlert().withInformation(
NVAlert().withInformation(
title: "updater.alerts.cannot_check_for_update.title".localized,
subtitle: "updater.alerts.cannot_check_for_update.subtitle".localized,
description: "updater.alerts.cannot_check_for_update.description".localized(

View File

@ -515,10 +515,10 @@
</objects>
<point key="canvasLocation" x="-374" y="2267"/>
</scene>
<!--Better AlertVC-->
<!--AlertVC-->
<scene sceneID="y9E-bB-wIG">
<objects>
<viewController storyboardIdentifier="noticeVC" id="hkw-9V-NxP" customClass="BetterAlertVC" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController">
<viewController storyboardIdentifier="noticeVC" id="hkw-9V-NxP" customClass="NVAlertVC" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="UPH-hV-Naz">
<rect key="frame" x="0.0" y="0.0" width="500" height="212"/>
<autoresizingMask key="autoresizingMask"/>
@ -826,6 +826,13 @@ Gw
<rect key="frame" x="0.0" y="0.0" width="626" height="309"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<progressIndicator maxValue="100" displayedWhenStopped="NO" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="ZiS-Gq-TLQ">
<rect key="frame" x="298" y="150" width="30" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="XK3-AR-Oc0"/>
<constraint firstAttribute="height" constant="30" id="lfW-dB-Eu3"/>
</constraints>
</progressIndicator>
<scrollView borderType="none" horizontalLineScroll="54" horizontalPageScroll="10" verticalLineScroll="54" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p0j-eB-I2i">
<rect key="frame" x="0.0" y="0.0" width="626" height="309"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" id="6IL-DW-37w">
@ -923,6 +930,50 @@ Gw
<outlet property="labelSiteName" destination="XJL-Uw-frD" id="f0t-vd-W68"/>
</connections>
</tableCellView>
<tableCellView identifier="domainListNameCellFavorited" wantsLayer="YES" id="Byb-te-u65" customClass="DomainListNameCell" customModule="PHP_Monitor" customModuleProvider="target">
<rect key="frame" x="69" y="54" width="200" height="54"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="aot-FJ-HIk">
<rect key="frame" x="33" y="26" width="145" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="my-domain-name.test" id="LHu-UF-QlC">
<font key="font" metaFont="systemSemibold" size="13"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="GNH-l8-oki">
<rect key="frame" x="33" y="12" width="75" height="14"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="~/path/to/site" id="LNw-Ju-0Ot">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="3Wp-DX-An9">
<rect key="frame" x="5" y="4" width="20" height="47"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="Q76-fI-lkW">
<imageReference key="image" image="star.circle.fill" catalog="system" symbolScale="large"/>
</imageCell>
<color key="contentTintColor" name="AccentColor"/>
</imageView>
</subviews>
<constraints>
<constraint firstItem="3Wp-DX-An9" firstAttribute="leading" secondItem="Byb-te-u65" secondAttribute="leading" constant="5" id="CTd-ON-loK"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="aot-FJ-HIk" secondAttribute="trailing" constant="20" symbolic="YES" id="Csc-Dy-H4K"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="GNH-l8-oki" secondAttribute="trailing" constant="20" symbolic="YES" id="H10-MG-hCG"/>
<constraint firstItem="GNH-l8-oki" firstAttribute="leading" secondItem="aot-FJ-HIk" secondAttribute="leading" id="Hk0-x3-RyN"/>
<constraint firstItem="3Wp-DX-An9" firstAttribute="top" secondItem="Byb-te-u65" secondAttribute="top" constant="9" id="erH-dR-K7S"/>
<constraint firstItem="aot-FJ-HIk" firstAttribute="top" secondItem="Byb-te-u65" secondAttribute="top" constant="12" id="ktI-fg-qaX"/>
<constraint firstAttribute="bottom" secondItem="3Wp-DX-An9" secondAttribute="bottom" constant="9" id="uyc-26-gZb"/>
<constraint firstItem="aot-FJ-HIk" firstAttribute="leading" secondItem="Byb-te-u65" secondAttribute="leading" constant="35" id="vXE-jj-lLF"/>
<constraint firstItem="GNH-l8-oki" firstAttribute="top" secondItem="aot-FJ-HIk" secondAttribute="bottom" id="wSX-fR-O7a"/>
</constraints>
<connections>
<outlet property="labelPathName" destination="GNH-l8-oki" id="GC1-TA-lIk"/>
<outlet property="labelSiteName" destination="aot-FJ-HIk" id="HdZ-Rh-ua6"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="ENVIRONMENT" width="100" minWidth="100" maxWidth="150" id="hzb-XI-Out">
@ -1092,13 +1143,6 @@ Gw
<autoresizingMask key="autoresizingMask"/>
</tableHeaderView>
</scrollView>
<progressIndicator maxValue="100" displayedWhenStopped="NO" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="ZiS-Gq-TLQ">
<rect key="frame" x="298" y="150" width="30" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="XK3-AR-Oc0"/>
<constraint firstAttribute="height" constant="30" id="lfW-dB-Eu3"/>
</constraints>
</progressIndicator>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="wcV-ed-8Bv">
<rect key="frame" x="113" y="5" width="400" height="300"/>
<constraints>
@ -1126,7 +1170,7 @@ Gw
</viewController>
<customObject id="HgD-aB-bQb" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="323" y="723"/>
<point key="canvasLocation" x="323" y="722.5"/>
</scene>
<!--Add ProxyVC-->
<scene sceneID="g8z-pE-RL9">
@ -1503,6 +1547,10 @@ Gw
<image name="Lock" width="30" height="30"/>
<image name="arrow.clockwise" catalog="system" width="14" height="16"/>
<image name="plus" catalog="system" width="14" height="13"/>
<image name="star.circle.fill" catalog="system" width="20" height="20"/>
<namedColor name="AccentColor">
<color red="0.0" green="0.46000000000000002" blue="0.89000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<namedColor name="IconColorGreen">
<color red="0.24699999392032623" green="0.69700002670288086" blue="0.50099998712539673" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>

View File

@ -8,6 +8,7 @@
import Foundation
import Cocoa
import NVAlert
class ValetServicesManager: ServicesManager {
override init() {
@ -131,7 +132,7 @@ class ValetServicesManager: ServicesManager {
Log.err("The service '\(named)' is now reporting an error.")
guard let errorLogPath = after.error_log_path else {
return BetterAlert().withInformation(
return NVAlert().withInformation(
title: "alert.service_error.title".localized(named),
subtitle: "alert.service_error.subtitle.no_error_log".localized(named),
description: "alert.service_error.extra".localized
@ -140,7 +141,7 @@ class ValetServicesManager: ServicesManager {
.show()
}
BetterAlert().withInformation(
NVAlert().withInformation(
title: "alert.service_error.title".localized(named),
subtitle: "alert.service_error.subtitle.error_log".localized(named),
description: "alert.service_error.extra".localized

View File

@ -7,9 +7,24 @@
import Foundation
import AppKit
import NVAlert
class Startup {
@MainActor static var startupTimer: Timer?
@MainActor func startTimeoutTimer() {
Self.startupTimer = Timer.scheduledTimer(
timeInterval: 30.0, target: self, selector: #selector(startupTimeout),
userInfo: nil, repeats: false
)
}
@MainActor static func invalidateTimeoutTimer() {
Self.startupTimer?.invalidate()
Self.startupTimer = nil
}
/**
Checks the user's environment and checks if PHP Monitor can be used properly.
This checks if PHP is installed, Valet is running, the appropriate permissions are set, and more.
@ -21,6 +36,11 @@ class Startup {
// Do the important system setup checks
Log.info("The user is running PHP Monitor with the architecture: \(App.architecture)")
// Set up a "background" timer on the main thread
Task { @MainActor in
startTimeoutTimer()
}
for group in self.groups {
if group.condition() {
Log.info("Now running \(group.checks.count) \(group.name) checks!")
@ -44,10 +64,34 @@ class Startup {
// If we get here, nothing has gone wrong. That's what we want!
initializeSwitcher()
Log.info("PHP Monitor has determined the application has successfully passed all checks.")
Log.separator(as: .info)
return true
}
/**
Displays an alert for when the application startup process takes too long.
*/
@MainActor @objc func startupTimeout() {
NVAlert()
.withInformation(
title: "startup.timeout.title".localized,
subtitle: "startup.timeout.subtitle".localized,
description: "startup.timeout.description".localized
)
.withPrimary(text: "alert.cannot_start.close".localized, action: { vc in
vc.close(with: .alertFirstButtonReturn)
exit(1)
})
.withSecondary(text: "startup.timeout.ignore".localized, action: { vc in
vc.close(with: .alertSecondButtonReturn)
})
.withTertiary(text: "", action: { _ in
NSWorkspace.shared.open(URL(string: "https://github.com/nicoverbruggen/phpmon/issues/294")!)
})
.show()
}
/**
Displays an alert for a particular check. There are two types of alerts:
- ones that require an app restart, which prompt the user to exit the app
@ -55,7 +99,7 @@ class Startup {
*/
@MainActor private func showAlert(for check: EnvironmentCheck) {
if check.requiresAppRestart {
BetterAlert()
NVAlert()
.withInformation(
title: check.titleText,
subtitle: check.subtitleText,
@ -66,7 +110,7 @@ class Startup {
}).show()
}
BetterAlert()
NVAlert()
.withInformation(
title: check.titleText,
subtitle: check.subtitleText,

View File

@ -7,6 +7,7 @@
//
import Foundation
import NVAlert
@MainActor class ComposerWindow {
private var shouldNotify: Bool! = nil
@ -109,7 +110,7 @@ import Foundation
// MARK: Alert
private func presentMissingAlert() {
BetterAlert()
NVAlert()
.withInformation(
title: "alert.composer_missing.title".localized,
subtitle: "alert.composer_missing.subtitle".localized,

View File

@ -35,10 +35,11 @@ class Brew {
/// Each formula for each PHP version that can be installed.
public static let phpVersionFormulae = [
"8.5": "shivammathur/php/php@8.5",
"8.4": "shivammathur/php/php@8.4",
"8.3": "php@8.3",
"8.2": "php@8.2",
"8.1": "php@8.1",
"8.3": "shivammathur/php/php@8.3",
"8.2": "shivammathur/php/php@8.2",
"8.1": "shivammathur/php/php@8.1",
"8.0": "shivammathur/php/php@8.0",
"7.4": "shivammathur/php/php@7.4",
"7.3": "shivammathur/php/php@7.3",

View File

@ -7,6 +7,7 @@
//
import Foundation
import NVAlert
class BrewDiagnostics {
/**
@ -183,7 +184,7 @@ class BrewDiagnostics {
*/
private static func presentAlertAboutConflict() {
Task { @MainActor in
BetterAlert()
NVAlert()
.withInformation(
title: "alert.php_alias_conflict.title".localized,
subtitle: "alert.php_alias_conflict.info".localized

View File

@ -21,11 +21,12 @@ struct BrewPhpFormula: Equatable {
/// The upgrade that is currently available, if it exists.
let upgradeVersion: String?
// TODO: A rebuild attribute could be checked, to check if a Tap update exists for a pre-release version
/// Whether this formula is a stable version of PHP.
let prerelease: Bool
/// Whether this formula's associated Homebrew file exists.
var hasFormulaFile: Bool = false
/// Whether the formula is currently installed.
var isInstalled: Bool {
return installedVersion != nil
@ -43,6 +44,7 @@ struct BrewPhpFormula: Equatable {
self.installedVersion = installedVersion
self.upgradeVersion = upgradeVersion
self.prerelease = prerelease
self.hasFormulaFile = checkFormulaFile()
}
/// Whether the formula can be upgraded.
@ -93,6 +95,21 @@ struct BrewPhpFormula: Equatable {
return isHealthy() ?? true
}
/**
Verify whether the formula file exists (sourced via `shivammathur/homebrew-php`).
If it does not exist, the formula cannot be installed.
*/
private func checkFormulaFile() -> Bool {
guard let version = shortVersion else {
return false
}
return FileSystem.fileExists(
"\(Paths.tapPath)/shivammathur/homebrew-php/Formula/php@\(version).rb"
.replacingOccurrences(of: "php@" + PhpEnvironments.brewPhpAlias, with: "php")
)
}
/**
* Determines if this PHP installation is healthy.
* Uses the cached installation health check as basis.

View File

@ -39,19 +39,25 @@ class BrewPhpFormulaeHandler: HandlesBrewPhpFormulae {
OutdatedFormulae.self,
from: rawJsonText
).formulae.filter({ formula in
formula.name.starts(with: "php")
formula.name.starts(with: "shivammathur/php/php") || formula.name.starts(with: "php")
})
}
return Brew.phpVersionFormulae.map { (version, formula) in
var fullVersion: String?
var upgradeVersion: String?
var isPrerelease: Bool = Constants.ExperimentalPhpVersions.contains(version)
if let install = PhpEnvironments.shared.cachedPhpInstallations[version] {
fullVersion = install.versionNumber.text
fullVersion = install.isPreRelease ? "\(fullVersion!)-dev" : fullVersion
upgradeVersion = outdated?.first(where: { formula in
return formula.name == install.formulaName
return formula.name.replacingOccurrences(of: "shivammathur/php/", with: "")
== install.formulaName.replacingOccurrences(of: "shivammathur/php/", with: "")
})?.current_version
isPrerelease = install.isPreRelease
}
return BrewPhpFormula(
@ -59,7 +65,7 @@ class BrewPhpFormulaeHandler: HandlesBrewPhpFormulae {
displayName: "PHP \(version)",
installedVersion: fullVersion,
upgradeVersion: upgradeVersion,
prerelease: Constants.ExperimentalPhpVersions.contains(version)
prerelease: isPrerelease
)
}.sorted { $0.displayName > $1.displayName }
}

View File

@ -24,4 +24,6 @@ protocol ValetListable {
func getListableUrl() -> URL?
func getListableFavorited() -> Bool
}

View File

@ -14,6 +14,11 @@ class ValetProxy: ValetListable {
var target: String
var secured: Bool = false
var favorited: Bool = false
var favoriteSignature: String {
"proxy:domain:\(domain).\(tld)|target:\(target)"
}
init(domain: String, target: String, secure: Bool, tld: String) {
self.domain = domain
self.tld = tld
@ -28,6 +33,8 @@ class ValetProxy: ValetListable {
secure: false,
tld: configuration.tld
)
self.favorited = Favorites.shared.contains(domain: self.domain)
self.determineSecured()
}
@ -61,12 +68,21 @@ class ValetProxy: ValetListable {
return URL(string: "\(self.secured ? "https://" : "http://")\(self.domain).\(self.tld)")
}
func getListableFavorited() -> Bool {
return self.favorited
}
// MARK: - Interactions
func determineSecured() {
self.secured = FileSystem.fileExists("~/.config/valet/Certificates/\(self.domain).\(self.tld).key")
}
func toggleFavorite() {
self.favorited.toggle()
Favorites.shared.toggle(domain: self.favoriteSignature)
}
func toggleSecure() async throws {
try await ValetInteractor.shared.toggleSecure(proxy: self)
}

View File

@ -61,6 +61,11 @@ class ValetSite: ValetListable {
?? "???"
}
var favorited: Bool = false
var favoriteSignature: String {
"site:domain:\(name).\(tld)|path:\(absolutePath)"
}
init(
name: String,
tld: String,
@ -75,6 +80,7 @@ class ValetSite: ValetListable {
self.secured = false
if makeDeterminations {
self.favorited = Favorites.shared.contains(domain: favoriteSignature)
determineSecured()
determineIsolated()
determineComposerPhpVersion()
@ -305,12 +311,21 @@ class ValetSite: ValetListable {
return URL(string: "\(self.secured ? "https://" : "http://")\(self.name).\(Valet.shared.config.tld)")
}
func getListableFavorited() -> Bool {
return self.favorited
}
// MARK: - Interactions
func toggleSecure() async throws {
try await ValetInteractor.shared.toggleSecure(site: self)
}
func toggleFavorite() {
self.favorited.toggle()
Favorites.shared.toggle(domain: self.favoriteSignature)
}
func isolate(version: String) async throws {
try await ValetInteractor.shared.isolate(site: self, version: version)
}

View File

@ -7,6 +7,7 @@
//
import Foundation
import NVAlert
extension Valet {
@ -16,7 +17,7 @@ extension Valet {
public func notifyAboutUnsupportedTLD() {
if Valet.shared.config.tld != "test" && Preferences.isEnabled(.warnAboutNonStandardTLD) {
Task { @MainActor in
BetterAlert().withInformation(
NVAlert().withInformation(
title: "alert.warnings.tld_issue.title".localized,
subtitle: "alert.warnings.tld_issue.subtitle".localized,
description: "alert.warnings.tld_issue.description".localized
@ -33,7 +34,7 @@ extension Valet {
public func notifyAboutOutdatedValetVersion(_ version: VersionNumber) {
Task { @MainActor in
BetterAlert()
NVAlert()
.withInformation(
title: "alert.min_valet_version.title".localized,
subtitle: "alert.min_valet_version.info".localized(
@ -60,7 +61,7 @@ extension Valet {
}
Task { @MainActor in
BetterAlert()
NVAlert()
.withInformation(
title: "alert.php_fpm_broken.title".localized,
subtitle: "alert.php_fpm_broken.info".localized,

View File

@ -7,6 +7,7 @@
//
import Cocoa
import NVAlert
extension MainMenu {
@ -20,7 +21,7 @@ extension MainMenu {
@MainActor @objc func displayUnlinkedInfo() {
Task { @MainActor in
BetterAlert()
NVAlert()
.withInformation(
title: "phpman.unlinked.title".localized,
subtitle: "phpman.unlinked.desc".localized,
@ -32,7 +33,7 @@ extension MainMenu {
}
@MainActor @objc func fixHomebrewPermissions() {
if !BetterAlert()
if !NVAlert()
.withInformation(
title: "alert.fix_homebrew_permissions.title".localized,
subtitle: "alert.fix_homebrew_permissions.subtitle".localized,
@ -47,7 +48,7 @@ extension MainMenu {
asyncExecution {
try Actions.fixHomebrewPermissions()
} success: {
BetterAlert()
NVAlert()
.withInformation(
title: "alert.fix_homebrew_permissions_done.title".localized,
subtitle: "alert.fix_homebrew_permissions_done.subtitle".localized,
@ -56,7 +57,7 @@ extension MainMenu {
.withPrimary(text: "generic.ok".localized)
.show()
} failure: { error in
BetterAlert.show(for: error as! HomebrewPermissionError)
NVAlert.show(for: error as! HomebrewPermissionError)
}
}
@ -175,7 +176,7 @@ extension MainMenu {
return
}
BetterAlert().withInformation(
NVAlert().withInformation(
title: "alert.revert_description.title".localized,
subtitle: "alert.revert_description.subtitle".localized(
preset.textDescription
@ -196,7 +197,7 @@ extension MainMenu {
}
@MainActor @objc func showPresetHelp() {
BetterAlert().withInformation(
NVAlert().withInformation(
title: "preset_help_title".localized,
subtitle: "preset_help_info".localized,
description: "preset_help_desc".localized
@ -263,7 +264,7 @@ extension MainMenu {
Task { MainMenu.shared.switchToPhpVersion(version) }
} else {
Task {
BetterAlert().withInformation(
NVAlert().withInformation(
title: "alert.php_switch_unavailable.title".localized,
subtitle: "alert.php_switch_unavailable.subtitle".localized(version)
).withPrimary(

View File

@ -8,6 +8,7 @@
import Foundation
import AppKit
import NVAlert
extension MainMenu {
@ -19,7 +20,7 @@ extension MainMenu {
return
}
if !BetterAlert()
if !NVAlert()
.withInformation(
title: "alert.fix_my_valet.title".localized,
subtitle: "alert.fix_my_valet.info".localized(PhpEnvironments.brewPhpAlias)
@ -43,7 +44,7 @@ extension MainMenu {
}
@MainActor private func presentAlertForMissingFormula() {
BetterAlert()
NVAlert()
.withInformation(
title: "alert.php_formula_missing.title".localized,
subtitle: "alert.php_formula_missing.info".localized
@ -53,7 +54,7 @@ extension MainMenu {
}
@MainActor private func presentAlertForSameVersion() {
BetterAlert()
NVAlert()
.withInformation(
title: "alert.fix_my_valet_done.title".localized,
subtitle: "alert.fix_my_valet_done.subtitle".localized,
@ -64,7 +65,7 @@ extension MainMenu {
}
@MainActor private func presentAlertForDifferentVersion(version: String) {
BetterAlert()
NVAlert()
.withInformation(
title: "alert.fix_my_valet_done.title".localized,
subtitle: "alert.fix_my_valet_done.subtitle".localized,

View File

@ -7,6 +7,7 @@
//
import Cocoa
import NVAlert
extension MainMenu {
/**
@ -114,6 +115,9 @@ extension MainMenu {
// Finally!
Log.info("PHP Monitor is ready to serve!")
// Avoid showing the "startup timeout" alert
Startup.invalidateTimeoutTimer()
// Check if we upgraded from a previous version
AppUpdater.checkIfUpdateWasPerformed()
}
@ -143,7 +147,7 @@ extension MainMenu {
*/
private func onEnvironmentFail() async {
Task { @MainActor [self] in
BetterAlert()
NVAlert()
.withInformation(
title: "alert.cannot_start.title".localized,
subtitle: "alert.cannot_start.subtitle".localized,

View File

@ -7,6 +7,7 @@
//
import Foundation
import NVAlert
extension MainMenu {
@ -75,7 +76,7 @@ extension MainMenu {
}
@MainActor private func suggestFixMyValet(failed version: String) {
let outcome = BetterAlert()
let outcome = NVAlert()
.withInformation(
title: "alert.php_switch_failed.title".localized(version),
subtitle: "alert.php_switch_failed.info".localized(version),
@ -90,7 +91,7 @@ extension MainMenu {
}
@MainActor private func suggestFixMyComposer() {
BetterAlert().withInformation(
NVAlert().withInformation(
title: "alert.global_composer_platform_issues.title".localized,
subtitle: "alert.global_composer_platform_issues.subtitle".localized,
description: "alert.global_composer_platform_issues.desc".localized

View File

@ -6,6 +6,7 @@
//
import Cocoa
import NVAlert
@MainActor
class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate {
@ -120,7 +121,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
@objc func showIncompatiblePhpVersionsAlert() {
Task { @MainActor in
BetterAlert().withInformation(
NVAlert().withInformation(
title: "startup.unsupported_versions_explanation.title".localized,
subtitle: "startup.unsupported_versions_explanation.subtitle".localized(
PhpEnvironments.shared.incompatiblePhpVersions
@ -185,7 +186,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
@objc func openLiteModeInfo() {
Task { @MainActor in
BetterAlert().withInformation(
NVAlert().withInformation(
title: "lite_mode_explanation.title".localized,
subtitle: "lite_mode_explanation.subtitle".localized,
description: "lite_mode_explanation.description".localized

View File

@ -1,122 +0,0 @@
//
// Notice.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 16/02/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
@MainActor
class BetterAlert {
var windowController: NSWindowController!
var noticeVC: BetterAlertVC {
return self.windowController.contentViewController as! BetterAlertVC
}
init() {
let storyboard = NSStoryboard(name: "Main", bundle: nil)
self.windowController = storyboard.instantiateController(
withIdentifier: "noticeWindow"
) as? NSWindowController
}
public static func make() -> BetterAlert {
return BetterAlert()
}
public func withPrimary(
text: String,
action: @MainActor @escaping (BetterAlertVC) -> Void = { vc in
vc.close(with: .alertFirstButtonReturn)
}
) -> Self {
self.noticeVC.buttonPrimary.title = text
self.noticeVC.actionPrimary = action
return self
}
public func withSecondary(
text: String,
action: (@MainActor (BetterAlertVC) -> Void)? = { vc in
vc.close(with: .alertSecondButtonReturn)
}
) -> Self {
self.noticeVC.buttonSecondary.title = text
self.noticeVC.actionSecondary = action
return self
}
public func withTertiary(
text: String = "",
action: (@MainActor (BetterAlertVC) -> Void)? = nil
) -> Self {
if text == "" {
self.noticeVC.buttonTertiary.bezelStyle = .helpButton
}
self.noticeVC.buttonTertiary.title = text
self.noticeVC.actionTertiary = action
return self
}
public func withInformation(
title: String,
subtitle: String,
description: String = ""
) -> Self {
self.noticeVC.labelTitle.stringValue = title
self.noticeVC.labelSubtitle.stringValue = subtitle
self.noticeVC.labelDescription.stringValue = description
// If the description is missing, handle the excess space and change the top margin
if description == "" {
self.noticeVC.labelDescription.isHidden = true
self.noticeVC.primaryButtonTopMargin.constant = 0
}
return self
}
/**
Shows the modal and returns a ModalResponse.
If you wish to simply show the alert and disregard the outcome, use `show`.
*/
@MainActor public func runModal() -> NSApplication.ModalResponse {
if !Thread.isMainThread {
fatalError("You should always present alerts on the main thread!")
}
NSApp.activate(ignoringOtherApps: true)
windowController.window?.makeKeyAndOrderFront(nil)
windowController.window?.setCenterPosition(offsetY: 70)
return NSApplication.shared.runModal(for: windowController.window!)
}
/** Shows the modal and returns true if the user pressed the primary button. */
@MainActor public func didSelectPrimary() -> Bool {
return self.runModal() == .alertFirstButtonReturn
}
/**
Shows the modal and does not return anything.
*/
@MainActor public func show() {
_ = self.runModal()
}
/**
Shows the modal for a particular error.
*/
@MainActor public static func show(for error: Error & AlertableError) {
let key = error.getErrorMessageKey()
return BetterAlert().withInformation(
title: "\(key).title".localized,
subtitle: "\(key).description".localized
).withPrimary(text: "generic.ok".localized).show()
}
}

View File

@ -1,77 +0,0 @@
//
// NoticeVC.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 16/02/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
class BetterAlertVC: NSViewController {
// MARK: - Outlets
@IBOutlet weak var labelTitle: NSTextField!
@IBOutlet weak var labelSubtitle: NSTextField!
@IBOutlet weak var labelDescription: NSTextField!
@IBOutlet weak var buttonPrimary: NSButton!
@IBOutlet weak var buttonSecondary: NSButton!
@IBOutlet weak var buttonTertiary: NSButton!
var actionPrimary: (@MainActor (BetterAlertVC) -> Void) = { _ in }
var actionSecondary: (@MainActor (BetterAlertVC) -> Void)?
var actionTertiary: (@MainActor (BetterAlertVC) -> Void)?
@IBOutlet weak var imageView: NSImageView!
@IBOutlet weak var primaryButtonTopMargin: NSLayoutConstraint!
// MARK: - Lifecycle
override func viewWillAppear() {
imageView.image = NSApp.applicationIconImage
if actionSecondary == nil {
buttonSecondary.isHidden = true
}
if actionTertiary == nil {
buttonTertiary.isHidden = true
}
}
override func viewDidAppear() {
view.window?.makeFirstResponder(buttonPrimary)
}
deinit {
// Log.perf("deinit: \(String(describing: self)).\(#function)")
}
// MARK: Outlet Actions
@IBAction func primaryButtonAction(_ sender: Any) {
self.actionPrimary(self)
}
@IBAction func secondaryButtonAction(_ sender: Any) {
if self.actionSecondary != nil {
self.actionSecondary!(self)
} else {
self.close(with: .alertSecondButtonReturn)
}
}
@IBAction func tertiaryButtonAction(_ sender: Any) {
if self.actionTertiary != nil {
self.actionTertiary!(self)
}
}
@MainActor public func close(with code: NSApplication.ModalResponse) {
self.view.window?.close()
NSApplication.shared.stopModal(withCode: code)
}
}

View File

@ -7,6 +7,7 @@
//
import Foundation
import NVAlert
class PhpGuard {
@ -43,7 +44,7 @@ class PhpGuard {
Log.info("PHP Guard noticed a different PHP version. An alert will be displayed!")
Task { @MainActor in
BetterAlert()
NVAlert()
.withInformation(
title: "startup.version_mismatch.title".localized,
subtitle: "startup.version_mismatch.subtitle".localized(

View File

@ -28,6 +28,10 @@ class Preferences {
environmentVariables: [:]
)
if isRunningSwiftUIPreview {
return
}
Task { await loadCustomPreferences() }
}

View File

@ -8,6 +8,7 @@
import Foundation
import Cocoa
import NVAlert
class Stats {
@ -123,7 +124,7 @@ class Stats {
}
Task { @MainActor in
let donate = BetterAlert()
let donate = NVAlert()
.withInformation(
title: "startup.sponsor_encouragement.title".localized,
subtitle: "startup.sponsor_encouragement.subtitle".localized,

View File

@ -7,6 +7,7 @@
//
import Foundation
import NVAlert
struct Preset: Codable, Equatable {
let name: String
@ -139,7 +140,7 @@ struct Preset: Codable, Equatable {
return true
} else {
Task { @MainActor in
BetterAlert().withInformation(
NVAlert().withInformation(
title: "alert.php_switch_unavailable.title".localized,
subtitle: "alert.php_switch_unavailable.subtitle".localized(version!),
description: "alert.php_switch_unavailable.info".localized(

View File

@ -10,6 +10,11 @@ import Foundation
import SwiftUI
struct HelpButton: View {
@State var frameSize: CGFloat = 14
@State var textSize: CGFloat = 12
@State var shadowOpacity: CGFloat = 0.3
@State var shadowRadius: CGFloat = 1
var action: () -> Void
var body: some View {
@ -18,9 +23,10 @@ struct HelpButton: View {
Circle()
.strokeBorder(Color(NSColor.separatorColor), lineWidth: 0.5)
.background(Circle().foregroundColor(Color(NSColor.controlColor)).opacity(0.7))
.shadow(color: Color(NSColor.separatorColor).opacity(0.3), radius: 1)
.frame(width: 14, height: 14)
Text("?").font(.system(size: 12, weight: .medium))
.shadow(color: Color(NSColor.separatorColor)
.opacity(shadowOpacity), radius: shadowRadius)
.frame(width: frameSize, height: frameSize)
Text("?").font(.system(size: textSize, weight: .medium))
.foregroundColor(Color(NSColor.labelColor))
}
})

View File

@ -8,6 +8,7 @@
import Foundation
import SwiftUI
import NVAlert
struct ServicesView: View {
@ -81,7 +82,7 @@ struct ServicesView: View {
: "key_service_not_running"
// Show an alert with more information
BetterAlert().withInformation(
NVAlert().withInformation(
title: "alert.\(type).title".localized,
subtitle: "alert.\(type).subtitle".localized,
description: "alert.\(type).desc".localized
@ -116,7 +117,7 @@ struct ServiceView: View {
if service.status == .missing {
Button {
Task { @MainActor in
BetterAlert().withInformation(
NVAlert().withInformation(
title: "alert.warnings.service_missing.title".localized,
subtitle: "alert.warnings.service_missing.subtitle".localized,
description: "alert.warnings.service_missing.description".localized

View File

@ -40,7 +40,7 @@
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019-2023 Nico Verbruggen. All rights reserved.</string>
<string>Copyright © 2019-2024 Nico Verbruggen. All rights reserved.</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
<key>NSPrincipalClass</key>

View File

@ -0,0 +1,38 @@
//
// Favorites.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 25/08/2024.
// Copyright © 2024 Nico Verbruggen. All rights reserved.
//
import Foundation
class Favorites {
static var shared: Favorites = Favorites()
var items: [String]
init() {
if let items = UserDefaults.standard.array(forKey: "user_favorites") as? [String] {
self.items = items
} else {
self.items = []
}
}
public func contains(domain: String) -> Bool {
return self.items.contains(domain)
}
public func toggle(domain: String) {
if let index = items.firstIndex(of: domain) {
items.remove(at: index)
} else {
items.append(domain)
}
UserDefaults.standard.setValue(items, forKey: "user_favorites")
UserDefaults.standard.synchronize()
}
}

View File

@ -10,6 +10,7 @@ import Cocoa
import AppKit
protocol DomainListCellProtocol {
static func getCellIdentifier(for domain: ValetListable) -> String
func populateCell(with site: ValetSite)
func populateCell(with proxy: ValetProxy)
}

View File

@ -10,10 +10,12 @@ import Cocoa
import AppKit
class DomainListKindCell: NSTableCellView, DomainListCellProtocol {
static let reusableName = "domainListKindCell"
@IBOutlet weak var imageViewType: NSImageView!
static func getCellIdentifier(for domain: ValetListable) -> String {
return "domainListKindCell"
}
func populateCell(with site: ValetSite) {
// If the `aliasPath` is nil, we're dealing with a parked site (otherwise: linked).
imageViewType.image = site.aliasPath == nil

View File

@ -10,11 +10,13 @@ import Cocoa
import AppKit
class DomainListNameCell: NSTableCellView, DomainListCellProtocol {
static let reusableName = "domainListNameCell"
@IBOutlet weak var labelSiteName: NSTextField!
@IBOutlet weak var labelPathName: NSTextField!
static func getCellIdentifier(for domain: ValetListable) -> String {
return domain.getListableFavorited() ? "domainListNameCellFavorited" : "domainListNameCell"
}
func populateCell(with site: ValetSite) {
labelSiteName.stringValue = "\(site.name).\(site.tld)"
labelPathName.stringValue = site.absolutePathRelative

View File

@ -11,13 +11,15 @@ import AppKit
import SwiftUI
class DomainListPhpCell: NSTableCellView, DomainListCellProtocol {
static let reusableName = "domainListPhpCell"
var site: ValetSite?
@IBOutlet weak var buttonPhpVersion: NSButton!
@IBOutlet weak var imageViewPhpVersionOK: NSImageView!
static func getCellIdentifier(for domain: ValetListable) -> String {
return "domainListPhpCell"
}
func populateCell(with site: ValetSite) {
self.site = site

View File

@ -10,10 +10,12 @@ import Cocoa
import AppKit
class DomainListTLSCell: NSTableCellView, DomainListCellProtocol {
static let reusableName = "domainListTLSCell"
@IBOutlet weak var imageViewLock: NSImageView!
static func getCellIdentifier(for domain: ValetListable) -> String {
return "domainListTLSCell"
}
func populateCell(with site: ValetSite) {
imageViewLock.contentTintColor = site.secured
? NSColor(named: "IconColorGreen")

View File

@ -10,11 +10,13 @@ import Cocoa
import AppKit
class DomainListTypeCell: NSTableCellView, DomainListCellProtocol {
static let reusableName = "domainListTypeCell"
@IBOutlet weak var labelDriver: NSTextField!
@IBOutlet weak var labelPhpVersion: NSTextField!
static func getCellIdentifier(for domain: ValetListable) -> String {
return "domainListTypeCell"
}
func populateCell(with site: ValetSite) {
labelDriver.stringValue = site.driver ?? "driver.not_detected".localized

View File

@ -8,6 +8,7 @@
import Foundation
import Cocoa
import NVAlert
extension DomainListVC {
@ -17,7 +18,7 @@ extension DomainListVC {
}
guard let url = selected.getListableUrl() else {
BetterAlert()
NVAlert()
.withInformation(
title: "domain_list.alert.invalid_folder_name".localized,
subtitle: "domain_list.alert.invalid_folder_name_desc".localized
@ -66,6 +67,14 @@ extension DomainListVC {
// MARK: - Interactions with `valet` or terminal
@objc func toggleFavorite() {
guard let selected = self.selected else {
return
}
Task { await toggleFavorite(domain: selected) }
}
@objc func toggleSecure() {
if selected is ValetSite {
Task { await toggleSecure(site: selected as! ValetSite) }
@ -93,6 +102,22 @@ extension DomainListVC {
}
}
func toggleFavorite(domain: any ValetListable) async {
waitAndExecute {
do {
if let site = domain as? ValetSite {
site.toggleFavorite()
}
if let proxy = domain as? ValetProxy {
proxy.toggleFavorite()
}
// Reload the entire table as the sorting may be affected
self.reloadTable()
}
}
}
func toggleSecure(site: ValetSite) async {
waitAndExecute {
do {
@ -249,7 +274,7 @@ extension DomainListVC {
}
private func notifyAboutUsingIsolatedPhpVersionInTerminal(version: VersionNumber) {
BetterAlert()
NVAlert()
.withInformation(
title: "domain_list.alerts_isolated_php_terminal.title".localized(version.short),
subtitle: "domain_list.alerts_isolated_php_terminal.subtitle".localized(
@ -263,7 +288,7 @@ extension DomainListVC {
}
private func notifyAboutFailedSecureStatus(command: String) {
BetterAlert()
NVAlert()
.withInformation(
title: "domain_list.alerts_status_not_changed.title".localized,
subtitle: "domain_list.alerts_status_not_changed.desc".localized(command)
@ -273,7 +298,7 @@ extension DomainListVC {
}
private func notifyAboutFailedSiteIsolation(command: String) {
BetterAlert()
NVAlert()
.withInformation(
title: "domain_list.alerts_isolation_failed.title".localized,
subtitle: "domain_list.alerts_isolation_failed.subtitle".localized,

View File

@ -65,6 +65,7 @@ extension DomainListVC {
menu.addItem(HeaderView.asMenuItem(text: "domain_list.actions".localized))
addToggleFavorite(to: menu, favorited: site.favorited)
addToggleSecure(to: menu, secured: site.secured)
addUnlink(to: menu, with: site)
@ -172,6 +173,16 @@ extension DomainListVC {
)
}
private func addToggleFavorite(to menu: NSMenu, favorited: Bool) {
menu.addItem(
withTitle: favorited
? "domain_list.unfavorite".localized
: "domain_list.favorite".localized,
action: #selector(toggleFavorite),
keyEquivalent: ""
)
}
private func addMenuItemsForExtensions(to menu: NSMenu, for extensions: [PhpExtension], version: String) {
var items: [NSMenuItem] = [
NSMenuItem(title: "domain_list.applies_to".localized(version))
@ -200,6 +211,7 @@ extension DomainListVC {
let menu = NSMenu()
addOpenProxyInBrowser(to: menu)
addSeparator(to: menu)
addToggleFavorite(to: menu, favorited: proxy.favorited)
addToggleSecure(to: menu, secured: proxy.secured)
addRemoveProxy(to: menu)
return menu

View File

@ -221,7 +221,9 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
default: break
}
self.domains = descriptor.ascending ? sorted.reversed() : sorted
sorted = descriptor.ascending ? sorted.reversed() : sorted
self.domains = sorted.sorted { $0.getListableFavorited() && !$1.getListableFavorited() }
}
func addedNewSite(name: String, secureAfterLinking: Bool) async {
@ -261,16 +263,17 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let mapping: [String: String] = [
"TLS": DomainListTLSCell.reusableName,
"DOMAIN": DomainListNameCell.reusableName,
"ENVIRONMENT": DomainListPhpCell.reusableName,
"KIND": DomainListKindCell.reusableName,
"TYPE": DomainListTypeCell.reusableName
let mapping: [String: DomainListCellProtocol.Type] = [
"TLS": DomainListTLSCell.self,
"DOMAIN": DomainListNameCell.self,
"ENVIRONMENT": DomainListPhpCell.self,
"KIND": DomainListKindCell.self,
"TYPE": DomainListTypeCell.self
]
let columnName = tableColumn!.identifier.rawValue
let identifier = NSUserInterfaceItemIdentifier(rawValue: mapping[columnName]!)
guard let cellType = mapping[columnName] else { return nil }
let identifier = NSUserInterfaceItemIdentifier(rawValue: cellType.getCellIdentifier(for: domains[row]))
guard let userCell = tableView.makeView(withIdentifier: identifier, owner: self)
as? DomainListCellProtocol else { return nil }

View File

@ -112,5 +112,4 @@ struct ByteLimitView: View {
#Preview("Config Manager") {
ConfigManagerView()
.frame(width: 600, height: .infinity)
.previewDisplayName("Config Manager")
}

View File

@ -27,13 +27,14 @@ extension PhpExtensionManagerView {
)
}
public func install(_ ext: BrewPhpExtension) {
public func install(_ ext: BrewPhpExtension, onCompletion: @escaping () -> Void = {}) {
Task {
await self.runCommand(InstallPhpExtensionCommand(install: [ext]))
onCompletion()
}
}
public func confirmUninstall(_ ext: BrewPhpExtension) {
public func confirmUninstall(_ ext: BrewPhpExtension, onCompletion: @escaping () -> Void = {}) {
Alert.confirm(
onWindow: App.shared.phpExtensionManagerWindowController!.window!,
messageText: "phpextman.warnings.removal.title".localized(ext.name),
@ -45,6 +46,7 @@ extension PhpExtensionManagerView {
onFirstButtonPressed: {
Task {
await self.runCommand(RemovePhpExtensionCommand(remove: ext))
onCompletion()
}
}
)

View File

@ -12,7 +12,9 @@ import SwiftUI
struct PhpExtensionManagerView: View {
@ObservedObject var manager: BrewExtensionsObservable
@ObservedObject var status: BusyStatus
@State var searchText: String
@State private var highlightedExtension: String?
init() {
self.searchText = ""
@ -22,11 +24,22 @@ struct PhpExtensionManagerView: View {
self.status.busy = false
}
var availablePhpVersions: [String] {
if isRunningSwiftUIPreview {
return [manager.phpVersion]
}
return PhpEnvironments.shared.availablePhpVersions
}
var filteredExtensions: [BrewPhpExtension] {
guard !searchText.isEmpty else {
return manager.extensions
return manager.extensions.sorted { $0.isInstalled && !$1.isInstalled }
}
return manager.extensions.filter { $0.name.contains(searchText) }
return manager.extensions
.filter { $0.name.contains(searchText) }
.sorted { $0.isInstalled && !$1.isInstalled }
}
var body: some View {
@ -48,14 +61,19 @@ struct PhpExtensionManagerView: View {
title: self.status.title,
text: self.status.description
) {
List(Array(self.filteredExtensions.enumerated()), id: \.1.name) { (_, ext) in
listContent(for: ext)
.padding(.vertical, 8)
.padding(.horizontal, 8)
ScrollViewReader { proxy in
List(Array(self.filteredExtensions.enumerated()), id: \.1.name) { (_, ext) in
listContent(for: ext, proxy: proxy)
}
.edgesIgnoringSafeArea(.top)
.listStyle(PlainListStyle())
.searchable(text: $searchText)
.onChange(of: manager.phpVersion, perform: { _ in
if let ext = self.filteredExtensions.first {
proxy.scrollTo(ext.name)
}
})
}
.edgesIgnoringSafeArea(.top)
.listStyle(PlainListStyle())
.searchable(text: $searchText)
}
}
.frame(minWidth: 600, minHeight: 600)
@ -76,9 +94,8 @@ struct PhpExtensionManagerView: View {
// MARK: View Variables
private var phpVersionPicker: some View {
Picker("",
selection: $manager.phpVersion) {
ForEach(PhpEnvironments.shared.availablePhpVersions, id: \.self) {
Picker("", selection: $manager.phpVersion) {
ForEach(self.availablePhpVersions, id: \.self) {
Text("PHP \($0)")
.tag($0)
.font(.system(size: 12))
@ -147,17 +164,16 @@ struct PhpExtensionManagerView: View {
}
}
private func listContent(for ext: BrewPhpExtension) -> some View {
private func listContent(for ext: BrewPhpExtension, proxy: ScrollViewProxy) -> some View {
HStack(alignment: .center, spacing: 7.0) {
VStack(alignment: .center, spacing: 0) {
HStack {
HStack {
Image(systemName: ext.isInstalled || ext.hasAlternativeInstall
? "puzzlepiece.extension.fill"
: "puzzlepiece.extension")
.resizable()
.frame(width: 24, height: 20)
.foregroundColor(ext.hasAlternativeInstall ? Color.gray : Color.blue)
? "puzzlepiece.extension.fill" : "puzzlepiece.extension")
.resizable()
.frame(width: 24, height: 20)
.foregroundColor(ext.hasAlternativeInstall ? Color.gray : Color.blue)
}.frame(width: 36, height: 24)
VStack(alignment: .leading, spacing: 5) {
@ -184,19 +200,41 @@ struct PhpExtensionManagerView: View {
HStack {
if ext.isInstalled {
Button("phpman.buttons.uninstall".localizedForSwiftUI, role: .destructive) {
self.confirmUninstall(ext)
}.disabled(ext.firstDependent(in: self.manager.extensions) != nil)
self.confirmUninstall(ext, onCompletion: {
scrollAndAnimate(ext, proxy)
})
}
.disabled(ext.firstDependent(in: self.manager.extensions) != nil)
} else {
Button("phpman.buttons.install".localizedForSwiftUI) {
self.install(ext)
self.install(ext, onCompletion: {
scrollAndAnimate(ext, proxy)
})
}.disabled(ext.hasAlternativeInstall)
}
}
}
.padding(.vertical, 8)
.padding(.horizontal, 8)
.background(highlightedExtension == ext.name ? Color.accentColor.opacity(0.3) : Color.clear)
.cornerRadius(8)
.animation(.easeInOut(duration: 0.5), value: highlightedExtension)
}
private func scrollAndAnimate(_ ext: BrewPhpExtension, _ proxy: ScrollViewProxy) {
withAnimation {
highlightedExtension = ext.name
proxy.scrollTo(ext.name, anchor: .top)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
withAnimation {
highlightedExtension = nil
}
}
}
}
#Preview {
PhpExtensionManagerView()
.frame(width: 600, height: 600)
PhpExtensionManagerView().frame(width: 600, height: 600)
}

View File

@ -9,6 +9,7 @@
import Foundation
import SwiftUI
// swiftlint:disable type_body_length
struct PhpVersionManagerView: View {
@ObservedObject var formulae: BrewFormulaeObservable
@ObservedObject var status: BusyStatus
@ -192,6 +193,7 @@ struct PhpVersionManagerView: View {
.buttonStyle(.automatic)
.controlSize(.large)
}
.accessibilityIdentifier("RefreshButton")
.focusable(false)
.disabled(self.status.busy)
@ -231,14 +233,18 @@ struct PhpVersionManagerView: View {
}
}
if formula.isInstalled {
if formula.hasUpgradedFormulaAlias {
HelpButton(frameSize: 18, textSize: 14, shadowOpacity: 1, shadowRadius: 2, action: {
NSWorkspace.shared.open(Constants.Urls.WikiPhpUpgrade)
})
} else if formula.isInstalled {
Button("phpman.buttons.uninstall".localizedForSwiftUI, role: .destructive) {
Task { await self.confirmUninstall(formula) }
}
} else {
Button("phpman.buttons.install".localizedForSwiftUI) {
Task { await self.install(formula) }
}.disabled(formula.hasUpgradedFormulaAlias)
}.disabled(formula.hasUpgradedFormulaAlias || !formula.hasFormulaFile)
}
}
}
@ -268,6 +274,8 @@ struct PhpVersionManagerView: View {
Text("phpman.version.installed".localized(formula.installedVersion!))
.font(.system(size: 11))
.foregroundColor(.gray)
} else if !formula.hasFormulaFile {
unavailableFormula()
} else {
Text("phpman.version.available_for_installation".localizedForSwiftUI)
.font(.system(size: 11))
@ -291,7 +299,19 @@ struct PhpVersionManagerView: View {
.foregroundColor(formula.iconColor)
.padding(.horizontal, 5)
}
private func unavailableFormula() -> some View {
HStack(spacing: 5) {
Text("phpman.version.unavailable".localizedForSwiftUI)
.font(.system(size: 11))
.foregroundColor(.gray)
HelpButton(action: {
NSWorkspace.shared.open(Constants.Urls.WikiPhpUnavailable)
})
}
}
}
// swiftlint:enable type_body_length
#Preview {
PhpVersionManagerView(

View File

@ -45,7 +45,7 @@ extension NSEvent.ModifierFlags {
}
}
extension NSEvent.ModifierFlags: CustomStringConvertible {
extension NSEvent.ModifierFlags: @retroactive CustomStringConvertible {
public var description: String {
var output = ""

View File

@ -301,6 +301,8 @@ Möglicherweise werden Sie während des Deinstallationsvorgangs nach Ihrem Passw
"domain_list.always_use_php" = "Immer PHP %@ verwenden";
"domain_list.isolation_unavailable" = "Isolierung wird nicht unterstützt (in Valet 2)";
"domain_list.favorite" = "Als Favorit markieren";
"domain_list.unfavorite" = "Aus den Favoriten entfernen";
"domain_list.actions" = "Aktionen";
"domain_list.unlink" = "Ordner-Link aufheben";
"domain_list.secure" = "Domain sichern";
@ -616,7 +618,7 @@ Sie können dies tun, indem Sie `composer global update` in Ihrem Terminal ausf
// STARTUP
/// 0. Architecture mismatch
// 0. Architecture mismatch
"alert.homebrew_missing.title" = "PHP Monitor kann nicht starten!";
"alert.homebrew_missing.subtitle" = "Eine funktionierende Homebrew-Binärdatei konnte nicht am üblichen Ort gefunden werden. Bitte starten Sie die Anwendung neu, nachdem Sie dieses Problem behoben haben.";
@ -624,32 +626,32 @@ Sie können dies tun, indem Sie `composer global update` in Ihrem Terminal ausf
"alert.homebrew_missing.quit" = "Schließen";
/// PHP binary not found
// PHP binary not found
"startup.errors.php_binary.title" = "PHP ist nicht korrekt installiert";
"startup.errors.php_binary.subtitle" = "Sie müssen PHP über Homebrew installieren. Die App wird nicht richtig funktionieren, bis Sie dieses Problem behoben haben.";
"startup.errors.php_binary.desc" = "Normalerweise wird dieses Problem durch das Ausführen von `brew link php` in Ihrem Terminal gelöst.\n\n Um herauszufinden, was falsch ist, können Sie versuchen, `which php` in Ihrem Terminal auszuführen, es sollte `%@` zurückgeben.";
/// PHP not found in /usr/local/opt or /opt/homebrew/opt
// PHP not found in /usr/local/opt or /opt/homebrew/opt
"startup.errors.php_opt.title" = "PHP ist nicht korrekt installiert";
"startup.errors.php_opt.subtitle" = "Der PHP-Alias wurde in `%@` nicht gefunden. Die Anwendung wird nicht korrekt funktionieren, bis Sie dieses Problem behoben haben.";
"startup.errors.php_opt.desc" = "Wenn Sie bereits die `php` Formel installiert haben, müssen Sie eventuell `brew install php` ausführen, damit PHP Monitor diese Installation erkennt.";
/// PHP binary is broken
// PHP binary is broken
"startup.errors.dyld_library.title" = "PHP ist installiert, scheint aber defekt zu sein";
"startup.errors.dyld_library.subtitle" = "Wenn PHP Monitor versucht, Befehle auszuführen, gelingt es ihm nicht richtig. Dies ist normalerweise ein Hinweis auf eine fehlerhafte PHP-Installation.";
"startup.errors.dyld_library.desc" = "Das Ausführen von `brew reinstall php && brew link php` in Ihrem Terminal kann dieses Problem lösen, also versuchen Sie es bitte.";
/// Valet is not installed
// Valet is not installed
"startup.errors.valet_executable.title" = "Laravel Valet ist nicht korrekt installiert";
"startup.errors.valet_executable.subtitle" = "Sie müssen Valet mit Composer installieren. Die App wird nicht richtig funktionieren, bis Sie dieses Problem behoben haben.";
"startup.errors.valet_executable.desc" = "Wenn Sie Laravel Valet noch nicht installiert haben, tun Sie dies bitte zuerst. Wenn Sie es installiert haben, aber trotzdem diese Meldung sehen, dann versuchen Sie, `which valet` im Terminal auszuführen, es sollte zurückgegeben werden: `%@`.";
/// Valet configuration file missing or broken
// Valet configuration file missing or broken
"startup.errors.valet_json_invalid.title" = "Laravel Valet-Konfigurationsdatei ungültig oder fehlt";
"startup.errors.valet_json_invalid.subtitle" = "PHP Monitor muss in der Lage sein, die Konfigurationsdatei zu lesen. Es scheint, dass die Datei falsch formatiert ist oder fehlt. Bitte überprüfen Sie, ob sie existiert und korrekt formatiert ist.";
"startup.errors.valet_json_invalid.desc" = "Sie finden die Datei unter `~/.config/valet/config.json`. Wenn Laravel Valet die Konfigurationsdatei nicht parsen kann, wird die JSON-Datei durch die Ausführung eines beliebigen `valet`-Befehls normalerweise automatisch korrigiert. Versuchen Sie, `valet --version` auszuführen, um die Datei automatisch zu korrigieren.";
/// Valet version not readable
// Valet version not readable
"startup.errors.valet_version_unknown.title" = "Ihre Valet-Version konnte nicht gelesen werden";
"startup.errors.valet_version_unknown.subtitle" = "Das Auslesen der Ausgabe von `valet --version` ist fehlgeschlagen. Stellen Sie sicher, dass Ihre Valet-Installation funktioniert und aktuell ist.";
"startup.errors.valet_version_unknown.desc" = "Versuchen Sie, `valet --version` in einem Terminal auszuführen, um herauszufinden, was los ist.";
@ -665,27 +667,27 @@ Wenn Sie diese Meldung sehen, aber nicht wissen, warum dieser Ordner verschwunde
"startup.errors.valet_version_not_supported.subtitle" = "Sie verwenden eine Version von Valet, die derzeit nicht unterstützt wird. PHP Monitor funktioniert derzeit mit Valet v2, v3 und v4. Um Probleme auf Ihrem System zu vermeiden, kann PHP Monitor nicht gestartet werden.";
"startup.errors.valet_version_not_supported.desc" = "Sie müssen eine Version von Valet installieren, die mit PHP Monitor kompatibel ist, oder Sie müssen ein Upgrade auf eine neuere Version von PHP Monitor durchführen, die möglicherweise die Kompatibilität mit dieser Version von Valet beinhaltet (weitere Informationen finden Sie in den aktuellen Release Notes).";
/// Brew & sudoers
// Brew & sudoers
"startup.errors.sudoers_brew.title" = "Brew wurde nicht zu sudoers.d hinzugefügt";
"startup.errors.sudoers_brew.subtitle" = "Sie müssen `sudo valet trust` ausführen, um sicherzustellen, dass Valet Dienste starten und stoppen kann, ohne jedes Mal sudo verwenden zu müssen. Die Anwendung wird nicht richtig funktionieren, bis Sie dieses Problem behoben haben.";
"startup.errors.sudoers_brew.desc" = "Wenn Sie diesen Fehler immer wieder sehen, kann es sein, dass PHP Monitor die Datei nicht validieren kann. Dies lässt sich in der Regel wie folgt beheben: `sudo chmod +r /private/etc/sudoers.d/brew`.";
/// Valet & sudoers
// Valet & sudoers
"startup.errors.sudoers_valet.title" = "Valet wurde nicht zu sudoers.d hinzugefügt";
"startup.errors.sudoers_valet.subtitle" = "Sie müssen `sudo valet trust` ausführen, um sicherzustellen, dass Valet Dienste starten und stoppen kann, ohne jedes Mal sudo verwenden zu müssen. Die Anwendung wird nicht richtig funktionieren, bis Sie dieses Problem behoben haben. Wenn Sie dies bereits getan haben, führen Sie bitte `sudo valet trust` erneut aus.";
"startup.errors.sudoers_valet.desc" = "Wenn Sie diesen Fehler immer wieder sehen, kann es sein, dass PHP Monitor die Datei nicht validieren kann. Dies lässt sich in der Regel wie folgt beheben: `sudo chmod +r /private/etc/sudoers.d/valet`.";
/// Platform issue detected
// Platform issue detected
"startup.errors.global_composer_platform_issues.title" = "PHP Monitor und Valet funktionieren nicht richtig: Composer meldet ein Problem mit Ihrer Plattform";
"startup.errors.global_composer_platform_issues.subtitle" = "Bitte befolgen Sie die folgenden empfohlenen Schritte, um dieses Problem in Zukunft zu vermeiden:\n\n1. Führen Sie `composer global update` aus.\n2. Starten Sie PHP Monitor neu. (Es sollte wieder funktionieren.)\n3. Wechseln Sie zur ältesten PHP-Version, die Sie installiert haben.\n4. Führen Sie `composer global update` erneut aus.";
"startup.errors.global_composer_platform_issues.desc" = "Sie können in den Einstellungen die Option \"Globale Abhängigkeiten automatisch aktualisieren\" aktivieren. Dadurch werden Ihre globalen Composer-Abhängigkeiten bei jeder Änderung der PHP-Version aktualisiert, was nicht ideal ist, wenn Sie nicht ständig Zugang zum Internet haben. Valet funktioniert derzeit nicht mit den installierten Abhängigkeiten. Normalerweise wird dies durch eine Versionsabweichung verursacht: d.h. installierte Abhängigkeiten für eine neuere Version von PHP als die derzeit aktive Version.";
/// Cannot retrieve services
// Cannot retrieve services
"startup.errors.services_json_error.title" = "Status der Dienste kann nicht ermittelt werden";
"startup.errors.services_json_error.subtitle" = "PHP Monitor fragt normalerweise `brew` mit dem folgenden Befehl ab, um zu testen, ob die Dienste abgerufen werden können: `sudo brew services info nginx --json`.\nPHP Monitor konnte diese Antwort nicht interpretieren.";
"startup.errors.services_json_error.desc" = "Dies kann passieren, wenn Ihre Homebrew-Installation veraltet ist. In diesem Fall gibt Homebrew noch kein JSON zurück. Sie können dies normalerweise beheben, indem Sie `brew update` oder `brew tap homebrew/services` ausführen. Sie können auch versuchen, `sudo brew services info nginx --json` in einem Terminal Ihrer Wahl auszuführen.";
/// Issue with `which` alias
// Issue with `which` alias
"startup.errors.which_alias_issue.title" = "Es wurde ein Konfigurationsproblem festgestellt";
"startup.errors.which_alias_issue.subtitle" = "Es scheint, dass es eine Datei in `/usr/local/bin/which` gibt. Diese wird normalerweise von NodeJS eingerichtet, aber `node` ist nicht im PATH in `/usr/local/bin`. Um dies zu beheben, lesen Sie weiter.";
"startup.errors.which_alias_issue.desc" = "Sie müssen einen Symlink von `node` in das Verzeichnis `/usr/local/bin` setzen, um sicherzustellen, dass PHP Monitor erfolgreich starten kann. Für weitere Informationen siehe: https://github.com/nicoverbruggen/phpmon/issues/174";

View File

@ -120,7 +120,7 @@
"phpextman.list.status.external" = "This extension is already installed via another source, and cannot be managed.";
"phpextman.list.status.installable" = "This extension can be installed.";
"phpextman.list.status.dependent" = "You cannot uninstall this before uninstalling %@.";
"phpextman.list.status.can_manage" = "This extension is installed and can be managed by PHP Monitor.";
"phpextman.list.status.can_manage" = "This extension is installed and managed by PHP Monitor.";
"phpextman.errors.not_found.title" = "Uh oh. No extensions discovered!";
"phpextman.errors.not_found.desc" = "This is not supposed to happen. You may need to run the following command in your terminal:
@ -138,7 +138,9 @@ and restart PHP Monitor for extensions to become visible. If the problem persist
"phpman.version.has_update" = "Version %@ installed, %@ available.";
"phpman.version.installed" = "Version %@ is currently installed.";
"phpman.version.available_for_installation" = "This version can be installed.";
"phpman.version.automatic_upgrade" = "This version will be automatically installed by upgrading an older version.";
"phpman.version.unavailable" = "This version is temporarily unavailable.";
"phpman.version.automatic_upgrade" = "This version will be automatically installed by upgrading an older version.
(Both this new version and the old one will be available after upgrading.)";
"phpman.buttons.uninstall" = "Uninstall";
"phpman.buttons.install" = "Install";
"phpman.buttons.update" = "Update";
@ -330,6 +332,8 @@ You may be asked for your password during the uninstallation process if file per
"domain_list.always_use_php" = "Always use PHP %@";
"domain_list.isolation_unavailable" = "Isolation Not Supported (in Valet 2)";
"domain_list.favorite" = "Favorite Domain";
"domain_list.unfavorite" = "Unfavorite Domain";
"domain_list.actions" = "Actions";
"domain_list.unlink" = "Unlink Directory";
"domain_list.secure" = "Secure Domain";
@ -645,7 +649,7 @@ You can do this by running `composer global update` in your terminal. After that
// STARTUP
/// 0. Architecture mismatch
// 0. Architecture mismatch
"alert.homebrew_missing.title" = "PHP Monitor cannot start!";
"alert.homebrew_missing.subtitle" = "A working Homebrew binary could not be found in the usual location. Please restart the app after fixing this issue.";
@ -653,32 +657,32 @@ You can do this by running `composer global update` in your terminal. After that
"alert.homebrew_missing.quit" = "Quit";
/// PHP binary not found
// PHP binary not found
"startup.errors.php_binary.title" = "PHP is not correctly installed";
"startup.errors.php_binary.subtitle" = "You must install PHP via Homebrew. The app will not work correctly until you resolve this issue.";
"startup.errors.php_binary.desc" = "Usually running `brew link php` in your Terminal will resolve this issue.\n\nTo diagnose what is wrong, you can try running `which php` in your Terminal, it should return `%@`.";
/// PHP not found in /usr/local/opt or /opt/homebrew/opt
// PHP not found in /usr/local/opt or /opt/homebrew/opt
"startup.errors.php_opt.title" = "PHP is not correctly installed";
"startup.errors.php_opt.subtitle" = "The PHP alias was not found in `%@`. The app will not work correctly until you resolve this issue.";
"startup.errors.php_opt.desc" = "If you already have the `php` formula installed, you may need to run `brew install php` in order for PHP Monitor to detect this installation.";
/// PHP binary is broken
// PHP binary is broken
"startup.errors.dyld_library.title" = "PHP is installed, but appears to be broken";
"startup.errors.dyld_library.subtitle" = "When PHP Monitor is attempting to run commands, it is failing to do so correctly. This is usually an indicator of a broken PHP installation.";
"startup.errors.dyld_library.desc" = "Running `brew reinstall php && brew link php` in your Terminal may resolve this issue, so please give that a try.";
/// Valet is not installed
// Valet is not installed
"startup.errors.valet_executable.title" = "Laravel Valet is not correctly installed";
"startup.errors.valet_executable.subtitle" = "You must install Valet with Composer. The app will not work correctly until you resolve this issue.";
"startup.errors.valet_executable.desc" = "If you haven't installed Laravel Valet yet, please do so first. If you have it installed but are seeing this message anyway, then try running `which valet` in Terminal, it should return: `%@`.";
/// Valet configuration file missing or broken
// Valet configuration file missing or broken
"startup.errors.valet_json_invalid.title" = "Laravel Valet configuration file invalid or missing";
"startup.errors.valet_json_invalid.subtitle" = "PHP Monitor needs to be able to read the configuration file. It appears the file is malformed or missing. Please check that it exists and is formatted correctly.";
"startup.errors.valet_json_invalid.desc" = "You can find the file at `~/.config/valet/config.json`. If Laravel Valet cannot parse the configuration file, running any `valet` command will usually automatically fix the JSON file. Try running `valet --version` to automatically fix the file.";
/// Valet version not readable
// Valet version not readable
"startup.errors.valet_version_unknown.title" = "Your Valet version could not be read";
"startup.errors.valet_version_unknown.subtitle" = "Parsing the output of `valet --version` failed. Make sure your Valet installation works and is up-to-date.";
"startup.errors.valet_version_unknown.desc" = "Try running `valet --version` in a terminal to find out what's going on.";
@ -694,32 +698,32 @@ If you are seeing this message but are confused why this folder has gone missing
"startup.errors.valet_version_not_supported.subtitle" = "You are running a version of Valet that is currently not supported. PHP Monitor currently works with Valet v2, v3 and v4. In order to avoid causing issues on your system, PHP Monitor cannot start.";
"startup.errors.valet_version_not_supported.desc" = "You must install a version of Valet that is compatible with PHP Monitor, or you may need to upgrade to a newer version of PHP Monitor which may include compatibility for this version of Valet (consult the latest release notes for more info).";
/// Brew & sudoers
// Brew & sudoers
"startup.errors.sudoers_brew.title" = "Brew has not been added to sudoers.d";
"startup.errors.sudoers_brew.subtitle" = "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue.";
"startup.errors.sudoers_brew.desc" = "If you keep seeing this error, it is possible that there is a permission issue where PHP Monitor cannot validate the file, which can usually be resolved by running: `sudo chmod +r /private/etc/sudoers.d/brew`";
/// Valet & sudoers
// Valet & sudoers
"startup.errors.sudoers_valet.title" = "Valet has not been added to sudoers.d";
"startup.errors.sudoers_valet.subtitle" = "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue. If you did this before, please run `sudo valet trust` again.";
"startup.errors.sudoers_valet.desc" = "If you keep seeing this error, it is possible that there is a permission issue where PHP Monitor cannot validate the file, which can usually be resolved by running: `sudo chmod +r /private/etc/sudoers.d/valet`";
/// Platform issue detected
// Platform issue detected
"startup.errors.global_composer_platform_issues.title" = "PHP Monitor and Valet cannot work correctly: Composer is reporting an issue with your platform";
"startup.errors.global_composer_platform_issues.subtitle" = "Please follow these recommended steps to avoid seeing this issue in the future:\n\n1. Run `composer global update`.\n2. Restart PHP Monitor. (It should work again.)\n3. Switch to the oldest PHP version you have installed.\n4. Run `composer global update` again.";
"startup.errors.global_composer_platform_issues.desc" = "You can go to Preferences and check the 'Automatically update global dependencies' option. This will update your global Composer dependencies whenever you change PHP versions, so this may not be ideal if you may not have constant access to the internet.\n\nTo find out exactly what's going wrong, try running `valet --version`. Valet is currently not functional with the installed dependencies. Usually this is caused by a version mismatch: i.e. installed dependencies for a newer version of PHP than the version that is currently active.";
/// Cannot retrieve services
// Cannot retrieve services
"startup.errors.services_json_error.title" = "Cannot determine services status";
"startup.errors.services_json_error.subtitle" = "PHP Monitor usually queries `brew` using the following command to test if the services can be retrieved: `sudo brew services info nginx --json`.\n\nPHP Monitor could not interpret this response.";
"startup.errors.services_json_error.desc" = "This can happen if your Homebrew installation is out of date, in which case Homebrew won't return JSON yet. You can usually fix this by running `brew update` or `brew tap homebrew/services`. You can also try running `sudo brew services info nginx --json` in your terminal of choice.";
/// Issue with `which` alias
// Issue with `which` alias
"startup.errors.which_alias_issue.title" = "A configuration issue was detected";
"startup.errors.which_alias_issue.subtitle" = "It appears that there's a file in `/usr/local/bin/which`. This is usually set up by NodeJS, but `node` isn't in the PATH in `/usr/local/bin`. To fix this, keep reading.";
"startup.errors.which_alias_issue.desc" = "You will need to symlink `node` into the `/usr/local/bin` directory to make sure PHP Monitor can start successfully. For more info, see: https://github.com/nicoverbruggen/phpmon/issues/174";
/// Laravel Herd conflicts
// Laravel Herd conflicts
"startup.errors.herd_running.title" = "Laravel Herd appears to be running";
"startup.errors.herd_running.subtitle" = "It seems that Laravel Herd is currently running. Herd's built-in Valet setup may conflict with your regular Valet installation, so please quit Herd before continuing. (You can perfectly mix usage of Herd and regular Valet but you shouldn't run both at the same time.)";
"startup.errors.herd_running.desc" = "You may also find that the `php` alias by Herd added to your $PATH may prevent `php` aliasing of PHP Monitor from working, so keep that in mind. You can check out `~/.zshrc` and see what Herd has added to your $PATH.";
@ -749,7 +753,7 @@ Valet might break if you link these PHP versions so PHP Monitor won't let you sw
"startup.sponsor_encouragement.learn_more" = "Learn More";
"startup.sponsor_encouragement.skip" = "No Thanks";
// ERROR MESSAGES (based on AlertableError)
// ERROR MESSAGES
"alert.errors.homebrew_permissions.applescript_returned_nil.title" = "Restore Homebrew Permissions has been cancelled.";
"alert.errors.homebrew_permissions.applescript_returned_nil.description" = "The outcome of the script that is executed to adjust the permissions returned nil, which usually means that you did not grant administrative permissions to PHP Monitor.\n\nIf you clicked on Cancel during the authentication prompt, this is normal. If you did actually authenticate and you are still seeing this message, something probably went wrong.";
@ -862,3 +866,14 @@ Please note that some features (greyed out below) are currently unavailable beca
"alert.language_changed.title" = "You must restart PHP Monitor!";
"alert.language_changed.subtitle" = "You just changed the display language of PHP Monitor. The menu will immediately use the correct language, but you may need to restart the app for all text throughout the app to reflect your new language choice.";
// STARTUP TIMEOUT
"startup.timeout.ignore" = "Ignore";
"startup.timeout.title" = "PHP Monitor is taking too long to initialize!";
"startup.timeout.subtitle" = "If PHP Monitor remains busy for longer than 30 seconds, there may be something wrong with your Homebrew setup.";
"startup.timeout.description" = "Sometimes, due to various file permission issues, things may break. You can try using `brew doctor` and `brew cleanup` to fix this.
It is recommended to restart PHP Monitor afterwards. Learn more about this issue at: https://github.com/nicoverbruggen/phpmon/issues/294.
If PHP Monitor has finished initializing anyway or you want to wait a bit longer, feel free to click 'Ignore' and use PHP Monitor as usual. Either way, you may want to investigate, because this isn't supposed to take this long.";

View File

@ -317,6 +317,8 @@ Il se peut que vous deviez saisir votre mot de passe pendant le processus de dé
"domain_list.always_use_php" = "Tojours utiliser PHP %@";
"domain_list.isolation_unavailable" = "Isolement non pris en charge (dans Valet 2)";
"domain_list.favorite" = "Marquer comme favori";
"domain_list.unfavorite" = "Démarquer des favoris";
"domain_list.actions" = "Actions";
"domain_list.unlink" = "Supprimer le Lien vers le Dossier";
"domain_list.secure" = "Sécuriser le Domaine";
@ -629,7 +631,7 @@ Vous pouvez mettre à jour en executant `composer global update` dans votre term
// STARTUP
/// 0. Architecture mismatch
// 0. Architecture mismatch
"alert.homebrew_missing.title" = "Le PHP Monitor ne peut pas démarrer !";
"alert.homebrew_missing.subtitle" = "Un binaire Homebrew fonctionnel n'a pas pu être trouvé à l'emplacement standard. Veuillez redémarrer l'application après avoir résolu ce problème.";
@ -637,32 +639,32 @@ Vous pouvez mettre à jour en executant `composer global update` dans votre term
"alert.homebrew_missing.quit" = "Quitter";
/// PHP binary not found
// PHP binary not found
"startup.errors.php_binary.title" = "PHP n'est pas correctement installé";
"startup.errors.php_binary.subtitle" = "Vous devez installer PHP via Homebrew. L'application ne fonctionnera pas correctement tant que vous n'aurez pas résolu ce problème.";
"startup.errors.php_binary.desc" = "Généralement, l'exécution de `brew link php` dans votre Terminal résoudra ce problème.\n\nPour diagnostiquer ce qui ne va pas, vous pouvez essayer de lancer `which php` dans votre Terminal, il devrait retourner `%@`.";
/// PHP not found in /usr/local/opt or /opt/homebrew/opt
// PHP not found in /usr/local/opt or /opt/homebrew/opt
"startup.errors.php_opt.title" = "PHP n'est pas correctement installé";
"startup.errors.php_opt.subtitle" = "L'alias PHP n'a pas été trouvé dans `%@`. L'application ne fonctionnera pas correctement tant que vous n'aurez pas résolu ce problème.";
"startup.errors.php_opt.desc" = "Si vous avez déjà installé la formule `php`, vous pouvez avoir besoin de lancer `brew install php` pour que PHP Monitor détecte cette installation.";
/// PHP binary is broken
// PHP binary is broken
"startup.errors.dyld_library.title" = "PHP est installé, mais ne fonctionne manifestement pas.";
"startup.errors.dyld_library.subtitle" = "Lorsque le PHP Monitor tente d'exécuter des commandes, il ne parvient pas à le faire correctement. C'est généralement un indicateur d'une installation PHP défectueuse.";
"startup.errors.dyld_library.desc" = "Lancer `brew reinstall php && brew link php` dans votre Terminal peut résoudre ce problème, veuillez donc essayer.";
/// Valet is not installed
// Valet is not installed
"startup.errors.valet_executable.title" = "Laravel Valet n'est pas correctement installé";
"startup.errors.valet_executable.subtitle" = "Vous devez installer Valet avec Composer. L'application ne fonctionnera pas correctement tant que vous n'aurez pas résolu ce problème.";
"startup.errors.valet_executable.desc" = "Si vous n'avez pas encore installé Laravel Valet, faites-le d'abord. Si vous l'avez installé mais que vous voyez quand même ce message, essayez de lancer `which valet` dans Terminal, il devrait retourner : `%@`.";
/// Valet configuration file missing or broken
// Valet configuration file missing or broken
"startup.errors.valet_json_invalid.title" = "Fichier de configuration de Laravel Valet invalide ou manquant";
"startup.errors.valet_json_invalid.subtitle" = "PHP Monitor doit pouvoir lire le fichier de configuration. Il semble que le fichier soit malformé ou manquant. Veuillez vérifier qu'il existe et qu'il est correctement formaté.";
"startup.errors.valet_json_invalid.desc" = "Vous pouvez trouver le fichier à `~/.config/valet/config.json`. Si Laravel Valet ne peut pas analyser le fichier de configuration, l'exécution de n'importe quelle commande `valet` corrigera automatiquement le fichier JSON. Essayez d'exécuter `valet --version` pour corriger automatiquement le fichier.";
/// Valet version not readable
// Valet version not readable
"startup.errors.valet_version_unknown.title" = "La version de votre Valet n'a pu être lue";
"startup.errors.valet_version_unknown.subtitle" = "L'analyse de la réponse de `valet --version` a échoué. Assurez-vous que votre installation de Valet fonctionne et qu'elle est à jour.";
"startup.errors.valet_version_unknown.desc" = "Essayez de lancer `valet --version` dans un terminal pour savoir ce qui se passe.";
@ -678,32 +680,32 @@ Si vous voyez ce message mais que vous ne savez pas pourquoi ce dossier a dispar
"startup.errors.valet_version_not_supported.subtitle" = "Vous utilisez une version de Valet qui n'est pas supportée actuellement. PHP Monitor fonctionne actuellement avec Valet v2, v3 et v4. Afin d'éviter de causer des problèmes sur votre système, PHP Monitor ne peut pas démarrer.";
"startup.errors.valet_version_not_supported.desc" = "Vous devez installer une version de Valet qui est compatible avec PHP Monitor, ou vous pouvez avoir besoin de mettre à jour vers une version plus récente de PHP Monitor qui peut inclure la compatibilité avec cette version de Valet (consultez les dernières notes de version pour plus d'informations).";
/// Brew & sudoers
// Brew & sudoers
"startup.errors.sudoers_brew.title" = "Brew n'a pas été ajouté à sudoers.d";
"startup.errors.sudoers_brew.subtitle" = "Vous devez exécuter `sudo valet trust` pour vous assurer que Valet peut démarrer et arrêter les services sans avoir à utiliser sudo à chaque fois. L'application ne fonctionnera pas correctement tant que vous n'aurez pas résolu ce problème.";
"startup.errors.sudoers_brew.desc" = "Si vous continuez à voir cette erreur, il est possible qu'il y ait un problème de permission où PHP Monitor ne peut pas valider le fichier, ce qui peut généralement être résolu en exécutant : `sudo chmod +r /private/etc/sudoers.d/brew`";
/// Valet & sudoers
// Valet & sudoers
"startup.errors.sudoers_valet.title" = "Valet n'a pas été ajouté à sudoers.d";
"startup.errors.sudoers_valet.subtitle" = "Vous devez exécuter `sudo valet trust` pour vous assurer que Valet peut démarrer et arrêter les services sans avoir à utiliser sudo à chaque fois. L'application ne fonctionnera pas correctement tant que vous n'aurez pas résolu ce problème. Si vous l'avez déjà fait, exécutez à nouveau `sudo valet trust`.";
"startup.errors.sudoers_valet.desc" = "Si vous continuez à voir cette erreur, il est possible qu'il y ait un problème de permission où PHP Monitor ne peut pas valider le fichier, ce qui peut généralement être résolu en exécutant : `sudo chmod +r /private/etc/sudoers.d/valet`";
/// Platform issue detected
// Platform issue detected
"startup.errors.global_composer_platform_issues.title" = "PHP Monitor et Valet ne peuvent pas fonctionner correctement : Composer signale un problème avec votre plateforme";
"startup.errors.global_composer_platform_issues.subtitle" = "Veuillez suivre les étapes suivantes pour éviter ce problème à l'avenir :\n\n1. Exécuter `composer global update`.\n2. Redémarrer PHP Monitor. (Il devrait fonctionner à nouveau.)\n3. Passez à la version la plus ancienne de PHP que vous avez installée.\n4. Exécutez à nouveau `composer global update`.";
"startup.errors.global_composer_platform_issues.desc" = "Vous pouvez aller dans Préférences et cocher l'option 'Mettre à jour automatiquement les dépendances globales'. Cela mettra à jour les dépendances globales de Composer à chaque fois que vous changerez de version de PHP, ce qui n'est pas idéal si vous n'avez pas un accès constant à internet.\n\nPour savoir exactement ce qui ne va pas, essayez de lancer `valet --version`. Valet n'est actuellement pas fonctionnel avec les dépendances installées. Habituellement, cela est dû à une incompatibilité de version : c'est-à-dire que les dépendances installées correspondent à une version de PHP plus récente que la version actuellement active.";
/// Cannot retrieve services
// Cannot retrieve services
"startup.errors.services_json_error.title" = "Impossible de déterminer l'état des services";
"startup.errors.services_json_error.subtitle" = "PHP Monitor interroge habituellement `brew` en utilisant la commande suivante pour tester si les services peuvent être récupérés : `sudo brew services info nginx --json`.\n\n Le moniteur PHP n'a pas pu interpréter cette réponse.";
"startup.errors.services_json_error.desc" = "Cela peut arriver si votre installation Homebrew n'est pas à jour, auquel cas Homebrew ne renverra pas encore de JSON. Vous pouvez généralement corriger cela en lançant `brew update` ou `brew tap homebrew/services`. Vous pouvez aussi essayer de lancer `sudo brew services info nginx --json` dans le terminal de votre choix.";
/// Issue with `which` alias
// Issue with `which` alias
"startup.errors.which_alias_issue.title" = "Un problème de configuration a été détecté";
"startup.errors.which_alias_issue.subtitle" = "Il semble qu'il y ait un fichier dans `/usr/local/bin/which`. Ceci est généralement mis en place par NodeJS, mais `node` n'est pas dans le PATH de `/usr/local/bin`. Pour corriger cela, continuez à lire.";
"startup.errors.which_alias_issue.desc" = "Vous devrez faire un lien symbolique entre `node` et le répertoire `/usr/local/bin` pour vous assurer que PHP Monitor peut démarrer avec succès. Pour plus d'informations, voir : https://github.com/nicoverbruggen/phpmon/issues/174";
/// Laravel Herd conflicts
// Laravel Herd conflicts
"startup.errors.herd_running.title" = "Laravel Herd semble être lancé";
"startup.errors.herd_running.subtitle" = "Il semble que Laravel Herd soit en cours d'exécution. La configuration de Valet intégrée à Herd peut entrer en conflit avec votre installation habituelle de Valet, veuillez donc quitter Herd avant de continuer. (Vous pouvez parfaitement mélanger l'utilisation de Herd et de Valet mais vous ne devez pas utiliser les deux en même temps).";
"startup.errors.herd_running.desc" = "Vous pouvez également constater que l'alias `php` ajouté par Herd à votre $PATH peut empêcher l'alias `php` de PHP Monitor de fonctionner, alors gardez cela à l'esprit. Vous pouvez vérifier `~/.zshrc` et voir ce que Herd a ajouté à votre $PATH.";

View File

@ -302,6 +302,8 @@ Tijdens het verwijderingsproces kan u worden gevraagd om uw wachtwoord indien de
"domain_list.always_use_php" = "Altijd PHP %@ gebruiken";
"domain_list.isolation_unavailable" = "Isolatie wordt niet ondersteund (in Valet 2)";
"domain_list.favorite" = "Markeren als favoriet";
"domain_list.unfavorite" = "Verwijderen uit favorieten";
"domain_list.actions" = "Acties";
"domain_list.unlink" = "Link verwijderen";
"domain_list.secure" = "Domein beveiligen";
@ -617,38 +619,38 @@ U kunt dit doen door `composer global update` uit te voeren in uw terminal. Voer
// STARTUP
/// Architecture mismatch
// Architecture mismatch
"alert.homebrew_missing.title" = "PHP Monitor kan niet starten!";
"alert.homebrew_missing.subtitle" = "Er kon geen werkende Homebrew-binair worden gevonden op de gebruikelijke locatie. Start de app opnieuw nadat u dit probleem hebt verholpen.";
"alert.homebrew_missing.info" = "U gebruikt PHP Monitor met de volgende architectuur: %@. Hierdoor wordt verwacht dat er een werkende Homebrew binary gevonden wordt in `%@`, maar deze is niet gevonden. Daarom kan PHP Monitor niet werken.\n\nAls u Homebrew nog niet hebt geïnstalleerd, doe dit dan nu. Als u gebruikmaakt van Apple Silicon, zorg er dan voor dat uw Homebrew en PHP Monitor dezelfde architectuur gebruiken door Rosetta in of uit te schakelen waar nodig.";
"alert.homebrew_missing.quit" = "Afsluiten";
/// PHP binary not found
// PHP binary not found
"startup.errors.php_binary.title" = "PHP is niet correct geïnstalleerd";
"startup.errors.php_binary.subtitle" = "U moet PHP installeren via Homebrew. De app werkt niet correct totdat u dit probleem oplost.";
"startup.errors.php_binary.desc" = "Meestal lost u dit probleem op door `brew link php` uit te voeren in uw Terminal.\n\nOm te achterhalen wat er mis is, kunt u proberen `which php` uit te voeren in uw Terminal. Het zou `%@` moeten retourneren.";
/// PHP not found in /usr/local/opt or /opt/homebrew/opt
// PHP not found in /usr/local/opt or /opt/homebrew/opt
"startup.errors.php_opt.title" = "PHP is niet correct geïnstalleerd";
"startup.errors.php_opt.subtitle" = "De PHP-alias is niet gevonden in `%@`. De app werkt niet correct totdat u dit probleem oplost.";
"startup.errors.php_opt.desc" = "Als u de `php` formule al hebt geïnstalleerd, moet u mogelijk `brew install php` uitvoeren zodat PHP Monitor deze installatie kan detecteren.";
/// PHP binary is broken
// PHP binary is broken
"startup.errors.dyld_library.title" = "PHP is geïnstalleerd, maar lijkt defect te zijn";
"startup.errors.dyld_library.subtitle" = "Bij het uitvoeren van opdrachten slaagt PHP Monitor er niet in om dit correct te doen. Dit is meestal een indicatie van een defecte PHP-installatie.";
"startup.errors.dyld_library.desc" = "Het opnieuw installeren van PHP met `brew reinstall php && brew link php` in uw Terminal kan dit probleem verhelpen, dus probeer dat eens.";
/// Valet is not installed
// Valet is not installed
"startup.errors.valet_executable.title" = "Laravel Valet is niet correct geïnstalleerd";
"startup.errors.valet_executable.subtitle" = "U moet Valet installeren met Composer. De app werkt niet correct totdat u dit probleem oplost.";
"startup.errors.valet_executable.desc" = "Als u Laravel Valet nog niet hebt geïnstalleerd, doe dit dan eerst. Als u het al hebt geïnstalleerd maar toch dit bericht ziet, probeer dan `which valet` uit te voeren in Terminal. Het zou `%@` moeten retourneren.";
/// Valet configuration file missing or broken
// Valet configuration file missing or broken
"startup.errors.valet_json_invalid.title" = "Laravel Valet configuratiebestand ongeldig of ontbreekt";
"startup.errors.valet_json_invalid.subtitle" = "PHP Monitor moet in staat zijn om het configuratiebestand te lezen. Het lijkt erop dat het bestand onjuist is opgemaakt of ontbreekt. Controleer of het bestaat en correct is opgemaakt.";
"startup.errors.valet_json_invalid.desc" = "U kunt het bestand vinden op `~/.config/valet/config.json`. Als Laravel Valet het configuratiebestand niet kan parsen, zal het uitvoeren van een `valet`-opdracht meestal automatisch het JSON-bestand herstellen. Probeer `valet --version` uit te voeren om het bestand automatisch te herstellen.";
/// Valet version not readable
// Valet version not readable
"startup.errors.valet_version_unknown.title" = "Kan uw Valet-versie niet lezen";
"startup.errors.valet_version_unknown.subtitle" = "Het parsen van de uitvoer van `valet --version` is mislukt. Zorg ervoor dat uw Valet-installatie werkt en up-to-date is.";
"startup.errors.valet_version_unknown.desc" = "Probeer `valet --version` uit te voeren in een terminal om erachter te komen wat er aan de hand is.";
@ -664,32 +666,32 @@ Als u dit bericht ziet, maar verward bent waarom deze map ontbreekt, wilt u moge
"startup.errors.valet_version_not_supported.subtitle" = "U gebruikt een versie van Valet die momenteel niet wordt ondersteund. PHP Monitor werkt momenteel met Valet v2, v3 en v4. Om problemen op uw systeem te voorkomen, kan PHP Monitor niet starten.";
"startup.errors.valet_version_not_supported.desc" = "U moet een versie van Valet installeren die compatibel is met PHP Monitor, of u moet mogelijk upgraden naar een nieuwere versie van PHP Monitor die compatibiliteit biedt met deze versie van Valet (raadpleeg de nieuwste release-opmerkingen voor meer informatie).";
/// Brew & sudoers
// Brew & sudoers
"startup.errors.sudoers_brew.title" = "Brew is niet toegevoegd aan sudoers.d";
"startup.errors.sudoers_brew.subtitle" = "U moet `sudo valet trust` uitvoeren om ervoor te zorgen dat Valet services kan starten en stoppen zonder telkens sudo te hoeven gebruiken. De app werkt niet correct totdat u dit probleem oplost.";
"startup.errors.sudoers_brew.desc" = "Als u deze fout blijft zien, is het mogelijk dat er een machtigingsprobleem is waarbij PHP Monitor het bestand niet kan valideren. Dit kan meestal worden opgelost door `sudo chmod +r /private/etc/sudoers.d/brew` uit te voeren";
/// Valet & sudoers
// Valet & sudoers
"startup.errors.sudoers_valet.title" = "Valet is niet toegevoegd aan sudoers.d";
"startup.errors.sudoers_valet.subtitle" = "U moet `sudo valet trust` uitvoeren om ervoor te zorgen dat Valet services kan starten en stoppen zonder telkens sudo te hoeven gebruiken. De app werkt niet correct totdat u dit probleem oplost. Als u dit al eerder hebt gedaan, voer dan `sudo valet trust` opnieuw uit.";
"startup.errors.sudoers_valet.desc" = "Als u deze fout blijft zien, is het mogelijk dat er een machtigingsprobleem is waarbij PHP Monitor het bestand niet kan valideren. Dit kan meestal worden opgelost door `sudo chmod +r /private/etc/sudoers.d/valet` uit te voeren";
/// Platform issue detected
// Platform issue detected
"startup.errors.global_composer_platform_issues.title" = "PHP Monitor en Valet kunnen niet correct werken: Composer meldt een probleem met uw platform";
"startup.errors.global_composer_platform_issues.subtitle" = "Volg deze aanbevolen stappen om dit probleem in de toekomst te voorkomen:\n\n1. Voer `composer global update` uit.\n2. Start PHP Monitor opnieuw op. (Het zou weer moeten werken.)\n3. Schakel over naar de oudste geïnstalleerde versie van PHP.\n4. Voer opnieuw `composer global update` uit.";
"startup.errors.global_composer_platform_issues.desc" = "U kunt naar Voorkeuren gaan en de optie 'Automatisch globale afhankelijkheden bijwerken' controleren. Hiermee worden uw globale Composer-afhankelijkheden bijgewerkt wanneer u PHP-versies wijzigt, dus dit is mogelijk niet ideaal als u mogelijk geen constante toegang tot internet heeft.\n\nOm erachter te komen wat er precies misgaat, probeer `valet --version` uit te voeren. Valet is momenteel niet functioneel met de geïnstalleerde afhankelijkheden. Dit wordt meestal veroorzaakt door een versieverschil: bijvoorbeeld geïnstalleerde afhankelijkheden voor een nieuwere versie van PHP dan de momenteel actieve versie.";
/// Cannot retrieve services
// Cannot retrieve services
"startup.errors.services_json_error.title" = "Kan de status van services niet bepalen";
"startup.errors.services_json_error.subtitle" = "PHP Monitor maakt meestal gebruik van de volgende opdracht om `brew` te raadplegen en te controleren of de services kunnen worden opgehaald: `sudo brew services info nginx --json`.\n\nPHP Monitor kon deze reactie niet interpreteren.";
"startup.errors.services_json_error.desc" = "Dit kan gebeuren als uw Homebrew-installatie verouderd is, waardoor Homebrew nog geen JSON retourneert. U kunt dit meestal oplossen door `brew update` of `brew tap homebrew/services` uit te voeren. U kunt ook proberen `sudo brew services info nginx --json` uit te voeren in uw terminal naar keuze.";
/// Issue with `which` alias
// Issue with `which` alias
"startup.errors.which_alias_issue.title" = "Er is een configuratieprobleem gedetecteerd";
"startup.errors.which_alias_issue.subtitle" = "Het lijkt erop dat er een bestand is in `/usr/local/bin/which`. Dit wordt meestal ingesteld door NodeJS, maar `node` staat niet in de PATH in `/usr/local/bin`. Om dit op te lossen, gaat u verder met lezen.";
"startup.errors.which_alias_issue.desc" = "U moet `node` symbolisch koppelen aan de `/usr/local/bin`-directory om ervoor te zorgen dat PHP Monitor succesvol kan starten. Voor meer informatie, zie: https://github.com/nicoverbruggen/phpmon/issues/174";
/// Laravel Herd conflicts
// Laravel Herd conflicts
"startup.errors.herd_running.title" = "Laravel Herd lijkt actief te zijn";
"startup.errors.herd_running.subtitle" = "Het lijkt erop dat Laravel Herd momenteel actief is. De ingebouwde Valet-configuratie van Herd kan conflicteren met je reguliere Valet-installatie, dus sluit Herd af voordat je verdergaat. (Je kunt perfect gebruik maken van zowel Herd als reguliere Valet, maar je moet ze niet tegelijkertijd uitvoeren.)";
"startup.errors.herd_running.desc" = "Je kan ook merken dat de `php`-alias die Herd aan je $PATH heeft toegevoegd, niet werkt met de aliases van PHP Monitor, dus hou daar rekening mee. Je kunt `~/.zshrc` bekijken om te zien wat Herd aan je $PATH heeft toegevoegd.";

View File

@ -302,6 +302,8 @@ Poderá ser-lhe solicitada a sua palavra-passe durante o processo de desinstala
"domain_list.always_use_php" = "Usar sempre PHP %@";
"domain_list.isolation_unavailable" = "Isolamento não suportado (no Laravel Valet 2)";
"domain_list.favorite" = "Marcar como favorito";
"domain_list.unfavorite" = "Desmarcar do favoritado";
"domain_list.actions" = "Ações";
"domain_list.unlink" = "Desvincular diretoria";
"domain_list.secure" = "Domínio seguro";
@ -616,7 +618,7 @@ Pode faze-lo executando `composer global update` no seu Terminal. Depois disso,
// STARTUP
/// 0. Architecture mismatch
// 0. Architecture mismatch
"alert.homebrew_missing.title" = "PHP Monitor não pode iniciar!";
"alert.homebrew_missing.subtitle" = "Não foi encontrado um executável do Homebrew da diretoria por defeito. Reinicie a aplicação depois de corrigir o problema.";
@ -624,32 +626,32 @@ Pode faze-lo executando `composer global update` no seu Terminal. Depois disso,
"alert.homebrew_missing.quit" = "Sair";
/// PHP binary not found
// PHP binary not found
"startup.errors.php_binary.title" = "O PHP não está instalado corretamente";
"startup.errors.php_binary.subtitle" = "Deve instalar o PHP através do Homebrew. Esta aplicação não funcionará corretamente até que resolva este problema.";
"startup.errors.php_binary.desc" = "Normalmente, executar `brew link php` no seu Terminal resolve este problema.\n\nPara diagnosticar o que está errado, pode tentar executar `which php` no seu Terminal, que deverá mostrar `%@`.";
/// PHP not found in /usr/local/opt or /opt/homebrew/opt
// PHP not found in /usr/local/opt or /opt/homebrew/opt
"startup.errors.php_opt.title" = "O PHP não está instalado corretamente";
"startup.errors.php_opt.subtitle" = "O alias do PHP não foi encontrado em `%@`. Esta aplicação não funcionará corretamente até que resolva este problema.";
"startup.errors.php_opt.desc" = "Se já possui a fórmula `php` instalada, pode ser necessário executar `brew install php` para que o PHP Monitor detecte a instalação.";
/// PHP binary is broken
// PHP binary is broken
"startup.errors.dyld_library.title" = "O PHP está instalado, mas parece estar incorretamente instalado";
"startup.errors.dyld_library.subtitle" = "Quando o PHP Monitor tenta executar comandos, falha. Isto geralmente é um indicador de uma instalação do PHP incorretamente instalado.";
"startup.errors.dyld_library.desc" = "Executar `brew reinstall php && brew link php` no seu Terminal pode resolver este problema, pelo que deve tentar executar este comando.";
/// Valet is not installed
// Valet is not installed
"startup.errors.valet_executable.title" = "Laravel Valet não está instalado corretamente";
"startup.errors.valet_executable.subtitle" = "Deve instalar o Laravel Valet com o Composer. Esta aplicação não funcionará corretamente até que resolva este problema.";
"startup.errors.valet_executable.desc" = "Se ainda não instalou o Laravel Valet, faça-o agora. Se já instalou, tente executar `which valet` no seu Terminal, que deverá mostrar: `%@`.";
/// Valet configuration file missing or broken
// Valet configuration file missing or broken
"startup.errors.valet_json_invalid.title" = "ficheiro de configuração Laravel Valet inválido ou inexistente";
"startup.errors.valet_json_invalid.subtitle" = "O PHP Monitor precisa de ler o ficheiro de configuração. Parece que o ficheiro está malformado ou inexistente. Por favor, verifique se o ficheiro existe e se está formatado corretamente.";
"startup.errors.valet_json_invalid.desc" = "Pode encontrar o ficheiro em `~/.config/valet/config.json`. Se o Laravel Valet não puder analisar o ficheiro de configuração, execute um comando `valet` qualquer que geralmente corrigirá automaticamente o ficheiro JSON. Tente executar `valet --version` para corrigir automaticamente o ficheiro.";
/// Valet version not readable
// Valet version not readable
"startup.errors.valet_version_unknown.title" = "A versão Laravel Valet não pôde ser obtida";
"startup.errors.valet_version_unknown.subtitle" = "A análise do output de `valet --version` falhou. Certifique-se de que a instalação do Laravel Valet funciona e está atualizada.";
"startup.errors.valet_version_unknown.desc" = "Tente executar `valet --version` no seu Terminal para descobrir o motivo do problema.";
@ -665,27 +667,27 @@ Se ainda vê esta mensagem, e não percebe porque a diretoria desapareceu, conv
"startup.errors.valet_version_not_supported.subtitle" = "Está a executar uma versão do Laravel Valet que não é compatível de momento. O PHP Monitor atualmente funciona apenas com Laravel Valet v2, v3 e v4. Para evitar problemas no seu sistema, o PHP Monitor não pode ser iniciado.";
"startup.errors.valet_version_not_supported.desc" = "Deve instalar uma versão do Laravel Valet que seja compatível com o PHP Monitor, ou pode ser necessário atualizar para uma versão mais recente do PHP Monitor que pode incluir compatibilidade para esta versão do Laravel Valet (consulte as notas de versão mais recentes para obter mais informações).";
/// Brew & sudoers
// Brew & sudoers
"startup.errors.sudoers_brew.title" = "Brew não foi adicionado a sudoers.d";
"startup.errors.sudoers_brew.subtitle" = "Deve executar `sudo valet trust` no seu Terminal para garantir que o Laravel Valet pode iniciar e parar os serviços sem ter que usar sempre o sudo. Esta aplicação não funcionará corretamente até que resolva este problema.";
"startup.errors.sudoers_brew.desc" = "Se continuar a ver este erro, é possível que haja um problema de permissão em que o PHP Monitor não pode validar o ficheiro, o que normalmente pode ser resolvido executando no seu Terminal: `sudo chmod +r /private/etc/sudoers.d/brew`";
/// Valet & sudoers
// Valet & sudoers
"startup.errors.sudoers_valet.title" = "Laravel Valet não foi adicionado a sudoers.d";
"startup.errors.sudoers_valet.subtitle" = "Você deve executar `sudo valet trust` para garantir que o Laravel Valet possa iniciar e parar os serviços sem ter que usar o sudo todas as vezes. Esta aplicação não funcionará corretamente até que você resolva este problema. Mesmo que já tenha feito isto antes, experimente executar novamente no seu Terminal `sudo valet trust`.";
"startup.errors.sudoers_valet.desc" = "Se continuar a ver este erro, é possível que haja um problema de permissão em que o PHP Monitor não pode validar o ficheiro, que normalmente pode ser resolvido executando no seu Terminal: `sudo chmod +r /private/etc/sudoers.d/valet`";
/// Platform issue detected
// Platform issue detected
"startup.errors.global_composer_platform_issues.title" = "O PHP Monitor e o Laravel Valet não funcionam corretamente: o Composer reporta um problema com sua plataforma";
"startup.errors.global_composer_platform_issues.subtitle" = "Siga estes passos recomendados para evitar este problema de futuro:\n\n1. Execute a `atualização global do compositor`.\n2. Reinicie o PHP Monitor. (Deve funcionar novamente.)\n3. Mude para a versão mais antiga do PHP que instalou.\n4. Execute a `atualização global do compositor` novamente.";
"startup.errors.global_composer_platform_issues.desc" = "Pode ir a Preferências e marcar a opção 'Atualizar dependências globais automaticamente'. Isto atualizará as suas dependências globais do Composer sempre que alterar versões do PHP, portanto, no entanto, pode não ser a solução ideal se não tiver acesso à Internet.\n\nPara descobrir exatamente o que está errado, tente executar no seu Terminal `valet --version`. O Laravel Valet não está a funcionar com as dependências instaladas. Normalmente, isso é provocado por uma incompatibilidade de versão: ou seja, dependências instaladas para uma versão mais recente do PHP do que a versão que está vinculada no momento.";
/// Cannot retrieve services
// Cannot retrieve services
"startup.errors.services_json_error.title" = "Não é possível determinar o status dos serviços";
"startup.errors.services_json_error.subtitle" = "O PHP Monitor geralmente usar o `brew` usando o seguinte comando para testar se os serviços podem ser recuperados: `sudo brew services info nginx --json`.\n\nO PHP Monitor não pôde interpretar a resposta do `brew`.";
"startup.errors.services_json_error.desc" = "Isto pode acontecer se a instalação do Homebrew estiver desatualizada, e o Homebrew ainda não retornar JSON. Geralmente, pode corrigir isto executando no seu Terminal `brew update` ou `brew tap homebrew/services`. Também pode tentar executar no seu Terminal `sudo brew services info nginx --json`.";
/// Issue with `which` alias
// Issue with `which` alias
"startup.errors.which_alias_issue.title" = "Foi detectado um problema de configuração";
"startup.errors.which_alias_issue.subtitle" = "Parece que há um ficheiro em `/usr/local/bin/which`. Isto geralmente é configurado pelo NodeJS, mas `node` não está no PATH em `/usr/local/bin`. Para corrigir isto, continue a ler.";
"startup.errors.which_alias_issue.desc" = "Precisará vincular `node` à diretoria `/usr/local/bin` para garantir que o PHP Monitor possa iniciar com sucesso. Para mais informações, consulte: https://github.com/nicoverbruggen/phpmon/issues/174";

View File

@ -292,6 +292,8 @@ Bạn có thể được yêu cầu nhập mật khẩu của mình trong quá t
"domain_list.always_use_php" = "Luôn sử dụng PHP %@";
"domain_list.isolation_unavailable" = "Không hỗ trợ cô lập (trong Valet 2)";
"domain_list.favorite" = "Điểm yêu thích";
"domain_list.unfavorite" = "Bỏ khỏi điểm yêu thích";
"domain_list.actions" = "Hành động";
"domain_list.unlink" = "Hủy liên kết Thư mục";
"domain_list.secure" = "Bảo mật Tên miền";
@ -609,7 +611,7 @@ Bạn có thể làm điều này bằng cách chạy `composer global update` t
// STARTUP
/// 0. Architecture mismatch
// 0. Architecture mismatch
"alert.homebrew_missing.title" = "PHP Monitor không thể khởi động!";
"alert.homebrew_missing.subtitle" = "Không tìm thấy binary Homebrew hoạt động ở vị trí thông thường. Vui lòng khởi động lại ứng dụng sau khi khắc phục vấn đề này.";
@ -617,32 +619,32 @@ Bạn có thể làm điều này bằng cách chạy `composer global update` t
"alert.homebrew_missing.quit" = "Thoát";
/// PHP binary not found
// PHP binary not found
"startup.errors.php_binary.title" = "PHP chưa được cài đặt đúng cách";
"startup.errors.php_binary.subtitle" = "Bạn phải cài đặt PHP qua Homebrew. Ứng dụng sẽ không hoạt động chính xác cho đến khi bạn giải quyết vấn đề này.";
"startup.errors.php_binary.desc" = "Thường thì chạy `brew link php` trong Terminal của bạn sẽ giải quyết vấn đề này.\n\nĐể chẩn đoán lỗi, bạn có thể thử chạy `which php` trong Terminal của bạn, nó sẽ trả về `%@`.";
/// PHP not found in /usr/local/opt or /opt/homebrew/opt
// PHP not found in /usr/local/opt or /opt/homebrew/opt
"startup.errors.php_opt.title" = "PHP chưa được cài đặt đúng cách";
"startup.errors.php_opt.subtitle" = "Bí danh PHP không được tìm thấy ở `%@`. Ứng dụng sẽ không hoạt động chính xác cho đến khi bạn giải quyết vấn đề này.";
"startup.errors.php_opt.desc" = "Nếu bạn đã cài đặt công thức `php`, bạn có thể cần chạy `brew install php` để PHP Monitor nhận ra cài đặt này.";
/// PHP binary is broken
// PHP binary is broken
"startup.errors.dyld_library.title" = "PHP đã được cài đặt, nhưng có vẻ bị lỗi";
"startup.errors.dyld_library.subtitle" = "Khi PHP Monitor cố gắng chạy các lệnh, nó không thể chạy chúng chính xác. Điều này thường là một dấu hiệu của một cài đặt PHP bị lỗi.";
"startup.errors.dyld_library.desc" = "Chạy `brew reinstall php && brew link php` trong Terminal của bạn có thể giải quyết vấn đề này, vì vậy hãy thử điều đó.";
/// Valet is not installed
// Valet is not installed
"startup.errors.valet_executable.title" = "Laravel Valet chưa được cài đặt đúng cách";
"startup.errors.valet_executable.subtitle" = "Bạn phải cài đặt Valet với Composer. Ứng dụng sẽ không hoạt động chính xác cho đến khi bạn giải quyết vấn đề này.";
"startup.errors.valet_executable.desc" = "Nếu bạn chưa cài đặt Laravel Valet, hãy cài đặt trước. Nếu bạn đã cài đặt, nhưng vẫn nhìn thấy thông báo này, thì hãy thử chạy `which valet` trong Terminal, nó sẽ trả về: `%@`.";
/// Valet configuration file missing or broken
// Valet configuration file missing or broken
"startup.errors.valet_json_invalid.title" = "Tệp cấu hình Laravel Valet không hợp lệ hoặc bị thiếu";
"startup.errors.valet_json_invalid.subtitle" = "PHP Monitor cần có thể đọc tệp cấu hình. Dường như tệp đã bị lỗi hoặc bị thiếu. Vui lòng kiểm tra xem nó tồn tại và được định dạng đúng.";
"startup.errors.valet_json_invalid.desc" = "Bạn có thể tìm thấy tệp tại `~/.config/valet/config.json`. Nếu Laravel Valet không thể phân tích cú pháp tệp cấu hình, chạy bất kỳ lệnh `valet` nào thường sẽ tự động sửa tệp JSON. Thử chạy `valet -- version` để tự động sửa file.";
/// Valet version not readable
// Valet version not readable
"startup.errors.valet_version_unknown.title" = "Phiên bản Valet của bạn không thể đọc được";
"startup.errors.valet_version_unknown.subtitle" = "Phân tích kết quả của `valet --version` đã thất bại. Hãy đảm bảo cài đặt Valet của bạn hoạt động và được cập nhật.";
"startup.errors.valet_version_unknown.desc" = "Thử chạy `valet --version` trong terminal để tìm hiểu điều gì đang xảy ra.";
@ -658,32 +660,32 @@ Nếu bạn nhìn thấy thông báo này nhưng bối rối vì thư mục này
"startup.errors.valet_version_not_supported.subtitle" = "Bạn đang chạy một phiên bản của Valet hiện không được hỗ trợ. PHP Monitor hiện tại hoạt động với Valet v2, v3 và v4. Để tránh gây ra sự cố trên hệ thống của bạn, PHP Monitor không thể khởi động.";
"startup.errors.valet_version_not_supported.desc" = "Bạn phải cài đặt một phiên bản của Valet tương thích với PHP Monitor, hoặc bạn có thể cần nâng cấp lên phiên bản mới hơn của PHP Monitor có thể bao gồm tính năng tương thích cho phiên bản này của Valet (xem thông tin chi tiết về các bản phát hành mới nhất để biết thêm thông tin).";
/// Brew & sudoers
// Brew & sudoers
"startup.errors.sudoers_brew.title" = "Brew chưa được thêm vào sudoers.d";
"startup.errors.sudoers_brew.subtitle" = "Bạn phải chạy lệnh `sudo valet trust` để đảm bảo Valet có thể bắt đầu và dừng dịch vụ mà không cần phải sử dụng sudo mỗi lần. Ứng dụng sẽ không hoạt động chính xác cho đến khi bạn giải quyết vấn đề này.";
"startup.errors.sudoers_brew.desc" = "Nếu bạn tiếp tục nhìn thấy lỗi này, có thể có vấn đề về quyền truy cập mà PHP Monitor không thể xác minh tệp, điều này thường có thể giải quyết bằng cách chạy: `sudo chmod +r /private/etc/sudoers.d/brew`";
/// Valet & sudoers
// Valet & sudoers
"startup.errors.sudoers_valet.title" = "Valet chưa được thêm vào sudoers.d";
"startup.errors.sudoers_valet.subtitle" = "Bạn phải chạy lệnh `sudo valet trust` để đảm bảo Valet có thể bắt đầu và dừng dịch vụ mà không cần phải sử dụng sudo mỗi lần. Ứng dụng sẽ không hoạt động chính xác cho đến khi bạn giải quyết vấn đề này. Nếu bạn đã làm điều này trước đó, hãy chạy lại lệnh `sudo valet trust`.";
"startup.errors.sudoers_valet.desc" = "Nếu bạn tiếp tục nhìn thấy lỗi này, có thể có vấn đề về quyền truy cập mà PHP Monitor không thể xác minh tệp, điều này thường có thể giải quyết bằng cách chạy: `sudo chmod +r /private/etc/sudoers.d/valet`";
/// Platform issue detected
// Platform issue detected
"startup.errors.global_composer_platform_issues.title" = "PHP Monitor và Valet không thể hoạt động chính xác: Composer báo cáo vấn đề với nền tảng của bạn";
"startup.errors.global_composer_platform_issues.subtitle" = "Vui lòng làm theo những bước khuyến nghị sau để tránh gặp vấn đề này trong tương lai:\n\n1. Chạy lệnh `composer global update`.\n2. Khởi động lại PHP Monitor. (Nó sẽ hoạt động lại).\n3. Chuyển sang phiên bản PHP cũ nhất mà bạn đã cài đặt.\n4. Chạy lại lệnh `composer global update`.";
"startup.errors.global_composer_platform_issues.desc" = "Bạn có thể vào Tùy chọn và kiểm tra tùy chọn 'Tự động cập nhật các phụ thuộc toàn cầu'. Điều này sẽ cập nhật các phụ thuộc Composer toàn cầu của bạn mỗi khi bạn thay đổi phiên bản PHP, vì vậy điều này có thể không lý tưởng nếu bạn không có quyền truy cập internet liên tục.\n\nĐể biết chính xác điều gì đang sai, hãy thử chạy `valet --version`. Hiện tại, Valet không hoạt động với các phụ thuộc đã cài đặt. Thông thường điều này được gây ra bởi sự không phù hợp của phiên bản: ví dụ như các phụ thuộc đã cài đặt cho một phiên bản PHP mới hơn phiên bản hiện tại đang hoạt động.";
/// Cannot retrieve services
// Cannot retrieve services
"startup.errors.services_json_error.title" = "Không thể xác định trạng thái dịch vụ";
"startup.errors.services_json_error.subtitle" = "PHP Monitor thường sử dụng lệnh sau để kiểm tra liệu các dịch vụ có thể được truy xuất hay không: `sudo brew services info nginx --json`.\n\nPHP Monitor không thể giải thích được câu trả lời này.";
"startup.errors.services_json_error.desc" = "Điều này có thể xảy ra nếu cài đặt Homebrew của bạn đã lỗi thời, trong trường hợp đó Homebrew sẽ không trở lại JSON. Bạn có thể khắc phục điều này bằng cách chạy `brew update` hoặc `brew tap homebrew/services`. Bạn cũng có thể thử chạy `sudo brew services info nginx --json` trong bất kỳ terminal nào bạn muốn.";
/// Issue with `which` alias
// Issue with `which` alias
"startup.errors.which_alias_issue.title" = "Phát hiện vấn đề cấu hình";
"startup.errors.which_alias_issue.subtitle" = "Dường như có một tệp trong `/usr/local/bin/which`. Điều này thường được thiết lập bởi NodeJS, nhưng `node` không có trong PATH trong `/usr/local/bin`. Để khắc phục điều này, hãy đọc tiếp.";
"startup.errors.which_alias_issue.desc" = "Bạn sẽ cần tạo liên kết tượng trưng cho `node` vào thư mục `/usr/local/bin` để đảm bảo PHP Monitor có thể khởi động thành công. Để biết thêm thông tin, xem: https://github.com/nicoverbruggen/phpmon/issues/174";
/// Laravel Herd conflicts
// Laravel Herd conflicts
"startup.errors.herd_running.title" = "Có vẻ như Laravel Herd đang chạy";
"startup.errors.herd_running.subtitle" = "Có vẻ như Laravel Herd hiện đang chạy. Cài đặt Valet tích hợp của Herd có thể xung đột với cài đặt Valet thông thường của bạn, vì vậy hãy thoát Herd trước khi tiếp tục. (Bạn có thể hoàn toàn kết hợp sử dụng Herd và Valet thông thường nhưng bạn không nên chạy cả hai cùng một lúc.)";
"startup.errors.herd_running.desc" = "Bạn cũng có thể thấy rằng bí danh `php` mà Herd thêm vào $PATH của bạn có thể ngăn chặn việc đặt bí danh `php` cho PHP Monitor hoạt động, vì vậy hãy lưu ý điều đó. Bạn có thể kiểm tra tệp `~/.zshrc` và xem Herd đã thêm gì vào $PATH của bạn.";

View File

@ -1,4 +1,4 @@
// 菜单项(MI)
// MENU ITEMS (MI)
"mi_busy" = "PHP Monitor 正忙着...";
"mi_unsure" = "我们不确定您正在运行的 PHP 版本";
@ -211,32 +211,32 @@
"phpman.services.inactive" = "关键服务未运行";
"phpman.services.all_ok" = "所有 Valet 服务均正常";
// 精简模式
// LITE MODE
"lite_mode_explanation.title" = "您当前正以独立模式运行 PHP Monitor";
"lite_mode_explanation.subtitle" = "PHP Monitor 有一些附加功能,如果你恰好是 Laravel Valet 的用户就可以使用这些功能。目前PHP Monitor 无法检测到您的系统中正在安装 Valet因此这些功能不可用。";
"lite_mode_explanation.description" = "如需了解更多信息,我建议查看 README(可在 GitHub 上获取),其中将解释安装 Valet 所需的步骤,以及如何使 PHP Monitor 在安装 Valet 后正常运行。安装 Laravel Valet 后,需要重启 PHP Monitor它才会脱离独立模式";
// 通用
// GENERIC
"generic.ok" = "确定";
"generic.cancel" = "取消";
"generic.retry" = "重试";
"generic.notice" = "通知";
// 加载预设
// PRESET LOADING
"preset_help_title" = "使用配置预设";
"preset_help_info" = "您可以在 config.json 文件中设置配置预设,该文件位于 ~/.config/phpmon/config.json。这些预设可以一次性应用选定的配置值。这是一项强大的功能但目前需要手动设置";
"preset_help_desc" = "重启 PHP Monitor 后,将加载文件中找到的所有预设。如果没有预设出现,则可能是文件无法正确解析。\n\n您可以点击此提示中的问号转到 GitHub 上的 FAQ在那里您可以找到有关此功能的更多信息包括一个示例文件。";
// 菜单项(如果窗口已打开)
// MENU ITEMS (if window is open)
"mm_add_folder_as_link" = "将文件夹添加为链接...";
"mm_reload_domain_list" = "重新载入域名列表";
"mm_find_in_domain_list" = "在域名列表中搜索";
// 站点列表
// SITE LIST
"domain_list.title" = "域名";
"domain_list.subtitle" = "";
@ -278,7 +278,7 @@
"domain_list.extensions" = "切换扩展";
"domain_list.applies_to" = "适用于 PHP %@";
// 选择要添加的内容
// CHOOSE WHAT TO ADD
"selection.title" = "您想设置什么样的域名?";
"selection.description" = "链接用于直接为项目提供服务。如果你有一个包含代码的 Laravel、Symfony、WordPress 等文件夹,你需要创建一个链接,并选择代码所在的文件夹。\n\n如果需要代理可以将容器等代理到特定域名。 例如,这在与 Docker 结合使用时非常有用。";
@ -286,7 +286,7 @@
"selection.create_proxy" = "创建代理";
"selection.cancel" = "取消";
// 将代理添加到域名列表
// ADD PROXY TO DOMAINS LIST
"domain_list.add.set_up_proxy" = "设置代理";
"domain_list.add.proxy_subject" = "代理主机(必须包括协议和端口)";
@ -297,7 +297,7 @@
(!)重要提示:在该域名的 nginx 配置文件中手动添加 `proxy_ssl_verify off;` 之前,该代理可能无法工作。建议使用不安全的域名作为代理主体。";
// 将站点添加到域名列表
// ADD SITE TO DOMAINS LIST
"domain_list.add.link_folder" = "链接文件夹";
"domain_list.add.domain_name_placeholder" = "在此输入域名";
@ -313,7 +313,7 @@
"domain_list.add.errors.subject_invalid" = "您输入的主题无效";
"domain_list.add.errors.already_exists" = "该名称的链接已经存在";
// 添加站点错误:选择后文件夹丢失
// ADD SITE ERROR: FOLDER MISSING SINCE SELECTION
"domain_list.alert.folder_missing.desc" = "您选择的文件夹似乎已不存在。您要取消添加此文件夹吗?如果您移动了文件夹,可以将其放回去再试一次。";
"domain_list.alert.folder_missing.title" = "文件夹丢失!";
@ -322,7 +322,7 @@
"domain_list.add.modal_description" = "首先,选择要链接的文件夹";
// 站点列表操作
// SITE LIST ACTIONS
"domain_list.isolate" = "切换 PHP 版本";
"domain_list.site_isolation" = "站点隔离";
@ -330,6 +330,8 @@
"domain_list.always_use_php" = "始终使用 PHP %@";
"domain_list.isolation_unavailable" = "不支持隔离(在 Valet 2 中)";
"domain_list.favorite" = "添加到收藏";
"domain_list.unfavorite" = "从收藏中移除";
"domain_list.actions" = "操作";
"domain_list.unlink" = "取消链接目录";
"domain_list.secure" = "安全域名";
@ -365,23 +367,23 @@ pm%@
"domain_list.columns.type" = "类型";
"domain_list.columns.kind" = "种类";
// 驱动程序
// DRIVERS
"driver.not_detected" = "其他";
// 预设
// PRESET
"preset.extension" = "%i 扩展";
"preset.extensions" = "%i 扩展";
"preset.preference" = "%i 偏好";
"preset.preferences" = "%i 偏好";
// 编辑器
// EDITORS
"editors.alert.try_again" = "再试一次";
"editors.alert.cancel" = "取消";
// 预设
// PREFERENCES
"prefs.title" = "PHP Monitor";
"prefs.subtitle" = "首选项";
@ -489,7 +491,7 @@ pm%@
"prefs.display_misc_desc" = "如果禁用,将无法访问急救与服务菜单";
"prefs.display_misc" = "急救与服务菜单";
// 通知
// NOTIFICATIONS
"notification.version_changed_title" = "PHP %@ 已激活";
"notification.version_changed_desc" = "PHP Monitor 已切换到 PHP %@";
@ -513,7 +515,7 @@ pm%@
"notification.phpmon_updated.desc" = "您现在运行的是 PHP Monitor v%@。感谢您保持更新!";
"notification.phpmon_updated_dev.desc" = "PHP Monitor v%@ (build %@)已安装并激活";
// 组件更新
// Composer Update
"alert.composer_missing.title" = "未找到 Composer";
"alert.composer_missing.subtitle" = "PHP Monitor 无法找到 Composer。请确保已安装 Composer 并重试。";
"alert.composer_missing.desc" = "PHP Monitor 假定 Composer 位于以下任一位置:
@ -534,7 +536,7 @@ pm%@
"alert.composer_success.title" = "Composer 已完成更新!";
"alert.composer_success.info" = "您的全局 Composer 依赖项已成功更新";
// Composer 版本
// Composer Version
"alert.composer_php_isolated.desc" = "该站点已被隔离,这意味着 Valet 将专门为该站点提供 PHP %@。全局版本目前为 PHP %@。";
"alert.composer_php_requirement.title" = "`%@` 需要 PHP %@";
@ -549,31 +551,31 @@ pm%@
"alert.php_version_incorrect" = "当前活动的 PHP 版本与此站点所需的约束设置不匹配";
"alert.php_suggestions" = "可能有其他更接近约束条件的 PHP 版本";
// 建议修复我的 Valet 工具
// Suggest Fix My Valet
"alert.php_switch_failed.title" = "切换到 PHP %@ 似乎失败了";
"alert.php_switch_failed.info" = "PHP Monitor 检测到 PHP %@ 在完成切换程序后没有激活。您可以尝试运行 `Fix My Valet` 并在运行后重新切换。您想试试这个修复方法吗?";
"alert.php_switch_failed.desc" = "首先,如果您还没有尝试过 `Fix My Valet`,您应该试试。如果在此之后 PHP Monitor 仍然无法更改活动的 PHP 版本,则可能需要升级 Valet 和系统中的 Homebrew 软件包。您可以通过运行 `brew update && brew upgrade` 和运行 `composer global update && valet install` 来升级 Valet";
"alert.php_switch_failed.confirm" = "是的,运行 `Fix My Valet`";
"alert.php_switch_failed.cancel" = "不运行";
// PHP Formula 丢失
// PHP Formula Missing
"alert.php_formula_missing.title" = "哎呀Fix My Valet 必须安装 `php` Formula...";
"alert.php_formula_missing.info" = "看来您没有安装 `php` Formula导致 PHP Monitor 无法运行 Fix My Valet。请使用 `brew install php` 安装,重启 PHP Monitor 并重试。";
// Fix My Valet 启动
// Fix My Valet Started
"alert.fix_my_valet.title" = "有问题Fix My Valet 已准备就绪!";
"alert.fix_my_valet.info" = "这可能需要一段时间。请耐心等待。\n\n完成后所有其他服务都将停止PHP %@ 将被链接。一旦这项操作完成,您就可以切换到所需的 PHP 版本了。\n\n(一旦 Fix My Valet 服务完成,您将收到另一条提醒。)";
"alert.fix_my_valet.ok" = "继续";
"alert.fix_my_valet.cancel" = "中止";
// 修复我的 Valet 操作完成
// Fix My Valet Done
"alert.fix_my_valet_done.title" = "Fix My Valet 已完成操作";
"alert.fix_my_valet_done.subtitle" = "已停止所有相应的服务并重新启动了正确的服务,最新版本的 PHP 现在应该处于活动状态。您现在可以尝试切换到另一个版本的 PHP。";
"alert.fix_my_valet_done.stay" = "停留在 PHP %@";
"alert.fix_my_valet_done.switch_back" = "切换回 PHP %@";
"alert.fix_my_valet_done.desc" = "如果访问网站仍然无法正常工作,您可以尝试再次运行 `valet install`,这可以修复 502 问题(网关错误)。如果 Valet 已损坏且无法运行 `valet install`,您可能需要运行 `composer global update`。如果您遇到其他问题,请查阅 GitHub 上的 FAQ";
// 恢复 Homebrew 权限
// Restore Homebrew Permissions
"alert.fix_homebrew_permissions.title" = "关于 `恢复Homebrew权限`";
"alert.fix_homebrew_permissions.subtitle" = "创建此功能是为了让您可以在没有权限问题的情况下运行 `brew upgrade` 或 `brew cleanup`。\n\n(应用此修复后,您将收到通知。)";
"alert.fix_homebrew_permissions.desc" = "这将需要管理权限,因为由于 Valet 服务以根用户身份运行PHP Monitor 将恢复您对当前由 `root` 用户拥有的文件和文件夹的所有权";
@ -584,19 +586,19 @@ pm%@
"alert.fix_homebrew_permissions_done.subtitle" = "因此Valet 的所有服务目前都不再运行。您现在可以与 Homebrew 互动,但您的 Valet 站点将无法使用,因为所有服务都已禁用。";
"alert.fix_homebrew_permissions_done.desc" = "当您完成 Homebrew 的操作后(例如在运行 `brew upgrade` 之后),如果您希望 Valet 重新运行,您应该重启 PHP Monitor 并选择 `Restart Valet Services`。建议每次使用 `brew upgrade` 升级 PHP 版本时都重启 PHP Monitor否则可能会出错";
// PHP FPM 已损坏
// PHP FPM Broken
"alert.php_fpm_broken.title" = "您的 PHP-FPM 配置没有指向 Valet 套接字!";
"alert.php_fpm_broken.info" = "PHP Monitor 已确定您的 PHP-FPM 配置存在问题。如果您访问通过 Valet 链接的网站,这将导致 `502 Bad Gateway` 响应";
"alert.php_fpm_broken.description" = "如果已经有一段时间了,你通常可以通过运行 `valet install` 来解决这个问题,它可以更新你的 PHP-FPM 配置.\n\n如果你看到这个消息而你正在尝试运行一个预发布版本的 PHP有可能是 Valet 还不支持这个预发布版本的 PHP.\n\n你可能需要将你安装的 Laravel Valet 升级到至少 v3.1.11,之后你应该运行 `valet install`。更多信息请点击https://phpmon.app/prerelease-php";
// PHP Monitor 无法启动
// PHP Monitor Cannot Start
"alert.cannot_start.title" = "由于您的系统配置问题PHP Monitor 无法启动";
"alert.cannot_start.subtitle" = "刚才通知您的问题导致 PHP Monitor 无法正常运行";
"alert.cannot_start.description" = "您可能不需要退出 PHP Monitor 并重新启动它。如果您已解决问题(或不记得具体问题是什么)可以点击重试PHP Monitor 将重试启动检查";
"alert.cannot_start.close" = "退出";
"alert.cannot_start.retry" = "重试";
// PHP 别名问题
// PHP alias issue
"alert.php_alias_conflict.title" = "检测到 Homebrew 的 `php` Formula 别名冲突";
"alert.php_alias_conflict.info" = "PHP Monitor 在您的 Homebrew 设置中检测到了相互冲突的 `php` 别名,这两个别名都已被检测到安装";
@ -607,7 +609,7 @@ pm%@
在终端运行 `composer global update` 即可。然后,再次运行 `valet install`。为获得最佳效果,请重新启动 PHP Monitor。在此问题解决之前PHP Monitor 可能无法正常运行。";
// 预设文本描述
// Preset text description
"alert.preset_description.switcher_version" = "切换到 PHP %@.\n\n";
"alert.preset_description.applying_extensions" = "应用以下扩展:\n";
"alert.preset_description.applying_config" = "应用以下配置值:\n";
@ -615,13 +617,13 @@ pm%@
"alert.preset_description.disabled" = "禁用";
"alert.preset_description.empty" = "(空)";
// PHP 版本不可用
// PHP version unavailable
"alert.php_switch_unavailable.title" = "不支持的 PHP 版本";
"alert.php_switch_unavailable.subtitle" = "PHP Monitor 无法切换到 PHP %@,因为它可能未安装或不可用。已取消应用此预设。";
"alert.php_switch_unavailable.info" = "请确保已安装 PHP %@,并可在下拉菜单中切换到它。目前支持的版本包括 PHP %@.";
"alert.php_switch_unavailable.ok" = "确定";
// 服务错误
// Service error
"alert.service_error.title" = "服务 `%@` 报告错误!";
"alert.service_error.subtitle.error_log" = "这意味着服务 `%@` 没有运行。这可能导致 Valet 无法正常工作。不过,该服务有一个相关的日志文件,您可能需要检查一下。";
"alert.service_error.subtitle.no_error_log" = "这意味着服务 `%@` 没有运行。这可能会妨碍 Valet 正常工作。不幸的是,该服务没有相关日志文件。";
@ -630,22 +632,22 @@ pm%@
"alert.service_error.button.show_log" = "查看错误日志";
"alert.service_error.button.close" = "关闭";
// Composer 问题
// Composer issues
"alert.global_composer_platform_issues.title" = "Composer 检测到您的平台存在问题";
"alert.global_composer_platform_issues.subtitle" = "您切换到的 PHP 版本对于您安装的全局 Composer 依赖项来说太旧。这些依赖项需要更新。";
"alert.global_composer_platform_issues.desc" = "防止将来发生此问题的最简单方法是切换到已安装的最旧 PHP 版本,并再次运行 `composer global update`。\另外,你也可以在偏好设置中选择 `自动更新全局依赖` 选项来避免这个问题。如果你在尝试更新全局依赖之后仍然看到这个消息,你可能需要查看一下你的全局 composer 配置文件,它位于 `~/.composer/composer.json`";
"alert.global_composer_platform_issues.buttons.update" = "更新全局依赖";
"alert.global_composer_platform_issues.buttons.quit" = "退出 PHP Monitor";
// 还原
// Revert
"alert.revert_description.title" = "还原配置?";
"alert.revert_description.subtitle" = "PHP Monitor 可以还原到之前激活的配置。以下是将应用的配置: \n\n%@";
"alert.revert_description.ok" = "恢复";
"alert.revert_description.cancel" = "取消";
// 启动
// STARTUP
/// 0. 架构不匹配
// 0. Architecture mismatch
"alert.homebrew_missing.title" = "PHP Monitor 无法启动!";
"alert.homebrew_missing.subtitle" = "无法在常规位置找到可用的 Homebrew 二进制文件。请在修复此问题后重新启动应用程序。";
@ -653,32 +655,32 @@ pm%@
"alert.homebrew_missing.quit" = "退出";
/// 未找到 PHP 二进制文件
// PHP binary not found
"startup.errors.php_binary.title" = "PHP 未正确安装";
"startup.errors.php_binary.subtitle" = "您必须通过 Homebrew 安装 PHP。在解决此问题之前应用程序将无法正常运行";
"startup.errors.php_binary.desc" = "通常在终端运行 `brew link php` 可以解决这个问题";
/// 在 /usr/local/opt /opt/homebrew/opt 中找不到 PHP
// PHP not found in /usr/local/opt or /opt/homebrew/opt
"startup.errors.php_opt.title" = "PHP 未正确安装";
"startup.errors.php_opt.subtitle" = "在 `%@` 中找不到 PHP 别名。在您解决此问题之前,应用程序将无法正常运行";
"startup.errors.php_opt.desc" = "如果您已经安装了 `php` Formula则可能需要运行 `brew install php` 才能使 PHP Monitor 检测到该安装";
/// PHP 二进制文件已损坏
// PHP binary is broken
"startup.errors.dyld_library.title" = "PHP 已安装,但似乎已损坏";
"startup.errors.dyld_library.subtitle" = "PHP Monitor 尝试运行命令时,未能正确执行。这通常表明 PHP 安装已损坏";
"startup.errors.dyld_library.desc" = "在终端运行 `brew reinstall php && brew link php` 可能会解决此问题,因此请尝试一下";
/// Valet 未安装
// Valet is not installed
"startup.errors.valet_executable.title" = "Laravel Valet 未正确安装";
"startup.errors.valet_executable.subtitle" = "您必须使用 Composer 安装 Valet。在解决此问题之前应用程序将无法正常运行。";
"startup.errors.valet_executable.desc" = "如果您尚未安装 Laravel Valet请先安装。如果你已经安装但还是看到了这条信息那么请尝试在终端运行 `which valet`,它应该会返回:`%@`.";
/// valet 配置文件丢失或损坏
// Valet configuration file missing or broken
"startup.errors.valet_json_invalid.title" = "Laravel Valet 配置文件无效或丢失";
"startup.errors.valet_json_invalid.subtitle" = "PHP Monitor 需要能够读取配置文件。文件似乎畸形或丢失。请检查该文件是否存在,格式是否正确。";
"startup.errors.valet_json_invalid.desc" = "您可以在 `~/.config/valet/config.json` 找到该文件。如果 Laravel Valet 无法解析配置文件,运行 `valet` 命令通常会自动修复 JSON 文件。尝试运行 `valet --version` 来自动修复文件";
/// Valet 版本不可读
// Valet version not readable
"startup.errors.valet_version_unknown.title" = "无法读取您的 Valet 版本";
"startup.errors.valet_version_unknown.subtitle" = "解析 `valet --version` 输出失败。请确保您的 Valet 安装正常运行且为最新版本。";
"startup.errors.valet_version_unknown.desc" = "尝试在终端中运行 `valet --version` 以找出问题所在";
@ -689,49 +691,49 @@ pm%@
如果你看到此消息,但不明白为什么这个文件夹不见了,那么你可能想调查一下它消失的原因--它不应该就这样消失,这意味着你的 Valet 安装被破坏了。";
// Valet 版本太新或太旧
// Valet version too new or old
"startup.errors.valet_version_not_supported.title" = "不支持此 Valet 版本";
"startup.errors.valet_version_not_supported.subtitle" = "您运行的 Valet 版本目前不受支持。PHP Monitor 目前适用于 Valet v2、v3 和 v4。为了避免给您的系统带来问题PHP Monitor 无法启动";
"startup.errors.valet_version_not_supported.desc" = "您必须安装与 PHP Monitor 兼容的 Valet 版本,或者您可能需要升级到较新版本的 PHP Monitor该版本可能包含对该 Valet 版本的兼容性(更多信息请查阅最新发布说明)";
/// Brew sudoers
// Brew & sudoers
"startup.errors.sudoers_brew.title" = "Brew 没有被添加到 sudoers.d";
"startup.errors.sudoers_brew.subtitle" = "您必须运行 `sudo valet trust` 以确保 Valet 可以启动和停止服务,而无需每次都使用 sudo。在您解决此问题之前应用程序将无法正常工作。";
"startup.errors.sudoers_brew.desc" = "如果您一直看到此错误则可能存在权限问题PHP Monitor 无法验证该文件,通常可通过运行:`sudo chmod +r /private/etc/sudoers.d/brew` 解决该问题";
/// 验证和 sudoers
// Valet & sudoers
"startup.errors.sudoers_valet.title" = "Valet 尚未添加到 sudoers.d";
"startup.errors.sudoers_valet.subtitle" = "您必须运行 `sudo valet trust` 以确保 Valet 可以启动和停止服务,而无需每次都使用 sudo。在您解决此问题之前应用程序将无法正常运行。如果您之前执行过此操作请再次运行 `sudo valet trust`。";
"startup.errors.sudoers_valet.desc" = "如果一直看到此错误则可能存在权限问题PHP Monitor 无法验证文件,通常可通过运行:`sudo chmod +r /private/etc/sudoers.d/valet` 解决";
/// 检测到平台问题
// Platform issue detected
"startup.errors.global_composer_platform_issues.title" = "PHP Monitor 和 Valet 无法正常工作Composer 报告您的平台存在问题";
"startup.errors.global_composer_platform_issues.subtitle" = "请按照以下建议步骤操作,以避免将来出现此问题:\n\n1. 运行 `composer global update`.\n2. 重新启动 PHP Monitor。(它应该可以重新工作。)\n3. 切换到已安装的最早的 PHP 版本。再次运行 `composer global update`";
"startup.errors.global_composer_platform_issues.desc" = "您可以进入首选项并选中 `自动更新全局依赖` 选项。这将在您更改 PHP 版本时更新您的全局 Composer 依赖项,因此,如果您不能持续访问互联网,这可能不是最理想的选择。要找出确切的问题所在,请尝试运行 `valet --version`。Valet 目前无法使用已安装的依赖项。通常这是由于版本不匹配造成的:即安装的 PHP 依赖版本比当前激活的版本要新";
/// 无法检索服务
// Cannot retrieve services
"startup.errors.services_json_error.title" = "无法确定服务状态";
"startup.errors.services_json_error.subtitle" = "PHP Monitor 通常使用以下命令查询 `brew` 以测试是否可以检索服务:`sudo brew services info nginx --json`.\n\nPHP Monitor 无法解释此响应。";
"startup.errors.services_json_error.desc" = "如果您的 Homebrew 安装过时可能会出现这种情况在这种情况下Homebrew 还不会返回 JSON。通常可以通过运行 `brew update` 或 `brew tap homebrew/services` 来解决这个问题。你也可以尝试在终端运行 `sudo brew services info nginx --json`";
/// `which` 别名的问题
// Issue with `which` alias
"startup.errors.which_alias_issue.title" = "检测到配置问题";
"startup.errors.which_alias_issue.subtitle" = "`/usr/local/bin/which` 中似乎有一个文件。这通常是由 NodeJS 设置的,但 `/usr/local/bin` 的 PATH 中没有 `node`。要解决这个问题,请继续阅读";
"startup.errors.which_alias_issue.desc" = "您需要将 `node` 链接到 `/usr/local/bin` 目录,以确保 PHP Monitor 能够成功启动。更多信息请参见https://github.com/nicoverbruggen/phpmon/issues/174";
/// Laravel Herd 冲突
// Laravel Herd conflicts
"startup.errors.herd_running.title" = "Laravel Herd 似乎正在运行";
"startup.errors.herd_running.subtitle" = "Laravel Herd 似乎正在运行。Herd 的内置 Valet 设置可能与您的常规 Valet 安装冲突,因此请在继续之前退出 Herd。(你完全可以混合使用 Herd 和常规 Valet但不应同时运行两者)";
"startup.errors.herd_running.desc" = "你可能还会发现Herd 添加到 $PATH 的 `php` 别名可能会阻止 PHP Monitor 的 `php` 别名生效,所以请记住这一点。您可以查看 `~/.zshrc` 并了解 Herd 在您的 $PATH 中添加了什么。";
// 关于链接的 PHP 版本与上次不同的警告
// Warning about a different PHP version linked than last time
"startup.version_mismatch.title" = "您的活动 PHP 版本已更改";
"startup.version_mismatch.subtitle" = "自 PHP Monitor 上次激活以来,您链接的 PHP 版本已更改为 PHP %@。您想切换回 PHP %@,还是继续使用当前版本?";
"startup.version_mismatch.desc" = "PHP Monitor 会跟踪全局链接的 PHP 版本。全局版本可能由于其他程序或 Homebrew 升级后链接了不同的 Formula 而发生变化";
"startup.version_mismatch.button_switch_back" = "切换回 PHP %@";
"startup.version_mismatch.button_stay" = "继续使用 PHP %@";
// 关于不支持的 PHP 版本的警告
// Warning about unsupported PHP versions
"startup.unsupported_versions_explanation.title" = "检测到 Valet 不支持的 PHP 安装!";
"startup.unsupported_versions_explanation.subtitle" = "您的系统中安装了以下 PHP 版本,但该版本的 Valet 不支持这些版本。
@ -740,7 +742,7 @@ pm%@
如果链接这些 PHP 版本Valet 可能会崩溃,因此 PHP Monitor 不会让您切换到这些版本。";
"startup.unsupported_versions_explanation.desc" = " 如果您需要旧版本的 PHP 支持,您可能需要降级到旧版本的 Valet。否则最好卸载任何不使用的过时版本。也可能是 Valet 的版本太旧。只有在重启 PHP Monitor 后,此消息才会被删除。";
// 赞助鼓励
// Sponsor encouragement
"startup.sponsor_encouragement.title" = "如果 PHP Monitor 对您或贵公司有用,请考虑留下小费";
"startup.sponsor_encouragement.subtitle" = "100%透明:我计划保持 PHP Monitor 的开源和免费。您的支持使这一决定变得非常容易。";
"startup.sponsor_encouragement.desc" = "如果您已经捐赠,那么您就是应用程序能够获得所有这些更新的原因。在这种情况下,这是给您的感谢信息。感谢您的支持。";
@ -749,7 +751,7 @@ pm%@
"startup.sponsor_encouragement.learn_more" = "了解更多信息";
"startup.sponsor_encouragement.skip" = "不,谢谢";
// 错误消息(基于 AlertableError)
// ERROR MESSAGES
"alert.errors.homebrew_permissions.applescript_returned_nil.title" = "恢复 Homebrew 权限已被取消。";
"alert.errors.homebrew_permissions.applescript_returned_nil.description" = "为调整权限而执行的脚本的结果返回为零,这通常意味着您没有授予 PHP Monitor 管理权限。如果您确实进行了身份验证,但仍看到此消息,则可能出了问题。";
@ -782,7 +784,7 @@ PHP Monitor 将尝试重新启动服务,如果失败(很有可能),它将提
· dnsmasq如果出现错误状态可能是 dnsmasq 配置文件损坏(通常位于 ~/.config/valet/dnsmasq.d)";
// 检查更新
// CHECK FOR UPDATES
"updater.alerts.newer_version_available.title" = "PHP Monitor v%@ 现已可用!";
"updater.alerts.newer_version_available.subtitle" = "强烈建议将 PHP Monitor 保持更新,因为新版本通常会修复漏洞并包含支持最新版本 Valet 和 PHP 的修正";
@ -800,14 +802,14 @@ PHP Monitor 将尝试重新启动服务,如果失败(很有可能),它将提
"updater.alerts.buttons.install" = "安装更新";
"updater.alerts.buttons.dismiss" = "解除";
// 关于非默认 TLD 的警告
// WARNINGS ABOUT NON-DEFAULT TLD
"alert.warnings.tld_issue.title" = "您没有使用 `.test` 作为 Valet 的顶级域名";
"alert.warnings.tld_issue.subtitle" = "使用非默认顶级域名可能无法正常工作,并且不受官方支持";
"alert.warnings.tld_issue.description" = "PHP Monitor 仍将正常运行,但可能会出现一些问题:应用程序可能无法正确显示哪些域名已被安全保护。为获得最佳效果,请转到 Valet 配置文件(Valet 目录中的 config.json)并将 TLD 改回 `test`。";
"alert.do_not_tell_again" = "不要再告诉我";
// 警告
// WARNINGS
"warnings.limits_error.title" = "PHP Monitor 无法检索限制";
"warnings.limits_error.steps" = "尝试在终端运行 `php -v`";
@ -835,7 +837,7 @@ PHP Monitor 将尝试重新启动服务,如果失败(很有可能),它将提
"warnings.none" = "现在没有可用的建议。您可以放心使用!";
// 开机
// ONBOARDING
"onboarding.title" = "Welcome Tour";
"onboarding.welcome" = "欢迎来到 PHP Monitor!";
@ -855,11 +857,10 @@ PHP Monitor 将尝试重新启动服务,如果失败(很有可能),它将提
"onboarding.tour.once" = "您只能看到一次 Welcome Tour。您可以稍后通过菜单栏图标(可在 `急救与服务` 下的菜单中找到)重新打开 Welcome Tour。";
"onboarding.tour.close" = "关闭 Tour";
// 语言选择
// LANGUAGE CHOICE
"prefs.language" = "语言:";
"prefs.language_options_desc" = "选择不同的语言与 PHP Monitor 一起使用。要完全应用此更改,必须重新启动应用程序";
"alert.language_changed.title" = "您必须重新启动 PHP Monitor";
"alert.language_changed.subtitle" = "您刚刚更改了 PHP Monitor 的显示语言。菜单将立即使用正确的语言,但您可能需要重新启动应用程序,以使整个应用程序中的所有文本都应用您的新语言选择";

View File

@ -35,6 +35,19 @@ class TestableConfigurations {
"loopback": "127.0.0.1"
}
"""),
"/opt/homebrew/Library/Taps/shivammathur/homebrew-php/Formula/php.rb" : .fake(.text),
// "/opt/homebrew/Library/Taps/shivammathur/homebrew-php/Formula/php@8.5.rb" : .fake(.text),
"/opt/homebrew/Library/Taps/shivammathur/homebrew-php/Formula/php@8.4.rb" : .fake(.text),
"/opt/homebrew/Library/Taps/shivammathur/homebrew-php/Formula/php@8.3.rb" : .fake(.text),
"/opt/homebrew/Library/Taps/shivammathur/homebrew-php/Formula/php@8.2.rb" : .fake(.text),
"/opt/homebrew/Library/Taps/shivammathur/homebrew-php/Formula/php@8.1.rb" : .fake(.text),
"/opt/homebrew/Library/Taps/shivammathur/homebrew-php/Formula/php@8.0.rb" : .fake(.text),
"/opt/homebrew/Library/Taps/shivammathur/homebrew-php/Formula/php@7.4.rb" : .fake(.text),
"/opt/homebrew/Library/Taps/shivammathur/homebrew-php/Formula/php@7.3.rb" : .fake(.text),
"/opt/homebrew/Library/Taps/shivammathur/homebrew-php/Formula/php@7.2.rb" : .fake(.text),
"/opt/homebrew/Library/Taps/shivammathur/homebrew-php/Formula/php@7.1.rb" : .fake(.text),
"/opt/homebrew/Library/Taps/shivammathur/homebrew-php/Formula/php@7.0.rb" : .fake(.text),
"/opt/homebrew/Library/Taps/shivammathur/homebrew-php/Formula/php@5.6.rb" : .fake(.text)
],
shellOutput: [
"/opt/homebrew/bin/brew --version"

View File

@ -85,22 +85,27 @@ final class MainMenuTest: UITestCase {
final func test_can_open_php_version_manager() throws {
let app = launch(openMenu: true)
app.mainMenuItem(withText: "mi_php_version_manager".localized).click()
// Should display loader
assertExists(app.staticTexts["phpman.busy.title".localized], 1)
// After loading, should display PHP 8.2 and PHP 8.3
// After loading, should display PHP 8.2, PHP 8.3, PHP 8.4
assertExists(app.staticTexts["PHP 8.2"], 5)
assertExists(app.staticTexts["PHP 8.3"])
assertExists(app.staticTexts["PHP 8.4"])
// Should also display pre-release version
assertExists(app.staticTexts["PHP 8.4"])
assertExists(app.staticTexts["PHP 8.5"])
assertExists(app.staticTexts["phpman.version.prerelease".localized.uppercased()])
assertExists(app.staticTexts["phpman.version.available_for_installation".localized])
// But not PHP 8.5 (yet)
assertNotExists(app.staticTexts["PHP 8.5"])
// The pre-release version should be unavailable
assertExists(app.staticTexts["phpman.version.unavailable".localized])
// But not PHP 8.6 (yet)
assertNotExists(app.staticTexts["PHP 8.6"])
// Also, PHP 8.2 should have an update available
assertExists(app.staticTexts["phpman.version.has_update".localized(

View File

@ -51,7 +51,7 @@ final class UpdateCheckTest: UITestCase {
let app = launch(openMenu: false, with: configuration)
// Expect to see the content of the appropriate alert box
assertExists(app.staticTexts["updater.alerts.newer_version_available.title".localized("99.0.0 (9999)")], 2)
assertExists(app.staticTexts["updater.alerts.newer_version_available.title".localized("99.0.0 (9999)")], 3.0)
assertExists(app.buttons["updater.alerts.buttons.install".localized])
assertExists(app.buttons["updater.alerts.buttons.dismiss".localized])
}