mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2025-08-10 05:00:06 +02:00
Compare commits
51 Commits
Author | SHA1 | Date | |
---|---|---|---|
94da7fb255 | |||
46d2d35c1a | |||
cba41b29bc | |||
7a8f47b995 | |||
40062c5091 | |||
6081ef6b02 | |||
4cffe5a662 | |||
813aec2b42 | |||
f2452bbc70 | |||
28fb685bfc | |||
0661ca00ff | |||
0b04619003 | |||
85d7b6aa57 | |||
bba961269c | |||
d0f7d2c5e9 | |||
eeeb3eb184 | |||
f00f8d26f6 | |||
74817beec6 | |||
7b6809245c | |||
5b40a8fd41 | |||
193f459be1 | |||
c4c19a5b47 | |||
7d103c70e7 | |||
2ffe90948e | |||
8e61aaacde | |||
29c8fcbde2 | |||
8dd21f46aa | |||
e688dde2aa | |||
987e1e1bdb | |||
510257c436 | |||
bb1572f32a | |||
45276034b1 | |||
0d4a144524 | |||
a0e5102ca7 | |||
69c0f5ace9 | |||
d0962c2387 | |||
4670894cfd | |||
a2f6c70a03 | |||
ef469868d8 | |||
c9ba872529 | |||
1e15042be2 | |||
7647978da5 | |||
|
f82f3052f2 | ||
10b299ff65 | |||
e4ff0418fd | |||
a2b25e31ca | |||
c4772db808 | |||
38c2d9131b | |||
1566323fca | |||
|
bf0a923eb2 | ||
e372480249 |
@@ -24,6 +24,11 @@
|
|||||||
54FCFD31276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */; };
|
54FCFD31276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */; };
|
||||||
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */; };
|
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */; };
|
||||||
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */; };
|
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */; };
|
||||||
|
C40B24F127A3106D0018C7D2 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E67279DE0540010F296 /* ServicesView.swift */; };
|
||||||
|
C40B24F227A310770018C7D2 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; };
|
||||||
|
C40B24F327A310780018C7D2 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; };
|
||||||
|
C40B24F427A310830018C7D2 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; };
|
||||||
|
C40B24F527A3108B0018C7D2 /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E6C279DF87A0010F296 /* Async.swift */; };
|
||||||
C40C7F1E2772136000DDDCDC /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; };
|
C40C7F1E2772136000DDDCDC /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; };
|
||||||
C40C7F1F2772136000DDDCDC /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; };
|
C40C7F1F2772136000DDDCDC /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; };
|
||||||
C40C7F202772136000DDDCDC /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; };
|
C40C7F202772136000DDDCDC /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; };
|
||||||
@@ -39,6 +44,8 @@
|
|||||||
C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; };
|
C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; };
|
||||||
C40C7F3227722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; };
|
C40C7F3227722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; };
|
||||||
C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; };
|
C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; };
|
||||||
|
C415937F27A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; };
|
||||||
|
C415938027A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; };
|
||||||
C415D3B72770F294005EF286 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; };
|
C415D3B72770F294005EF286 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; };
|
||||||
C415D3B82770F294005EF286 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; };
|
C415D3B82770F294005EF286 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; };
|
||||||
C415D3E12770F34D005EF286 /* AllowedArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3DE2770F34D005EF286 /* AllowedArguments.swift */; };
|
C415D3E12770F34D005EF286 /* AllowedArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3DE2770F34D005EF286 /* AllowedArguments.swift */; };
|
||||||
@@ -101,6 +108,7 @@
|
|||||||
C4998F0626175E7200B2526E /* HotKey in Frameworks */ = {isa = PBXBuildFile; productRef = C4998F0526175E7200B2526E /* HotKey */; };
|
C4998F0626175E7200B2526E /* HotKey in Frameworks */ = {isa = PBXBuildFile; productRef = C4998F0526175E7200B2526E /* HotKey */; };
|
||||||
C4998F0A2617633900B2526E /* PrefsWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PrefsWC.swift */; };
|
C4998F0A2617633900B2526E /* PrefsWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PrefsWC.swift */; };
|
||||||
C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PrefsWC.swift */; };
|
C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PrefsWC.swift */; };
|
||||||
|
C49E171F27A5736E00787921 /* PMServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49E171E27A5736E00787921 /* PMServicesView.swift */; };
|
||||||
C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; };
|
C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; };
|
||||||
C4AF9F71275445FF00D44ED0 /* valet-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AF9F70275445FF00D44ED0 /* valet-config.json */; };
|
C4AF9F71275445FF00D44ED0 /* valet-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AF9F70275445FF00D44ED0 /* valet-config.json */; };
|
||||||
C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AF9F70275445FF00D44ED0 /* valet-config.json */; };
|
C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AF9F70275445FF00D44ED0 /* valet-config.json */; };
|
||||||
@@ -142,19 +150,20 @@
|
|||||||
C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; };
|
C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; };
|
||||||
C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; };
|
C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; };
|
||||||
C4D9ADCA277611A0007277F4 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; };
|
C4D9ADCA277611A0007277F4 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; };
|
||||||
|
C4DEB7D427A5D60B00834718 /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DEB7D327A5D60B00834718 /* Stats.swift */; };
|
||||||
C4EC1E66279DE0380010F296 /* ServicesView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C4EC1E65279DE0380010F296 /* ServicesView.xib */; };
|
C4EC1E66279DE0380010F296 /* ServicesView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C4EC1E65279DE0380010F296 /* ServicesView.xib */; };
|
||||||
C4EC1E68279DE0540010F296 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E67279DE0540010F296 /* ServicesView.swift */; };
|
C4EC1E68279DE0540010F296 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E67279DE0540010F296 /* ServicesView.swift */; };
|
||||||
C4EC1E6D279DF87A0010F296 /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E6C279DF87A0010F296 /* Async.swift */; };
|
C4EC1E6D279DF87A0010F296 /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E6C279DF87A0010F296 /* Async.swift */; };
|
||||||
C4EC1E6E279DF87A0010F296 /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E6C279DF87A0010F296 /* Async.swift */; };
|
C4EC1E6E279DF87A0010F296 /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E6C279DF87A0010F296 /* Async.swift */; };
|
||||||
C4EC1E73279DFCF40010F296 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; };
|
C4EC1E73279DFCF40010F296 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; };
|
||||||
C4EC1E74279DFCF40010F296 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; };
|
|
||||||
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; };
|
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; };
|
||||||
C4EE55A927708B9E001DF387 /* PMHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A627708B9E001DF387 /* PMHeaderView.swift */; };
|
C4EE55A927708B9E001DF387 /* PMHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A627708B9E001DF387 /* PMHeaderView.swift */; };
|
||||||
C4EE55AA27708B9E001DF387 /* PMHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A627708B9E001DF387 /* PMHeaderView.swift */; };
|
C4EE55AA27708B9E001DF387 /* PMHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A627708B9E001DF387 /* PMHeaderView.swift */; };
|
||||||
C4EE55AB27708B9E001DF387 /* Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A727708B9E001DF387 /* Preview.swift */; };
|
C4EE55AB27708B9E001DF387 /* Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A727708B9E001DF387 /* Preview.swift */; };
|
||||||
C4EE55AC27708B9E001DF387 /* Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A727708B9E001DF387 /* Preview.swift */; };
|
|
||||||
C4EE55AD27708B9E001DF387 /* PMStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A827708B9E001DF387 /* PMStatsView.swift */; };
|
C4EE55AD27708B9E001DF387 /* PMStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A827708B9E001DF387 /* PMStatsView.swift */; };
|
||||||
C4EE55AE27708B9E001DF387 /* PMStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A827708B9E001DF387 /* PMStatsView.swift */; };
|
C4EE55AE27708B9E001DF387 /* PMStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A827708B9E001DF387 /* PMStatsView.swift */; };
|
||||||
|
C4EED88927A48778006D7272 /* InterAppHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EED88827A48778006D7272 /* InterAppHandler.swift */; };
|
||||||
|
C4EED88A27A48778006D7272 /* InterAppHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EED88827A48778006D7272 /* InterAppHandler.swift */; };
|
||||||
C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; };
|
C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; };
|
||||||
C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; };
|
C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; };
|
||||||
C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; };
|
C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; };
|
||||||
@@ -179,7 +188,6 @@
|
|||||||
C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; };
|
C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; };
|
||||||
C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; };
|
C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; };
|
||||||
C4F780C625D80B75000DBC97 /* XibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9225CC804200CC7490 /* XibLoadable.swift */; };
|
C4F780C625D80B75000DBC97 /* XibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9225CC804200CC7490 /* XibLoadable.swift */; };
|
||||||
C4F780C725D80B75000DBC97 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; };
|
|
||||||
C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; };
|
C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; };
|
||||||
C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; };
|
C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; };
|
||||||
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; };
|
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; };
|
||||||
@@ -228,6 +236,7 @@
|
|||||||
C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ActivePhpInstallation+Checks.swift"; sourceTree = "<group>"; };
|
C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ActivePhpInstallation+Checks.swift"; sourceTree = "<group>"; };
|
||||||
C40C7F2F27722E8D00DDDCDC /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
C40C7F2F27722E8D00DDDCDC /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||||
C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackage.swift; sourceTree = "<group>"; };
|
C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackage.swift; sourceTree = "<group>"; };
|
||||||
|
C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpFrameworks.swift; sourceTree = "<group>"; };
|
||||||
C415D3B62770F294005EF286 /* Actions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = "<group>"; };
|
C415D3B62770F294005EF286 /* Actions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = "<group>"; };
|
||||||
C415D3D62770F341005EF286 /* phpmon-cli */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "phpmon-cli"; sourceTree = BUILT_PRODUCTS_DIR; };
|
C415D3D62770F341005EF286 /* phpmon-cli */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "phpmon-cli"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
C415D3DE2770F34D005EF286 /* AllowedArguments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllowedArguments.swift; sourceTree = "<group>"; };
|
C415D3DE2770F34D005EF286 /* AllowedArguments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllowedArguments.swift; sourceTree = "<group>"; };
|
||||||
@@ -273,6 +282,7 @@
|
|||||||
C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpVersionNumberTest.swift; sourceTree = "<group>"; };
|
C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpVersionNumberTest.swift; sourceTree = "<group>"; };
|
||||||
C4930849279F331F009C240B /* AddSiteVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSiteVC.swift; sourceTree = "<group>"; };
|
C4930849279F331F009C240B /* AddSiteVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSiteVC.swift; sourceTree = "<group>"; };
|
||||||
C4998F092617633900B2526E /* PrefsWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsWC.swift; sourceTree = "<group>"; };
|
C4998F092617633900B2526E /* PrefsWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsWC.swift; sourceTree = "<group>"; };
|
||||||
|
C49E171E27A5736E00787921 /* PMServicesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PMServicesView.swift; sourceTree = "<group>"; };
|
||||||
C4ACA38E25C754C100060C66 /* PhpExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtension.swift; sourceTree = "<group>"; };
|
C4ACA38E25C754C100060C66 /* PhpExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtension.swift; sourceTree = "<group>"; };
|
||||||
C4AF9F70275445FF00D44ED0 /* valet-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "valet-config.json"; sourceTree = "<group>"; };
|
C4AF9F70275445FF00D44ED0 /* valet-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "valet-config.json"; sourceTree = "<group>"; };
|
||||||
C4AF9F76275447F100D44ED0 /* ValetConfigParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetConfigParserTest.swift; sourceTree = "<group>"; };
|
C4AF9F76275447F100D44ED0 /* ValetConfigParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetConfigParserTest.swift; sourceTree = "<group>"; };
|
||||||
@@ -295,6 +305,7 @@
|
|||||||
C4D89BC52783C99400A02B68 /* ComposerJson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerJson.swift; sourceTree = "<group>"; };
|
C4D89BC52783C99400A02B68 /* ComposerJson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerJson.swift; sourceTree = "<group>"; };
|
||||||
C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpSwitcher.swift; sourceTree = "<group>"; };
|
C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpSwitcher.swift; sourceTree = "<group>"; };
|
||||||
C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalSwitcher.swift; sourceTree = "<group>"; };
|
C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalSwitcher.swift; sourceTree = "<group>"; };
|
||||||
|
C4DEB7D327A5D60B00834718 /* Stats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stats.swift; sourceTree = "<group>"; };
|
||||||
C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = "<group>"; };
|
C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = "<group>"; };
|
||||||
C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = "<group>"; };
|
C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = "<group>"; };
|
||||||
C4EC1E65279DE0380010F296 /* ServicesView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ServicesView.xib; sourceTree = "<group>"; };
|
C4EC1E65279DE0380010F296 /* ServicesView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ServicesView.xib; sourceTree = "<group>"; };
|
||||||
@@ -305,6 +316,7 @@
|
|||||||
C4EE55A627708B9E001DF387 /* PMHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PMHeaderView.swift; sourceTree = "<group>"; };
|
C4EE55A627708B9E001DF387 /* PMHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PMHeaderView.swift; sourceTree = "<group>"; };
|
||||||
C4EE55A727708B9E001DF387 /* Preview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preview.swift; sourceTree = "<group>"; };
|
C4EE55A727708B9E001DF387 /* Preview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preview.swift; sourceTree = "<group>"; };
|
||||||
C4EE55A827708B9E001DF387 /* PMStatsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PMStatsView.swift; sourceTree = "<group>"; };
|
C4EE55A827708B9E001DF387 /* PMStatsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PMStatsView.swift; sourceTree = "<group>"; };
|
||||||
|
C4EED88827A48778006D7272 /* InterAppHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterAppHandler.swift; sourceTree = "<group>"; };
|
||||||
C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewDiagnostics.swift; sourceTree = "<group>"; };
|
C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewDiagnostics.swift; sourceTree = "<group>"; };
|
||||||
C4F2E4392752F7D00020E974 /* PhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpInstallation.swift; sourceTree = "<group>"; };
|
C4F2E4392752F7D00020E974 /* PhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpInstallation.swift; sourceTree = "<group>"; };
|
||||||
C4F30B02278E16BA00755FCE /* HomebrewService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewService.swift; sourceTree = "<group>"; };
|
C4F30B02278E16BA00755FCE /* HomebrewService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewService.swift; sourceTree = "<group>"; };
|
||||||
@@ -353,6 +365,7 @@
|
|||||||
5420395826135DC100FB00FA /* PrefsVC.swift */,
|
5420395826135DC100FB00FA /* PrefsVC.swift */,
|
||||||
5420395E2613607600FB00FA /* Preferences.swift */,
|
5420395E2613607600FB00FA /* Preferences.swift */,
|
||||||
C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */,
|
C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */,
|
||||||
|
C4DEB7D327A5D60B00834718 /* Stats.swift */,
|
||||||
C41CD0272628D8E20065BBED /* Keybinds */,
|
C41CD0272628D8E20065BBED /* Keybinds */,
|
||||||
54FCFD28276C88C0004CE748 /* Views */,
|
54FCFD28276C88C0004CE748 /* Views */,
|
||||||
);
|
);
|
||||||
@@ -594,6 +607,7 @@
|
|||||||
C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */,
|
C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */,
|
||||||
C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */,
|
C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */,
|
||||||
C4D8016522B1584700C6DA1B /* Startup.swift */,
|
C4D8016522B1584700C6DA1B /* Startup.swift */,
|
||||||
|
C4EED88827A48778006D7272 /* InterAppHandler.swift */,
|
||||||
);
|
);
|
||||||
path = Core;
|
path = Core;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -627,6 +641,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
C4D89BC52783C99400A02B68 /* ComposerJson.swift */,
|
C4D89BC52783C99400A02B68 /* ComposerJson.swift */,
|
||||||
|
C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */,
|
||||||
);
|
);
|
||||||
path = Composer;
|
path = Composer;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -651,6 +666,7 @@
|
|||||||
C4EE55B027708BB2001DF387 /* SwiftUI */ = {
|
C4EE55B027708BB2001DF387 /* SwiftUI */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
C49E171E27A5736E00787921 /* PMServicesView.swift */,
|
||||||
C4EE55A627708B9E001DF387 /* PMHeaderView.swift */,
|
C4EE55A627708B9E001DF387 /* PMHeaderView.swift */,
|
||||||
C4EE55A827708B9E001DF387 /* PMStatsView.swift */,
|
C4EE55A827708B9E001DF387 /* PMStatsView.swift */,
|
||||||
C4EE55A727708B9E001DF387 /* Preview.swift */,
|
C4EE55A727708B9E001DF387 /* Preview.swift */,
|
||||||
@@ -846,6 +862,7 @@
|
|||||||
C40C7F2627721FA200DDDCDC /* Constants.swift in Sources */,
|
C40C7F2627721FA200DDDCDC /* Constants.swift in Sources */,
|
||||||
C4B585402770FE3900DA4FBE /* Paths.swift in Sources */,
|
C4B585402770FE3900DA4FBE /* Paths.swift in Sources */,
|
||||||
C415D3E62770F540005EF286 /* main.swift in Sources */,
|
C415D3E62770F540005EF286 /* main.swift in Sources */,
|
||||||
|
C40B24F327A310780018C7D2 /* Events.swift in Sources */,
|
||||||
C40C7F2227721F8200DDDCDC /* PhpInstallation.swift in Sources */,
|
C40C7F2227721F8200DDDCDC /* PhpInstallation.swift in Sources */,
|
||||||
C4B585432770FE3900DA4FBE /* Shell.swift in Sources */,
|
C4B585432770FE3900DA4FBE /* Shell.swift in Sources */,
|
||||||
C4D9ADC1277610E1007277F4 /* PhpSwitcher.swift in Sources */,
|
C4D9ADC1277610E1007277F4 /* PhpSwitcher.swift in Sources */,
|
||||||
@@ -854,7 +871,6 @@
|
|||||||
C40C7F2327721F8200DDDCDC /* ActivePhpInstallation.swift in Sources */,
|
C40C7F2327721F8200DDDCDC /* ActivePhpInstallation.swift in Sources */,
|
||||||
C4B585462770FE3900DA4FBE /* Command.swift in Sources */,
|
C4B585462770FE3900DA4FBE /* Command.swift in Sources */,
|
||||||
C4D9ADCA277611A0007277F4 /* InternalSwitcher.swift in Sources */,
|
C4D9ADCA277611A0007277F4 /* InternalSwitcher.swift in Sources */,
|
||||||
C4EC1E74279DFCF40010F296 /* Events.swift in Sources */,
|
|
||||||
C48D6C72279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */,
|
C48D6C72279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */,
|
||||||
C40C7F2527721F9800DDDCDC /* HomebrewPackage.swift in Sources */,
|
C40C7F2527721F9800DDDCDC /* HomebrewPackage.swift in Sources */,
|
||||||
C417DC76277614690015E6EE /* Helpers.swift in Sources */,
|
C417DC76277614690015E6EE /* Helpers.swift in Sources */,
|
||||||
@@ -878,6 +894,7 @@
|
|||||||
C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */,
|
C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */,
|
||||||
5420395926135DC100FB00FA /* PrefsVC.swift in Sources */,
|
5420395926135DC100FB00FA /* PrefsVC.swift in Sources */,
|
||||||
C43603A0275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */,
|
C43603A0275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */,
|
||||||
|
C49E171F27A5736E00787921 /* PMServicesView.swift in Sources */,
|
||||||
C4EE55AD27708B9E001DF387 /* PMStatsView.swift in Sources */,
|
C4EE55AD27708B9E001DF387 /* PMStatsView.swift in Sources */,
|
||||||
C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
|
C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
|
||||||
54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */,
|
54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */,
|
||||||
@@ -897,6 +914,7 @@
|
|||||||
C4C3ED4327834C5200AB15D8 /* CustomPrefs.swift in Sources */,
|
C4C3ED4327834C5200AB15D8 /* CustomPrefs.swift in Sources */,
|
||||||
54B48B5F275F66AE006D90C5 /* Application.swift in Sources */,
|
54B48B5F275F66AE006D90C5 /* Application.swift in Sources */,
|
||||||
C4B97B78275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */,
|
C4B97B78275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */,
|
||||||
|
C415937F27A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */,
|
||||||
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */,
|
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */,
|
||||||
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */,
|
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */,
|
||||||
C4F30B03278E16BA00755FCE /* HomebrewService.swift in Sources */,
|
C4F30B03278E16BA00755FCE /* HomebrewService.swift in Sources */,
|
||||||
@@ -921,6 +939,7 @@
|
|||||||
C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */,
|
C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */,
|
||||||
C4188989275FE8CB001EF227 /* Filesystem.swift in Sources */,
|
C4188989275FE8CB001EF227 /* Filesystem.swift in Sources */,
|
||||||
C4B97B7B275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */,
|
C4B97B7B275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */,
|
||||||
|
C4EED88927A48778006D7272 /* InterAppHandler.swift in Sources */,
|
||||||
C40C7F1E2772136000DDDCDC /* PhpEnv.swift in Sources */,
|
C40C7F1E2772136000DDDCDC /* PhpEnv.swift in Sources */,
|
||||||
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
|
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
|
||||||
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
|
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
|
||||||
@@ -936,6 +955,7 @@
|
|||||||
C464ADB2275A87CA003FCD53 /* SiteListCell.swift in Sources */,
|
C464ADB2275A87CA003FCD53 /* SiteListCell.swift in Sources */,
|
||||||
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */,
|
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */,
|
||||||
C493084A279F331F009C240B /* AddSiteVC.swift in Sources */,
|
C493084A279F331F009C240B /* AddSiteVC.swift in Sources */,
|
||||||
|
C4DEB7D427A5D60B00834718 /* Stats.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -950,7 +970,6 @@
|
|||||||
54AB03272763858F00A29D5F /* Timer.swift in Sources */,
|
54AB03272763858F00A29D5F /* Timer.swift in Sources */,
|
||||||
54FCFD2B276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */,
|
54FCFD2B276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */,
|
||||||
C415D3B82770F294005EF286 /* Actions.swift in Sources */,
|
C415D3B82770F294005EF286 /* Actions.swift in Sources */,
|
||||||
C4EE55AC27708B9E001DF387 /* Preview.swift in Sources */,
|
|
||||||
54B48B60275F66AE006D90C5 /* Application.swift in Sources */,
|
54B48B60275F66AE006D90C5 /* Application.swift in Sources */,
|
||||||
C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */,
|
C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */,
|
||||||
C493084B279F331F009C240B /* AddSiteVC.swift in Sources */,
|
C493084B279F331F009C240B /* AddSiteVC.swift in Sources */,
|
||||||
@@ -967,11 +986,12 @@
|
|||||||
C4AF9F7B2754499000D44ED0 /* Valet.swift in Sources */,
|
C4AF9F7B2754499000D44ED0 /* Valet.swift in Sources */,
|
||||||
C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */,
|
C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */,
|
||||||
C4CCBA6D275C567B008C7055 /* PMWindowController.swift in Sources */,
|
C4CCBA6D275C567B008C7055 /* PMWindowController.swift in Sources */,
|
||||||
|
C40B24F527A3108B0018C7D2 /* Async.swift in Sources */,
|
||||||
C4B5635F276AB09000F12CCB /* VersionExtractor.swift in Sources */,
|
C4B5635F276AB09000F12CCB /* VersionExtractor.swift in Sources */,
|
||||||
C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */,
|
C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */,
|
||||||
C4F780AE25D80B37000DBC97 /* ExtensionParserTest.swift in Sources */,
|
C4F780AE25D80B37000DBC97 /* ExtensionParserTest.swift in Sources */,
|
||||||
C4F780C725D80B75000DBC97 /* StatusMenu.swift in Sources */,
|
|
||||||
C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
|
C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
|
||||||
|
C4EED88A27A48778006D7272 /* InterAppHandler.swift in Sources */,
|
||||||
C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */,
|
C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */,
|
||||||
C43603A1275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */,
|
C43603A1275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */,
|
||||||
C42759682627662800093CAE /* NSMenuExtension.swift in Sources */,
|
C42759682627662800093CAE /* NSMenuExtension.swift in Sources */,
|
||||||
@@ -984,6 +1004,7 @@
|
|||||||
C464ADB3275A87CA003FCD53 /* SiteListCell.swift in Sources */,
|
C464ADB3275A87CA003FCD53 /* SiteListCell.swift in Sources */,
|
||||||
C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */,
|
C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */,
|
||||||
C4AF9F78275447F100D44ED0 /* ValetConfigParserTest.swift in Sources */,
|
C4AF9F78275447F100D44ED0 /* ValetConfigParserTest.swift in Sources */,
|
||||||
|
C40B24F427A310830018C7D2 /* StatusMenu.swift in Sources */,
|
||||||
C417DC75277614690015E6EE /* Helpers.swift in Sources */,
|
C417DC75277614690015E6EE /* Helpers.swift in Sources */,
|
||||||
C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */,
|
C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */,
|
||||||
C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */,
|
C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */,
|
||||||
@@ -995,12 +1016,15 @@
|
|||||||
C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */,
|
C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */,
|
||||||
C4F780C325D80B75000DBC97 /* HeaderView.swift in Sources */,
|
C4F780C325D80B75000DBC97 /* HeaderView.swift in Sources */,
|
||||||
C44C198E276E3A1C0072762D /* ProgressWindow.swift in Sources */,
|
C44C198E276E3A1C0072762D /* ProgressWindow.swift in Sources */,
|
||||||
|
C415938027A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */,
|
||||||
C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */,
|
C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */,
|
||||||
C4F30B0B278E203C00755FCE /* MainMenu+Startup.swift in Sources */,
|
C4F30B0B278E203C00755FCE /* MainMenu+Startup.swift in Sources */,
|
||||||
|
C40B24F227A310770018C7D2 /* Events.swift in Sources */,
|
||||||
C4F30B0A278E1A1A00755FCE /* ComposerJson.swift in Sources */,
|
C4F30B0A278E1A1A00755FCE /* ComposerJson.swift in Sources */,
|
||||||
C4AF9F7D275454A900D44ED0 /* ValetTest.swift in Sources */,
|
C4AF9F7D275454A900D44ED0 /* ValetTest.swift in Sources */,
|
||||||
C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */,
|
C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */,
|
||||||
C4B585452770FE3900DA4FBE /* Command.swift in Sources */,
|
C4B585452770FE3900DA4FBE /* Command.swift in Sources */,
|
||||||
|
C40B24F127A3106D0018C7D2 /* ServicesView.swift in Sources */,
|
||||||
C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */,
|
C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */,
|
||||||
C4F780B725D80B5D000DBC97 /* App.swift in Sources */,
|
C4F780B725D80B5D000DBC97 /* App.swift in Sources */,
|
||||||
C48D6C71279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */,
|
C48D6C71279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */,
|
||||||
@@ -1189,12 +1213,12 @@
|
|||||||
C41C1B4422B0098000E7CF16 /* Debug */ = {
|
C41C1B4422B0098000E7CF16 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconBeta;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements;
|
CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 500;
|
CURRENT_PROJECT_VERSION = 560;
|
||||||
DEVELOPMENT_TEAM = 8M54J5J787;
|
DEVELOPMENT_TEAM = 8M54J5J787;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
INFOPLIST_FILE = phpmon/Info.plist;
|
INFOPLIST_FILE = phpmon/Info.plist;
|
||||||
@@ -1203,8 +1227,8 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||||
MARKETING_VERSION = "5.0-b1";
|
MARKETING_VERSION = 5.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.beta;
|
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -1214,12 +1238,12 @@
|
|||||||
C41C1B4522B0098000E7CF16 /* Release */ = {
|
C41C1B4522B0098000E7CF16 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconBeta;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements;
|
CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 500;
|
CURRENT_PROJECT_VERSION = 560;
|
||||||
DEVELOPMENT_TEAM = 8M54J5J787;
|
DEVELOPMENT_TEAM = 8M54J5J787;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
INFOPLIST_FILE = phpmon/Info.plist;
|
INFOPLIST_FILE = phpmon/Info.plist;
|
||||||
@@ -1228,8 +1252,8 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||||
MARKETING_VERSION = "5.0-b1";
|
MARKETING_VERSION = 5.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.beta;
|
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
99
README.md
99
README.md
@@ -1,15 +1,17 @@
|
|||||||
# PHP Monitor
|
|
||||||
|
|
||||||
> If this software has been useful to you, I ask that you **please star the repository**, that way I know that the software is being used. Also, please consider leaving [a one-time donation](https://nicoverbruggen.be/sponsor) to support the project.
|
> If this software has been useful to you, I ask that you **please star the repository**, that way I know that the software is being used. Also, please consider leaving [a one-time donation](https://nicoverbruggen.be/sponsor) to support the project.
|
||||||
> You can also send me [feedback](https://twitter.com/nicoverbruggen) if the app came in handy.<br>**Thank you!** ⭐️
|
> You can also send me [feedback](https://twitter.com/nicoverbruggen) if the app came in handy.<br>**Thank you!** ⭐️
|
||||||
|
|
||||||
<img src="./phpmon/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png" alt="phpmon icon" width="128px" />
|
<h1 align="center"><b>PHP Monitor</b> (phpmon)</h1>
|
||||||
|
|
||||||
**PHP Monitor** (or phpmon) is a lightweight macOS utility app that runs on your Mac and displays the active PHP version in your status bar. It's tightly integrated with [Laravel Valet](https://github.com/laravel/valet), so you need to have it set up before you can use this.
|
<p align="center">
|
||||||
|
<img src="./phpmon/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png" alt="phpmon icon" width="128px" />
|
||||||
|
</p>
|
||||||
|
|
||||||
<img src="./docs/screenshot41.jpg" width="800px" alt="phpmon screenshot (menu bar app)"/>
|
**PHP Monitor** (or *phpmon*) is a lightweight macOS utility app that runs on your Mac and displays the active PHP version in your status bar. It's tightly integrated with [Laravel Valet](https://github.com/laravel/valet), so <u>you need to have it set up before you can use this</u>.
|
||||||
|
|
||||||
<small><i>Screenshot: A menu showing all of the functionality of PHP Monitor.</i></small>
|
<img src="./docs/screenshot50.jpg" width="1085px" alt="phpmon screenshot (menu bar app)"/>
|
||||||
|
|
||||||
|
<small><i>Screenshot: Showing the key functionality of PHP Monitor. You can also add new domains as links, manage various services, and perform First Aid to fix all kinds of common PHP link issues.</i></small>
|
||||||
|
|
||||||
It's super convenient to switch between different versions of PHP. You'll even get notifications (only if you choose to opt-in, of course)!
|
It's super convenient to switch between different versions of PHP. You'll even get notifications (only if you choose to opt-in, of course)!
|
||||||
|
|
||||||
@@ -19,7 +21,7 @@ PHP Monitor also gives you quick access to various useful functionality (like ac
|
|||||||
|
|
||||||
## 🖥 System requirements
|
## 🖥 System requirements
|
||||||
|
|
||||||
PHP Monitor is a universal application that runs on Apple Silicon **and** Intel-based Macs.
|
PHP Monitor is a universal application that runs natively on Apple Silicon **and** Intel-based Macs.
|
||||||
|
|
||||||
* macOS 11 Big Sur or higher (supports macOS 12 Monterey)
|
* macOS 11 Big Sur or higher (supports macOS 12 Monterey)
|
||||||
* Homebrew is installed in `/usr/local/homebrew` or `/opt/homebrew`
|
* Homebrew is installed in `/usr/local/homebrew` or `/opt/homebrew`
|
||||||
@@ -30,7 +32,7 @@ _You may need to update your Valet installation to keep everything working if a
|
|||||||
|
|
||||||
## 🚀 How to install
|
## 🚀 How to install
|
||||||
|
|
||||||
You can install via Homebrew (recommended), or may download the latest release on GitHub.
|
Again, make sure you have **Laravel Valet** installed first. Once that's done, you can install via Homebrew (recommended), or may download the latest release on GitHub.
|
||||||
|
|
||||||
To install via Homebrew, run:
|
To install via Homebrew, run:
|
||||||
|
|
||||||
@@ -41,7 +43,11 @@ To upgrade your existing installation, run:
|
|||||||
|
|
||||||
brew upgrade phpmon
|
brew upgrade phpmon
|
||||||
|
|
||||||
_The app is signed and notarized, meaning all you have to do is approve its first launch._
|
(You may need to run `brew update` first in order to update the cask file if you ran a Homebrew operation recently.)
|
||||||
|
|
||||||
|
## 🔑 Is the app signed & notarized?
|
||||||
|
|
||||||
|
Yes, the app is signed and notarized, meaning all you have to do is approve its first launch (or whenever it updates).
|
||||||
|
|
||||||
## 👨💻 Why build this?
|
## 👨💻 Why build this?
|
||||||
|
|
||||||
@@ -55,6 +61,8 @@ PHP Monitor performs some integrity checks to ensure a good experience when usin
|
|||||||
|
|
||||||
**Follow instructions as specified in the alert in order to resolve any issues.**
|
**Follow instructions as specified in the alert in order to resolve any issues.**
|
||||||
|
|
||||||
|
(If the app crashes at launch without showing you any of these messages, you might have a non-standard Homebrew and Valet setup. Those are not supported.)
|
||||||
|
|
||||||
## 🙋♂️ FAQ & Troubleshooting
|
## 🙋♂️ FAQ & Troubleshooting
|
||||||
|
|
||||||
> If you are having issues, the first thing you should be doing is installing the latest version of PHP Monitor _and_ Laravel Valet. This can resolve a variety of issues. To upgrade Valet, run `composer global update`. Don't forget to run `valet install` after upgrading.
|
> If you are having issues, the first thing you should be doing is installing the latest version of PHP Monitor _and_ Laravel Valet. This can resolve a variety of issues. To upgrade Valet, run `composer global update`. Don't forget to run `valet install` after upgrading.
|
||||||
@@ -258,12 +266,63 @@ You can add your own apps by creating and editing a `~/.phpmon.conf.json` file,
|
|||||||
You can put as many apps as you'd like in the `scan_apps` array, and PHP Monitor will check for the existence of these apps. You do not need to set the full path, just the name of the app should work. Not all apps support opening a folder, though, so your success might vary.
|
You can put as many apps as you'd like in the `scan_apps` array, and PHP Monitor will check for the existence of these apps. You do not need to set the full path, just the name of the app should work. Not all apps support opening a folder, though, so your success might vary.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>How can the app integrate with third party tools, like Alfred?</strong></summary>
|
||||||
|
|
||||||
|
There's an Alfred workflow usually included in the release list, you can grab it by going to releases and downloading the asset `phpmon.alfredworkflow`.
|
||||||
|
|
||||||
|
If you'd like to integrate something yourself, all you need to to is use the `phpmon://` protocol and ensure that third party app integrations are enabled in Preferences (in PHP Monitor).
|
||||||
|
|
||||||
|
Using app callbacks, macOS and PHP Monitor allow for the following to be called:
|
||||||
|
|
||||||
|
* phpmon://list
|
||||||
|
* phpmon://services/stop
|
||||||
|
* phpmon://services/restart/all
|
||||||
|
* phpmon://services/restart/nginx
|
||||||
|
* phpmon://services/restart/php
|
||||||
|
* phpmon://services/restart/dnsmasq
|
||||||
|
* phpmon://locate/config
|
||||||
|
* phpmon://locate/composer
|
||||||
|
* phpmon://locate/valet
|
||||||
|
* phpmon://phpinfo
|
||||||
|
* phpmon://switch/php/{version}
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>How does the app know what PHP version is required for my app?</strong></summary>
|
||||||
|
|
||||||
|
The `composer.json` file in the root of the folder (if it exists) is scanned and interpreted.
|
||||||
|
|
||||||
|
If the version is set in `platform`, it takes precendence.
|
||||||
|
If the version is not set in `platform` but it is in `require` (most common) then that version is used.
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>What do the checkmarks next to the PHP version mean in the site list?</strong></summary>
|
||||||
|
|
||||||
|
You'll see a checkmark next to the version number if the currently enabled PHP version is compatible with the version required to run the site.
|
||||||
|
|
||||||
|
This is determined by evaluating the PHP requirement constraint (e.g. `^8.0`, `~8.0` or a specific version: `8.0`).
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Why can't I see the driver type any more? It says "Project Type" now.</strong></summary>
|
||||||
|
|
||||||
|
PHP Monitor currently checks your `composer.json` file to try to figure out what project you are running.
|
||||||
|
|
||||||
|
This approach is a lot faster than asking for a driver when you have many sites linked, but is slightly less reliable since the framework or type of project inferred via `composer.json` might not be 100% accurate.
|
||||||
|
|
||||||
|
You can always still ask Valet using the command line, should it be necessary. In my experience fetching the drivers slowed down the app unnecessarily.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><strong>After running PHP Monitor, Homebrew sometimes has issues with `brew upgrade` or `brew cleanup`!</strong></summary>
|
<summary><strong>After running PHP Monitor, Homebrew sometimes has issues with `brew upgrade` or `brew cleanup`!</strong></summary>
|
||||||
|
|
||||||
<strike>This is a security feature of Homebrew. When you start a service as an administrator, the root user becomes the owner of relevant binaries. You will need to manually clean up those folders yourself using `rm -rf` (or by manually removing those folders via Finder).</strike>
|
This is a security feature of Homebrew. When you start a service as an administrator, the root user becomes the owner of relevant binaries. You will need to manually clean up those folders yourself using `rm -rf` (or by manually removing those folders via Finder).
|
||||||
|
|
||||||
**Update**: If you are using the Valet switcher (currently not available in the latest stable build) you will not encounter this issue. For more information on this, see [this issue](https://github.com/nicoverbruggen/phpmon/issues/17) and also [this issue about switching to Valet's switcher](https://github.com/nicoverbruggen/phpmon/issues/34).
|
If you would like to know more, consult [this issue](https://github.com/nicoverbruggen/phpmon/issues/85) for more information.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -272,6 +331,10 @@ You can put as many apps as you'd like in the `scan_apps` array, and PHP Monitor
|
|||||||
|
|
||||||
Please get in touch and open an issue. PHP Monitor shouldn't crash... (unless you are actually removing PHP *while* the app is running, that’s considered normal behaviour!)
|
Please get in touch and open an issue. PHP Monitor shouldn't crash... (unless you are actually removing PHP *while* the app is running, that’s considered normal behaviour!)
|
||||||
|
|
||||||
|
If you would like to report a crash, please include the associated **log files** so I can find out what exactly went wrong.
|
||||||
|
|
||||||
|
To find the logs, take a look in `~/Library/Logs/DiagnosticReports` (in Finder) and see if there's any (log) files that start with "PHP Monitor".
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## 📝 Having another issue?
|
## 📝 Having another issue?
|
||||||
@@ -291,12 +354,10 @@ Donations really help with the Apple Developer Program cost, and keep me motivat
|
|||||||
While I did make this application during my own free time, I have been lucky enough to do various experiments during work hours at [DIVE](https://dive.be). I'd also like to shout out the following folks:
|
While I did make this application during my own free time, I have been lucky enough to do various experiments during work hours at [DIVE](https://dive.be). I'd also like to shout out the following folks:
|
||||||
|
|
||||||
* My colleagues at [DIVE](https://dive.be)
|
* My colleagues at [DIVE](https://dive.be)
|
||||||
* The [Homebrew](https://brew.sh/) team who maintain
|
* The [Homebrew](https://brew.sh/) team & [Valet maintainers](https://github.com/laravel/valet/graphs/contributors)
|
||||||
* The [developers & maintainers of Valet](https://github.com/laravel/valet/graphs/contributors)
|
* Various folks who [reached](https://twitter.com/stauffermatt) [out](https://twitter.com/marcelpociot) when PHP Monitor was still very much a small app with a handful of stars or so
|
||||||
* Everyone in the Laravel community who shared the app (thanks!)
|
* Everyone in the Laravel community who shared the app (thanks!)
|
||||||
* Various folks who [reached](https://twitter.com/stauffermatt) [out](https://twitter.com/marcelpociot)
|
* Everyone who left feedback via issues & who donated to keep the project up and running
|
||||||
* Everyone who left feedback via issues
|
|
||||||
* Everyone who donated to keep the project up and running
|
|
||||||
|
|
||||||
Thank you very much for your contributions, kind words and support.
|
Thank you very much for your contributions, kind words and support.
|
||||||
|
|
||||||
@@ -326,7 +387,7 @@ If an extension or other process writes to a single file a bunch of times in a s
|
|||||||
|
|
||||||
1. **Location of your sites**: PHP Monitor uses the Valet configuration file to determine which folders to look into. Each folder is scanned and then PHP Monitor will validate if a composer.json file exists to determine the desired PHP version.
|
1. **Location of your sites**: PHP Monitor uses the Valet configuration file to determine which folders to look into. Each folder is scanned and then PHP Monitor will validate if a composer.json file exists to determine the desired PHP version.
|
||||||
1. **Sites secured or not secured**: Whether the directory has been secured is determined by checking if a matching certificate exists under Valet's `Certificates` directory for that site name.
|
1. **Sites secured or not secured**: Whether the directory has been secured is determined by checking if a matching certificate exists under Valet's `Certificates` directory for that site name.
|
||||||
1. **Site drivers**: PHP Monitor runs `valet which` to determine which driver is currently in use for each individual site. This command is executed once for each site whenever the site list is refreshed.
|
1. **Project type**: PHP Monitor checks your `composer.json` file for "notable dependencies". If you have `laravel/framework` in your `require`, there's a good chance the project type is `Laravel`, after all.
|
||||||
|
|
||||||
*Note*: If you have linked a folder in Documents, Desktop or Downloads you might need to grant PHP Monitor access to those directories for PHP Monitor to work correctly.
|
*Note*: If you have linked a folder in Documents, Desktop or Downloads you might need to grant PHP Monitor access to those directories for PHP Monitor to work correctly.
|
||||||
|
|
||||||
@@ -334,6 +395,10 @@ If an extension or other process writes to a single file a bunch of times in a s
|
|||||||
|
|
||||||
If you want to know more about how this works, I recommend you check out the source code.
|
If you want to know more about how this works, I recommend you check out the source code.
|
||||||
|
|
||||||
|
I have done my best to annotate as much as humanly possible, and have avoided using an overly complicated architecture to keep the code as easy to maintain as possible. The code isn't perfect by a long shot (lots of cleanup can still happen!) but the application works well.
|
||||||
|
|
||||||
|
I also have a few tests for key parts of the application that I found needed to be tested. In the future, I would like to add even more tests for some of the UI stuff, but for now the tests are more unit tests than feature tests.
|
||||||
|
|
||||||
## 🔧 Build instructions
|
## 🔧 Build instructions
|
||||||
|
|
||||||
<img src="./docs/build.png" width="404px" alt="build button in Xcode"/>
|
<img src="./docs/build.png" width="404px" alt="build button in Xcode"/>
|
||||||
|
@@ -4,15 +4,15 @@
|
|||||||
|
|
||||||
Generally speaking, only the latest version of **PHP Monitor** is supported, except during transition periods (for example, when particular system requirements go up):
|
Generally speaking, only the latest version of **PHP Monitor** is supported, except during transition periods (for example, when particular system requirements go up):
|
||||||
|
|
||||||
| Version | Apple silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version |
|
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version |
|
||||||
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
|
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
|
||||||
| 5.x | ✅ Universal binary | ✅ Yes | Big Sur (11.0) and Monterey (12.0) | macOS 11+ | PHP 5.6—PHP 8.2 | TBD |
|
| 5.0 | ✅ Universal binary | ✅ Yes | Big Sur (11.0) and Monterey (12.0) | macOS 11+ | PHP 5.6—PHP 8.2 | 2.16.2 |
|
||||||
|
|
||||||
## Legacy versions
|
## Legacy versions
|
||||||
|
|
||||||
These versions of PHP Monitor are no longer supported, but if you’re using an older computer with an older version of Homebrew, Valet or macOS, you might want to use one of these versions.
|
These versions of PHP Monitor are no longer supported, but if you’re using an older computer with an older version of Homebrew, Valet or macOS, you might want to use one of these versions.
|
||||||
|
|
||||||
| Version | Apple silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version |
|
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version |
|
||||||
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
|
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
|
||||||
| 4.1 | ✅ Universal binary | ❌ | Big Sur (11.0) and Monterey (12.0) | macOS 11+ | PHP 5.6—PHP 8.2 | 2.16.2 |
|
| 4.1 | ✅ Universal binary | ❌ | Big Sur (11.0) and Monterey (12.0) | macOS 11+ | PHP 5.6—PHP 8.2 | 2.16.2 |
|
||||||
| 4.0 | ✅ Universal binary | ❌ | Big Sur (11.0) and Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 | 2.13 |
|
| 4.0 | ✅ Universal binary | ❌ | Big Sur (11.0) and Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 | 2.13 |
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 396 KiB |
BIN
docs/screenshot50.jpg
Normal file
BIN
docs/screenshot50.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 370 KiB |
BIN
integrations/phpmon.alfredworkflow
Normal file
BIN
integrations/phpmon.alfredworkflow
Normal file
Binary file not shown.
@@ -88,7 +88,7 @@ class Actions {
|
|||||||
return URL(string: "file:///private/tmp/phpmon_phpinfo.html")!
|
return URL(string: "file:///private/tmp/phpmon_phpinfo.html")!
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Quick Fix
|
// MARK: - Fix My Valet
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Detects all currently available PHP versions,
|
Detects all currently available PHP versions,
|
||||||
@@ -100,7 +100,7 @@ class Actions {
|
|||||||
If this does not solve the issue, the user may need to install additional
|
If this does not solve the issue, the user may need to install additional
|
||||||
extensions and/or run `composer global update`.
|
extensions and/or run `composer global update`.
|
||||||
*/
|
*/
|
||||||
public static func fixMyPhp()
|
public static func fixMyValet()
|
||||||
{
|
{
|
||||||
brew("services restart dnsmasq", sudo: true)
|
brew("services restart dnsmasq", sudo: true)
|
||||||
|
|
||||||
@@ -111,11 +111,14 @@ class Actions {
|
|||||||
brew("services stop \(formula)", sudo: true)
|
brew("services stop \(formula)", sudo: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
brew("services stop dnsmasq")
|
||||||
brew("services stop php")
|
brew("services stop php")
|
||||||
brew("services stop nginx")
|
brew("services stop nginx")
|
||||||
brew("link php")
|
|
||||||
|
brew("link php --overwrite --force")
|
||||||
|
|
||||||
brew("services restart dnsmasq", sudo: true)
|
brew("services restart dnsmasq", sudo: true)
|
||||||
brew("services stop php", sudo: true)
|
brew("services restart php", sudo: true)
|
||||||
brew("services stop nginx", sudo: true)
|
brew("services restart nginx", sudo: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -51,4 +51,9 @@ class Constants {
|
|||||||
"8.2"
|
"8.2"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
The URL that people can visit if they wish to help support the project.
|
||||||
|
*/
|
||||||
|
static let DonationUrl = URL(string: "https://nicoverbruggen.be/sponsor#pay-now")!
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -8,8 +8,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The `Paths` class is used to locate various binaries on the system,
|
The `Paths` class is used to locate various binaries on the system.
|
||||||
and provides a full
|
The path to the Homebrew directory and the user's name are fetched only once, at boot.
|
||||||
*/
|
*/
|
||||||
public class Paths {
|
public class Paths {
|
||||||
|
|
||||||
@@ -17,8 +17,11 @@ public class Paths {
|
|||||||
|
|
||||||
private var baseDir : Paths.HomebrewDir
|
private var baseDir : Paths.HomebrewDir
|
||||||
|
|
||||||
|
private var userName : String
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
baseDir = Shell.fileExists("\(HomebrewDir.opt.rawValue)/bin/brew") ? .opt : .usr
|
baseDir = Shell.fileExists("\(HomebrewDir.opt.rawValue)/bin/brew") ? .opt : .usr
|
||||||
|
userName = String(Shell.pipe("whoami").split(separator: "\n")[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// - MARK: Binaries
|
// - MARK: Binaries
|
||||||
@@ -42,7 +45,7 @@ public class Paths {
|
|||||||
// - MARK: Paths
|
// - MARK: Paths
|
||||||
|
|
||||||
public static var whoami: String {
|
public static var whoami: String {
|
||||||
return String(Shell.pipe("whoami").split(separator: "\n")[0])
|
return shared.userName
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var binPath: String {
|
public static var binPath: String {
|
||||||
|
@@ -123,7 +123,7 @@ public class Shell {
|
|||||||
|
|
||||||
let task = Process()
|
let task = Process()
|
||||||
task.launchPath = self.shell
|
task.launchPath = self.shell
|
||||||
task.arguments = ["--login", "-c", tailoredCommand]
|
task.arguments = ["--noprofile", "-norc", "--login", "-c", tailoredCommand]
|
||||||
|
|
||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
|
@@ -155,4 +155,16 @@ class PhpEnv {
|
|||||||
.matching(constraint: $0.trimmingCharacters(in: .whitespacesAndNewlines))
|
.matching(constraint: $0.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Validates whether the currently running version matches the provided version.
|
||||||
|
*/
|
||||||
|
public func validate(_ version: String) -> Bool {
|
||||||
|
if self.currentInstall.version.short == version {
|
||||||
|
print("Switching to version \(version) seems to have succeeded. Validation passed.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -32,7 +32,7 @@ public struct PhpVersionNumberCollection: Equatable {
|
|||||||
https://getcomposer.org/doc/articles/versions.md#writing-version-constraints
|
https://getcomposer.org/doc/articles/versions.md#writing-version-constraints
|
||||||
|
|
||||||
- Parameter constraint: The full constraint as a string (e.g. "^7.0")
|
- Parameter constraint: The full constraint as a string (e.g. "^7.0")
|
||||||
- Parameter strict: Whether the minor version check is strict. See more below.
|
- Parameter strict: Whether the patch version check is strict. See more below.
|
||||||
|
|
||||||
The strict mode does not matter if a patch version is provided for all versions in the collection.
|
The strict mode does not matter if a patch version is provided for all versions in the collection.
|
||||||
|
|
||||||
@@ -45,7 +45,8 @@ public struct PhpVersionNumberCollection: Equatable {
|
|||||||
|
|
||||||
Given versions 8.0.? and 8.1.?, but the requirement is ^8.0.1, in strict mode only 8.1.? will
|
Given versions 8.0.? and 8.1.?, but the requirement is ^8.0.1, in strict mode only 8.1.? will
|
||||||
be considered valid (8.0 translates to 8.0.0 and as such is older than 8.0.1, 8.1.0 is OK).
|
be considered valid (8.0 translates to 8.0.0 and as such is older than 8.0.1, 8.1.0 is OK).
|
||||||
When checking against actual PHP versions installed by the user, use strict mode.
|
When checking against actual PHP versions installed by the user (with patch precision), use
|
||||||
|
strict mode.
|
||||||
|
|
||||||
**NON-STRICT MODE (= patch precision off)**
|
**NON-STRICT MODE (= patch precision off)**
|
||||||
|
|
||||||
@@ -58,43 +59,34 @@ public struct PhpVersionNumberCollection: Equatable {
|
|||||||
public func matching(constraint: String, strict: Bool = false) -> [PhpVersionNumber] {
|
public func matching(constraint: String, strict: Bool = false) -> [PhpVersionNumber] {
|
||||||
if let version = PhpVersionNumber.make(from: constraint, type: .versionOnly) {
|
if let version = PhpVersionNumber.make(from: constraint, type: .versionOnly) {
|
||||||
// Strict constraint (e.g. "7.0") -> returns specific version
|
// Strict constraint (e.g. "7.0") -> returns specific version
|
||||||
return self.versions.filter {
|
return self.versions.filter { $0.isSameAs(version, strict) }
|
||||||
$0.major == version.major
|
|
||||||
&& $0.minor == version.minor
|
|
||||||
&& (strict ? $0.patch(strict, version) == version.patch(strict) : true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let version = PhpVersionNumber.make(from: constraint, type: .caretVersionRange) {
|
if let version = PhpVersionNumber.make(from: constraint, type: .caretVersionRange) {
|
||||||
// Caret range means that the major version is never higher but minor version can be higher
|
// Caret range means that the major version is never higher but minor version can be higher
|
||||||
// ^7.2 will be compatible with all versions between 7.2 and 8.0
|
// ^7.2 will be compatible with all versions between 7.2 and 8.0
|
||||||
return self.versions.filter {
|
return self.versions.filter { $0.hasNewerMinorVersionOrPatch(version, strict) }
|
||||||
$0.major == version.major &&
|
|
||||||
(
|
|
||||||
// Either the minor version is the same and the patch is higher or equal
|
|
||||||
$0.minor == version.minor && $0.patch(strict) >= version.patch(strict, $0)
|
|
||||||
// or the minor version number has been bumped
|
|
||||||
|| $0.minor > version.minor
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let version = PhpVersionNumber.make(from: constraint, type: .tildeVersionRange) {
|
if let version = PhpVersionNumber.make(from: constraint, type: .tildeVersionRange) {
|
||||||
// Tilde range means that most specific digit is used as the basis.
|
// Tilde range means that most specific digit is used as the basis.
|
||||||
if version.patch != nil {
|
return self.versions.filter {
|
||||||
|
version.patch != nil
|
||||||
// If a patch is provided then the minor version cannot be bumped.
|
// If a patch is provided then the minor version cannot be bumped.
|
||||||
return self.versions.filter {
|
? $0.hasSameMajorAndMinorButNewerOrSamePatch(version, strict)
|
||||||
$0.major == version.major && $0.minor == version.minor
|
|
||||||
&& $0.patch(strict, version) >= version.patch!
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If a patch is not provided then the major version cannot be bumped.
|
// If a patch is not provided then the major version cannot be bumped.
|
||||||
return self.versions.filter {
|
: $0.hasSameMajorButNewerOrSameMinor(version, strict)
|
||||||
$0.major == version.major && $0.minor >= version.minor
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let version = PhpVersionNumber.make(from: constraint, type: .greaterThanOrEqual) {
|
||||||
|
return self.versions.filter { $0.isSameAs(version, strict) || $0.isNewerThan(version, strict) }
|
||||||
|
}
|
||||||
|
|
||||||
|
if let version = PhpVersionNumber.make(from: constraint, type: .greaterThan) {
|
||||||
|
return self.versions.filter { $0.isNewerThan(version, strict) }
|
||||||
|
}
|
||||||
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,7 +96,7 @@ public struct PhpVersionNumber: Equatable {
|
|||||||
let minor: Int
|
let minor: Int
|
||||||
let patch: Int?
|
let patch: Int?
|
||||||
|
|
||||||
public func patch(_ strictFallback: Bool, _ constraint: PhpVersionNumber? = nil) -> Int {
|
public func patch(_ strictFallback: Bool = true, _ constraint: PhpVersionNumber? = nil) -> Int {
|
||||||
return patch ?? (strictFallback ? 0 : constraint?.patch ?? 999)
|
return patch ?? (strictFallback ? 0 : constraint?.patch ?? 999)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +108,14 @@ public struct PhpVersionNumber: Equatable {
|
|||||||
case versionOnly = #"^(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
case versionOnly = #"^(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||||
case caretVersionRange = #"^\^(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
case caretVersionRange = #"^\^(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||||
case tildeVersionRange = #"^~(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
case tildeVersionRange = #"^~(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||||
|
case greaterThanOrEqual = #"^>=(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||||
|
case greaterThan = #"^>(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||||
|
|
||||||
|
// TODO: (5.1) Handle these cases (even though I suspect these are uncommon)
|
||||||
|
/*
|
||||||
|
case smallerThanOrEqual = #"^<=(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||||
|
case smallerThan = #"^<(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func make(from versionString: String, type: MatchType = .versionOnly) -> Self? {
|
public static func make(from versionString: String, type: MatchType = .versionOnly) -> Self? {
|
||||||
@@ -138,4 +138,39 @@ public struct PhpVersionNumber: Equatable {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Comparison Logic
|
||||||
|
|
||||||
|
internal func isSameAs(_ version: PhpVersionNumber, _ strict: Bool) -> Bool {
|
||||||
|
return self.major == version.major
|
||||||
|
&& self.minor == version.minor
|
||||||
|
&& (strict ? self.patch(strict, version) == version.patch(strict) : true)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal func isNewerThan(_ version: PhpVersionNumber, _ strict: Bool) -> Bool {
|
||||||
|
return (
|
||||||
|
self.major > version.major ||
|
||||||
|
self.major == version.major && self.minor > version.minor ||
|
||||||
|
self.major == version.major && self.minor == version.minor
|
||||||
|
&& self.patch(strict) > version.patch(strict)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal func hasNewerMinorVersionOrPatch(_ version: PhpVersionNumber, _ strict: Bool) -> Bool {
|
||||||
|
return self.major == version.major &&
|
||||||
|
(
|
||||||
|
(self.minor == version.minor && self.patch(strict) >= version.patch(strict, self))
|
||||||
|
|| self.minor > version.minor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal func hasSameMajorAndMinorButNewerOrSamePatch(_ version: PhpVersionNumber, _ strict: Bool) -> Bool {
|
||||||
|
return self.major == version.major && self.minor == version.minor
|
||||||
|
&& self.patch(strict, version) >= version.patch(strict)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal func hasSameMajorButNewerOrSameMinor(_ version: PhpVersionNumber, _ strict: Bool) -> Bool {
|
||||||
|
return self.major == version.major
|
||||||
|
&& self.minor >= version.minor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -50,6 +50,9 @@ class InternalSwitcher: PhpSwitcher {
|
|||||||
brew("link \(formula) --overwrite --force")
|
brew("link \(formula) --overwrite --force")
|
||||||
brew("services start \(formula)", sudo: true)
|
brew("services start \(formula)", sudo: true)
|
||||||
|
|
||||||
|
Log.info("Restarting nginx, just to be sure!")
|
||||||
|
brew("services restart nginx", sudo: true)
|
||||||
|
|
||||||
Log.info("The new version has been linked!")
|
Log.info("The new version has been linked!")
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
|
@@ -191,4 +191,86 @@ class PhpVersionNumberTest: XCTestCase {
|
|||||||
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all
|
.make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testCanCheckGreaterThanOrEqualConstraints() throws {
|
||||||
|
XCTAssertEqual(
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||||
|
.matching(constraint: ">=7.0", strict: true),
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]).all
|
||||||
|
)
|
||||||
|
|
||||||
|
XCTAssertEqual(
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||||
|
.matching(constraint: ">=7.0.0", strict: true),
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]).all
|
||||||
|
)
|
||||||
|
|
||||||
|
// Strict check (>7.2.5 is too new for 7.2 which resolves to 7.2.0)
|
||||||
|
XCTAssertEqual(
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||||
|
.matching(constraint: ">=7.2.5", strict: true),
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.4", "7.3"]).all
|
||||||
|
)
|
||||||
|
|
||||||
|
// Non-strict check (ignoring patch, 7.2 resolves to 7.2.999)
|
||||||
|
XCTAssertEqual(
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||||
|
.matching(constraint: ">=7.2.5", strict: false),
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.4", "7.3", "7.2"]).all
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCanCheckGreaterThanConstraints() throws {
|
||||||
|
XCTAssertEqual(
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||||
|
.matching(constraint: ">7.0"),
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.4", "7.3", "7.2", "7.1"]).all
|
||||||
|
)
|
||||||
|
|
||||||
|
XCTAssertEqual(
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||||
|
.matching(constraint: ">7.2.5"),
|
||||||
|
// 7.2 will be valid due to non-strict mode (resolves to 7.2.999)
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.4", "7.3", "7.2"]).all
|
||||||
|
)
|
||||||
|
|
||||||
|
XCTAssertEqual(
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
|
||||||
|
.matching(constraint: ">7.2.5", strict: true),
|
||||||
|
// 7.2 will not be valid due to strict mode (resolves to 7.2.0)
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.4", "7.3"]).all
|
||||||
|
)
|
||||||
|
|
||||||
|
XCTAssertEqual(
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.3.1", "7.2.9", "7.2.8", "7.2.6", "7.2.5", "7.2"])
|
||||||
|
.matching(constraint: ">7.2.8"),
|
||||||
|
// 7.2 will be valid due to non-strict mode (resolves to 7.2.999)
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.3.1", "7.2.9", "7.2"]).all
|
||||||
|
)
|
||||||
|
|
||||||
|
XCTAssertEqual(
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.3.1", "7.2.9", "7.2.8", "7.2.6", "7.2.5", "7.2"])
|
||||||
|
.matching(constraint: ">7.2.8", strict: true),
|
||||||
|
// 7.2 will not be valid due to strict mode (resolves to 7.2.0)
|
||||||
|
PhpVersionNumberCollection
|
||||||
|
.make(from: ["7.3.1", "7.2.9"]).all
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
38
phpmon/Assets.xcassets/IconColorGreen.colorset/Contents.json
Normal file
38
phpmon/Assets.xcassets/IconColorGreen.colorset/Contents.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.501",
|
||||||
|
"green" : "0.697",
|
||||||
|
"red" : "0.247"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.501",
|
||||||
|
"green" : "0.765",
|
||||||
|
"red" : "0.247"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.000",
|
||||||
|
"green" : "0.000",
|
||||||
|
"red" : "0.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "1.000",
|
||||||
|
"green" : "1.000",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
38
phpmon/Assets.xcassets/IconColorRed.colorset/Contents.json
Normal file
38
phpmon/Assets.xcassets/IconColorRed.colorset/Contents.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.180",
|
||||||
|
"green" : "0.000",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.426",
|
||||||
|
"green" : "0.363",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
@@ -16,6 +16,7 @@
|
|||||||
<p><b>Want to spread the love?</b> Leave a <a href="https://github.com/nicoverbruggen/phpmon">star on GitHub</a>!</p>
|
<p><b>Want to spread the love?</b> Leave a <a href="https://github.com/nicoverbruggen/phpmon">star on GitHub</a>!</p>
|
||||||
<p><b>Having issues?</b> Consult the <a href="https://github.com/nicoverbruggen/phpmon#%EF%B8%8F-faq--troubleshooting">FAQ & Troubleshooting</a> section.</p>
|
<p><b>Having issues?</b> Consult the <a href="https://github.com/nicoverbruggen/phpmon#%EF%B8%8F-faq--troubleshooting">FAQ & Troubleshooting</a> section.</p>
|
||||||
<p><b>Want to support me?</b> You can <a href="https://nicoverbruggen.be/sponsor">financially support</a> the continued development of this app.</p>
|
<p><b>Want to support me?</b> You can <a href="https://nicoverbruggen.be/sponsor">financially support</a> the continued development of this app.</p>
|
||||||
|
<p><b>Get the latest on Twitter</b> Give me a <a href="https://twitter.com/nicoverbruggen">follow on Twitter</a> to learn about the latest and greatest updates of this app.</p>
|
||||||
<br>
|
<br>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -11,9 +11,37 @@ import Foundation
|
|||||||
|
|
||||||
extension AppDelegate {
|
extension AppDelegate {
|
||||||
|
|
||||||
|
/**
|
||||||
|
This is an entry point for future development for integrating with the PHP Monitor
|
||||||
|
application URL. You can use the `phpmon://` protocol to communicate with the app.
|
||||||
|
|
||||||
|
At this time you can trigger the site list using Alfred (or some other application)
|
||||||
|
by opening the following URL: `phpmon://list`.
|
||||||
|
|
||||||
|
Please note that PHP Monitor needs to be running in the background for this to work.
|
||||||
|
*/
|
||||||
func application(_ application: NSApplication, open urls: [URL]) {
|
func application(_ application: NSApplication, open urls: [URL]) {
|
||||||
print(urls)
|
|
||||||
|
if !Preferences.isEnabled(.allowProtocolForIntegrations) {
|
||||||
|
Log.info("Acting on commands via phpmon:// has been disabled.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let url = urls.first else { return }
|
||||||
|
|
||||||
|
self.interpretCommand(
|
||||||
|
url.absoluteString.replacingOccurrences(of: "phpmon://", with: ""),
|
||||||
|
commands: InterApp.getCommands()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func interpretCommand(_ command: String, commands: [InterApp.Action]) {
|
||||||
|
commands.forEach { action in
|
||||||
|
if command.starts(with: action.command) {
|
||||||
|
let lastElement = String(command.split(separator: "/").last!)
|
||||||
|
action.action(lastElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,6 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import AppKit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Any outlets connected to the app's main menu (not the menu that shows when the icon in
|
Any outlets connected to the app's main menu (not the menu that shows when the icon in
|
||||||
@@ -24,6 +25,13 @@ extension AppDelegate {
|
|||||||
|
|
||||||
// MARK: - Menu Interactions
|
// MARK: - Menu Interactions
|
||||||
|
|
||||||
|
@IBAction func addSiteLinkPressed(_ sender: Any) {
|
||||||
|
SiteListVC.show()
|
||||||
|
|
||||||
|
guard let windowController = App.shared.siteListWindowController else { return }
|
||||||
|
windowController.pressedAddLink(nil)
|
||||||
|
}
|
||||||
|
|
||||||
@IBAction func reloadSiteListPressed(_ sender: Any) {
|
@IBAction func reloadSiteListPressed(_ sender: Any) {
|
||||||
let vc = App.shared.siteListWindowController?
|
let vc = App.shared.siteListWindowController?
|
||||||
.window?.contentViewController as? SiteListVC
|
.window?.contentViewController as? SiteListVC
|
||||||
@@ -37,4 +45,11 @@ extension AppDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IBAction func focusSearchField(_ sender: Any) {
|
||||||
|
SiteListVC.show()
|
||||||
|
|
||||||
|
guard let windowController = App.shared.siteListWindowController else { return }
|
||||||
|
windowController.searchToolbarItem.searchField.becomeFirstResponder()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,10 @@ extension AppDelegate {
|
|||||||
|
|
||||||
// MARK: - Notifications
|
// MARK: - Notifications
|
||||||
|
|
||||||
|
/**
|
||||||
|
Sets up notifications. That does mean we need to ask for permission first.
|
||||||
|
If we cannot get permission, we should log this.
|
||||||
|
*/
|
||||||
public func setupNotifications() {
|
public func setupNotifications() {
|
||||||
let notificationCenter = UNUserNotificationCenter.current()
|
let notificationCenter = UNUserNotificationCenter.current()
|
||||||
notificationCenter.delegate = self
|
notificationCenter.delegate = self
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
<deployment identifier="macosx"/>
|
<deployment identifier="macosx"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
|
||||||
<capability name="Image references" minToolsVersion="12.0"/>
|
<capability name="Image references" minToolsVersion="12.0"/>
|
||||||
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
<capability name="Search Toolbar Item" minToolsVersion="12.0" minSystemVersion="11.0"/>
|
<capability name="Search Toolbar Item" minToolsVersion="12.0" minSystemVersion="11.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
@@ -49,11 +50,31 @@
|
|||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="Sites" id="YTZ-bb-TOG">
|
<menu key="submenu" title="Sites" id="YTZ-bb-TOG">
|
||||||
<items>
|
<items>
|
||||||
<menuItem title="Reload Site List" keyEquivalent="r" id="Ema-AU-Nbr">
|
<menuItem title="add-as-link" keyEquivalent="n" id="du1-bO-N2U" userLabel="Add Link" customClass="LocalizedMenuItem" customModule="PHP_Monitor" customModuleProvider="target">
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="string" keyPath="localizationKey" value="mm_add_folder_as_link"/>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
|
<connections>
|
||||||
|
<action selector="addSiteLinkPressed:" target="Voe-Tx-rLC" id="DzS-MY-6g0"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="reload-list" keyEquivalent="r" id="Ema-AU-Nbr" customClass="LocalizedMenuItem" customModule="PHP_Monitor" customModuleProvider="target">
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="string" keyPath="localizationKey" value="mm_reload_site_list"/>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="reloadSiteListPressed:" target="Voe-Tx-rLC" id="geC-Ld-haX"/>
|
<action selector="reloadSiteListPressed:" target="Voe-Tx-rLC" id="geC-Ld-haX"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="2ux-8Q-UjK"/>
|
||||||
|
<menuItem title="focus-find" keyEquivalent="f" id="I95-fb-EL7" customClass="LocalizedMenuItem" customModule="PHP_Monitor" customModuleProvider="target">
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="string" keyPath="localizationKey" value="mm_find_in_site_list"/>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
|
<connections>
|
||||||
|
<action selector="focusSearchField:" target="Voe-Tx-rLC" id="O8j-1B-hll"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
</items>
|
</items>
|
||||||
</menu>
|
</menu>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
@@ -298,7 +319,7 @@
|
|||||||
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="PHP_Monitor" customModuleProvider="target"/>
|
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="PHP_Monitor" customModuleProvider="target"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="-484" y="32"/>
|
<point key="canvasLocation" x="-495" y="-44"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Window Controller-->
|
<!--Window Controller-->
|
||||||
<scene sceneID="PQa-AT-b2a">
|
<scene sceneID="PQa-AT-b2a">
|
||||||
@@ -474,7 +495,7 @@ Gw
|
|||||||
</string>
|
</string>
|
||||||
</buttonCell>
|
</buttonCell>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="pressedCancel:" target="glS-wF-sEU" id="MZS-Vg-Vjf"/>
|
<action selector="pressedCancel:" target="glS-wF-sEU" id="q0L-YZ-F3J"/>
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZX9-s1-23i">
|
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZX9-s1-23i">
|
||||||
@@ -544,6 +565,7 @@ Gw
|
|||||||
<constraint firstAttribute="bottom" secondItem="SwS-o8-pbl" secondAttribute="bottom" constant="20" symbolic="YES" id="24Z-vC-4E8"/>
|
<constraint firstAttribute="bottom" secondItem="SwS-o8-pbl" secondAttribute="bottom" constant="20" symbolic="YES" id="24Z-vC-4E8"/>
|
||||||
<constraint firstItem="900-Z2-tID" firstAttribute="centerY" secondItem="PVw-cM-qAB" secondAttribute="centerY" id="578-2f-4x8"/>
|
<constraint firstItem="900-Z2-tID" firstAttribute="centerY" secondItem="PVw-cM-qAB" secondAttribute="centerY" id="578-2f-4x8"/>
|
||||||
<constraint firstItem="ZX9-s1-23i" firstAttribute="leading" secondItem="6JT-Vt-3q0" secondAttribute="trailing" constant="-440" id="6eF-GS-Xcn"/>
|
<constraint firstItem="ZX9-s1-23i" firstAttribute="leading" secondItem="6JT-Vt-3q0" secondAttribute="trailing" constant="-440" id="6eF-GS-Xcn"/>
|
||||||
|
<constraint firstItem="SwS-o8-pbl" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="900-Z2-tID" secondAttribute="trailing" constant="10" id="9uc-R7-CZk"/>
|
||||||
<constraint firstItem="6JT-Vt-3q0" firstAttribute="top" secondItem="P0B-Ht-R8n" secondAttribute="bottom" constant="8" symbolic="YES" id="DGN-4k-X0h"/>
|
<constraint firstItem="6JT-Vt-3q0" firstAttribute="top" secondItem="P0B-Ht-R8n" secondAttribute="bottom" constant="8" symbolic="YES" id="DGN-4k-X0h"/>
|
||||||
<constraint firstItem="P0B-Ht-R8n" firstAttribute="top" secondItem="JJJ-T9-Yuv" secondAttribute="top" constant="20" symbolic="YES" id="F2r-6E-qxh"/>
|
<constraint firstItem="P0B-Ht-R8n" firstAttribute="top" secondItem="JJJ-T9-Yuv" secondAttribute="top" constant="20" symbolic="YES" id="F2r-6E-qxh"/>
|
||||||
<constraint firstItem="mmQ-7e-dlb" firstAttribute="top" secondItem="KZf-b0-9cm" secondAttribute="bottom" constant="8" symbolic="YES" id="G21-Vd-tgl"/>
|
<constraint firstItem="mmQ-7e-dlb" firstAttribute="top" secondItem="KZf-b0-9cm" secondAttribute="bottom" constant="8" symbolic="YES" id="G21-Vd-tgl"/>
|
||||||
@@ -560,16 +582,18 @@ Gw
|
|||||||
<constraint firstAttribute="trailing" secondItem="mmQ-7e-dlb" secondAttribute="trailing" constant="20" symbolic="YES" id="hjv-Xq-cxV"/>
|
<constraint firstAttribute="trailing" secondItem="mmQ-7e-dlb" secondAttribute="trailing" constant="20" symbolic="YES" id="hjv-Xq-cxV"/>
|
||||||
<constraint firstItem="6JT-Vt-3q0" firstAttribute="leading" secondItem="P0B-Ht-R8n" secondAttribute="leading" id="jxP-vM-eA9"/>
|
<constraint firstItem="6JT-Vt-3q0" firstAttribute="leading" secondItem="P0B-Ht-R8n" secondAttribute="leading" id="jxP-vM-eA9"/>
|
||||||
<constraint firstItem="P0B-Ht-R8n" firstAttribute="leading" secondItem="JJJ-T9-Yuv" secondAttribute="leading" constant="20" symbolic="YES" id="msC-eG-Fop"/>
|
<constraint firstItem="P0B-Ht-R8n" firstAttribute="leading" secondItem="JJJ-T9-Yuv" secondAttribute="leading" constant="20" symbolic="YES" id="msC-eG-Fop"/>
|
||||||
|
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="P0B-Ht-R8n" secondAttribute="trailing" constant="20" symbolic="YES" id="nvj-Ij-dcd"/>
|
||||||
<constraint firstItem="VzR-5a-cmT" firstAttribute="top" secondItem="ZX9-s1-23i" secondAttribute="bottom" constant="8" symbolic="YES" id="sVP-EV-07F"/>
|
<constraint firstItem="VzR-5a-cmT" firstAttribute="top" secondItem="ZX9-s1-23i" secondAttribute="bottom" constant="8" symbolic="YES" id="sVP-EV-07F"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="ZX9-s1-23i" secondAttribute="trailing" constant="20" symbolic="YES" id="tZ3-2X-JC9"/>
|
<constraint firstAttribute="trailing" secondItem="ZX9-s1-23i" secondAttribute="trailing" constant="20" symbolic="YES" id="tZ3-2X-JC9"/>
|
||||||
|
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="KZf-b0-9cm" secondAttribute="trailing" constant="20" symbolic="YES" id="zq0-Ce-sCs"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
<connections>
|
<connections>
|
||||||
|
<outlet property="buttonCancel" destination="SwS-o8-pbl" id="N1v-uy-2Mi"/>
|
||||||
<outlet property="buttonCreateLink" destination="PVw-cM-qAB" id="0Oo-xW-He7"/>
|
<outlet property="buttonCreateLink" destination="PVw-cM-qAB" id="0Oo-xW-He7"/>
|
||||||
<outlet property="buttonSecure" destination="KZf-b0-9cm" id="5A7-Bn-NB7"/>
|
<outlet property="buttonSecure" destination="KZf-b0-9cm" id="5A7-Bn-NB7"/>
|
||||||
<outlet property="linkName" destination="ZX9-s1-23i" id="yT6-80-Zr1"/>
|
<outlet property="linkName" destination="ZX9-s1-23i" id="yT6-80-Zr1"/>
|
||||||
<outlet property="pathControl" destination="6JT-Vt-3q0" id="f5K-8h-VOd"/>
|
<outlet property="pathControl" destination="6JT-Vt-3q0" id="f5K-8h-VOd"/>
|
||||||
<outlet property="pressedCancel" destination="SwS-o8-pbl" id="cLR-Yn-TSs"/>
|
|
||||||
<outlet property="previewText" destination="VzR-5a-cmT" id="qwd-wX-645"/>
|
<outlet property="previewText" destination="VzR-5a-cmT" id="qwd-wX-645"/>
|
||||||
<outlet property="textFieldError" destination="900-Z2-tID" id="qUk-FE-IKW"/>
|
<outlet property="textFieldError" destination="900-Z2-tID" id="qUk-FE-IKW"/>
|
||||||
<outlet property="textFieldSecure" destination="mmQ-7e-dlb" id="LeA-YS-hRM"/>
|
<outlet property="textFieldSecure" destination="mmQ-7e-dlb" id="LeA-YS-hRM"/>
|
||||||
@@ -588,6 +612,10 @@ Gw
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="600" height="309"/>
|
<rect key="frame" x="0.0" y="0.0" width="600" height="309"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
|
<customView id="j65-Lf-0lG">
|
||||||
|
<rect key="frame" x="9" y="0.0" width="581" height="203"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
</customView>
|
||||||
<scrollView autohidesScrollers="YES" horizontalLineScroll="54" horizontalPageScroll="10" verticalLineScroll="54" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p0j-eB-I2i">
|
<scrollView autohidesScrollers="YES" 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="600" height="309"/>
|
<rect key="frame" x="0.0" y="0.0" width="600" height="309"/>
|
||||||
<clipView key="contentView" id="6IL-DW-37w">
|
<clipView key="contentView" id="6IL-DW-37w">
|
||||||
@@ -690,7 +718,7 @@ Gw
|
|||||||
<constraint firstAttribute="width" constant="14" id="wrl-lJ-3eN"/>
|
<constraint firstAttribute="width" constant="14" id="wrl-lJ-3eN"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="Checkmark" id="R5o-Cd-a91"/>
|
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="Checkmark" id="R5o-Cd-a91"/>
|
||||||
<color key="contentTintColor" name="systemGreenColor" catalog="System" colorSpace="catalog"/>
|
<color key="contentTintColor" name="IconColorGreen"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
@@ -723,6 +751,7 @@ Gw
|
|||||||
<outlet property="imageViewPhpVersionOK" destination="5aN-ZI-D7U" id="ePz-Cb-dWk"/>
|
<outlet property="imageViewPhpVersionOK" destination="5aN-ZI-D7U" id="ePz-Cb-dWk"/>
|
||||||
<outlet property="imageViewType" destination="0NQ-ZD-CqD" id="Cph-FN-LaY"/>
|
<outlet property="imageViewType" destination="0NQ-ZD-CqD" id="Cph-FN-LaY"/>
|
||||||
<outlet property="labelDriver" destination="TbX-e2-3QL" id="qJh-Ak-Dge"/>
|
<outlet property="labelDriver" destination="TbX-e2-3QL" id="qJh-Ak-Dge"/>
|
||||||
|
<outlet property="labelDriverType" destination="jKi-Ls-7FZ" id="ZTq-pP-qUC"/>
|
||||||
<outlet property="labelPathName" destination="CXK-Q9-CpO" id="iVZ-cL-azB"/>
|
<outlet property="labelPathName" destination="CXK-Q9-CpO" id="iVZ-cL-azB"/>
|
||||||
<outlet property="labelSiteName" destination="XJL-Uw-frD" id="f0t-vd-W68"/>
|
<outlet property="labelSiteName" destination="XJL-Uw-frD" id="f0t-vd-W68"/>
|
||||||
</connections>
|
</connections>
|
||||||
@@ -774,7 +803,7 @@ Gw
|
|||||||
</viewController>
|
</viewController>
|
||||||
<customObject id="HgD-aB-bQb" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
<customObject id="HgD-aB-bQb" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="251" y="742"/>
|
<point key="canvasLocation" x="251" y="741.5"/>
|
||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
@@ -783,5 +812,8 @@ Gw
|
|||||||
<image name="Lock" width="30" height="30"/>
|
<image name="Lock" width="30" height="30"/>
|
||||||
<image name="arrow.clockwise" catalog="system" width="14" height="16"/>
|
<image name="arrow.clockwise" catalog="system" width="14" height="16"/>
|
||||||
<image name="plus" catalog="system" width="14" height="13"/>
|
<image name="plus" catalog="system" width="14" height="13"/>
|
||||||
|
<namedColor name="IconColorGreen">
|
||||||
|
<color red="0.24699999392032623" green="0.69700002670288086" blue="0.50099998712539673" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</namedColor>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
64
phpmon/Domain/Core/InterAppHandler.swift
Normal file
64
phpmon/Domain/Core/InterAppHandler.swift
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
//
|
||||||
|
// InterAppHandler.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 28/01/2022.
|
||||||
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class InterApp {
|
||||||
|
|
||||||
|
public static var bindings: [Action] = []
|
||||||
|
|
||||||
|
public static func register(_ action: Action) {
|
||||||
|
self.bindings.append(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Action {
|
||||||
|
let command: String
|
||||||
|
let action: (String) -> Void
|
||||||
|
}
|
||||||
|
|
||||||
|
static func getCommands() -> [InterApp.Action] { return [
|
||||||
|
InterApp.Action(command: "list", action: { _ in
|
||||||
|
SiteListVC.show()
|
||||||
|
}),
|
||||||
|
InterApp.Action(command: "services/stop", action: { _ in
|
||||||
|
MainMenu.shared.stopAllServices()
|
||||||
|
}),
|
||||||
|
InterApp.Action(command: "services/restart/all", action: { _ in
|
||||||
|
MainMenu.shared.restartAllServices()
|
||||||
|
}),
|
||||||
|
InterApp.Action(command: "services/restart/nginx", action: { _ in
|
||||||
|
MainMenu.shared.restartNginx()
|
||||||
|
}),
|
||||||
|
InterApp.Action(command: "services/restart/php", action: { _ in
|
||||||
|
MainMenu.shared.restartPhpFpm()
|
||||||
|
}),
|
||||||
|
InterApp.Action(command: "services/restart/dnsmasq", action: { _ in
|
||||||
|
MainMenu.shared.restartDnsMasq()
|
||||||
|
}),
|
||||||
|
InterApp.Action(command: "locate/config", action: { _ in
|
||||||
|
MainMenu.shared.openActiveConfigFolder()
|
||||||
|
}),
|
||||||
|
InterApp.Action(command: "locate/composer", action: { _ in
|
||||||
|
MainMenu.shared.openGlobalComposerFolder()
|
||||||
|
}),
|
||||||
|
InterApp.Action(command: "locate/valet", action: { _ in
|
||||||
|
MainMenu.shared.openValetConfigFolder()
|
||||||
|
}),
|
||||||
|
InterApp.Action(command: "phpinfo", action: { _ in
|
||||||
|
MainMenu.shared.openPhpInfo()
|
||||||
|
}),
|
||||||
|
InterApp.Action(command: "switch/php/", action: { version in
|
||||||
|
if PhpEnv.shared.availablePhpVersions.contains(version) {
|
||||||
|
MainMenu.shared.switchToPhpVersion(version)
|
||||||
|
} else {
|
||||||
|
Alert.notify(message: "Unsupported version", info: "PHP Monitor can't switch to PHP \(version), as it may not be installed or available.")
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
|
||||||
|
}
|
@@ -49,6 +49,21 @@ class Startup {
|
|||||||
breaking: true
|
breaking: true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Valet.shared.version = VersionExtractor.from(valet("--version"))
|
||||||
|
performEnvironmentCheck(
|
||||||
|
Valet.shared.version == nil,
|
||||||
|
messageText: "startup.errors.valet_version_unknown.title".localized,
|
||||||
|
informativeText: "startup.errors.valet_version_unknown.desc".localized,
|
||||||
|
breaking: true
|
||||||
|
)
|
||||||
|
|
||||||
|
performEnvironmentCheck(
|
||||||
|
HomebrewDiagnostics.cannotLoadService(),
|
||||||
|
messageText: "startup.errors.services_json_error.title".localized,
|
||||||
|
informativeText: "startup.errors.services_json_error.desc".localized,
|
||||||
|
breaking: true
|
||||||
|
)
|
||||||
|
|
||||||
performEnvironmentCheck(
|
performEnvironmentCheck(
|
||||||
!Shell.pipe("cat /private/etc/sudoers.d/brew").contains("\(Paths.binPath)/brew"),
|
!Shell.pipe("cat /private/etc/sudoers.d/brew").contains("\(Paths.binPath)/brew"),
|
||||||
messageText: "startup.errors.sudoers_brew.title".localized,
|
messageText: "startup.errors.sudoers_brew.title".localized,
|
||||||
|
@@ -16,3 +16,14 @@ extension NSMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IBDesignable class LocalizedMenuItem: NSMenuItem {
|
||||||
|
|
||||||
|
@IBInspectable
|
||||||
|
var localizationKey: String? {
|
||||||
|
didSet {
|
||||||
|
self.title = localizationKey?.localized ?? self.title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@@ -8,6 +8,15 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
/**
|
||||||
|
This generic async helper is something I'd like to use in more places.
|
||||||
|
|
||||||
|
The `DispatchQueue.global` into `DispatchQueue.main.async` logic is common in the app.
|
||||||
|
|
||||||
|
I could also use try `async` support which was introduced in Swift but that would
|
||||||
|
require too much refactoring at this time to consider. I also need to read up on async
|
||||||
|
in order to properly grasp all the gotchas. Looking into that later at some point.
|
||||||
|
*/
|
||||||
public func runAsync(_ execute: @escaping () -> Void, completion: @escaping () -> Void = {})
|
public func runAsync(_ execute: @escaping () -> Void, completion: @escaping () -> Void = {})
|
||||||
{
|
{
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
|
@@ -65,6 +65,9 @@ class MenuBarImageGenerator {
|
|||||||
return targetImage
|
return targetImage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
The same as before, but also attempts to add an icon to the left.
|
||||||
|
*/
|
||||||
public static func textToImageWithIcon(text: String) -> NSImage {
|
public static func textToImageWithIcon(text: String) -> NSImage {
|
||||||
let textImage = self.textToImage(text: text)
|
let textImage = self.textToImage(text: text)
|
||||||
let iconImage = NSImage(named: "StatusBarPHP")!
|
let iconImage = NSImage(named: "StatusBarPHP")!
|
||||||
|
@@ -10,28 +10,35 @@ import Foundation
|
|||||||
|
|
||||||
class VersionExtractor {
|
class VersionExtractor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
This attempts to extract the version number from the command line output of Valet.
|
||||||
|
*/
|
||||||
public static func from(_ string: String) -> String? {
|
public static func from(_ string: String) -> String? {
|
||||||
let regex = try! NSRegularExpression(
|
do {
|
||||||
pattern: #"Laravel Valet (?<version>(\d+)(.)(\d+)((.)(\d+))?)"#,
|
let regex = try NSRegularExpression(
|
||||||
options: []
|
pattern: #"Laravel Valet (?<version>(\d+)(.)(\d+)((.)(\d+))?)"#,
|
||||||
)
|
options: []
|
||||||
|
)
|
||||||
|
|
||||||
let match = regex.matches(
|
let match = regex.matches(
|
||||||
in: string,
|
in: string,
|
||||||
options: [],
|
options: [],
|
||||||
range: NSMakeRange(0, string.count)
|
range: NSMakeRange(0, string.count)
|
||||||
).first
|
).first
|
||||||
|
|
||||||
guard let match = match else {
|
guard let match = match else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let range = Range(
|
||||||
|
match.range(withName: "version"),
|
||||||
|
in: string
|
||||||
|
)!
|
||||||
|
|
||||||
|
return String(string[range])
|
||||||
|
} catch {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let range = Range(
|
|
||||||
match.range(withName: "version"),
|
|
||||||
in: string
|
|
||||||
)!
|
|
||||||
|
|
||||||
return String(string[range])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -8,11 +8,37 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
/**
|
||||||
|
This `Decodable` class is used to directly map `composer.json`
|
||||||
|
to this object.
|
||||||
|
*/
|
||||||
struct ComposerJson: Decodable {
|
struct ComposerJson: Decodable {
|
||||||
|
|
||||||
|
// MARK: - JSON structure
|
||||||
|
|
||||||
let dependencies: Dictionary<String, String>?
|
let dependencies: Dictionary<String, String>?
|
||||||
let devDependencies: Dictionary<String, String>?
|
let devDependencies: Dictionary<String, String>?
|
||||||
let configuration: Config?
|
let configuration: Config?
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case dependencies = "require"
|
||||||
|
case devDependencies = "require-dev"
|
||||||
|
case configuration = "config"
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Config: Decodable {
|
||||||
|
let platform: Platform?
|
||||||
|
}
|
||||||
|
struct Platform: Decodable {
|
||||||
|
let php: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checks what the PHP version constraint is.
|
||||||
|
Returns a tuple (constraint, location of constraint).
|
||||||
|
*/
|
||||||
public func getPhpVersion() -> (String, String) {
|
public func getPhpVersion() -> (String, String) {
|
||||||
// Check if in platform
|
// Check if in platform
|
||||||
if configuration?.platform?.php != nil {
|
if configuration?.platform?.php != nil {
|
||||||
@@ -25,12 +51,18 @@ struct ComposerJson: Decodable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unknown!
|
// Unknown!
|
||||||
return ("", "unknown")
|
return ("???", "unknown")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checks if any notable dependencies can be resolved.
|
||||||
|
Only notable dependencies are saved.
|
||||||
|
*/
|
||||||
public func getNotableDependencies() -> [String: String] {
|
public func getNotableDependencies() -> [String: String] {
|
||||||
var notable: [String: String] = [:]
|
var notable: [String: String] = [:]
|
||||||
let scan = ["php", "laravel/framework"]
|
|
||||||
|
var scan = Array(PhpFrameworks.DependencyList.keys)
|
||||||
|
scan.append("php")
|
||||||
|
|
||||||
scan.forEach { dependency in
|
scan.forEach { dependency in
|
||||||
if dependencies?[dependency] != nil {
|
if dependencies?[dependency] != nil {
|
||||||
@@ -41,19 +73,6 @@ struct ComposerJson: Decodable {
|
|||||||
return notable
|
return notable
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
|
||||||
case dependencies = "require"
|
|
||||||
case devDependencies = "require-dev"
|
|
||||||
case configuration = "config"
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Config: Decodable {
|
|
||||||
let platform: Platform?
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Platform: Decodable {
|
|
||||||
let php: String?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
82
phpmon/Domain/Integrations/Composer/PhpFrameworks.swift
Normal file
82
phpmon/Domain/Integrations/Composer/PhpFrameworks.swift
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
//
|
||||||
|
// Frameworks.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 26/01/2022.
|
||||||
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct PhpFrameworks {
|
||||||
|
|
||||||
|
/**
|
||||||
|
This list should probably be reversed when checked, because some of these
|
||||||
|
will also require either `laravel/framework` or `symfony/symfony`.
|
||||||
|
*/
|
||||||
|
public static let DependencyList = [
|
||||||
|
|
||||||
|
// COMMON FRAMEWORKS
|
||||||
|
"laravel/framework" : "Laravel",
|
||||||
|
"symfony/symfony": "Symfony",
|
||||||
|
"laravel/lumen": "Lumen",
|
||||||
|
|
||||||
|
// VARIOUS CMS
|
||||||
|
"roots/bedrock": "Bedrock",
|
||||||
|
"cakephp/app": "CakePHP",
|
||||||
|
"craftcms/craft": "Craft",
|
||||||
|
"drupal/core": "Drupal",
|
||||||
|
"flarum/core": "Flarum",
|
||||||
|
"tightenco/jigsaw": "Jigsaw",
|
||||||
|
"joomla/uri": "Joomla",
|
||||||
|
"themsaid/katana": "Katana",
|
||||||
|
"getkirby/cms": "Kirby",
|
||||||
|
"october/october": "OctoberCMS",
|
||||||
|
"sculpin/sculpin": "Sculpin",
|
||||||
|
"statamic/cms": "Statamic",
|
||||||
|
"johnpbloch/wordpress-core": "WordPress",
|
||||||
|
"zendframework/zendframework": "Zend",
|
||||||
|
"zendframework/zend-mvc": "Zend"
|
||||||
|
|
||||||
|
// TODO (5.1): Handle these in v5.1
|
||||||
|
// "magento/*": "Magento",
|
||||||
|
// "concrete5/*": "Concrete5",
|
||||||
|
// "contao/*": "Contao",
|
||||||
|
// "slim/*": "Slim",
|
||||||
|
]
|
||||||
|
|
||||||
|
public static let FileMapping: [String: [String]] = [
|
||||||
|
"Drupal": [
|
||||||
|
// Legacy installations
|
||||||
|
"/misc/drupal.js",
|
||||||
|
"/core/lib/Drupal.php",
|
||||||
|
// The default (new) installation w/ Composer puts the modules in /web
|
||||||
|
"/web/misc/drupal.js",
|
||||||
|
"/web/core/lib/Drupal.php"
|
||||||
|
],
|
||||||
|
"WordPress": [
|
||||||
|
"/wp-config.php",
|
||||||
|
"/wp-config-sample.php"
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
There are two cases where users are unlikely to use `composer`,
|
||||||
|
when setting up a Drupal or a WordPress project. For performance
|
||||||
|
reasons, we only check that here!
|
||||||
|
*/
|
||||||
|
public static func detectFallbackDependency(_ basePath: String) -> String? {
|
||||||
|
for entry in Self.FileMapping {
|
||||||
|
let found = entry.value
|
||||||
|
.map { path in return Filesystem.fileExists(basePath + path) }
|
||||||
|
.contains(true)
|
||||||
|
|
||||||
|
if found {
|
||||||
|
return entry.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -10,19 +10,6 @@ import Foundation
|
|||||||
|
|
||||||
class HomebrewDiagnostics {
|
class HomebrewDiagnostics {
|
||||||
|
|
||||||
enum Errors: String {
|
|
||||||
case aliasConflict = "alias_conflict"
|
|
||||||
}
|
|
||||||
|
|
||||||
static let shared = HomebrewDiagnostics()
|
|
||||||
var errors: [HomebrewDiagnostics.Errors] = []
|
|
||||||
|
|
||||||
init() {
|
|
||||||
if determineAliasConflicts() {
|
|
||||||
errors.append(.aliasConflict)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
It is possible to have the `shivammathur/php` tap installed, and for the core homebrew information to be outdated.
|
It is possible to have the `shivammathur/php` tap installed, and for the core homebrew information to be outdated.
|
||||||
This will then result in two different aliases claiming to point to the same formula (`php`).
|
This will then result in two different aliases claiming to point to the same formula (`php`).
|
||||||
@@ -30,7 +17,7 @@ class HomebrewDiagnostics {
|
|||||||
|
|
||||||
This check only needs to be performed if the `shivammathur/php` tap is active.
|
This check only needs to be performed if the `shivammathur/php` tap is active.
|
||||||
*/
|
*/
|
||||||
public func determineAliasConflicts() -> Bool
|
public static func hasAliasConflict() -> Bool
|
||||||
{
|
{
|
||||||
let tapAlias = Shell.pipe("\(Paths.brew) info shivammathur/php/php --json")
|
let tapAlias = Shell.pipe("\(Paths.brew) info shivammathur/php/php --json")
|
||||||
|
|
||||||
@@ -67,4 +54,21 @@ class HomebrewDiagnostics {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
In order to see if we support the --json syntax, we'll query nginx.
|
||||||
|
If the JSON response cannot be parsed, Homebrew is probably out of date.
|
||||||
|
*/
|
||||||
|
public static func cannotLoadService(_ name: String = "nginx") -> Bool
|
||||||
|
{
|
||||||
|
let serviceInfo = try? JSONDecoder().decode(
|
||||||
|
[HomebrewService].self,
|
||||||
|
from: Shell.pipe(
|
||||||
|
"sudo \(Paths.brew) services info \(name) --json",
|
||||||
|
requiresPath: true
|
||||||
|
).data(using: .utf8)!
|
||||||
|
)
|
||||||
|
|
||||||
|
return serviceInfo == nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,31 +13,48 @@ class Valet {
|
|||||||
static let shared = Valet()
|
static let shared = Valet()
|
||||||
|
|
||||||
/// The version of Valet that was detected.
|
/// The version of Valet that was detected.
|
||||||
var version: String
|
var version: String! = nil
|
||||||
|
|
||||||
/// The Valet configuration file.
|
/// The Valet configuration file.
|
||||||
var config: Valet.Configuration
|
var config: Valet.Configuration!
|
||||||
|
|
||||||
/// A cached list of sites that were detected after analyzing the paths set up for Valet.
|
/// A cached list of sites that were detected after analyzing the paths set up for Valet.
|
||||||
var sites: [Site] = []
|
var sites: [Site] = []
|
||||||
|
|
||||||
init() {
|
/// Whether we're busy with some blocking operation.
|
||||||
version = VersionExtractor.from(valet("--version"))
|
var isBusy: Bool = false
|
||||||
?? "UNKNOWN"
|
|
||||||
|
|
||||||
|
/// When initialising the Valet singleton, extract the Valet version and assume no sites loaded.
|
||||||
|
init() {
|
||||||
|
self.version = nil
|
||||||
|
self.sites = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
We don't want to load the initial config.json file as soon as the class is initialised.
|
||||||
|
Instead, we'll defer the loading of the configuration file once the initial app checks
|
||||||
|
have passed: if the user does not have Valet installed, we'll crash the app because we
|
||||||
|
force unwrap the file. Currently, this does also mean that if the JSON is invalid or
|
||||||
|
incompatible with the `Decodable` `Valet.Configuration` class, that the app will crash.
|
||||||
|
*/
|
||||||
|
public func loadConfiguration() {
|
||||||
let file = FileManager.default.homeDirectoryForCurrentUser
|
let file = FileManager.default.homeDirectoryForCurrentUser
|
||||||
.appendingPathComponent(".config/valet/config.json")
|
.appendingPathComponent(".config/valet/config.json")
|
||||||
|
|
||||||
|
// TODO: (5.1) Fix loading of invalid JSON: do not crash the app
|
||||||
config = try! JSONDecoder().decode(
|
config = try! JSONDecoder().decode(
|
||||||
Valet.Configuration.self,
|
Valet.Configuration.self,
|
||||||
from: try! String(contentsOf: file, encoding: .utf8).data(using: .utf8)!
|
from: try! String(contentsOf: file, encoding: .utf8).data(using: .utf8)!
|
||||||
)
|
)
|
||||||
|
|
||||||
self.sites = []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Starts the preload of sites, but only if the maximum amount of sites is 30.
|
||||||
|
For users with more sites, the site list is loaded when they bring up the site list window.
|
||||||
|
(This is done to keep the startup speed as fast as possible.)
|
||||||
|
*/
|
||||||
public func startPreloadingSites() {
|
public func startPreloadingSites() {
|
||||||
let maximumPreload = 10
|
let maximumPreload = 30
|
||||||
let foundSites = self.countPaths()
|
let foundSites = self.countPaths()
|
||||||
if foundSites <= maximumPreload {
|
if foundSites <= maximumPreload {
|
||||||
// Preload the sites and their drivers
|
// Preload the sites and their drivers
|
||||||
@@ -48,23 +65,32 @@ class Valet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Reloads the list of sites, assuming that the list isn't being reloaded at the time.
|
||||||
|
We don't want to do duplicate or parallel work!
|
||||||
|
*/
|
||||||
public func reloadSites() {
|
public func reloadSites() {
|
||||||
|
if (isBusy) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
resolvePaths(tld: config.tld)
|
resolvePaths(tld: config.tld)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checks if the version of Valet is more recent than the minimum version required for PHP Monitor to function.
|
||||||
|
Should this procedure fail, the user will get an alert notifying them that the version of Valet they have
|
||||||
|
installed is not recent enough.
|
||||||
|
*/
|
||||||
public func validateVersion() -> Void {
|
public func validateVersion() -> Void {
|
||||||
if version == "UNKNOWN" {
|
|
||||||
return Log.warn("The Valet version could not be extracted... that does not bode well.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if version.versionCompare(Constants.MinimumRecommendedValetVersion) == .orderedAscending {
|
if version.versionCompare(Constants.MinimumRecommendedValetVersion) == .orderedAscending {
|
||||||
let version = version
|
let version = version
|
||||||
Log.warn("Valet version \(version) is too old! (recommended: \(Constants.MinimumRecommendedValetVersion))")
|
Log.warn("Valet version \(version!) is too old! (recommended: \(Constants.MinimumRecommendedValetVersion))")
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
Alert.notify(message: "alert.min_valet_version.title".localized, info: "alert.min_valet_version.info".localized(version, Constants.MinimumRecommendedValetVersion))
|
Alert.notify(message: "alert.min_valet_version.title".localized, info: "alert.min_valet_version.info".localized(version!, Constants.MinimumRecommendedValetVersion))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.info("Valet version \(version) is recent enough, OK (recommended: \(Constants.MinimumRecommendedValetVersion))")
|
Log.info("Valet version \(version!) is recent enough, OK (recommended: \(Constants.MinimumRecommendedValetVersion))")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +114,8 @@ class Valet {
|
|||||||
Resolves all paths and creates linked or parked site instances that can be referenced later.
|
Resolves all paths and creates linked or parked site instances that can be referenced later.
|
||||||
*/
|
*/
|
||||||
private func resolvePaths(tld: String) {
|
private func resolvePaths(tld: String) {
|
||||||
|
isBusy = true
|
||||||
|
|
||||||
sites = []
|
sites = []
|
||||||
|
|
||||||
for path in config.paths {
|
for path in config.paths {
|
||||||
@@ -98,6 +126,8 @@ class Valet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sites = sites.sorted { $0.absolutePath < $1.absolutePath }
|
sites = sites.sorted { $0.absolutePath < $1.absolutePath }
|
||||||
|
|
||||||
|
isBusy = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,6 +183,13 @@ class Valet {
|
|||||||
/// The absolute path to the directory that is served.
|
/// The absolute path to the directory that is served.
|
||||||
var absolutePath: String!
|
var absolutePath: String!
|
||||||
|
|
||||||
|
/// The absolute path to the directory that is served,
|
||||||
|
/// replacing the user's home folder with ~.
|
||||||
|
lazy var absolutePathRelative: String = {
|
||||||
|
return self.absolutePath
|
||||||
|
.replacingOccurrences(of: "/Users/\(Paths.whoami)", with: "~")
|
||||||
|
}()
|
||||||
|
|
||||||
/// Location of the alias. If set, this is a linked domain.
|
/// Location of the alias. If set, this is a linked domain.
|
||||||
var aliasPath: String?
|
var aliasPath: String?
|
||||||
|
|
||||||
@@ -162,12 +199,18 @@ class Valet {
|
|||||||
/// What driver is currently in use. If not detected, defaults to nil.
|
/// What driver is currently in use. If not detected, defaults to nil.
|
||||||
var driver: String? = nil
|
var driver: String? = nil
|
||||||
|
|
||||||
|
/// Whether the driver was determined by checking the Composer file.
|
||||||
|
var driverDeterminedByComposer: Bool = false
|
||||||
|
|
||||||
/// A list of notable Composer dependencies.
|
/// A list of notable Composer dependencies.
|
||||||
var notableComposerDependencies: [String: String] = [:]
|
var notableComposerDependencies: [String: String] = [:]
|
||||||
|
|
||||||
/// The PHP version as discovered in composer.json.
|
/// The PHP version as discovered in `composer.json`.
|
||||||
var composerPhp: String = "???"
|
var composerPhp: String = "???"
|
||||||
|
|
||||||
|
/// Check whether the PHP version is valid for the currently linked version.
|
||||||
|
var composerPhpCompatibleWithLinked: Bool = false
|
||||||
|
|
||||||
/// How the PHP version was determined.
|
/// How the PHP version was determined.
|
||||||
var composerPhpSource: String = "unknown"
|
var composerPhpSource: String = "unknown"
|
||||||
|
|
||||||
@@ -179,8 +222,8 @@ class Valet {
|
|||||||
self.name = URL(fileURLWithPath: absolutePath).lastPathComponent
|
self.name = URL(fileURLWithPath: absolutePath).lastPathComponent
|
||||||
self.aliasPath = nil
|
self.aliasPath = nil
|
||||||
determineSecured(tld)
|
determineSecured(tld)
|
||||||
determineDriver()
|
|
||||||
determineComposerPhpVersion()
|
determineComposerPhpVersion()
|
||||||
|
determineDriver()
|
||||||
}
|
}
|
||||||
|
|
||||||
convenience init(aliasPath: String, tld: String) {
|
convenience init(aliasPath: String, tld: String) {
|
||||||
@@ -189,28 +232,31 @@ class Valet {
|
|||||||
self.name = URL(fileURLWithPath: aliasPath).lastPathComponent
|
self.name = URL(fileURLWithPath: aliasPath).lastPathComponent
|
||||||
self.aliasPath = aliasPath
|
self.aliasPath = aliasPath
|
||||||
determineSecured(tld)
|
determineSecured(tld)
|
||||||
determineDriver()
|
|
||||||
determineComposerPhpVersion()
|
determineComposerPhpVersion()
|
||||||
|
determineDriver()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checks if a certificate file can be found in the `valet/Certificates` directory.
|
||||||
|
- Note: The file is not validated, only its presence is checked.
|
||||||
|
*/
|
||||||
public func determineSecured(_ tld: String) {
|
public func determineSecured(_ tld: String) {
|
||||||
secured = Shell.fileExists("~/.config/valet/Certificates/\(self.name!).\(tld).key")
|
secured = Shell.fileExists("~/.config/valet/Certificates/\(self.name!).\(tld).key")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func determineDriver() {
|
/**
|
||||||
let driver = Shell.pipe("cd '\(absolutePath!)' && valet which", requiresPath: true)
|
Checks if `composer.json` exists in the folder, and extracts notable information:
|
||||||
if driver.contains("This site is served by") {
|
|
||||||
self.driver = driver
|
|
||||||
// TODO: Use a regular expression to retrieve the driver instead?
|
|
||||||
.replacingOccurrences(of: "This site is served by [", with: "")
|
|
||||||
.replacingOccurrences(of: "ValetDriver].\n", with: "")
|
|
||||||
} else {
|
|
||||||
self.driver = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
- The PHP version required (the constraint, so it could be `^8.0`, for example)
|
||||||
|
- Where the PHP version was found (`require` or `platform`)
|
||||||
|
- Notable PHP dependencies (determined via `PhpFrameworks.DependencyList`)
|
||||||
|
|
||||||
|
The method then also checks if the determined constraint (if found) is compatible
|
||||||
|
with the currently linked version of PHP (see `composerPhpMatchesSystem`).
|
||||||
|
*/
|
||||||
public func determineComposerPhpVersion() {
|
public func determineComposerPhpVersion() {
|
||||||
let path = "\(absolutePath!)/composer.json"
|
let path = "\(absolutePath!)/composer.json"
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if Filesystem.fileExists(path) {
|
if Filesystem.fileExists(path) {
|
||||||
let decoded = try JSONDecoder().decode(
|
let decoded = try JSONDecoder().decode(
|
||||||
@@ -224,6 +270,60 @@ class Valet {
|
|||||||
} catch {
|
} catch {
|
||||||
Log.err("Something went wrong reading the composer JSON file.")
|
Log.err("Something went wrong reading the composer JSON file.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.composerPhp == "???" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the composer list (on "|") to evaluate multiple constraints
|
||||||
|
// For example, for Laravel 8 projects the value is "^7.3|^8.0"
|
||||||
|
self.composerPhpCompatibleWithLinked =
|
||||||
|
self.composerPhp.split(separator: "|").map { string in
|
||||||
|
return PhpVersionNumberCollection.make(from: [PhpEnv.phpInstall.version.long])
|
||||||
|
.matching(constraint: string.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||||
|
.count > 0
|
||||||
|
}.contains(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Determine the driver to be displayed in the list of sites. In v5.0, this has been changed
|
||||||
|
to load the "framework" or "project type" instead.
|
||||||
|
*/
|
||||||
|
public func determineDriver() {
|
||||||
|
self.determineDriverViaComposer()
|
||||||
|
|
||||||
|
if self.driver == nil {
|
||||||
|
self.driver = PhpFrameworks.detectFallbackDependency(self.absolutePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Check the dependency list and see if a particular dependency can't be found.
|
||||||
|
We'll revert the dependency list so that Laravel and Symfony are detected last.
|
||||||
|
|
||||||
|
(Some other frameworks might use Laravel, so if we found it first the detection would be incorrect:
|
||||||
|
this would happen with Statamic, for example.)
|
||||||
|
*/
|
||||||
|
private func determineDriverViaComposer() {
|
||||||
|
self.driverDeterminedByComposer = true
|
||||||
|
|
||||||
|
PhpFrameworks.DependencyList.reversed().forEach { (key: String, value: String) in
|
||||||
|
if self.notableComposerDependencies.keys.contains(key) {
|
||||||
|
self.driver = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, deprecated, renamed: "determineDriver")
|
||||||
|
private func determineDriverViaValet() {
|
||||||
|
let driver = Shell.pipe("cd '\(absolutePath!)' && valet which", requiresPath: true)
|
||||||
|
if driver.contains("This site is served by") {
|
||||||
|
self.driver = driver
|
||||||
|
.replacingOccurrences(of: "This site is served by [", with: "")
|
||||||
|
.replacingOccurrences(of: "ValetDriver].\n", with: "")
|
||||||
|
} else {
|
||||||
|
self.driver = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,8 +335,8 @@ class Valet {
|
|||||||
/// The paths that need to be checked.
|
/// The paths that need to be checked.
|
||||||
let paths: [String]
|
let paths: [String]
|
||||||
|
|
||||||
/// The loopback address.
|
/// The loopback address. Optional.
|
||||||
let loopback: String
|
let loopback: String?
|
||||||
|
|
||||||
/// The default site that is served if the domain is not found. Optional.
|
/// The default site that is served if the domain is not found. Optional.
|
||||||
let defaultSite: String?
|
let defaultSite: String?
|
||||||
|
@@ -30,7 +30,7 @@ extension MainMenu {
|
|||||||
private func onEnvironmentPass() {
|
private func onEnvironmentPass() {
|
||||||
PhpEnv.detectPhpVersions()
|
PhpEnv.detectPhpVersions()
|
||||||
|
|
||||||
if HomebrewDiagnostics.shared.errors.contains(.aliasConflict) {
|
if HomebrewDiagnostics.hasAliasConflict() {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
Alert.notify(
|
Alert.notify(
|
||||||
message: "alert.php_alias_conflict.title".localized,
|
message: "alert.php_alias_conflict.title".localized,
|
||||||
@@ -69,8 +69,11 @@ extension MainMenu {
|
|||||||
App.shared.loadGlobalHotkey()
|
App.shared.loadGlobalHotkey()
|
||||||
|
|
||||||
// Attempt to find out more info about Valet
|
// Attempt to find out more info about Valet
|
||||||
Log.info("PHP Monitor has extracted the version number of Valet: \(Valet.shared.version)")
|
if Valet.shared.version != nil {
|
||||||
|
Log.info("PHP Monitor has extracted the version number of Valet: \(Valet.shared.version!)")
|
||||||
|
}
|
||||||
|
|
||||||
|
Valet.shared.loadConfiguration()
|
||||||
Valet.shared.validateVersion()
|
Valet.shared.validateVersion()
|
||||||
Valet.shared.startPreloadingSites()
|
Valet.shared.startPreloadingSites()
|
||||||
|
|
||||||
@@ -88,6 +91,9 @@ extension MainMenu {
|
|||||||
repeats: true
|
repeats: true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Stats.incrementSuccessfulLaunchCount()
|
||||||
|
Stats.evaluateSponsorMessageShouldBeDisplayed()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -104,6 +104,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
|||||||
PhpEnv.shared.isBusy = false
|
PhpEnv.shared.isBusy = false
|
||||||
|
|
||||||
DispatchQueue.main.async { [self] in
|
DispatchQueue.main.async { [self] in
|
||||||
|
PhpEnv.shared.currentInstall = ActivePhpInstallation()
|
||||||
updatePhpVersionInStatusBar()
|
updatePhpVersionInStatusBar()
|
||||||
NotificationCenter.default.post(name: Events.ServicesUpdated, object: nil)
|
NotificationCenter.default.post(name: Events.ServicesUpdated, object: nil)
|
||||||
completion()
|
completion()
|
||||||
@@ -233,21 +234,26 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func forceRestartLatestPhp() {
|
@objc func fixMyValet() {
|
||||||
// Tell the user the switch is about to occur
|
// Tell the user the switch is about to occur
|
||||||
Alert.notify(
|
if Alert.present(
|
||||||
message: "alert.force_reload.title".localized,
|
messageText: "alert.fix_my_valet.title".localized,
|
||||||
info: "alert.force_reload.info".localized
|
informativeText: "alert.fix_my_valet.info".localized(PhpEnv.brewPhpVersion),
|
||||||
)
|
buttonTitle: "alert.fix_my_valet.ok".localized,
|
||||||
|
secondButtonTitle: "alert.fix_my_valet.cancel".localized,
|
||||||
// Start switching
|
style: .warning
|
||||||
waitAndExecute {
|
) {
|
||||||
Actions.fixMyPhp()
|
// Start the fix
|
||||||
} completion: {
|
waitAndExecute {
|
||||||
Alert.notify(
|
Actions.fixMyValet()
|
||||||
message: "alert.force_reload_done.title".localized,
|
} completion: {
|
||||||
info: "alert.force_reload_done.info".localized
|
Alert.notify(
|
||||||
)
|
message: "alert.fix_my_valet_done.title".localized,
|
||||||
|
info: "alert.fix_my_valet_done.info".localized
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.info("The user has chosen to abort Fix My Valet")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,6 +284,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
|||||||
self.switchToPhpVersion(sender.version)
|
self.switchToPhpVersion(sender.version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO (5.1): Investigate if `waitAndExecute` cannot be used here
|
||||||
@objc func switchToPhpVersion(_ version: String) {
|
@objc func switchToPhpVersion(_ version: String) {
|
||||||
setBusyImage()
|
setBusyImage()
|
||||||
PhpEnv.shared.isBusy = true
|
PhpEnv.shared.isBusy = true
|
||||||
@@ -289,7 +296,16 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
|||||||
// Update the menu
|
// Update the menu
|
||||||
rebuild()
|
rebuild()
|
||||||
|
|
||||||
|
let sendLocalNotification = {
|
||||||
|
LocalNotification.send(
|
||||||
|
title: String(format: "notification.version_changed_title".localized, version),
|
||||||
|
subtitle: String(format: "notification.version_changed_desc".localized, version)
|
||||||
|
)
|
||||||
|
PhpEnv.phpInstall.notifyAboutBrokenPhpFpm()
|
||||||
|
}
|
||||||
|
|
||||||
let completion = {
|
let completion = {
|
||||||
|
// Fire off the delegate method
|
||||||
PhpEnv.shared.delegate?.switcherDidCompleteSwitch()
|
PhpEnv.shared.delegate?.switcherDidCompleteSwitch()
|
||||||
|
|
||||||
// Mark as no longer busy
|
// Mark as no longer busy
|
||||||
@@ -300,12 +316,16 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
|||||||
updatePhpVersionInStatusBar()
|
updatePhpVersionInStatusBar()
|
||||||
rebuild()
|
rebuild()
|
||||||
|
|
||||||
let sendLocalNotification = {
|
if !PhpEnv.shared.validate(version) {
|
||||||
LocalNotification.send(
|
let outcome = Alert.present(
|
||||||
title: String(format: "notification.version_changed_title".localized, version),
|
messageText: "alert.php_switch_failed.title".localized(version),
|
||||||
subtitle: String(format: "notification.version_changed_desc".localized, version)
|
informativeText: "alert.php_switch_failed.info".localized(version),
|
||||||
)
|
buttonTitle: "alert.php_switch_failed.confirm".localized,
|
||||||
PhpEnv.phpInstall.notifyAboutBrokenPhpFpm()
|
secondButtonTitle: "alert.php_switch_failed.cancel".localized, style: .informational)
|
||||||
|
if outcome {
|
||||||
|
MainMenu.shared.fixMyValet()
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run composer updates
|
// Run composer updates
|
||||||
@@ -314,6 +334,10 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
|||||||
} else {
|
} else {
|
||||||
sendLocalNotification()
|
sendLocalNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update stats
|
||||||
|
Stats.incrementSuccessfulSwitchCount()
|
||||||
|
Stats.evaluateSponsorMessageShouldBeDisplayed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,6 +361,10 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
|||||||
SiteListVC.show()
|
SiteListVC.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func openDonate() {
|
||||||
|
NSWorkspace.shared.open(Constants.DonationUrl)
|
||||||
|
}
|
||||||
|
|
||||||
@objc func terminateApp() {
|
@objc func terminateApp() {
|
||||||
NSApplication.shared.terminate(nil)
|
NSApplication.shared.terminate(nil)
|
||||||
}
|
}
|
||||||
@@ -356,9 +384,17 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
|||||||
// MARK: - Private Methods
|
// MARK: - Private Methods
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Updates the global dependencies and runs the completion callback when done.
|
||||||
*/
|
*/
|
||||||
private func updateGlobalDependencies(notify: Bool, completion: @escaping (Bool) -> Void) {
|
private func updateGlobalDependencies(notify: Bool, completion: @escaping (Bool) -> Void) {
|
||||||
|
if !Shell.fileExists("/usr/local/bin/composer") {
|
||||||
|
Alert.notify(
|
||||||
|
message: "alert.composer_missing.title".localized,
|
||||||
|
info: "alert.composer_missing.info".localized
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
PhpEnv.shared.isBusy = true
|
PhpEnv.shared.isBusy = true
|
||||||
setBusyImage()
|
setBusyImage()
|
||||||
self.rebuild()
|
self.rebuild()
|
||||||
@@ -378,14 +414,12 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
|||||||
window?.setType(info: true)
|
window?.setType(info: true)
|
||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
let output = Shell.user.executeSynchronously(
|
let task = Shell.user.createTask(
|
||||||
"composer global update", requiresPath: true
|
for: "/usr/local/bin/composer global update", requiresPath: true
|
||||||
)
|
)
|
||||||
|
|
||||||
let task = Shell.user.createTask(for: "composer global update", requiresPath: true)
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
window?.addToConsole("composer global update\n")
|
window?.addToConsole("/usr/local/bin/composer global update\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
Shell.captureOutput(
|
Shell.captureOutput(
|
||||||
@@ -409,7 +443,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
|
|||||||
Shell.haltCapturingOutput(task)
|
Shell.haltCapturingOutput(task)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
if output.task.terminationStatus <= 0 {
|
if task.terminationStatus <= 0 {
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
||||||
window?.close()
|
window?.close()
|
||||||
if (notify) {
|
if (notify) {
|
||||||
|
@@ -9,6 +9,16 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
|
/**
|
||||||
|
The ServicesView is an example of a view that I consider to be "poorly" set up.
|
||||||
|
Why ship it like this, then? Well, it works — that's reason number one, really.
|
||||||
|
|
||||||
|
However, I do believe this should be refactored at some point. Here's why:
|
||||||
|
this view is responsible for retaining the information about the services status.
|
||||||
|
|
||||||
|
The status of the services should live somewhere else, and the fetching of said
|
||||||
|
service information should also not happen in a view. Yet here we are.
|
||||||
|
*/
|
||||||
class ServicesView: NSView, XibLoadable {
|
class ServicesView: NSView, XibLoadable {
|
||||||
|
|
||||||
@IBOutlet weak var imageViewPhp: NSImageView!
|
@IBOutlet weak var imageViewPhp: NSImageView!
|
||||||
@@ -21,6 +31,9 @@ class ServicesView: NSView, XibLoadable {
|
|||||||
|
|
||||||
static func asMenuItem() -> NSMenuItem {
|
static func asMenuItem() -> NSMenuItem {
|
||||||
let view = Self.createFromXib()!
|
let view = Self.createFromXib()!
|
||||||
|
[view.imageViewPhp, view.imageViewNginx, view.imageViewDnsmasq].forEach { imageView in
|
||||||
|
imageView?.contentTintColor = NSColor(named: "IconColorNormal")
|
||||||
|
}
|
||||||
let item = NSMenuItem()
|
let item = NSMenuItem()
|
||||||
item.view = view
|
item.view = view
|
||||||
item.target = self
|
item.target = self
|
||||||
@@ -41,6 +54,7 @@ class ServicesView: NSView, XibLoadable {
|
|||||||
self.loadData()
|
self.loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: (5.1) Move data fetching, caching & retrieval somewhere else
|
||||||
func loadData() {
|
func loadData() {
|
||||||
// Use stale data
|
// Use stale data
|
||||||
self.applyAllInfoFieldsFromCachedValue()
|
self.applyAllInfoFieldsFromCachedValue()
|
||||||
@@ -78,13 +92,20 @@ class ServicesView: NSView, XibLoadable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func applyServiceStyling(_ serviceName: String, _ imageView: NSImageView) {
|
func applyServiceStyling(_ serviceName: String, _ imageView: NSImageView) {
|
||||||
if ServicesView.services[serviceName] != nil && ServicesView.services[serviceName]!.running {
|
if ServicesView.services[serviceName] == nil {
|
||||||
imageView.image = NSImage(named: "ServiceOn")
|
imageView.image = NSImage(named: "ServiceLoading")
|
||||||
imageView.contentTintColor = NSColor.black
|
imageView.contentTintColor = NSColor(named: "IconColorNormal")
|
||||||
} else {
|
return
|
||||||
imageView.image = NSImage(named: "ServiceOff")
|
|
||||||
imageView.contentTintColor = NSColor.init(red: 246/255, green: 71/255, blue: 71/255, alpha: 1.0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ServicesView.services[serviceName]!.running {
|
||||||
|
imageView.image = NSImage(named: "ServiceOn")
|
||||||
|
imageView.contentTintColor = NSColor(named: "IconColorNormal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
imageView.image = NSImage(named: "ServiceOff")
|
||||||
|
imageView.contentTintColor = NSColor(named: "IconColorRed")
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
@@ -45,13 +45,13 @@ class StatusMenu : NSMenu {
|
|||||||
|
|
||||||
if !PhpEnv.shared.availablePhpVersions.contains(PhpEnv.brewPhpVersion) {
|
if !PhpEnv.shared.availablePhpVersions.contains(PhpEnv.brewPhpVersion) {
|
||||||
servicesMenu.addItem(NSMenuItem(
|
servicesMenu.addItem(NSMenuItem(
|
||||||
title: "mi_force_load_latest_unavailable".localized(PhpEnv.brewPhpVersion),
|
title: "mi_fix_my_valet_unavailable".localized(PhpEnv.brewPhpVersion),
|
||||||
action: nil, keyEquivalent: "f"
|
action: nil, keyEquivalent: "f"
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
servicesMenu.addItem(NSMenuItem(
|
servicesMenu.addItem(NSMenuItem(
|
||||||
title: "mi_force_load_latest".localized(PhpEnv.brewPhpVersion),
|
title: "mi_fix_my_valet".localized(PhpEnv.brewPhpVersion),
|
||||||
action: #selector(MainMenu.forceRestartLatestPhp), keyEquivalent: "f"))
|
action: #selector(MainMenu.fixMyValet), keyEquivalent: "f"))
|
||||||
}
|
}
|
||||||
|
|
||||||
servicesMenu.addItem(NSMenuItem(title: "mi_services".localized, action: nil, keyEquivalent: ""))
|
servicesMenu.addItem(NSMenuItem(title: "mi_services".localized, action: nil, keyEquivalent: ""))
|
||||||
@@ -95,7 +95,11 @@ class StatusMenu : NSMenu {
|
|||||||
self.addItem(NSMenuItem.separator())
|
self.addItem(NSMenuItem.separator())
|
||||||
self.addItem(HeaderView.asMenuItem(text: "mi_composer".localized))
|
self.addItem(HeaderView.asMenuItem(text: "mi_composer".localized))
|
||||||
self.addItem(NSMenuItem(title: "mi_global_composer".localized, action: #selector(MainMenu.openGlobalComposerFolder), keyEquivalent: "g"))
|
self.addItem(NSMenuItem(title: "mi_global_composer".localized, action: #selector(MainMenu.openGlobalComposerFolder), keyEquivalent: "g"))
|
||||||
self.addItem(NSMenuItem(title: "mi_update_global_composer".localized, action: PhpEnv.shared.isBusy ? nil : #selector(MainMenu.updateGlobalComposerDependencies), keyEquivalent: ""))
|
|
||||||
|
let composerMenuItem = NSMenuItem(title: "mi_update_global_composer".localized, action: PhpEnv.shared.isBusy ? nil : #selector(MainMenu.updateGlobalComposerDependencies), keyEquivalent: "g")
|
||||||
|
composerMenuItem.keyEquivalentModifierMask = .shift
|
||||||
|
|
||||||
|
self.addItem(composerMenuItem)
|
||||||
|
|
||||||
if (PhpEnv.shared.isBusy) {
|
if (PhpEnv.shared.isBusy) {
|
||||||
return
|
return
|
||||||
|
@@ -8,6 +8,9 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
/**
|
||||||
|
These are the keys used for every preference in the app.
|
||||||
|
*/
|
||||||
enum PreferenceName: String {
|
enum PreferenceName: String {
|
||||||
case wasLaunchedBefore = "launched_before"
|
case wasLaunchedBefore = "launched_before"
|
||||||
case shouldDisplayDynamicIcon = "use_dynamic_icon"
|
case shouldDisplayDynamicIcon = "use_dynamic_icon"
|
||||||
@@ -15,9 +18,19 @@ enum PreferenceName: String {
|
|||||||
case fullPhpVersionDynamicIcon = "full_php_in_menu_bar"
|
case fullPhpVersionDynamicIcon = "full_php_in_menu_bar"
|
||||||
case autoServiceRestartAfterExtensionToggle = "auto_restart_after_extension_toggle"
|
case autoServiceRestartAfterExtensionToggle = "auto_restart_after_extension_toggle"
|
||||||
case autoComposerGlobalUpdateAfterSwitch = "auto_composer_global_update_after_switch"
|
case autoComposerGlobalUpdateAfterSwitch = "auto_composer_global_update_after_switch"
|
||||||
|
case allowProtocolForIntegrations = "allow_protocol_for_integrations"
|
||||||
case globalHotkey = "global_hotkey"
|
case globalHotkey = "global_hotkey"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
These are internal stats. They NEVER get shared.
|
||||||
|
*/
|
||||||
|
enum InternalStats: String {
|
||||||
|
case launchCount = "times_launched"
|
||||||
|
case switchCount = "times_switched_versions"
|
||||||
|
case didSeeSponsorEncouragement = "did_see_sponsor_encouragement"
|
||||||
|
}
|
||||||
|
|
||||||
class Preferences {
|
class Preferences {
|
||||||
|
|
||||||
// MARK: - Singleton
|
// MARK: - Singleton
|
||||||
@@ -49,16 +62,23 @@ class Preferences {
|
|||||||
*/
|
*/
|
||||||
static func handleFirstTimeLaunch() {
|
static func handleFirstTimeLaunch() {
|
||||||
UserDefaults.standard.register(defaults: [
|
UserDefaults.standard.register(defaults: [
|
||||||
|
/// Preferences
|
||||||
PreferenceName.shouldDisplayDynamicIcon.rawValue: true,
|
PreferenceName.shouldDisplayDynamicIcon.rawValue: true,
|
||||||
PreferenceName.shouldDisplayPhpHintInIcon.rawValue: true,
|
PreferenceName.shouldDisplayPhpHintInIcon.rawValue: true,
|
||||||
PreferenceName.fullPhpVersionDynamicIcon.rawValue: false,
|
PreferenceName.fullPhpVersionDynamicIcon.rawValue: false,
|
||||||
PreferenceName.autoServiceRestartAfterExtensionToggle.rawValue: true,
|
PreferenceName.autoServiceRestartAfterExtensionToggle.rawValue: true,
|
||||||
PreferenceName.autoComposerGlobalUpdateAfterSwitch.rawValue: false
|
PreferenceName.autoComposerGlobalUpdateAfterSwitch.rawValue: false,
|
||||||
|
PreferenceName.allowProtocolForIntegrations.rawValue: true,
|
||||||
|
/// Stats
|
||||||
|
InternalStats.switchCount.rawValue: 0,
|
||||||
|
InternalStats.launchCount.rawValue: 0,
|
||||||
|
InternalStats.didSeeSponsorEncouragement.rawValue: false
|
||||||
])
|
])
|
||||||
|
|
||||||
if UserDefaults.standard.bool(forKey: PreferenceName.wasLaunchedBefore.rawValue) {
|
if UserDefaults.standard.bool(forKey: PreferenceName.wasLaunchedBefore.rawValue) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.info("Saving first-time preferences!")
|
Log.info("Saving first-time preferences!")
|
||||||
UserDefaults.standard.setValue(true, forKey: PreferenceName.wasLaunchedBefore.rawValue)
|
UserDefaults.standard.setValue(true, forKey: PreferenceName.wasLaunchedBefore.rawValue)
|
||||||
UserDefaults.standard.synchronize()
|
UserDefaults.standard.synchronize()
|
||||||
@@ -96,6 +116,7 @@ class Preferences {
|
|||||||
.fullPhpVersionDynamicIcon: UserDefaults.standard.bool(forKey: PreferenceName.fullPhpVersionDynamicIcon.rawValue) as Any,
|
.fullPhpVersionDynamicIcon: UserDefaults.standard.bool(forKey: PreferenceName.fullPhpVersionDynamicIcon.rawValue) as Any,
|
||||||
.autoServiceRestartAfterExtensionToggle: UserDefaults.standard.bool(forKey: PreferenceName.autoServiceRestartAfterExtensionToggle.rawValue) as Any,
|
.autoServiceRestartAfterExtensionToggle: UserDefaults.standard.bool(forKey: PreferenceName.autoServiceRestartAfterExtensionToggle.rawValue) as Any,
|
||||||
.autoComposerGlobalUpdateAfterSwitch: UserDefaults.standard.bool(forKey: PreferenceName.autoComposerGlobalUpdateAfterSwitch.rawValue) as Any,
|
.autoComposerGlobalUpdateAfterSwitch: UserDefaults.standard.bool(forKey: PreferenceName.autoComposerGlobalUpdateAfterSwitch.rawValue) as Any,
|
||||||
|
.allowProtocolForIntegrations: UserDefaults.standard.bool(forKey: PreferenceName.allowProtocolForIntegrations.rawValue) as Any,
|
||||||
|
|
||||||
// Part 2: Always Strings
|
// Part 2: Always Strings
|
||||||
.globalHotkey: UserDefaults.standard.string(forKey: PreferenceName.globalHotkey.rawValue) as Any,
|
.globalHotkey: UserDefaults.standard.string(forKey: PreferenceName.globalHotkey.rawValue) as Any,
|
||||||
|
@@ -94,7 +94,14 @@ class PrefsVC: NSViewController {
|
|||||||
sectionText: "prefs.global_shortcut".localized,
|
sectionText: "prefs.global_shortcut".localized,
|
||||||
descriptionText: "prefs.shortcut_desc".localized,
|
descriptionText: "prefs.shortcut_desc".localized,
|
||||||
self
|
self
|
||||||
)
|
),
|
||||||
|
CheckboxPreferenceView.make(
|
||||||
|
sectionText: "prefs.integrations".localized,
|
||||||
|
descriptionText: "prefs.open_protocol_desc".localized,
|
||||||
|
checkboxText: "prefs.open_protocol_title".localized,
|
||||||
|
preference: .allowProtocolForIntegrations,
|
||||||
|
action: {}
|
||||||
|
),
|
||||||
].forEach({ self.stackView.addArrangedSubview($0) })
|
].forEach({ self.stackView.addArrangedSubview($0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
116
phpmon/Domain/Preferences/Stats.swift
Normal file
116
phpmon/Domain/Preferences/Stats.swift
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
//
|
||||||
|
// Stats.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 29/01/2022.
|
||||||
|
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class Stats {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Keep track of how many times the app has been successfully launched.
|
||||||
|
|
||||||
|
This is used to determine whether it is time to show the sponsor
|
||||||
|
encouragement alert, but I'd like to include this stat somewhere
|
||||||
|
else as well.
|
||||||
|
*/
|
||||||
|
public static var successfulLaunchCount: Int {
|
||||||
|
UserDefaults.standard.integer(
|
||||||
|
forKey: InternalStats.launchCount.rawValue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Keep track of how many times the app has successfully switched
|
||||||
|
between different PHP versions.
|
||||||
|
|
||||||
|
This is used to determine whether it is time to show the sponsor
|
||||||
|
encouragement alert, but I'd like to include this stat somewhere
|
||||||
|
else as well.
|
||||||
|
*/
|
||||||
|
public static var successfulSwitchCount: Int {
|
||||||
|
UserDefaults.standard.integer(
|
||||||
|
forKey: InternalStats.switchCount.rawValue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Did the user see the sponsor encouragement / thank you message?
|
||||||
|
Annoying the user is the worst, so let's not show the message twice.
|
||||||
|
*/
|
||||||
|
public static var didSeeSponsorEncouragement: Bool {
|
||||||
|
UserDefaults.standard.bool(
|
||||||
|
forKey: InternalStats.didSeeSponsorEncouragement.rawValue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Increment the successful launch count. This should only be
|
||||||
|
called when the user has not encountered ANY issues starting
|
||||||
|
up the application.
|
||||||
|
*/
|
||||||
|
public static func incrementSuccessfulLaunchCount() {
|
||||||
|
UserDefaults.standard.set(
|
||||||
|
Stats.successfulLaunchCount + 1,
|
||||||
|
forKey: InternalStats.launchCount.rawValue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Increment the successful switch count.
|
||||||
|
*/
|
||||||
|
public static func incrementSuccessfulSwitchCount() {
|
||||||
|
UserDefaults.standard.set(
|
||||||
|
Stats.successfulSwitchCount + 1,
|
||||||
|
forKey: InternalStats.switchCount.rawValue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Determine if the sponsor message should be displayed.
|
||||||
|
|
||||||
|
The rationale behind this is simple, some of the stats
|
||||||
|
increasing beyond a certain point indicate the app
|
||||||
|
is being used.
|
||||||
|
|
||||||
|
We evaluate, first:
|
||||||
|
- Successful version switches
|
||||||
|
OR
|
||||||
|
- Successful starts of the application
|
||||||
|
|
||||||
|
AND, of course, you must never have seen the alert before.
|
||||||
|
(see `didSeeSponsorEncouragement`)
|
||||||
|
*/
|
||||||
|
public static func evaluateSponsorMessageShouldBeDisplayed() {
|
||||||
|
if Bundle.main.bundleIdentifier?.contains("beta") ?? false {
|
||||||
|
return Log.info("Sponsor messages never apply to beta builds.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if Stats.didSeeSponsorEncouragement {
|
||||||
|
return Log.info("Awesome, the user has already seen the sponsor message.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if Stats.successfulLaunchCount < 7 && Stats.successfulSwitchCount < 40 {
|
||||||
|
return Log.info("It is too soon to see the sponsor message (launched \(Stats.successfulLaunchCount) times, switched \(Stats.successfulSwitchCount) times).")
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
let donate = Alert.present(
|
||||||
|
messageText: "startup.sponsor_encouragement.title".localized,
|
||||||
|
informativeText: "startup.sponsor_encouragement.desc".localized,
|
||||||
|
buttonTitle: "startup.sponsor_encouragement.accept".localized,
|
||||||
|
secondButtonTitle: "startup.sponsor_encouragement.skip".localized,
|
||||||
|
style: .informational)
|
||||||
|
if donate {
|
||||||
|
Log.info("The user is an absolute badass for choosing this option. Thank you.")
|
||||||
|
NSWorkspace.shared.open(Constants.DonationUrl)
|
||||||
|
}
|
||||||
|
UserDefaults.standard.set(true, forKey: InternalStats.didSeeSponsorEncouragement.rawValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -16,6 +16,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate {
|
|||||||
@IBOutlet weak var previewText: NSTextField!
|
@IBOutlet weak var previewText: NSTextField!
|
||||||
@IBOutlet weak var buttonSecure: NSButton!
|
@IBOutlet weak var buttonSecure: NSButton!
|
||||||
@IBOutlet weak var buttonCreateLink: NSButton!
|
@IBOutlet weak var buttonCreateLink: NSButton!
|
||||||
|
@IBOutlet weak var buttonCancel: NSButton!
|
||||||
|
|
||||||
@IBOutlet weak var textFieldTitle: NSTextField!
|
@IBOutlet weak var textFieldTitle: NSTextField!
|
||||||
@IBOutlet weak var textFieldSecure: NSTextField!
|
@IBOutlet weak var textFieldSecure: NSTextField!
|
||||||
@@ -39,6 +40,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate {
|
|||||||
textFieldTitle.stringValue = "site_list.add.link_folder".localized
|
textFieldTitle.stringValue = "site_list.add.link_folder".localized
|
||||||
linkName.placeholderString = "site_list.add.domain_name_placeholder".localized
|
linkName.placeholderString = "site_list.add.domain_name_placeholder".localized
|
||||||
textFieldSecure.stringValue = "site_list.add.secure_description".localized
|
textFieldSecure.stringValue = "site_list.add.secure_description".localized
|
||||||
|
buttonCancel.stringValue = "site_list.add.cancel".localized
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Outlet Interactions
|
// MARK: - Outlet Interactions
|
||||||
|
@@ -15,6 +15,7 @@ class SiteListCell: NSTableCellView
|
|||||||
|
|
||||||
@IBOutlet weak var labelSiteName: NSTextField!
|
@IBOutlet weak var labelSiteName: NSTextField!
|
||||||
@IBOutlet weak var labelPathName: NSTextField!
|
@IBOutlet weak var labelPathName: NSTextField!
|
||||||
|
@IBOutlet weak var labelDriverType: NSTextField!
|
||||||
|
|
||||||
@IBOutlet weak var imageViewLock: NSImageView!
|
@IBOutlet weak var imageViewLock: NSImageView!
|
||||||
@IBOutlet weak var imageViewType: NSImageView!
|
@IBOutlet weak var imageViewType: NSImageView!
|
||||||
@@ -35,8 +36,7 @@ class SiteListCell: NSTableCellView
|
|||||||
labelSiteName.stringValue = "\(site.name!).\(Valet.shared.config.tld)"
|
labelSiteName.stringValue = "\(site.name!).\(Valet.shared.config.tld)"
|
||||||
|
|
||||||
// Show the absolute path, except make sure to replace the /Users/username segment with ~ for readability
|
// Show the absolute path, except make sure to replace the /Users/username segment with ~ for readability
|
||||||
labelPathName.stringValue = site.absolutePath
|
labelPathName.stringValue = site.absolutePathRelative
|
||||||
.replacingOccurrences(of: "/Users/\(Paths.whoami)", with: "~")
|
|
||||||
|
|
||||||
// If the `aliasPath` is nil, we're dealing with a parked site (otherwise: linked).
|
// If the `aliasPath` is nil, we're dealing with a parked site (otherwise: linked).
|
||||||
imageViewType.image = NSImage(
|
imageViewType.image = NSImage(
|
||||||
@@ -47,13 +47,17 @@ class SiteListCell: NSTableCellView
|
|||||||
imageViewType.contentTintColor = NSColor.tertiaryLabelColor
|
imageViewType.contentTintColor = NSColor.tertiaryLabelColor
|
||||||
|
|
||||||
// Show the green or red lock based on whether the site was secured
|
// Show the green or red lock based on whether the site was secured
|
||||||
imageViewLock.image = NSImage(named: site.secured ? "Lock" : "LockUnlocked")
|
// imageViewLock.image = NSImage(named: site.secured ? "Lock" : "LockUnlocked")
|
||||||
imageViewLock.contentTintColor = site.secured ?
|
imageViewLock.contentTintColor = site.secured ?
|
||||||
NSColor.init(red: 63/255, green: 195/255, blue: 128/255, alpha: 1.0) // green
|
NSColor(named: "IconColorGreen") // green
|
||||||
: NSColor.init(red: 246/255, green: 71/255, blue: 71/255, alpha: 1.0) // red
|
: NSColor(named: "IconColorRed")
|
||||||
|
|
||||||
// Show the current driver
|
// Show the current driver
|
||||||
labelDriver.stringValue = "\(site.driver ?? "???")"
|
labelDriverType.stringValue = site.driverDeterminedByComposer
|
||||||
|
? "Project Type".uppercased()
|
||||||
|
: "Driver Type".uppercased()
|
||||||
|
|
||||||
|
labelDriver.stringValue = site.driver ?? "driver.not_detected".localized
|
||||||
|
|
||||||
// Determine the Laravel version
|
// Determine the Laravel version
|
||||||
if site.driver == "Laravel" && site.notableComposerDependencies.keys.contains("laravel/framework") {
|
if site.driver == "Laravel" && site.notableComposerDependencies.keys.contains("laravel/framework") {
|
||||||
@@ -65,15 +69,8 @@ class SiteListCell: NSTableCellView
|
|||||||
buttonPhpVersion.title = " PHP \(site.composerPhp) "
|
buttonPhpVersion.title = " PHP \(site.composerPhp) "
|
||||||
buttonPhpVersion.isHidden = (site.composerPhp == "???")
|
buttonPhpVersion.isHidden = (site.composerPhp == "???")
|
||||||
|
|
||||||
// Split the composer list (on "|") to evaluate multiple constraints
|
|
||||||
// For example, for Laravel 8 projects the value is "^7.3|^8.0"
|
|
||||||
let matchesConstraint = site.composerPhp.split(separator: "|").map { string in
|
|
||||||
return PhpVersionNumberCollection.make(from: [PhpEnv.phpInstall.version.long])
|
|
||||||
.matching(constraint: string.trimmingCharacters(in: .whitespacesAndNewlines))
|
|
||||||
.count > 0
|
|
||||||
}.contains(true)
|
|
||||||
|
|
||||||
imageViewPhpVersionOK.isHidden = (site.composerPhp == "???" || !matchesConstraint)
|
imageViewPhpVersionOK.isHidden = (site.composerPhp == "???" || !site.composerPhpCompatibleWithLinked)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func pressedPhpVersion(_ sender: Any) {
|
@IBAction func pressedPhpVersion(_ sender: Any) {
|
||||||
|
@@ -93,6 +93,7 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
|
|||||||
progressIndicator.startAnimation(nil)
|
progressIndicator.startAnimation(nil)
|
||||||
tableView.alphaValue = 0.3
|
tableView.alphaValue = 0.3
|
||||||
tableView.isEnabled = false
|
tableView.isEnabled = false
|
||||||
|
tableView.selectRowIndexes([], byExtendingSelection: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -201,8 +202,14 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let splitSearchString: [String] = searchString
|
||||||
|
.split(separator: " ")
|
||||||
|
.map { return String($0) }
|
||||||
|
|
||||||
sites = Valet.shared.sites.filter({ site in
|
sites = Valet.shared.sites.filter({ site in
|
||||||
return site.name.lowercased().contains(searchString)
|
return !splitSearchString.map { searchString in
|
||||||
|
return site.name.lowercased().contains(searchString)
|
||||||
|
}.contains(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
@@ -43,7 +43,7 @@ class SiteListWC: PMWindowController, NSSearchFieldDelegate, NSToolbarDelegate {
|
|||||||
|
|
||||||
self.searchTimer?.invalidate()
|
self.searchTimer?.invalidate()
|
||||||
|
|
||||||
searchTimer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: false, block: { _ in
|
searchTimer = Timer.scheduledTimer(withTimeInterval: 0.15, repeats: false, block: { _ in
|
||||||
self.contentVC.searchedFor(text: searchField.stringValue)
|
self.contentVC.searchedFor(text: searchField.stringValue)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ class SiteListWC: PMWindowController, NSSearchFieldDelegate, NSToolbarDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func showSitePopup(_ folder: String) {
|
func showSitePopup(_ folder: String) {
|
||||||
let storyboard = NSStoryboard(name: "Main" , bundle : nil)
|
let storyboard = NSStoryboard(name: "Main", bundle : nil)
|
||||||
|
|
||||||
let windowController = storyboard.instantiateController(
|
let windowController = storyboard.instantiateController(
|
||||||
withIdentifier: "addSiteWindow"
|
withIdentifier: "addSiteWindow"
|
||||||
|
25
phpmon/Domain/SwiftUI/PMServicesView.swift
Normal file
25
phpmon/Domain/SwiftUI/PMServicesView.swift
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// PMHeaderView.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 15/04/2021.
|
||||||
|
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@available(OSX 11.0, *)
|
||||||
|
struct PMServicesView: View {
|
||||||
|
var body: some View {
|
||||||
|
PMServices().frame(minWidth: 0, maxWidth: 450, minHeight: 0, maxHeight: 50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(OSX 11.0, *)
|
||||||
|
struct PMServices: NSViewRepresentable {
|
||||||
|
func makeNSView(context: Context) -> some NSView {
|
||||||
|
return ServicesView.asMenuItem().view!
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateNSView(_ nsView: NSViewType, context: Context) {}
|
||||||
|
}
|
@@ -15,5 +15,6 @@ struct Preview_Previews: PreviewProvider {
|
|||||||
PMHeaderView(content: "You are running PHP 8.1")
|
PMHeaderView(content: "You are running PHP 8.1")
|
||||||
PMStatsView(content: "15 MB")
|
PMStatsView(content: "15 MB")
|
||||||
PMStatsView(content: "2 GB")
|
PMStatsView(content: "2 GB")
|
||||||
|
PMServicesView() // uses live services data!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
75
phpmon/Domain/Terminal/Paths.swift
Normal file
75
phpmon/Domain/Terminal/Paths.swift
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
//
|
||||||
|
// Paths.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum HomebrewDir: String {
|
||||||
|
case opt = "/opt/homebrew"
|
||||||
|
case usr = "/usr/local"
|
||||||
|
}
|
||||||
|
|
||||||
|
class Paths {
|
||||||
|
|
||||||
|
static let shared = Paths()
|
||||||
|
var baseDir : HomebrewDir
|
||||||
|
var userName = String(Shell.pipe("whoami").split(separator: "\n")[0])
|
||||||
|
|
||||||
|
init() {
|
||||||
|
let optBrewFound = Shell.fileExists("\(HomebrewDir.opt.rawValue)/bin/brew")
|
||||||
|
let usrBrewFound = Shell.fileExists("\(HomebrewDir.usr.rawValue)/bin/brew")
|
||||||
|
|
||||||
|
if (optBrewFound) {
|
||||||
|
// This is usually the case with Homebrew installed on Apple Silicon
|
||||||
|
baseDir = .opt
|
||||||
|
} else if (usrBrewFound) {
|
||||||
|
// This is usually the case with Homebrew installed on Intel (or Rosetta 2)
|
||||||
|
baseDir = .usr
|
||||||
|
} else {
|
||||||
|
// Falling back to default "legacy" Homebrew location (for Intel)
|
||||||
|
print("Seems like we couldn't determine the Homebrew directory.")
|
||||||
|
print("This usually means we're in trouble... (no Homebrew?)")
|
||||||
|
baseDir = .usr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// - MARK: Binaries
|
||||||
|
|
||||||
|
public static var valet: String {
|
||||||
|
return "\(binPath)/valet"
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var brew: String {
|
||||||
|
return "\(binPath)/brew"
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var php: String {
|
||||||
|
return "\(binPath)/php"
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var phpConfig: String {
|
||||||
|
return "\(binPath)/php-config"
|
||||||
|
}
|
||||||
|
|
||||||
|
// - MARK: Paths
|
||||||
|
|
||||||
|
public static var whoami: String {
|
||||||
|
return shared.userName
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var binPath: String {
|
||||||
|
return "\(shared.baseDir.rawValue)/bin"
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var optPath: String {
|
||||||
|
return "\(shared.baseDir.rawValue)/opt"
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var etcPath: String {
|
||||||
|
return "\(shared.baseDir.rawValue)/etc"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -40,7 +40,7 @@
|
|||||||
<key>LSUIElement</key>
|
<key>LSUIElement</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>Copyright © 2021 Nico Verbruggen. All rights reserved.</string>
|
<string>Copyright © 2019-2022 Nico Verbruggen. All rights reserved.</string>
|
||||||
<key>NSMainStoryboardFile</key>
|
<key>NSMainStoryboardFile</key>
|
||||||
<string>Main</string>
|
<string>Main</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
|
@@ -25,8 +25,8 @@
|
|||||||
"mi_manage_services" = "Manage Services";
|
"mi_manage_services" = "Manage Services";
|
||||||
"mi_restart_all_services" = "Restart All Services";
|
"mi_restart_all_services" = "Restart All Services";
|
||||||
"mi_stop_all_services" = "Stop All Services";
|
"mi_stop_all_services" = "Stop All Services";
|
||||||
"mi_force_load_latest" = "Force Load (Latest) PHP %@";
|
"mi_fix_my_valet" = "Fix My Valet (PHP & Services)";
|
||||||
"mi_force_load_latest_unavailable" = "Force Load Unavailable (PHP %@ Not Installed)";
|
"mi_fix_my_valet_unavailable" = "Fix My Valet Unavailable";
|
||||||
"mi_php_refresh" = "Refresh Information";
|
"mi_php_refresh" = "Refresh Information";
|
||||||
|
|
||||||
"mi_configuration" = "PHP Configuration";
|
"mi_configuration" = "PHP Configuration";
|
||||||
@@ -52,9 +52,16 @@
|
|||||||
"mi_sitelist" = "View Linked and Parked Domains...";
|
"mi_sitelist" = "View Linked and Parked Domains...";
|
||||||
|
|
||||||
"mi_preferences" = "Preferences...";
|
"mi_preferences" = "Preferences...";
|
||||||
|
"mi_donate" = "Donate...";
|
||||||
"mi_quit" = "Quit PHP Monitor";
|
"mi_quit" = "Quit PHP Monitor";
|
||||||
"mi_about" = "About PHP Monitor";
|
"mi_about" = "About PHP Monitor";
|
||||||
|
|
||||||
|
// MENU ITEMS (if window is open)
|
||||||
|
|
||||||
|
"mm_add_folder_as_link" = "Add Folder as Link...";
|
||||||
|
"mm_reload_site_list" = "Reload Site List";
|
||||||
|
"mm_find_in_site_list" = "Search in Site List";
|
||||||
|
|
||||||
// SITE LIST
|
// SITE LIST
|
||||||
|
|
||||||
"site_list.title" = "Domains";
|
"site_list.title" = "Domains";
|
||||||
@@ -92,7 +99,6 @@
|
|||||||
"site_list.alert.folder_missing.cancel" = "Cancel Link";
|
"site_list.alert.folder_missing.cancel" = "Cancel Link";
|
||||||
"site_list.alert.folder_missing.return" = "OK";
|
"site_list.alert.folder_missing.return" = "OK";
|
||||||
|
|
||||||
|
|
||||||
"site_list.add.modal_description" = "First, select which folder you would like to link.";
|
"site_list.add.modal_description" = "First, select which folder you would like to link.";
|
||||||
|
|
||||||
// SITE LIST ACTIONS
|
// SITE LIST ACTIONS
|
||||||
@@ -111,6 +117,10 @@
|
|||||||
"site_list.alert.invalid_folder_name" = "Invalid folder name";
|
"site_list.alert.invalid_folder_name" = "Invalid folder name";
|
||||||
"site_list.alert.invalid_folder_name_desc" = "This folder could not be resolved to a valid URL. This is usually because there’s a space in the folder name. Please rename the folder, reload the list of sites, and try again.";
|
"site_list.alert.invalid_folder_name_desc" = "This folder could not be resolved to a valid URL. This is usually because there’s a space in the folder name. Please rename the folder, reload the list of sites, and try again.";
|
||||||
|
|
||||||
|
// DRIVERS
|
||||||
|
|
||||||
|
"driver.not_detected" = "Other";
|
||||||
|
|
||||||
// EDITORS
|
// EDITORS
|
||||||
|
|
||||||
"editors.alert.try_again" = "Try Again";
|
"editors.alert.try_again" = "Try Again";
|
||||||
@@ -127,6 +137,7 @@
|
|||||||
"prefs.info_density" = "Info density:";
|
"prefs.info_density" = "Info density:";
|
||||||
"prefs.services" = "Services:";
|
"prefs.services" = "Services:";
|
||||||
"prefs.switcher" = "Switcher:";
|
"prefs.switcher" = "Switcher:";
|
||||||
|
"prefs.integrations" = "Integrations:";
|
||||||
|
|
||||||
"prefs.auto_restart_services_title" = "Auto-restart PHP-FPM";
|
"prefs.auto_restart_services_title" = "Auto-restart PHP-FPM";
|
||||||
"prefs.auto_restart_services_desc" = "When checked, will automatically restart PHP-FPM when you check or uncheck an extension. Slightly slower when enabled, but this applies the extension change immediately for all sites you're serving, no need to restart PHP-FPM manually.";
|
"prefs.auto_restart_services_desc" = "When checked, will automatically restart PHP-FPM when you check or uncheck an extension. Slightly slower when enabled, but this applies the extension change immediately for all sites you're serving, no need to restart PHP-FPM manually.";
|
||||||
@@ -143,6 +154,9 @@
|
|||||||
"prefs.auto_composer_update_title" = "Automatically update global dependencies";
|
"prefs.auto_composer_update_title" = "Automatically update global dependencies";
|
||||||
"prefs.auto_composer_update_desc" = "When checked, will automatically ask Composer to run `composer global update` whenever you switch between different PHP versions. You will be able to see what changes are being made, or should this fail.";
|
"prefs.auto_composer_update_desc" = "When checked, will automatically ask Composer to run `composer global update` whenever you switch between different PHP versions. You will be able to see what changes are being made, or should this fail.";
|
||||||
|
|
||||||
|
"prefs.open_protocol_title" = "Allow third-party integrations";
|
||||||
|
"prefs.open_protocol_desc" = "When checked, this will allow the interaction with third party utilities to work (e.g. Alfred, Raycast). If you disable this, PHP Monitor will still receive the commands, but will not act upon them.";
|
||||||
|
|
||||||
"prefs.shortcut_set" = "Set global shortcut";
|
"prefs.shortcut_set" = "Set global shortcut";
|
||||||
"prefs.shortcut_listening" = "<listening for keypress>";
|
"prefs.shortcut_listening" = "<listening for keypress>";
|
||||||
"prefs.shortcut_clear" = "Clear";
|
"prefs.shortcut_clear" = "Clear";
|
||||||
@@ -165,6 +179,9 @@
|
|||||||
// ALERTS
|
// ALERTS
|
||||||
|
|
||||||
// Composer Update
|
// Composer Update
|
||||||
|
"alert.composer_missing.title" = "Composer not found!";
|
||||||
|
"alert.composer_missing.info" = "Make sure you have Composer available in `/usr/local/bin/composer`. If Composer is located somewhere else, please create a symlink, like so (make sure to use the correct path):\n\n`ln -s /path/to/composer /user/local/bin`.";
|
||||||
|
|
||||||
"alert.composer_progress.title" = "Updating global dependencies...";
|
"alert.composer_progress.title" = "Updating global dependencies...";
|
||||||
"alert.composer_progress.info" = "You can see the progress in the terminal output below.";
|
"alert.composer_progress.info" = "You can see the progress in the terminal output below.";
|
||||||
|
|
||||||
@@ -180,13 +197,21 @@ problem manually, using your own Terminal app (this just shows you the output)."
|
|||||||
"alert.composer_php_requirement.title" = "`%@` has the following PHP requirement: \"php\":\n\"%@\".";
|
"alert.composer_php_requirement.title" = "`%@` has the following PHP requirement: \"php\":\n\"%@\".";
|
||||||
"alert.composer_php_requirement.info" = "This required PHP version was determined by checking the `%@` field in the `composer.json` file when the site list was last refreshed.";
|
"alert.composer_php_requirement.info" = "This required PHP version was determined by checking the `%@` field in the `composer.json` file when the site list was last refreshed.";
|
||||||
|
|
||||||
// Force Reload Started
|
// Suggest Fix My Valet
|
||||||
"alert.force_reload.title" = "PHP Monitor will force reload the latest version of PHP";
|
"alert.php_switch_failed.title" = "Switching to PHP %@ seems to have failed.";
|
||||||
"alert.force_reload.info" = "This can take a while. You'll get another alert when the force reload has completed.";
|
"alert.php_switch_failed.info" = "PHP Monitor has detected that PHP %@ is not active after completing its switching procedure. You can try to run \"Fix My Valet\" and switch again after that. Do you want to try \"Fix My Valet\"?";
|
||||||
|
"alert.php_switch_failed.confirm" = "Yes, run \"Fix My Valet\"";
|
||||||
|
"alert.php_switch_failed.cancel" = "Do Not Run";
|
||||||
|
|
||||||
// Force Reload Done
|
// Fix My Valet Started
|
||||||
"alert.force_reload_done.title" = "PHP has been force reloaded";
|
"alert.fix_my_valet.title" = "Having issues? Fix My Valet is ready to commence!";
|
||||||
"alert.force_reload_done.info" = "All appropriate services have been restarted, and the latest version of PHP is now active. You can now try switching to another version of PHP. If visiting sites still does not work, you may try running `valet install` again, this can fix a 502 issue (Bad Gateway).";
|
"alert.fix_my_valet.info" = "This can take a while. Please be patient.\n\nWhen this is done, all other services will be halted and PHP %@ will be linked. You will be able to switch to your desired version of PHP once this operation has completed.\n\n(You'll get another alert once Fix My Valet is done.)";
|
||||||
|
"alert.fix_my_valet.ok" = "Continue";
|
||||||
|
"alert.fix_my_valet.cancel" = "Abort";
|
||||||
|
|
||||||
|
// Fix My Valet Done
|
||||||
|
"alert.fix_my_valet_done.title" = "Fix My Valet has completed its operations.";
|
||||||
|
"alert.fix_my_valet_done.info" = "All appropriate services have been stopped and the correct ones restarted, and the latest version of PHP should now be active. You can now try switching to another version of PHP.\n\nIf visiting sites still does not work, you may try running `valet install` again, this can fix a 502 issue (Bad Gateway).\n\nIf Valet is broken and you cannot run `valet install`, you may need to run `composer global update`. Please consult the FAQ on GitHub if you have further issues.";
|
||||||
|
|
||||||
// PHP FPM Broken
|
// PHP FPM Broken
|
||||||
"alert.php_fpm_broken.title" = "PHP-FPM configuration is incorrect";
|
"alert.php_fpm_broken.title" = "PHP-FPM configuration is incorrect";
|
||||||
@@ -219,10 +244,18 @@ You can do this by running `composer global update` in your terminal. After that
|
|||||||
"startup.errors.php_opt.title" = "PHP is not correctly installed";
|
"startup.errors.php_opt.title" = "PHP is not correctly installed";
|
||||||
"startup.errors.php_opt.desc" = "PHP alias was not found in `/usr/local/opt` or `/opt/homebrew/opt`. The app will not work correctly until you resolve this issue. 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.";
|
"startup.errors.php_opt.desc" = "PHP alias was not found in `/usr/local/opt` or `/opt/homebrew/opt`. The app will not work correctly until you resolve this issue. 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.";
|
||||||
|
|
||||||
/// 3. Valet not installed
|
/// 3a. Valet not installed
|
||||||
"startup.errors.valet_executable.title" = "Laravel Valet is not correctly installed";
|
"startup.errors.valet_executable.title" = "Laravel Valet is not correctly installed";
|
||||||
"startup.errors.valet_executable.desc" = "You must install Valet with composer. Try running `which valet` in Terminal, it should return `/usr/local/bin/valet` or `/opt/homebrew/bin/valet`. The app will not work correctly until you resolve this issue. (PHP Monitor checks for the existence of `valet` in either of these paths.)";
|
"startup.errors.valet_executable.desc" = "You must install Valet with composer. Try running `which valet` in Terminal, it should return `/usr/local/bin/valet` or `/opt/homebrew/bin/valet`. The app will not work correctly until you resolve this issue. (PHP Monitor checks for the existence of `valet` in either of these paths.)";
|
||||||
|
|
||||||
|
/// 3b. Valet configuration file missing [currently not enabled]
|
||||||
|
"startup.errors.valet_config.title" = "Laravel Valet configuration file missing";
|
||||||
|
"startup.errors.valet_config.desc" = "PHP Monitor needs to be able to read the configuration file in `~/.config/valet/config.json`.";
|
||||||
|
|
||||||
|
/// 3c. Valet version not readable
|
||||||
|
"startup.errors.valet_version_unknown.title" = "Your Valet version could not be read (`valet --version` failed)";
|
||||||
|
"startup.errors.valet_version_unknown.desc" = "Make sure your Valet installation works and is up-to-date.\n\nTry running `valet --version` in a terminal to find out what's going on.";
|
||||||
|
|
||||||
/// 4. Brew & sudoers
|
/// 4. Brew & sudoers
|
||||||
"startup.errors.sudoers_brew.title" = "Brew has not been added to sudoers.d";
|
"startup.errors.sudoers_brew.title" = "Brew has not been added to sudoers.d";
|
||||||
"startup.errors.sudoers_brew.desc" = "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" = "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.";
|
||||||
@@ -231,6 +264,17 @@ You can do this by running `composer global update` in your terminal. After that
|
|||||||
"startup.errors.sudoers_valet.title" = "Valet has not been added to sudoers.d";
|
"startup.errors.sudoers_valet.title" = "Valet has not been added to sudoers.d";
|
||||||
"startup.errors.sudoers_valet.desc" = "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" = "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.";
|
||||||
|
|
||||||
/// 6. Multiple services active
|
/// 6. Cannot retrieve services
|
||||||
|
"startup.errors.services_json_error.title" = "Cannot determine services status";
|
||||||
|
"startup.errors.services_json_error.desc" = "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. This can happen if your Homebrew installation is out of date, in which case Homebrew won't return JSON yet.\n\nYou can usually fix this by running `brew update`.";
|
||||||
|
|
||||||
|
/// 7. Multiple services active
|
||||||
"startup.errors.services.title" = "Multiple PHP services are active";
|
"startup.errors.services.title" = "Multiple PHP services are active";
|
||||||
"startup.errors.services.desc" = "This can cause php-fpm to serve a more recent version of PHP than the one you'd like to see active. Please terminate all extra PHP processes.\n\nThe easiest solution is to choose the option 'Force load latest PHP version' in the menu bar.\n\nAlternatively, you can fix this manually. You can do this by running `brew services list` and running `sudo brew services stop php@7.3` (and use the version that applies).\n\nPHP Monitor usually handles the starting and stopping of these services, so once the correct version is the only PHP version running you should not have any issues. It is recommended to restart PHP Monitor once you have resolved this issue.\n\nFor more information about this issue, please see the README.md file in the repository on GitHub.";
|
"startup.errors.services.desc" = "This can cause php-fpm to serve a more recent version of PHP than the one you'd like to see active. Please terminate all extra PHP processes.\n\nThe easiest solution is to choose the option 'First Aid & Services > Fix My Valet' in the menu bar.\n\nAlternatively, you can fix this manually. You can do this by running `brew services list` and running `sudo brew services stop php@7.3` (and use the version that applies).\n\nPHP Monitor usually handles the starting and stopping of these services, so once the correct version is the only PHP version running you should not have any issues. It is recommended to restart PHP Monitor once you have resolved this issue.\n\nFor more information about this issue, please see the README.md file in the repository on GitHub.";
|
||||||
|
|
||||||
|
// SPONSOR ENCOURAGEMENT
|
||||||
|
|
||||||
|
"startup.sponsor_encouragement.title" = "If PHP Monitor has been useful to you or your company, please consider leaving a tip.";
|
||||||
|
"startup.sponsor_encouragement.desc" = "If you have already donated, then YOU are the reason why the app was able to get all these new features. In that case, this is a THANK YOU message to you.\n\nTo be 100% transparent: I plan to keep PHP Monitor open source and free. Your support makes this decision very easy.\n\n(You will only see this prompt once.)";
|
||||||
|
"startup.sponsor_encouragement.accept" = "Yes, I would like to sponsor";
|
||||||
|
"startup.sponsor_encouragement.skip" = "Nevermind";
|
||||||
|
Reference in New Issue
Block a user