1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-12-21 03:10:06 +01:00

♻️ Improvements to Container

- RealShell is not reloaded during runtime (bugfix?)
- Container variables are now private(set)
- Initialization now also sets `webApi` property (new)
- It is only possible to run `bind` on a `Container` once now
  (previously known as `prepare`)
- Preparation for upcoming WebApi to replace `curl` command
  (for checking for updates)
This commit is contained in:
2025-11-18 12:04:37 +01:00
parent 58083ba443
commit 7a60435421
18 changed files with 152 additions and 134 deletions

View File

@@ -28,14 +28,14 @@
0329A9A42E92A69000A62A12 /* WarningManager+Evaluations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0329A9A22E92A68B00A62A12 /* WarningManager+Evaluations.swift */; };
0329A9A52E92A69000A62A12 /* WarningManager+Evaluations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0329A9A22E92A68B00A62A12 /* WarningManager+Evaluations.swift */; };
0329A9A62E92A69000A62A12 /* WarningManager+Evaluations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0329A9A22E92A68B00A62A12 /* WarningManager+Evaluations.swift */; };
032DAC282E8BEB5B0018E01C /* RealApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealApi.swift */; };
032DAC292E8BEB5B0018E01C /* RealApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealApi.swift */; };
032DAC2A2E8BEB5B0018E01C /* RealApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealApi.swift */; };
032DAC2B2E8BEB5B0018E01C /* RealApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealApi.swift */; };
032DAC2D2E8BEB6B0018E01C /* ApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC2C2E8BEB690018E01C /* ApiProtocol.swift */; };
032DAC2E2E8BEB6B0018E01C /* ApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC2C2E8BEB690018E01C /* ApiProtocol.swift */; };
032DAC2F2E8BEB6B0018E01C /* ApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC2C2E8BEB690018E01C /* ApiProtocol.swift */; };
032DAC302E8BEB6B0018E01C /* ApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC2C2E8BEB690018E01C /* ApiProtocol.swift */; };
032DAC282E8BEB5B0018E01C /* RealWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealWebApi.swift */; };
032DAC292E8BEB5B0018E01C /* RealWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealWebApi.swift */; };
032DAC2A2E8BEB5B0018E01C /* RealWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealWebApi.swift */; };
032DAC2B2E8BEB5B0018E01C /* RealWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealWebApi.swift */; };
032DAC2D2E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC2C2E8BEB690018E01C /* WebApiProtocol.swift */; };
032DAC2E2E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC2C2E8BEB690018E01C /* WebApiProtocol.swift */; };
032DAC2F2E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC2C2E8BEB690018E01C /* WebApiProtocol.swift */; };
032DAC302E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC2C2E8BEB690018E01C /* WebApiProtocol.swift */; };
033D45982B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */; };
033D45992B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */; };
033D459A2B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */; };
@@ -75,14 +75,10 @@
0396160E2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
0396160F2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
039616102E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
039C29132E8AA163007F5FAB /* ActiveApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29122E8AA15F007F5FAB /* ActiveApi.swift */; };
039C29142E8AA163007F5FAB /* ActiveApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29122E8AA15F007F5FAB /* ActiveApi.swift */; };
039C29152E8AA163007F5FAB /* ActiveApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29122E8AA15F007F5FAB /* ActiveApi.swift */; };
039C29162E8AA163007F5FAB /* ActiveApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29122E8AA15F007F5FAB /* ActiveApi.swift */; };
039C29182E8AA314007F5FAB /* TestableApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableApi.swift */; };
039C29192E8AA314007F5FAB /* TestableApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableApi.swift */; };
039C291A2E8AA314007F5FAB /* TestableApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableApi.swift */; };
039C291B2E8AA314007F5FAB /* TestableApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableApi.swift */; };
039C29182E8AA314007F5FAB /* TestableWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableWebApi.swift */; };
039C29192E8AA314007F5FAB /* TestableWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableWebApi.swift */; };
039C291A2E8AA314007F5FAB /* TestableWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableWebApi.swift */; };
039C291B2E8AA314007F5FAB /* TestableWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableWebApi.swift */; };
039C291D2E8AA39A007F5FAB /* TestableApiTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C291C2E8AA399007F5FAB /* TestableApiTest.swift */; };
039E1D792E5F0F300072D13D /* ValetUpgrader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */; };
039E1D7A2E5F0F300072D13D /* ValetUpgrader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */; };
@@ -1019,8 +1015,8 @@
03263A372E86D5E800BD0415 /* UpdateScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateScheduler.swift; sourceTree = "<group>"; };
0329A9A02E92A2A800A62A12 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = "<group>"; };
0329A9A22E92A68B00A62A12 /* WarningManager+Evaluations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WarningManager+Evaluations.swift"; sourceTree = "<group>"; };
032DAC272E8BEB590018E01C /* RealApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealApi.swift; sourceTree = "<group>"; };
032DAC2C2E8BEB690018E01C /* ApiProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiProtocol.swift; sourceTree = "<group>"; };
032DAC272E8BEB590018E01C /* RealWebApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealWebApi.swift; sourceTree = "<group>"; };
032DAC2C2E8BEB690018E01C /* WebApiProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebApiProtocol.swift; sourceTree = "<group>"; };
0336CAAF2B0D0CDA009A1034 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallPhpExtensionCommand.swift; sourceTree = "<group>"; };
033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemovePhpExtensionCommand.swift; sourceTree = "<group>"; };
@@ -1039,8 +1035,7 @@
0392CDE52EB23B8F009176DA /* CertificateValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateValidator.swift; sourceTree = "<group>"; };
0392CDEA2EB25371009176DA /* SecurePopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurePopoverView.swift; sourceTree = "<group>"; };
0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggableEvent.swift; sourceTree = "<group>"; };
039C29122E8AA15F007F5FAB /* ActiveApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveApi.swift; sourceTree = "<group>"; };
039C29172E8AA311007F5FAB /* TestableApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableApi.swift; sourceTree = "<group>"; };
039C29172E8AA311007F5FAB /* TestableWebApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableWebApi.swift; sourceTree = "<group>"; };
039C291C2E8AA399007F5FAB /* TestableApiTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableApiTest.swift; sourceTree = "<group>"; };
039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetUpgrader.swift; sourceTree = "<group>"; };
03B675E82EBA30D200EE04A9 /* NSImageExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImageExtension.swift; sourceTree = "<group>"; };
@@ -1450,9 +1445,9 @@
039C29112E8AA159007F5FAB /* Http */ = {
isa = PBXGroup;
children = (
039C29122E8AA15F007F5FAB /* ActiveApi.swift */,
032DAC272E8BEB590018E01C /* RealApi.swift */,
032DAC2C2E8BEB690018E01C /* ApiProtocol.swift */,
032DAC272E8BEB590018E01C /* RealWebApi.swift */,
039C29172E8AA311007F5FAB /* TestableWebApi.swift */,
032DAC2C2E8BEB690018E01C /* WebApiProtocol.swift */,
);
path = Http;
sourceTree = "<group>";
@@ -2281,6 +2276,7 @@
isa = PBXGroup;
children = (
C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */,
C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */,
C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */,
);
path = Filesystem;
@@ -2348,6 +2344,7 @@
isa = PBXGroup;
children = (
C4B5853D2770FE3900DA4FBE /* RealCommand.swift */,
C4E49DEC28F764A00026AC4E /* TestableCommand.swift */,
C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */,
);
path = Command;
@@ -2410,6 +2407,7 @@
isa = PBXGroup;
children = (
C46EBC4628DB9644007ACC74 /* RealShell.swift */,
C46EBC4928DB966A007ACC74 /* TestableShell.swift */,
C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */,
);
path = Shell;
@@ -2418,10 +2416,6 @@
C4F787A728EF812600790735 /* Testables */ = {
isa = PBXGroup;
children = (
039C29172E8AA311007F5FAB /* TestableApi.swift */,
C46EBC4928DB966A007ACC74 /* TestableShell.swift */,
C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */,
C4E49DEC28F764A00026AC4E /* TestableCommand.swift */,
C4E2E85B28FC282B003B070C /* TestableConfiguration.swift */,
);
path = Testables;
@@ -2790,7 +2784,7 @@
C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */,
C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
C43B8FD52BA9BAD3000C02BE /* UnavailableContentView.swift in Sources */,
032DAC2A2E8BEB5B0018E01C /* RealApi.swift in Sources */,
032DAC2A2E8BEB5B0018E01C /* RealWebApi.swift in Sources */,
54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */,
C450C8C628C919EC002A2B4B /* PreferenceName.swift in Sources */,
0392CDEB2EB25371009176DA /* SecurePopoverView.swift in Sources */,
@@ -2812,7 +2806,7 @@
C4463FCC29804BCB007B93D5 /* RCFile.swift in Sources */,
C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */,
C4E9D2C02878B336008FFDAD /* OnboardingView.swift in Sources */,
032DAC2E2E8BEB6B0018E01C /* ApiProtocol.swift in Sources */,
032DAC2E2E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */,
C4F2E4372752F0870020E974 /* BrewDiagnostics.swift in Sources */,
031E2B692B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */,
C4AD38B228ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */,
@@ -2940,7 +2934,6 @@
C4BF56AB2949381100379603 /* FakeValetInteractor.swift in Sources */,
C4B5635E276AB09000F12CCB /* VersionExtractor.swift in Sources */,
C451AFF62969E40F0078E617 /* HelpButton.swift in Sources */,
039C29132E8AA163007F5FAB /* ActiveApi.swift in Sources */,
C46DC7A42C7B55DC00F19D17 /* Favorites.swift in Sources */,
54D9E0B627E4F51E003B9AD9 /* HotKey.swift in Sources */,
C4AFC4AE29C4F32F00BF4E0D /* BrewPhpFormula.swift in Sources */,
@@ -2968,7 +2961,7 @@
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */,
C493084A279F331F009C240B /* AddSiteVC.swift in Sources */,
C4DEB7D427A5D60B00834718 /* Stats.swift in Sources */,
039C29182E8AA314007F5FAB /* TestableApi.swift in Sources */,
039C29182E8AA314007F5FAB /* TestableWebApi.swift in Sources */,
C47015022C46D6910069AAE7 /* NVAlertExtension.swift in Sources */,
C4E49DEA28F7643D0026AC4E /* CommandProtocol.swift in Sources */,
);
@@ -2997,7 +2990,7 @@
C471E83728F9BB650021E251 /* DomainScanner.swift in Sources */,
C456A0C82AA614BD0080144F /* PhpPreference.swift in Sources */,
C490E3BE29BCA375006D2DE6 /* Measurements.swift in Sources */,
039C29192E8AA314007F5FAB /* TestableApi.swift in Sources */,
039C29192E8AA314007F5FAB /* TestableWebApi.swift in Sources */,
C471E83928F9BB650021E251 /* ValetSite.swift in Sources */,
C471E83A28F9BB650021E251 /* FakeValetSite.swift in Sources */,
C471E83C28F9BB650021E251 /* ValetDomainScanner.swift in Sources */,
@@ -3019,7 +3012,7 @@
C471E84528F9BB650021E251 /* App+GlobalHotkey.swift in Sources */,
C4513F922B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */,
C471E84628F9BB650021E251 /* InterAppHandler.swift in Sources */,
032DAC2B2E8BEB5B0018E01C /* RealApi.swift in Sources */,
032DAC2B2E8BEB5B0018E01C /* RealWebApi.swift in Sources */,
C471E84728F9BB650021E251 /* Startup.swift in Sources */,
C471E84828F9BB650021E251 /* EnvironmentCheck.swift in Sources */,
C471E84A28F9BB650021E251 /* AppVersion.swift in Sources */,
@@ -3067,7 +3060,6 @@
C4463FCE29804BCB007B93D5 /* RCFile.swift in Sources */,
C45B9150295608E300F4EC78 /* ValetServicesManager.swift in Sources */,
C471E86528F9BB650021E251 /* WarningManager.swift in Sources */,
039C29142E8AA163007F5FAB /* ActiveApi.swift in Sources */,
C471E86628F9BB650021E251 /* PhpDoctorWindowController.swift in Sources */,
C471E86728F9BB650021E251 /* OnboardingWindowController.swift in Sources */,
C471E86828F9BB650021E251 /* PreferencesWindowController.swift in Sources */,
@@ -3184,7 +3176,7 @@
C489E0BD2A220A4200323F5E /* FakeBrewFormulaeHandler.swift in Sources */,
C45E2A77291992DA005C7CFD /* FeatureTestCase.swift in Sources */,
C471E82028F9BB290021E251 /* NginxConfigurationFile.swift in Sources */,
032DAC2D2E8BEB6B0018E01C /* ApiProtocol.swift in Sources */,
032DAC2D2E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */,
C4513F902B13E2E6001AD760 /* PhpExtensionManagerWindowController.swift in Sources */,
C471E81528F9BAE80021E251 /* ArrayExtension.swift in Sources */,
C471E7DA28F9BA8F0021E251 /* TestableCommand.swift in Sources */,
@@ -3260,7 +3252,7 @@
C471E8B528F9BB8F0021E251 /* MainMenu+FixMyValet.swift in Sources */,
C471E8B628F9BB8F0021E251 /* MainMenu+Actions.swift in Sources */,
C471E8B728F9BB8F0021E251 /* StatusMenu.swift in Sources */,
032DAC302E8BEB6B0018E01C /* ApiProtocol.swift in Sources */,
032DAC302E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */,
C471E8B828F9BB8F0021E251 /* StatusMenu+Items.swift in Sources */,
036C390C2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */,
C471E8B928F9BB8F0021E251 /* DomainListCellProtocol.swift in Sources */,
@@ -3298,7 +3290,7 @@
C471E8CD28F9BB8F0021E251 /* PreferencesVC.swift in Sources */,
C471E8CE28F9BB8F0021E251 /* PreferenceName.swift in Sources */,
C471E8CF28F9BB8F0021E251 /* Preferences.swift in Sources */,
039C291B2E8AA314007F5FAB /* TestableApi.swift in Sources */,
039C291B2E8AA314007F5FAB /* TestableWebApi.swift in Sources */,
03CC1FF62E3D23130050FC18 /* ZshRunCommand.swift in Sources */,
C4415E902B0287E90035F520 /* BrewFormulaeObservable.swift in Sources */,
C4E2E84D28FC1E70003B070C /* DataExtension.swift in Sources */,
@@ -3307,7 +3299,6 @@
C471E8D228F9BB8F0021E251 /* Stats.swift in Sources */,
C471E8D328F9BB8F0021E251 /* GlobalKeybindPreference.swift in Sources */,
03C099462EA15C8E00B76D43 /* Container+Real.swift in Sources */,
039C29152E8AA163007F5FAB /* ActiveApi.swift in Sources */,
C471E8D528F9BB8F0021E251 /* CheckboxPreferenceView.swift in Sources */,
C471E8D728F9BB8F0021E251 /* SelectPreferenceView.swift in Sources */,
03C29A7A2EC88E3100FBA25E /* ValetServicesDataManager.swift in Sources */,
@@ -3378,7 +3369,7 @@
C471E7F828F9BACB0021E251 /* InternalSwitcher.swift in Sources */,
C471E82328F9BB2E0021E251 /* ComposerJson.swift in Sources */,
C471E82128F9BB2E0021E251 /* ProjectTypeDetection.swift in Sources */,
032DAC282E8BEB5B0018E01C /* RealApi.swift in Sources */,
032DAC282E8BEB5B0018E01C /* RealWebApi.swift in Sources */,
C471E7EF28F9BAC30021E251 /* Actions.swift in Sources */,
C471E82228F9BB2E0021E251 /* ComposerWindow.swift in Sources */,
C4D3660E29113F20006BD146 /* System.swift in Sources */,
@@ -3457,7 +3448,7 @@
54FCFD2B276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */,
C415D3B82770F294005EF286 /* Actions.swift in Sources */,
54B48B60275F66AE006D90C5 /* Application.swift in Sources */,
039C291A2E8AA314007F5FAB /* TestableApi.swift in Sources */,
039C291A2E8AA314007F5FAB /* TestableWebApi.swift in Sources */,
C4FE011228084FC200D1DE6D /* SelectionVC.swift in Sources */,
03C099472EA15C8E00B76D43 /* Container+Real.swift in Sources */,
C4D3661B291173EA006BD146 /* DictionaryExtension.swift in Sources */,
@@ -3519,7 +3510,7 @@
C456A0C72AA614BD0080144F /* PhpPreference.swift in Sources */,
C42106672AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */,
C46DC7A52C7B5BC900F19D17 /* Favorites.swift in Sources */,
032DAC2F2E8BEB6B0018E01C /* ApiProtocol.swift in Sources */,
032DAC2F2E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */,
C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
C4FC21B128391F8E00D368BB /* MainMenu+Actions.swift in Sources */,
54D9E0B927E4F51E003B9AD9 /* KeyCombo.swift in Sources */,
@@ -3528,7 +3519,7 @@
C450C8C728C919EC002A2B4B /* PreferenceName.swift in Sources */,
031F24832EA1071A00CFB8D9 /* Container+Fake.swift in Sources */,
C40D725B2A018ACC0054A067 /* BusyStatus.swift in Sources */,
032DAC292E8BEB5B0018E01C /* RealApi.swift in Sources */,
032DAC292E8BEB5B0018E01C /* RealWebApi.swift in Sources */,
C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */,
C40D72602A018AE30054A067 /* BrewFormula+UI.swift in Sources */,
C485707B28BF458900539B36 /* VersionPopoverView.swift in Sources */,
@@ -3622,7 +3613,6 @@
C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */,
C4BF56AC2949381100379603 /* FakeValetInteractor.swift in Sources */,
C471E79428F9B23B0021E251 /* FileSystemProtocol.swift in Sources */,
039C29162E8AA163007F5FAB /* ActiveApi.swift in Sources */,
C4F30B0B278E203C00755FCE /* MainMenu+Startup.swift in Sources */,
C485707C28BF459500539B36 /* NoWarningsView.swift in Sources */,
03D846252EB6344E006EFE3C /* DomainListVC+Window.swift in Sources */,

View File

@@ -1,25 +0,0 @@
//
// ActiveApi.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 29/09/2025.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
var Api: ApiProtocol {
return ActiveApi.shared
}
class ActiveApi {
static var shared: ApiProtocol = RealApi()
public static func useTestable(_ responses: [URL: FakeApiResponse]) {
Self.shared = TestableApi(responses: responses)
}
public static func useReal() {
Self.shared = RealApi()
}
}

View File

@@ -6,4 +6,10 @@
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
class RealApi: ApiProtocol {}
class RealWebApi: WebApiProtocol {
var container: Container
init(container: Container) {
self.container = container
}
}

View File

@@ -1,5 +1,5 @@
//
// TestableApi.swift
// TestableWebApi.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 29/09/2025.
@@ -8,10 +8,10 @@
import Foundation
class TestableApi: ApiProtocol {
private var fakeResponses: [URL: FakeApiResponse] = [:]
class TestableWebApi: WebApiProtocol {
private var fakeResponses: [URL: FakeWebApiResponse] = [:]
init(responses: [URL: FakeApiResponse]) {
init(responses: [URL: FakeWebApiResponse]) {
self.fakeResponses = responses
}
@@ -19,12 +19,12 @@ class TestableApi: ApiProtocol {
return fakeResponses.keys.contains(url)
}
public func getResponse(for url: URL) -> FakeApiResponse {
public func getResponse(for url: URL) -> FakeWebApiResponse {
return fakeResponses[url]!
}
}
struct FakeApiResponse {
struct FakeWebApiResponse {
let statusCode: Int
let headers: [String: String]
let data: Data?

View File

@@ -6,6 +6,6 @@
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
protocol ApiProtocol {
protocol WebApiProtocol {
}

View File

@@ -13,6 +13,7 @@ class RealShell: ShellProtocol {
init(container: Container) {
self.container = container
self.PATH = RealShell.getPath()
}
/**
@@ -25,7 +26,7 @@ class RealShell: ShellProtocol {
For some commands, we need to know what's in the user's PATH.
The entire PATH is retrieved here, so we can set the PATH in our own terminal as necessary.
*/
private(set) var PATH: String = { return RealShell.getPath() }()
private(set) var PATH: String
/**
Exports are additional environment variables set by the user via the custom configuration.
@@ -269,8 +270,9 @@ class RealShell: ShellProtocol {
})
}
func reload() {
container.shell = RealShell(container: container)
func reloadEnvPath() {
// Instead of replacing the entire shell instance, we simply re-fetch the PATH
self.PATH = RealShell.getPath()
}
}

View File

@@ -58,9 +58,9 @@ protocol ShellProtocol {
) async throws -> (Process, ShellOutput)
/**
Reloads the shell instance, which also reloads the PATH.
Reloads the PATH.
*/
func reload()
func reloadEnvPath()
}
enum ShellStream: Codable {

View File

@@ -65,7 +65,7 @@ public class TestableShell: ShellProtocol {
return (Process(), output)
}
func reload() {
func reloadEnvPath() {
// does nothing
}
}

View File

@@ -6,33 +6,9 @@
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
extension Container {
/**
Manually specify what overrides need to be active for the container.
*/
private func overrideFake(
shellExpectations: [String: BatchFakeShellOutput] = [:],
fileSystemFiles: [String: FakeFile] = [:],
commands: [String: String] = [:]
) {
self.shell = TestableShell(expectations: shellExpectations)
self.filesystem = TestableFileSystem(files: fileSystemFiles)
self.command = TestableCommand(commands: commands)
}
/**
Use a `TestableConfiguration` as the basis for shell, filesystem and more.
This is used for testing scenarios to avoid needing to have a specific system configuration.
Ideal for feature or UI tests, where a complete "computer configuration" needs to be mimicked.
*/
public func overrideWith(config: TestableConfiguration) {
self.overrideFake(
shellExpectations: config.shellOutput,
fileSystemFiles: config.filesystem,
commands: config.commandOutput
)
}
/**
Create a new DI `Container` with fake shell responses, filesystem structure and given commands.
Ideal for testing without a complex TestableConfiguration, so great for unit tests that
@@ -41,19 +17,21 @@ extension Container {
public static func fake(
shell: [String: BatchFakeShellOutput] = [:],
files: [String: FakeFile] = [:],
commands: [String: String] = [:]
commands: [String: String] = [:],
apiResponses: [URL: FakeWebApiResponse] = [:]
) -> Container {
// Create a new container
let container = Container()
// Fill the container with production (real) components
container.prepare()
container.bind()
// Replace the key ones with fake ones, so we don't touch the tester's OS, filesystem, etc.
container.overrideFake(
shellExpectations: shell,
fileSystemFiles: files,
commands: commands
commands: commands,
fakeApiResponses: apiResponses
)
// Return the newly created container

View File

@@ -9,7 +9,7 @@
extension Container {
public static func real() -> Container {
let container = Container()
container.prepare()
container.bind()
return container
}
}

View File

@@ -6,22 +6,34 @@
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
class Container {
// MARK: - Variables
// Primary
var shell: ShellProtocol!
var filesystem: FileSystemProtocol!
var command: CommandProtocol!
var paths: Paths!
private(set) var shell: ShellProtocol!
private(set) var filesystem: FileSystemProtocol!
private(set) var command: CommandProtocol!
private(set) var paths: Paths!
private(set) var webApi: WebApiProtocol!
// Secondary (uses primary instances above)
var preferences: Preferences!
var phpEnvs: PhpEnvironments!
var favorites: Favorites!
var warningManager: WarningManager!
private(set) var preferences: Preferences!
private(set) var phpEnvs: PhpEnvironments!
private(set) var favorites: Favorites!
private(set) var warningManager: WarningManager!
// Track initial preparation step
private var bound: Bool = false
// MARK: - Initializers
///
/// The initializer is empty. You must call `prepare` to enable the container.
/// To avoid issues with unsafe access, the actual objects are set in `prepare`.
/// The initializer is empty. You must call `bind` to enable the container.
///
/// To avoid issues with unsafe access, the actual objects are set in `bind`.
///
/// `self` is not available in this constructor, after all. The alternative
/// is to use lazy variables here, but I don't think it's that clean, especially
/// given the other initializers available via the extensions.
@@ -29,16 +41,28 @@ class Container {
init() {}
///
/// Creates new instances belonging to the container, while referencing
/// the container itself and passing the reference on to each component that needs it.
/// Creates new instances of all elements belonging to the `Container`, while referencing
/// the `Container` itself and passing the reference on to each component that needs it.
///
public func prepare() {
/// You can only call this method once. Running it again will crash with `fatalError`,
/// because it would cause all sorts of issues if individual DI elements are swapped out
/// without proper deinitialization.
///
/// (Swapping instances for specific dependencies can be introduced later with dedicated
/// methods if it ever becomes truly necessary.)
///
public func bind() {
if self.bound {
fatalError("You cannot call `bind` on a Container more than once.")
}
// These are the most basic building blocks. We need these before
// any of the other classes can be initialized!
self.shell = RealShell(container: self)
self.filesystem = RealFileSystem(container: self)
self.command = RealCommand()
self.paths = Paths(container: self)
self.webApi = RealWebApi(container: self)
// Please note that the order in which these are initialized, matters!
// For example, preferences leverages the Paths instance, so don't just
@@ -47,5 +71,39 @@ class Container {
self.phpEnvs = PhpEnvironments(container: self)
self.favorites = Favorites()
self.warningManager = WarningManager(container: self)
// At this point, our container has been bound.
self.bound = true
}
/**
Manually specify what testable overrides need to be active for the `Container`.
Only used for testing purposes, either via `TestableConfiguration` or for
explicit initialization of a fake Container instance.
*/
public func overrideFake(
shellExpectations: [String: BatchFakeShellOutput] = [:],
fileSystemFiles: [String: FakeFile] = [:],
commands: [String: String] = [:],
fakeApiResponses: [URL: FakeWebApiResponse] = [:]
) {
self.shell = TestableShell(expectations: shellExpectations)
self.filesystem = TestableFileSystem(files: fileSystemFiles)
self.command = TestableCommand(commands: commands)
self.webApi = TestableWebApi(responses: fakeApiResponses)
}
/**
Use a `TestableConfiguration` as the basis for shell, filesystem and more.
This is used for testing scenarios to avoid needing to have a specific system configuration.
Ideal for feature or UI tests, where a complete "computer configuration" needs to be mimicked.
*/
public func overrideWith(config: TestableConfiguration) {
self.overrideFake(
shellExpectations: config.shellOutput,
fileSystemFiles: config.filesystem,
commands: config.commandOutput
)
}
}

View File

@@ -49,7 +49,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
override init() {
// Prepare the container with the defaults
self.state = App.shared
self.state.container.prepare()
self.state.container.bind()
#if DEBUG
logger.verbosity = .performance

View File

@@ -60,7 +60,7 @@ class WarningManager: ObservableObject {
Checks the user's environment and checks if any special warnings apply.
*/
func checkEnvironment() async {
container.shell.reload()
container.shell.reloadEnvPath()
await BrewDiagnostics.shared.loadInstalledTaps()

View File

@@ -10,18 +10,26 @@ import Testing
import Foundation
struct TestableApiTest {
@Test func createFakeApi() {
let api = TestableApi(responses: [
url("https://api.phpmon.test"): FakeApiResponse(
private var container: Container
init() throws {
self.container = Container.fake(apiResponses: [
url("https://api.phpmon.test"): FakeWebApiResponse(
statusCode: 200,
headers: [:],
text: "{\"success\": true}"
)
])
}
#expect(api.hasResponse(for: url("https://api.phpmon.test")) == true)
var WebApi: TestableWebApi {
return container.webApi as! TestableWebApi
}
let response = api.getResponse(for: url("https://api.phpmon.test"))
@Test func createFakeApi() {
#expect(WebApi.hasResponse(for: url("https://api.phpmon.test")) == true)
let response = WebApi.getResponse(for: url("https://api.phpmon.test"))
#expect(response.statusCode == 200)
#expect(response.text.contains("success"))

View File

@@ -15,7 +15,7 @@ struct RealFileSystemTest {
init() throws {
let container = Container()
container.prepare()
container.bind()
filesystem = container.filesystem
}

View File

@@ -11,7 +11,8 @@ import Foundation
@Suite(.serialized)
struct TestableFileSystemTest {
var container: Container
private var container: Container
init() throws {
container = Container.fake(files: [
"/home/user/bin/foo": .fake(.binary),