From 6ddddc744af0781e9cbe9fc5f4a2ef6e9568b2af Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 9 Sep 2022 21:35:56 +0200 Subject: [PATCH 001/181] =?UTF-8?q?=F0=9F=94=A7=20Add=20target=20for=20PHP?= =?UTF-8?q?=20Monitor=20SE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor SE-Info.plist | 49 ++ PHP Monitor.xcodeproj/project.pbxproj | 427 +++++++++++++++++- .../xcschemes/PHP Monitor SE.xcscheme | 78 ++++ assets/affinity/icon_se.afdesign | Bin 0 -> 52343 bytes .../AppIconSE.appiconset/Contents.json | 68 +++ .../AppIconSE.appiconset/icon_128x128.png | Bin 0 -> 5622 bytes .../AppIconSE.appiconset/icon_128x128@2x.png | Bin 0 -> 12448 bytes .../AppIconSE.appiconset/icon_16x16.png | Bin 0 -> 575 bytes .../AppIconSE.appiconset/icon_16x16@2x.png | Bin 0 -> 1208 bytes .../AppIconSE.appiconset/icon_256x256.png | Bin 0 -> 12448 bytes .../AppIconSE.appiconset/icon_256x256@2x.png | Bin 0 -> 28480 bytes .../AppIconSE.appiconset/icon_32x32.png | Bin 0 -> 1208 bytes .../AppIconSE.appiconset/icon_32x32@2x.png | Bin 0 -> 2628 bytes .../AppIconSE.appiconset/icon_512x512.png | Bin 0 -> 28480 bytes .../AppIconSE.appiconset/icon_512x512@2x.png | Bin 0 -> 42434 bytes 15 files changed, 616 insertions(+), 6 deletions(-) create mode 100644 PHP Monitor SE-Info.plist create mode 100644 PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor SE.xcscheme create mode 100644 assets/affinity/icon_se.afdesign create mode 100644 phpmon/Assets.xcassets/AppIconSE.appiconset/Contents.json create mode 100644 phpmon/Assets.xcassets/AppIconSE.appiconset/icon_128x128.png create mode 100644 phpmon/Assets.xcassets/AppIconSE.appiconset/icon_128x128@2x.png create mode 100644 phpmon/Assets.xcassets/AppIconSE.appiconset/icon_16x16.png create mode 100644 phpmon/Assets.xcassets/AppIconSE.appiconset/icon_16x16@2x.png create mode 100644 phpmon/Assets.xcassets/AppIconSE.appiconset/icon_256x256.png create mode 100644 phpmon/Assets.xcassets/AppIconSE.appiconset/icon_256x256@2x.png create mode 100644 phpmon/Assets.xcassets/AppIconSE.appiconset/icon_32x32.png create mode 100644 phpmon/Assets.xcassets/AppIconSE.appiconset/icon_32x32@2x.png create mode 100644 phpmon/Assets.xcassets/AppIconSE.appiconset/icon_512x512.png create mode 100644 phpmon/Assets.xcassets/AppIconSE.appiconset/icon_512x512@2x.png diff --git a/PHP Monitor SE-Info.plist b/PHP Monitor SE-Info.plist new file mode 100644 index 0000000..7918ee3 --- /dev/null +++ b/PHP Monitor SE-Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleURLTypes + + + CFBundleTypeRole + Viewer + CFBundleURLName + com.nicoverbruggen.phpmon + CFBundleURLSchemes + + phpmon + + + + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSApplicationCategoryType + public.app-category.utilities + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + LSUIElement + + NSHumanReadableCopyright + Copyright © 2019-2022 Nico Verbruggen. All rights reserved. + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + + diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index e9e7926..57f6c0f 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -103,6 +103,148 @@ C42F26732805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; C42F26762805FEE200938AC7 /* nginx-secure-proxy.test in Resources */ = {isa = PBXBuildFile; fileRef = C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */; }; + C4358B1328CBCC2600121D18 /* WarningManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699EE28A2F2A30060FEB8 /* WarningManager.swift */; }; + C4358B1428CBCC2600121D18 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; }; + C4358B1528CBCC2600121D18 /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; }; + C4358B1628CBCC2600121D18 /* MainMenu+FixMyValet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */; }; + C4358B1728CBCC2600121D18 /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; }; + C4358B1828CBCC2600121D18 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* Shell.swift */; }; + C4358B1928CBCC2600121D18 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PreferencesWindowController.swift */; }; + C4358B1A28CBCC2600121D18 /* PhpConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */; }; + C4358B1B28CBCC2600121D18 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; }; + C4358B1C28CBCC2600121D18 /* Valet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F792754499000D44ED0 /* Valet.swift */; }; + C4358B1D28CBCC2600121D18 /* ValetProxy+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */; }; + C4358B1E28CBCC2600121D18 /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E628553117006F9937 /* ArrayExtension.swift */; }; + C4358B1F28CBCC2600121D18 /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; }; + C4358B2028CBCC2600121D18 /* AppDelegate+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */; }; + C4358B2128CBCC2600121D18 /* CreatedFromFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5489625728312FAD004F647A /* CreatedFromFile.swift */; }; + C4358B2228CBCC2600121D18 /* SelectPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA627B07A1300544CD5 /* SelectPreferenceView.swift */; }; + C4358B2328CBCC2600121D18 /* NoWarningsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C708C28AA7F7900E8D498 /* NoWarningsView.swift */; }; + C4358B2428CBCC2600121D18 /* BetterAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */; }; + C4358B2528CBCC2600121D18 /* NSWindowExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */; }; + C4358B2628CBCC2600121D18 /* ValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4205A7D27F4D21800191A39 /* ValetProxy.swift */; }; + C4358B2728CBCC2600121D18 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; }; + C4358B2828CBCC2600121D18 /* HotkeyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */; }; + C4358B2928CBCC2600121D18 /* PreferenceName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C450C8C528C919EC002A2B4B /* PreferenceName.swift */; }; + C4358B2A28CBCC2600121D18 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; + C4358B2B28CBCC2600121D18 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; }; + C4358B2C28CBCC2600121D18 /* AddProxyVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9F24A280B69E100DCD39A /* AddProxyVC.swift */; }; + C4358B2D28CBCC2600121D18 /* DomainListVC+ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41E87192763D42300161EE0 /* DomainListVC+ContextMenu.swift */; }; + C4358B2E28CBCC2600121D18 /* ActivePhpInstallation+Checks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */; }; + C4358B2F28CBCC2600121D18 /* PresetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C463E37F284930EE00422731 /* PresetHelper.swift */; }; + C4358B3028CBCC2600121D18 /* ValetSite+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */; }; + C4358B3128CBCC2600121D18 /* FakeSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8DE27F88AEB002D32A9 /* FakeSiteScanner.swift */; }; + C4358B3228CBCC2600121D18 /* SwiftUIHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */; }; + C4358B3328CBCC2600121D18 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */; }; + C4358B3428CBCC2600121D18 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; }; + C4358B3528CBCC2600121D18 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E428551F9B006F9937 /* HeaderView.swift */; }; + C4358B3628CBCC2600121D18 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; + C4358B3728CBCC2600121D18 /* ProgressVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44A874728905BB000498BC4 /* ProgressVC.swift */; }; + C4358B3828CBCC2600121D18 /* PMWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CCBA6B275C567B008C7055 /* PMWindowController.swift */; }; + C4358B3928CBCC2600121D18 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; }; + C4358B3A28CBCC2600121D18 /* DomainListNameCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F427E2582B0045BD4E /* DomainListNameCell.swift */; }; + C4358B3B28CBCC2600121D18 /* Preset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C5C9B2846A40600E28255 /* Preset.swift */; }; + C4358B3C28CBCC2600121D18 /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; }; + C4358B3D28CBCC2600121D18 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B609192853AAD300C95265 /* SectionHeaderView.swift */; }; + C4358B3E28CBCC2600121D18 /* DomainListPhpCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F627E258410045BD4E /* DomainListPhpCell.swift */; }; + C4358B3F28CBCC2600121D18 /* PreferencesWindowController+Hotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE7F288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift */; }; + C4358B4028CBCC2600121D18 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; }; + C4358B4128CBCC2600121D18 /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */; }; + C4358B4228CBCC2600121D18 /* DomainListKindCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */; }; + C4358B4328CBCC2600121D18 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CDA892288F1A71007CE25F /* Keys.swift */; }; + C4358B4428CBCC2600121D18 /* MainMenu+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F361602836BFD9003598CC /* MainMenu+Actions.swift */; }; + C4358B4528CBCC2600121D18 /* TerminalProgressWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */; }; + C4358B4628CBCC2600121D18 /* KeyCombo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AF27E4F51E003B9AD9 /* KeyCombo.swift */; }; + C4358B4728CBCC2600121D18 /* ProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E627F88B41002D32A9 /* ProxyScanner.swift */; }; + C4358B4828CBCC2600121D18 /* CustomPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */; }; + C4358B4928CBCC2600121D18 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Application.swift */; }; + C4358B4A28CBCC2600121D18 /* App+ActivationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */; }; + C4358B4B28CBCC2600121D18 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; }; + C4358B4C28CBCC2600121D18 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; }; + C4358B4D28CBCC2600121D18 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; }; + C4358B4E28CBCC2600121D18 /* EnvironmentCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */; }; + C4358B4F28CBCC2600121D18 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; }; + C4358B5028CBCC2600121D18 /* HomebrewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F30B02278E16BA00755FCE /* HomebrewService.swift */; }; + C4358B5128CBCC2600121D18 /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AD27E4F51E003B9AD9 /* Key.swift */; }; + C4358B5228CBCC2600121D18 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.swift */; }; + C4358B5328CBCC2600121D18 /* ValetSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E127F88B13002D32A9 /* ValetSiteScanner.swift */; }; + C4358B5428CBCC2600121D18 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; + C4358B5528CBCC2600121D18 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; }; + C4358B5628CBCC2600121D18 /* XibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9225CC804200CC7490 /* XibLoadable.swift */; }; + C4358B5728CBCC2600121D18 /* CheckboxPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD29276C8AA4004CE748 /* CheckboxPreferenceView.swift */; }; + C4358B5828CBCC2600121D18 /* Warning.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699F028A2F3150060FEB8 /* Warning.swift */; }; + C4358B5928CBCC2600121D18 /* HotKeysController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AC27E4F51E003B9AD9 /* HotKeysController.swift */; }; + C4358B5A28CBCC2600121D18 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; }; + C4358B5B28CBCC2600121D18 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; }; + C4358B5C28CBCC2600121D18 /* DomainListVC+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CA5EC2774F8EE00A2C80E /* DomainListVC+Actions.swift */; }; + C4358B5D28CBCC2600121D18 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; + C4358B5E28CBCC2600121D18 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; }; + C4358B5F28CBCC2600121D18 /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */; }; + C4358B6028CBCC2600121D18 /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; }; + C4358B6128CBCC2600121D18 /* MenuBarIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA927B0890D00544CD5 /* MenuBarIcons.swift */; }; + C4358B6228CBCC2600121D18 /* VersionPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */; }; + C4358B6328CBCC2600121D18 /* PhpConfigWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */; }; + C4358B6428CBCC2600121D18 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; }; + C4358B6528CBCC2600121D18 /* AppDelegate+InterApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */; }; + C4358B6628CBCC2600121D18 /* ValetProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C484437A2804BB560041A78A /* ValetProxyScanner.swift */; }; + C4358B6728CBCC2600121D18 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; }; + C4358B6828CBCC2600121D18 /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42759662627662800093CAE /* NSMenuExtension.swift */; }; + C4358B6928CBCC2600121D18 /* WarningListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDA928A2C49900CEAC97 /* WarningListView.swift */; }; + C4358B6A28CBCC2600121D18 /* DomainListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAE275A7A69003FCD53 /* DomainListVC.swift */; }; + C4358B6B28CBCC2600121D18 /* MainMenu+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */; }; + C4358B6C28CBCC2600121D18 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C1019A27C65C6F001FACC2 /* Process.swift */; }; + C4358B6D28CBCC2600121D18 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; }; + C4358B6E28CBCC2600121D18 /* DomainListTLSCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067FA27E25FD70045BD4E /* DomainListTLSCell.swift */; }; + C4358B6F28CBCC2600121D18 /* PMTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A81CA328C67101008DD9D1 /* PMTableView.swift */; }; + C4358B7028CBCC2600121D18 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; }; + C4358B7128CBCC2600121D18 /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; }; + C4358B7228CBCC2600121D18 /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; }; + C4358B7328CBCC2600121D18 /* SelectionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FE011028084FC200D1DE6D /* SelectionVC.swift */; }; + C4358B7428CBCC2600121D18 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4709CA128524B3400088BB8 /* StatsView.swift */; }; + C4358B7528CBCC2600121D18 /* AlertableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */; }; + C4358B7628CBCC2600121D18 /* Filesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4188988275FE8CB001EF227 /* Filesystem.swift */; }; + C4358B7728CBCC2600121D18 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B6091C2853AB9700C95265 /* ServicesView.swift */; }; + C4358B7828CBCC2600121D18 /* NSMenuItemExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */; }; + C4358B7928CBCC2600121D18 /* App+GlobalHotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */; }; + C4358B7A28CBCC2600121D18 /* InterAppHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EED88827A48778006D7272 /* InterAppHandler.swift */; }; + C4358B7B28CBCC2600121D18 /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; }; + C4358B7C28CBCC2600121D18 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; }; + C4358B7D28CBCC2600121D18 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; }; + C4358B7E28CBCC2600121D18 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; }; + C4358B7F28CBCC2600121D18 /* WarningsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDAC28A2DAC600CEAC97 /* WarningsWindowController.swift */; }; + C4358B8028CBCC2600121D18 /* ComposerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */; }; + C4358B8128CBCC2600121D18 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; }; + C4358B8228CBCC2600121D18 /* OnboardingWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE82288F1F9700FC478F /* OnboardingWindowController.swift */; }; + C4358B8328CBCC2600121D18 /* BetterAlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */; }; + C4358B8428CBCC2600121D18 /* VersionExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5635D276AB09000F12CCB /* VersionExtractor.swift */; }; + C4358B8528CBCC2600121D18 /* HotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AE27E4F51E003B9AD9 /* HotKey.swift */; }; + C4358B8628CBCC2600121D18 /* PhpHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D936C827E3EB6100BD69FE /* PhpHelper.swift */; }; + C4358B8728CBCC2600121D18 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; }; + C4358B8828CBCC2600121D18 /* DomainListTypeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F827E2585E0045BD4E /* DomainListTypeCell.swift */; }; + C4358B8928CBCC2600121D18 /* ModifierFlagsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0B027E4F51E003B9AD9 /* ModifierFlagsExtension.swift */; }; + C4358B8A28CBCC2600121D18 /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; }; + C4358B8B28CBCC2600121D18 /* NoDomainResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508AE28ADA23D008FAC1F /* NoDomainResultsView.swift */; }; + C4358B8C28CBCC2600121D18 /* ComposerJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D89BC52783C99400A02B68 /* ComposerJson.swift */; }; + C4358B8D28CBCC2600121D18 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; }; + C4358B8E28CBCC2600121D18 /* Shell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */; }; + C4358B8F28CBCC2600121D18 /* Xdebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42337A2281F19F000459A48 /* Xdebug.swift */; }; + C4358B9028CBCC2600121D18 /* AppDelegate+MenuOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */; }; + C4358B9128CBCC2600121D18 /* SiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A527E60D7A009F26CB /* SiteScanner.swift */; }; + C4358B9228CBCC2600121D18 /* DomainListWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* DomainListWindowController.swift */; }; + C4358B9328CBCC2600121D18 /* DomainListCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */; }; + C4358B9428CBCC2600121D18 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; }; + C4358B9528CBCC2600121D18 /* AddSiteVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4930849279F331F009C240B /* AddSiteVC.swift */; }; + C4358B9628CBCC2600121D18 /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DEB7D327A5D60B00834718 /* Stats.swift */; }; + C4358B9928CBCC2600121D18 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3A22B0098000E7CF16 /* Assets.xcassets */; }; + C4358B9A28CBCC2600121D18 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3C22B0098000E7CF16 /* Main.storyboard */; }; + C4358B9B28CBCC2600121D18 /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */; }; + C4358B9C28CBCC2600121D18 /* ProgressWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C44C1990276E44CB0072762D /* ProgressWindow.storyboard */; }; + C4358B9D28CBCC2600121D18 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = C4232EE42612526500158FC6 /* Credits.html */; }; + C4358B9E28CBCC2600121D18 /* SelectPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD25276C883F004CE748 /* SelectPreferenceView.xib */; }; + C4358B9F28CBCC2600121D18 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; + C4358BA028CBCC2600121D18 /* CheckboxPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C4068CA327B0780A00544CD5 /* CheckboxPreferenceView.xib */; }; + C4358BA128CBCC2600121D18 /* HotkeyPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD2C276C8D67004CE748 /* HotkeyPreferenceView.xib */; }; + C4358BA228CBCC2600121D18 /* InternetAccessPolicy.strings in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */; }; C43603A0275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */; }; C43603A1275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */; }; C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A1925D9CD1000591B77 /* Utility.swift */; }; @@ -386,6 +528,8 @@ C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Shell+PATH.swift"; sourceTree = ""; }; C42F26722805B4B400938AC7 /* DomainListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListable.swift; sourceTree = ""; }; C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-secure-proxy.test"; sourceTree = ""; }; + C4358BA728CBCC2600121D18 /* PHP Monitor SE.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PHP Monitor SE.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + C4358BA828CBCC2600121D18 /* PHP Monitor SE-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "PHP Monitor SE-Info.plist"; path = "/Users/nicoverbruggen/Code/phpmon/PHP Monitor SE-Info.plist"; sourceTree = ""; }; C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Notifications.swift"; sourceTree = ""; }; C43A8A1925D9CD1000591B77 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; C43A8A1F25D9D1D700591B77 /* brew-formula.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "brew-formula.json"; sourceTree = ""; }; @@ -507,6 +651,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C4358B9728CBCC2600121D18 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; C4F7807625D7F84B000DBC97 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -661,6 +812,7 @@ C4F7807A25D7F84B000DBC97 /* phpmon-tests */, C41C1B3422B0097F00E7CF16 /* Products */, C4D309E72770EF2F00958BCF /* Frameworks */, + C4358BA828CBCC2600121D18 /* PHP Monitor SE-Info.plist */, ); sourceTree = ""; }; @@ -669,6 +821,7 @@ children = ( C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */, C4F7807925D7F84B000DBC97 /* phpmon-tests.xctest */, + C4358BA728CBCC2600121D18 /* PHP Monitor SE.app */, ); name = Products; sourceTree = ""; @@ -1179,6 +1332,26 @@ productReference = C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */; productType = "com.apple.product-type.application"; }; + C4358B1128CBCC2600121D18 /* PHP Monitor SE */ = { + isa = PBXNativeTarget; + buildConfigurationList = C4358BA428CBCC2600121D18 /* Build configuration list for PBXNativeTarget "PHP Monitor SE" */; + buildPhases = ( + C4358B1228CBCC2600121D18 /* Sources */, + C4358B9728CBCC2600121D18 /* Frameworks */, + C4358B9828CBCC2600121D18 /* Resources */, + C4358BA328CBCC2600121D18 /* Run `swiftlint` */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "PHP Monitor SE"; + packageProductDependencies = ( + ); + productName = phpmon; + productReference = C4358BA728CBCC2600121D18 /* PHP Monitor SE.app */; + productType = "com.apple.product-type.application"; + }; C4F7807825D7F84B000DBC97 /* phpmon-tests */ = { isa = PBXNativeTarget; buildConfigurationList = C4F7808025D7F84B000DBC97 /* Build configuration list for PBXNativeTarget "phpmon-tests" */; @@ -1233,6 +1406,7 @@ projectRoot = ""; targets = ( C41C1B3222B0097F00E7CF16 /* PHP Monitor */, + C4358B1128CBCC2600121D18 /* PHP Monitor SE */, C4F7807825D7F84B000DBC97 /* phpmon-tests */, ); }; @@ -1256,6 +1430,23 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C4358B9828CBCC2600121D18 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C4358B9928CBCC2600121D18 /* Assets.xcassets in Resources */, + C4358B9A28CBCC2600121D18 /* Main.storyboard in Resources */, + C4358B9B28CBCC2600121D18 /* InternetAccessPolicy.plist in Resources */, + C4358B9C28CBCC2600121D18 /* ProgressWindow.storyboard in Resources */, + C4358B9D28CBCC2600121D18 /* Credits.html in Resources */, + C4358B9E28CBCC2600121D18 /* SelectPreferenceView.xib in Resources */, + C4358B9F28CBCC2600121D18 /* Localizable.strings in Resources */, + C4358BA028CBCC2600121D18 /* CheckboxPreferenceView.xib in Resources */, + C4358BA128CBCC2600121D18 /* HotkeyPreferenceView.xib in Resources */, + C4358BA228CBCC2600121D18 /* InternetAccessPolicy.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; C4F7807725D7F84B000DBC97 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1280,6 +1471,24 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + C4358BA328CBCC2600121D18 /* Run `swiftlint` */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run `swiftlint`"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint > /dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; C4F5FBCB28216985001065C5 /* Run `swiftlint` */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1440,6 +1649,145 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C4358B1228CBCC2600121D18 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C4358B1328CBCC2600121D18 /* WarningManager.swift in Sources */, + C4358B1428CBCC2600121D18 /* PhpExtension.swift in Sources */, + C4358B1528CBCC2600121D18 /* Startup.swift in Sources */, + C4358B1628CBCC2600121D18 /* MainMenu+FixMyValet.swift in Sources */, + C4358B1728CBCC2600121D18 /* PhpVersionNumber.swift in Sources */, + C4358B1828CBCC2600121D18 /* Shell.swift in Sources */, + C4358B1928CBCC2600121D18 /* PreferencesWindowController.swift in Sources */, + C4358B1A28CBCC2600121D18 /* PhpConfigurationFile.swift in Sources */, + C4358B1B28CBCC2600121D18 /* DateExtension.swift in Sources */, + C4358B1C28CBCC2600121D18 /* Valet.swift in Sources */, + C4358B1D28CBCC2600121D18 /* ValetProxy+Fake.swift in Sources */, + C4358B1E28CBCC2600121D18 /* ArrayExtension.swift in Sources */, + C4358B1F28CBCC2600121D18 /* PrefsVC.swift in Sources */, + C4358B2028CBCC2600121D18 /* AppDelegate+Notifications.swift in Sources */, + C4358B2128CBCC2600121D18 /* CreatedFromFile.swift in Sources */, + C4358B2228CBCC2600121D18 /* SelectPreferenceView.swift in Sources */, + C4358B2328CBCC2600121D18 /* NoWarningsView.swift in Sources */, + C4358B2428CBCC2600121D18 /* BetterAlert.swift in Sources */, + C4358B2528CBCC2600121D18 /* NSWindowExtension.swift in Sources */, + C4358B2628CBCC2600121D18 /* ValetProxy.swift in Sources */, + C4358B2728CBCC2600121D18 /* App+ConfigWatch.swift in Sources */, + C4358B2828CBCC2600121D18 /* HotkeyPreferenceView.swift in Sources */, + C4358B2928CBCC2600121D18 /* PreferenceName.swift in Sources */, + C4358B2A28CBCC2600121D18 /* ValetSite.swift in Sources */, + C4358B2B28CBCC2600121D18 /* PhpInstallation.swift in Sources */, + C4358B2C28CBCC2600121D18 /* AddProxyVC.swift in Sources */, + C4358B2D28CBCC2600121D18 /* DomainListVC+ContextMenu.swift in Sources */, + C4358B2E28CBCC2600121D18 /* ActivePhpInstallation+Checks.swift in Sources */, + C4358B2F28CBCC2600121D18 /* PresetHelper.swift in Sources */, + C4358B3028CBCC2600121D18 /* ValetSite+Fake.swift in Sources */, + C4358B3128CBCC2600121D18 /* FakeSiteScanner.swift in Sources */, + C4358B3228CBCC2600121D18 /* SwiftUIHelper.swift in Sources */, + C4358B3328CBCC2600121D18 /* OnboardingView.swift in Sources */, + C4358B3428CBCC2600121D18 /* HomebrewDiagnostics.swift in Sources */, + C4358B3528CBCC2600121D18 /* HeaderView.swift in Sources */, + C4358B3628CBCC2600121D18 /* AppVersion.swift in Sources */, + C4358B3728CBCC2600121D18 /* ProgressVC.swift in Sources */, + C4358B3828CBCC2600121D18 /* PMWindowController.swift in Sources */, + C4358B3928CBCC2600121D18 /* Command.swift in Sources */, + C4358B3A28CBCC2600121D18 /* DomainListNameCell.swift in Sources */, + C4358B3B28CBCC2600121D18 /* Preset.swift in Sources */, + C4358B3C28CBCC2600121D18 /* GlobalKeybindPreference.swift in Sources */, + C4358B3D28CBCC2600121D18 /* SectionHeaderView.swift in Sources */, + C4358B3E28CBCC2600121D18 /* DomainListPhpCell.swift in Sources */, + C4358B3F28CBCC2600121D18 /* PreferencesWindowController+Hotkey.swift in Sources */, + C4358B4028CBCC2600121D18 /* Actions.swift in Sources */, + C4358B4128CBCC2600121D18 /* StatusMenu+Items.swift in Sources */, + C4358B4228CBCC2600121D18 /* DomainListKindCell.swift in Sources */, + C4358B4328CBCC2600121D18 /* Keys.swift in Sources */, + C4358B4428CBCC2600121D18 /* MainMenu+Actions.swift in Sources */, + C4358B4528CBCC2600121D18 /* TerminalProgressWindowController.swift in Sources */, + C4358B4628CBCC2600121D18 /* KeyCombo.swift in Sources */, + C4358B4728CBCC2600121D18 /* ProxyScanner.swift in Sources */, + C4358B4828CBCC2600121D18 /* CustomPrefs.swift in Sources */, + C4358B4928CBCC2600121D18 /* Application.swift in Sources */, + C4358B4A28CBCC2600121D18 /* App+ActivationPolicy.swift in Sources */, + C4358B4B28CBCC2600121D18 /* MainMenu+Switcher.swift in Sources */, + C4358B4C28CBCC2600121D18 /* PhpFrameworks.swift in Sources */, + C4358B4D28CBCC2600121D18 /* App.swift in Sources */, + C4358B4E28CBCC2600121D18 /* EnvironmentCheck.swift in Sources */, + C4358B4F28CBCC2600121D18 /* MenuBarImageGenerator.swift in Sources */, + C4358B5028CBCC2600121D18 /* HomebrewService.swift in Sources */, + C4358B5128CBCC2600121D18 /* Key.swift in Sources */, + C4358B5228CBCC2600121D18 /* WarningView.swift in Sources */, + C4358B5328CBCC2600121D18 /* ValetSiteScanner.swift in Sources */, + C4358B5428CBCC2600121D18 /* DomainListable.swift in Sources */, + C4358B5528CBCC2600121D18 /* Preferences.swift in Sources */, + C4358B5628CBCC2600121D18 /* XibLoadable.swift in Sources */, + C4358B5728CBCC2600121D18 /* CheckboxPreferenceView.swift in Sources */, + C4358B5828CBCC2600121D18 /* Warning.swift in Sources */, + C4358B5928CBCC2600121D18 /* HotKeysController.swift in Sources */, + C4358B5A28CBCC2600121D18 /* MainMenu.swift in Sources */, + C4358B5B28CBCC2600121D18 /* Logger.swift in Sources */, + C4358B5C28CBCC2600121D18 /* DomainListVC+Actions.swift in Sources */, + C4358B5D28CBCC2600121D18 /* AppUpdateChecker.swift in Sources */, + C4358B5E28CBCC2600121D18 /* HomebrewPackage.swift in Sources */, + C4358B5F28CBCC2600121D18 /* PhpSwitcher.swift in Sources */, + C4358B6028CBCC2600121D18 /* ServicesManager.swift in Sources */, + C4358B6128CBCC2600121D18 /* MenuBarIcons.swift in Sources */, + C4358B6228CBCC2600121D18 /* VersionPopoverView.swift in Sources */, + C4358B6328CBCC2600121D18 /* PhpConfigWatcher.swift in Sources */, + C4358B6428CBCC2600121D18 /* Helpers.swift in Sources */, + C4358B6528CBCC2600121D18 /* AppDelegate+InterApp.swift in Sources */, + C4358B6628CBCC2600121D18 /* ValetProxyScanner.swift in Sources */, + C4358B6728CBCC2600121D18 /* AppDelegate.swift in Sources */, + C4358B6828CBCC2600121D18 /* NSMenuExtension.swift in Sources */, + C4358B6928CBCC2600121D18 /* WarningListView.swift in Sources */, + C4358B6A28CBCC2600121D18 /* DomainListVC.swift in Sources */, + C4358B6B28CBCC2600121D18 /* MainMenu+Async.swift in Sources */, + C4358B6C28CBCC2600121D18 /* Process.swift in Sources */, + C4358B6D28CBCC2600121D18 /* Events.swift in Sources */, + C4358B6E28CBCC2600121D18 /* DomainListTLSCell.swift in Sources */, + C4358B6F28CBCC2600121D18 /* PMTableView.swift in Sources */, + C4358B7028CBCC2600121D18 /* Errors.swift in Sources */, + C4358B7128CBCC2600121D18 /* Paths.swift in Sources */, + C4358B7228CBCC2600121D18 /* ActivePhpInstallation.swift in Sources */, + C4358B7328CBCC2600121D18 /* SelectionVC.swift in Sources */, + C4358B7428CBCC2600121D18 /* StatsView.swift in Sources */, + C4358B7528CBCC2600121D18 /* AlertableError.swift in Sources */, + C4358B7628CBCC2600121D18 /* Filesystem.swift in Sources */, + C4358B7728CBCC2600121D18 /* ServicesView.swift in Sources */, + C4358B7828CBCC2600121D18 /* NSMenuItemExtension.swift in Sources */, + C4358B7928CBCC2600121D18 /* App+GlobalHotkey.swift in Sources */, + C4358B7A28CBCC2600121D18 /* InterAppHandler.swift in Sources */, + C4358B7B28CBCC2600121D18 /* PhpEnv.swift in Sources */, + C4358B7C28CBCC2600121D18 /* Alert.swift in Sources */, + C4358B7D28CBCC2600121D18 /* LocalNotification.swift in Sources */, + C4358B7E28CBCC2600121D18 /* NginxConfigurationFile.swift in Sources */, + C4358B7F28CBCC2600121D18 /* WarningsWindowController.swift in Sources */, + C4358B8028CBCC2600121D18 /* ComposerWindow.swift in Sources */, + C4358B8128CBCC2600121D18 /* InternalSwitcher.swift in Sources */, + C4358B8228CBCC2600121D18 /* OnboardingWindowController.swift in Sources */, + C4358B8328CBCC2600121D18 /* BetterAlertVC.swift in Sources */, + C4358B8428CBCC2600121D18 /* VersionExtractor.swift in Sources */, + C4358B8528CBCC2600121D18 /* HotKey.swift in Sources */, + C4358B8628CBCC2600121D18 /* PhpHelper.swift in Sources */, + C4358B8728CBCC2600121D18 /* StatusMenu.swift in Sources */, + C4358B8828CBCC2600121D18 /* DomainListTypeCell.swift in Sources */, + C4358B8928CBCC2600121D18 /* ModifierFlagsExtension.swift in Sources */, + C4358B8A28CBCC2600121D18 /* MainMenu+Startup.swift in Sources */, + C4358B8B28CBCC2600121D18 /* NoDomainResultsView.swift in Sources */, + C4358B8C28CBCC2600121D18 /* ComposerJson.swift in Sources */, + C4358B8D28CBCC2600121D18 /* StringExtension.swift in Sources */, + C4358B8E28CBCC2600121D18 /* Shell+PATH.swift in Sources */, + C4358B8F28CBCC2600121D18 /* Xdebug.swift in Sources */, + C4358B9028CBCC2600121D18 /* AppDelegate+MenuOutlets.swift in Sources */, + C4358B9128CBCC2600121D18 /* SiteScanner.swift in Sources */, + C4358B9228CBCC2600121D18 /* DomainListWindowController.swift in Sources */, + C4358B9328CBCC2600121D18 /* DomainListCellProtocol.swift in Sources */, + C4358B9428CBCC2600121D18 /* Constants.swift in Sources */, + C4358B9528CBCC2600121D18 /* AddSiteVC.swift in Sources */, + C4358B9628CBCC2600121D18 /* Stats.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; C4F7807525D7F84B000DBC97 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1733,13 +2081,13 @@ C41C1B4422B0098000E7CF16 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDev; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AppColor; CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 965; + CURRENT_PROJECT_VERSION = 999; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -1749,7 +2097,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.6.0; + MARKETING_VERSION = 6.0; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1760,13 +2108,13 @@ C41C1B4522B0098000E7CF16 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDev; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AppColor; CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 965; + CURRENT_PROJECT_VERSION = 999; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -1776,7 +2124,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.6.0; + MARKETING_VERSION = 6.0; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1784,6 +2132,64 @@ }; name = Release; }; + C4358BA528CBCC2600121D18 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIconSE; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AppColor; + CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 999; + DEBUG = YES; + DEVELOPMENT_TEAM = 8M54J5J787; + ENABLE_HARDENED_RUNTIME = YES; + INFOPLIST_FILE = "PHP Monitor SE-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 6.0; + OTHER_SWIFT_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.se; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "SPONSOR DEBUG"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + C4358BA628CBCC2600121D18 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIconSE; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AppColor; + CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 999; + DEBUG = NO; + DEVELOPMENT_TEAM = 8M54J5J787; + ENABLE_HARDENED_RUNTIME = YES; + INFOPLIST_FILE = "PHP Monitor SE-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 6.0; + OTHER_SWIFT_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.se; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = SPONSOR; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; C4F7808125D7F84B000DBC97 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1843,6 +2249,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + C4358BA428CBCC2600121D18 /* Build configuration list for PBXNativeTarget "PHP Monitor SE" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C4358BA528CBCC2600121D18 /* Debug */, + C4358BA628CBCC2600121D18 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; C4F7808025D7F84B000DBC97 /* Build configuration list for PBXNativeTarget "phpmon-tests" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor SE.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor SE.xcscheme new file mode 100644 index 0000000..1430354 --- /dev/null +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor SE.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/affinity/icon_se.afdesign b/assets/affinity/icon_se.afdesign new file mode 100644 index 0000000000000000000000000000000000000000..54e00d5da3a7344fc160c75ed2711fd14a6dd674 GIT binary patch literal 52343 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 THgzuq-wojD>gTe~DWM4f>-Xop literal 0 HcmV?d00001 diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/Contents.json b/phpmon/Assets.xcassets/AppIconSE.appiconset/Contents.json new file mode 100644 index 0000000..64dc11e --- /dev/null +++ b/phpmon/Assets.xcassets/AppIconSE.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "filename" : "icon_16x16.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "icon_16x16@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "icon_32x32.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "icon_32x32@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "icon_128x128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "icon_128x128@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "icon_256x256.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "icon_256x256@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "icon_512x512.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "icon_512x512@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_128x128.png b/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..82ddfd30e702d44ee41dcf720d5a02485fa80342 GIT binary patch literal 5622 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 QiU0rr07*qoM6N<$f(vJtiU0rr literal 0 HcmV?d00001 diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_128x128@2x.png b/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_128x128@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..58966ccfc379a2f84787eed8a16b3269582fd139 GIT binary patch literal 12448 zcmZWwWn5HG)V{lPgY?qf0t&iPE?o+XG)Rebcf-;M5-Ol{hjb&obTcV^C+bD#5^GoRop@&vdLTmS$F6cuFN0RRY91OeEXsM~wzLURBRuuznd z)bIrDO^p)(OxQB*!Mkl1G>j`VCC~edVc&^R4gdeOjGGW=%-~LRA6Xbq8%f#Qir<{` z8Z6v$>r5p@b!W208m$tB*yi!n0!5`DSaixrNbZwn(FA`5%a65 zNz`J2nwy2blHdQvBjsQu=1NO-G9gLxT<#g6veBQ`*4DD5q@*u;r7v{LXDEG_mRPWZ z&^iUghNxUhoWmkgMMXt@KKZxFHAn`u>b5QAFXSy0x4kZ0V$ky-_K7{Ly}Wy&A4~%$ z)lYFzvkp+jHd)96-K=ZGg`>)OFjWQg$5u2Ifmhj`u{_}bY$A!_W*0xxRH8w~9S|_6?j$R1+%#I$C zr*U_8d(Yf^vRv~a>sTLz397OD+(0p>kzD{XEVlPA*hS;V4r)0M3z&@*l~|z$)!;;P zr{~P$OB)Z%`TLGio2tyv+{xp4_;pwTcUgMk1j(AuZ0epkt}j{o`uf6T?4#9ZI8rfX z)+sD0A%^z}h|MJphkql_ z1+JG|8*3y5+c6IN1ZefIZ3*CyzNtd>13>B+Y$ z(EwvY=*Gg8cFsus*a0^<7srx*E*xv33bzJiaW?8u)z6N0HJcY7^AJ*uv^fHrmrGD% z+FoqS{MaEN7MSx|@wyt^tG`CMz{R+Y6@cM$bjj9)sw6vNYX($hXtCMrZY_ctq0x&O zxpba@p<~MmI*i%lk@<S_Q>Gk{-!D zDFEtkzk8oO`C>tdh9JfS#KspF7x7|$slz5316uV;4@yi&GkEWqLy)99(P_(HSl@yU ziXW7eB=9?B)hk$CZ(9K{mTW47YNB6+5t>*n!s ztpzxlo16RiRgAB_W?EPEL^o9IfGqpZ$}4hRwFW%v4UkH_wMF(%-?3dvORxIhEP*}? zUh;|p*d_py~(<_@!7YwFx$%jc&QeVj{hY`$bVglk>@zXWL#8cV^3m{3dOLi=yzH)inCb48Y#e4Jo!5sf^{01BF#7M9F+uth) zETAEu<{8PPTbBZv{U&>IZmrwJ{j!gB!!Pn1PVFDs_WayDHD*p9RPmg6J;92OA5wAy zDeRm)JUH7?i@dlf-p%*RHuvD*pv<-P)bK)Vqnc>t+i-)0$D#AXj{>W{fyI{7^e-NN zvj`Wnw|{=wuv%WP5^{DYJ7>_2nREM;uUAWYRPR|bclmP*T-Ir^EULwajtd-h%3fVx zhaPvgZ0AD|?#>cFNkhK%LQ};kR312g7p5x^BzLJ7&?o$SymA)u)8x`~DuM35<(J|3 zi(9y9bK9%K{kHpmdmQaGdoZV++3IJ1R=*xYtji>$LYhGtP5>eU z0E$l@gLN-Uz(!M)5a7+T)Kx-`*x1{de!I%M>yty9={LD@!>fhLhe|@0?{t$J>c~n( z3_o93kmss1gpB>p0QWt_W;i-_KKrxse0FbLzP`HB^}*wAfB*HpW%tUeMtv(mxx>S` z6>yd6moMfkzyBWL4PYZ>k3@30eNA3BT$%iX)2Z0II6FU31Mrm2;~u`%Lj5Hrp^w(o z$t{AZ)2ZBYSm`_p1+Q`QZQ9T)I&a3GtEi+uO?}^SbY*dUt

?RXW#eNi6VZndgWa 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 literal 0 HcmV?d00001 diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_16x16.png b/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..3fdda5be5ff5e373305c4380e55a807d02e3df21 GIT binary patch literal 575 zcmV-F0>J%=P)0g^0_~ zi%n-ele66Fe!T($*hFpA!-uFWaKZ%QLMB?U?K~GEPhoW859tPu*4A~ z-833a96gXaWBdz~_*c#|jW6^@7{Z$6Vir+RQ9;7J$eaef5k`!>vqZv4Coc*o@t6Pr N002ovPDHLkV1l1=1d0Fv literal 0 HcmV?d00001 diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_16x16@2x.png b/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_16x16@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..64ac9c33ee644b51eedee8b5fdb75d06131617c5 GIT binary patch literal 1208 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

?RXW#eNi6VZndgWa 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 literal 0 HcmV?d00001 diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_256x256@2x.png b/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_256x256@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..22c2b158ef17842e30ab96f6a58ad62f87267d64 GIT binary patch literal 28480 zcmcF~9J)Ic1eBI;5CIV>K{|$zPU#*%8U#T?kW}eLLUL%3F6k1GZj@%; zgTH(4fAGQw_%LUmwbowySvRYn{-Je**2z5Y?FE6lP1h_TsTNod4lmkEZ|K;cJP=XdQe1uTR!Ii27c%X1& zpZR6gX$V4fIib-S;K9bWb*ZLYWX`!CGnF zX#4Shf2hN)z<;;+`T3hUlD@uFcJzdQ^0<=N>K)qO*!MgL*;X~dyoA6fPhZ2ohIT|n zL8H)ut)b&4K{NTZehral51z76M2P$f%=;aRnF!5JJfJ8ZD9yzDPV@eDII)Jk*NRVu zh$^6HCO_5Be)nG6=T=V^UPT7)wTYjl1{*)tk6#vQy@`&F7Opv{E7W@ASvqNZcC;ux zFM4Ki<=5ihGAp&@=r`RGI3^`+=eKe>)q1P5>l$7;*K$xj>?F3)Vt$QmK~0~3JmOfb z`-?&mWE+i2+Fw7KAD8|q>FB3;=2w5_Nh|(D)vxW`zBh@G!}AG&r1aWEN0Rvu!SzpX z)IOonID^m(SGBXn*2VKw{}kPu*|*zS-7eiW-J;#{-Q?Yq!c8mqQ=zo;%b0+?`NFZ> zcZxUrx$z@(wH2c31DCD+NqO$Ddj>VL$f198>f6|6!7WO(R6^NrR6Ni73NQf#1GLk z+1Pt3Ot*Gu&f4v3e18bg*eOW5f<%;OnPTI$pYea8AB*?OAlgw$+>)W}Lh1)2&(l3G zZxqxP4jl+Z%$0oYlz&@+yFjN9ykUL}RE+(SnKhM0F{WD%zfy|Wf+8^~p--07jurz41-2K*ihgqh4{@Vo4@fVZcQ9&lr)*@n z@_c)HMVcW6toc7?DowCdYP_E~td6aY4jNF`qEkF+;B_Ef_Jd{|3n9dbsro)lsXw^I zowAvTDdN;6&~>%->x0o0PH*ncb4WH2iVXdGLiyD}1vluo9VGV#ZI$w&`0eyL4~)$b z7XrwzqjM*8%ZJK*=0{r~Lwm~Ca}%j^y#41BJ^UH~j-DiVcJq|g zILy`Vh;1z9d3t*$96#aN}`bQf%gHMJYCma z#ENt-@e8`Xyn%ooprC&k2xpe2)6^X$0()t^3<5^W=8A!axrjyqlvN)zXRh?lW z+L1X(r?*Q?BA;&5nKaJ#rG)}Nm+P0d(4Alb?ApZe+1tl9eun5|{xBH}>C^mC6xW`m z=>Z|o74ix>lXp}X|I=~KIDdXNvxRsR^VGZrth8qe{*VVE;hTGw0t9G$u(okzP0I)*F0<myG&` zhD{$A&^JbIJUIa**)pAXki

3^G?3vaQwB+}_sKcEGnaWWRM40^|YWYdFW4=Jj4! zXx^(Pfm$p0TwGjR!Z$`w(cB~eq_dLs@U$ z9>|<6f%fGApS#l04yTvAHg7%0K(km@IAv0Sm=j$%9}H1{#aMQ5D|+$YCp(VFn0hdP z_cd3-3QcEOU$30N1vC-Ey&R9#AjgbuD6^fPrTvRn<6E~7fGM)6%{MTu=}G2jm?l7g z65PBEFPhx$-@F|M062`PJl;wm6>V*um+5`w6=POrW@csdmCl`!*2f;m)*t+9^wjx4 zDdGK_43x&1wBe&91~bY(&mEn?540z(zqx3u3>Ubehj0pqeO0B0&^ zwFT+NH}4R1=;0_5ryJ6AVW+Ygc}eEgb4CErg?_qsP;m830%MsMK17I;k8_F9dX$tu z0=kZTo(CMin^vCP;bjU(v38sX9>8!x7vcC7!X*H!C|+v0wa_giWp#D+>&Ddl66b3| z5Fj9`nd#{qlQ&{r!qxZ_KJ=pW4ia$buXj>eEf&g$qJHR`l z=}YrZAO^+ea4qdbd8EqdCa|^z{MO&jW)4He~*;x z&J6|(g!hjgxijfOQ3O6W@R9)~Zf#+)2H%)3r$T`-_9=m^Jv^rHesQ^qAOpApZ>48Z zYQqBq_BFU3WOpwuk%KV3OQ@U3ALwR3fc7pykcC_Zwe#)Vv!efApo#?eSED1%lA96IvsFWf-0s@OJLbMyAM?8a(2PG9_q2qFeyzu*gGjt@6txz zlr1x^&R{WN0?tslo0peYQ7-#|Gn6UX-S?#h2PXyX3s&(L-#v6+2(c%S3;i)I$j(+* zNX(=7X8T&<>MyH+bY<4afX6S6c?E}`%h=)Y%hF-#@5!B_QoG_ zC=l&#-ZzEr-DeRpNxnuanBY7t`F(S)RR~k9w{wm1Wrdp+U2QY*JBO^drW+fk(^u~7 z1b&pka?+UQ!#X+Bf3-!DcOyD9hrgI1BlOuF9fK@t+w5~gA6*AjyB(mPN15Su(YVk+ zsDbu*VHSS=>QqnA;Y`^@ZSu9)GA5_mwnxRX#*#Hlo+bpB_(icjnM25LDT$S4YMiyr z^zRl-!hZE$Flia%AZ@Op`JakpfFDMW8sGiM$Tg8`O?F!@CX%ST2WiIoDQg;3;!6ZS zQU&tKE52VLl`0kK05}lg%xzyk=bcz~K8Tgr_0mvw(U5#?kU@6Qu{u|eAGg7+PZrfy z!|V12)3PP)?W-&EioFcpKNA7F`AnalyoxVB#Xcq*p~vp*nHS@=;tk48YLX=+Y?(aDitIEIF?NPsP%3aK7!LE@fYmaGLDHS-JwDwzRp0y&|daz ze}+or3q_bpKl0OSC9o7@&Y|RNE{W(+UBEDtNk9nG0siQc=a0e~w%n0sE6Hb;j&u`Q z^~CXaG33ht>>+erK(>xcdP(Lb1M$^@q0A3n7F*JCLS&RDAsOCptetaxpr6 zw8XoM5<+JS5L8FB%EnI_e=v2vrii%cx65WS$(tHDGf^|+SZ3cVLEr(XGfE01yHdjM z$H3+jx;58_y>k;xPMReJCML4!Lae8|TX@IJRlK&UVicuHo!#ad(=K^MGy7{8Jm}u| zYtB(V=*h|5*?O9|oR(q0LPROT_2JL!Q1U|*6;QNTP~GjX%eWLLI^9+kMXibKNOZbaw1xDNGWf8LCDMk=0*1jn<{#!Ek^m`7SN zh{46_BOo#wAJ+Hv0DFvyTFA(@EHNqp zR#G+YlE@UH9&*I7(|8}Fn&CQHXF@L_CZ+kOxwVRmVNO*M zKDCjeKOG@+BHIU$vuqgH(Lx^)Ns`v+U z;2N7dk*XkuxAyuvfJdEUS3EV!}>%kN7Kw3v=luFCv0sAFbb7eN%1L zqhok_zlMdBp8@CZ%<#MDgn@Y^tT%-cB9j4;c{60~xZNbWK?Ph}6TLH&&oq*C!R2p| zj*pdlA|F{>$hn=q&=R+kRfy-?5)x52UX4;trCN72Db$jrQ_HD=U z6N=@2xPX%*vCB&kM}y4pp@_c#(J!s~MvB#@hxFv~E0Ta1?c2{@1no9>> z5){TCE>Y>sTh*BMX2rv5A?)s3x_;2>N&#>Gl-9(Lc#zAjS#m^$`IL|SEktJZA!6tO zzLCfc&n6RHilUUXNg>VK@kVZjS6C|(ohN0oBI42nU2TlPEA{8CAial3O3dpYrC2HQ^ z$DU~qehSD`&qyH6(Mn;!tSWSk4>_8^5(@|Fp&`zOf4?gTU~eoMp3>^l#z`hc_VJ7{ zebTk}By1N|3vq7RcyDzC&$bfok*jpZL_C-mStcfP?WDmP01nBZNWqu$vbUEE7pyA9 zm@9Z*-zdUVe;`l0`gALWDa3Ed-$|N;|6<2)|E_-{o7t5+JiSOLrx8Ew4{)vk%MUSu zpud0!IkVCjXM^bN{(E6>ueCjoDW*@3GgJaBxN&NO$da#0x|$>*^eCK;MW;^sw;jKv z5Mw66BV{2t37vXCvO2&UZANeaCBg1X4*bJbmD_|YK1I!|o zf#NO*fC;z@S9A97Z2ms)j$C>dCQlL;JB$PoRNX}ofhL9JBTXg_A<1?oMCks)LyGAf z#+7RW%Y4W{0w5~aenK2juO`JGrxxX-Z$!~XJ=ZDC3<+wDVncJ}ihJtc*scn^@1#b= z3g6Mn-APGc3IZr4)NWG)1w%_kU#X)#d)}|VgG%>5tVP>LNuXn3*kh1A{{)$S;}0zR z1Cw<)BT(R373aYSGr+Do3S=&ZY{Q4cbI7iZ2xTauGl1{_0)?Gg)?PzQB1Jqvoc*^K z8lsyPWDhJ#v!#kacC440s*2o27t&7khLZw{>|4V3Dxfrggp#BH9{hkcA+VwUc0KV1x=>`_U*VF!Clt zF?_1M&jc@!_*##%7VVv$Iw@-{S`_mT@F4;VzyrSeKmglw-)tR8I&sRkRjqzNm99O8 zVg0@LDQJ-_n247y00P1k#L|DvCkcwc=MNu!sMy=X)^Z-qa+j+lcRuN3pQx)OSEq8p zSuPOY$3_F!Okm0QHe=sVaHZzYijg$GH*B`>iTp5;XzfH>#sv&DsTl--%H*;rt>;1! zUG^Q#MsFr2T0_)OoIh1WAq1I(3ei6;d#h@foG7BrVmSL>gQp4&z(i=~LV;KTBYl!9 zVO7*!q#~m+PrV8lIKd5(xqI!as5_U+qw#=m-Dms0Z=A2KI+jFks&z{=v5~847VVwd zGJKuDVFMgM0!;nKwD+L^{`JRedzjj#d8K0;OvJ~L=7lI8@FYSRaN-4cf$LKIKzWSE z9!-qhJf-HMH^}X@M zF9`Y3Pj{_XVo-_ZC_)h`L5s25O1Q)VI}`})NVU_cc8RhGT_|E|l~Djo#;iN9sIuGl z&7YFAl8j~1U_sWKoJ#NsLB)DHMC#F%1W;EZ49*;UD z@+kV{(XV7L`{Ed6CeIU><#0Hi7-bBzG}g*F29%Bx>T;gXw0rRgeNHXBxYc7|S76@X ziQE1;Io`9TPtW}Lz~DqM&6ss?7_537#>up`%rhg$1EoDi5$4iV&WA79YnzCJUMrec zSX-g}c2MaIc8EOR_>2Chm1i~yv2oUIUc*e&xP;#^S{~xOD-2aN#Bq~@-dM0729sCl zRbsFvD#i=Xz>|uD{Y>SQBx}xgOlaj;E#SXS3U!;98N`iodop?C)P1C|*IRY|kk%h) zh{}G#>NVmx16zdTfThYTd|luBFtrz|is`kimsc}so6=?=cdq}VznUk^Tz^}hGY>(n zy|StRORPbLY{2L{^cNXfQHq!ovi*N81$cx$+c4vV;#*9Ub7b6~?B=gd6Vg^odPV48 zt&;I#szCk8i6zB1Ypd=XLd(@iHhx~u*Y~wEA{4e#+C*GFZcjlz@WGktRREEF^lwr( zJK5%J%$Nj4u{mm1@MGnNx((t4;!CiO#d5g`P2Vo^VWtD>Cj^|O#k8@$Ese&R8mbuGmBi!>4PP@rf zG?A27-bCXJQg#0%QTagBBHKL*4!>$zr z@Y*@B%=jFii_LlpP-HI9#NA|tsW1OZ7%4rq zi8%LmwOwDa78K7BqI#2U0Xhccr^QsU$#AHq%ZB&8Qpo;xba+IqYd{g2*oyTg5{dPc$a!-Ij>nLMp%?msAX)V;F?oS+ z_BP$O<#JR&)9JG0Q8Xm_tUGu3bB#tSPlubs2<3|^Lky_5Hb=vsl5i^_mgQd0Has-3 zlv!S{2LP@%Yp&^gJpMRtOVGN*GX@}CpK$S}D`OV|FWfp=LaX$$qTAm|fX>sKoO4;;Oc@oLAcE!d8(ojCe|vqZhmUy20nqLD)v{EQ4>NpZyc_{2 zU~=1p3qNV?pavWpi=_br<2a<;&9=6R)yebasTLcp$8#g=LG~qLBr4^4`GcQ18=Ue# z`2^TC*-{FoQ=x$*b>9rg#;g#$GN%F@R|VX!KS2^HzsCCy^G_aB=awjBzG~Yn+!8r< z;C=8GZ?!}6UB4OK?N18&{_a9xya(o37dv9g6zl+7)CsG?$30=rL8~~Fju)&KGIif@z=t>EAYNZ%qcYX=Bq8)@Tu@;;zY2l}_#M#!GiF`0a-)H0 z0Nu5fIP{~Dq>IkdLw%2;Y#D_{0@l-jF9c!l29folf=Bat==NN3or}YA6{v8c-78k~ zVaFFTa~`ah{!bjzzCF}_lj^L7;E5q_p+N+e*I8 zH-}Qik4dWQK_RyWVank$HRR&(6A*ZHOMAwm^hj>fCumB;3l>}U7M$J7LpUQbAlmts z+^P!S)uz$E%R0}|N(m~YLJ&Ndz~y7Y^t7Q^gDFguPGYuhk?{G_lq6PoD=Zg|s{7m9 z8ioqr;`yR3+ycL1E!nQ0nHf4eCWGb;#}G5fnZJF_z&rqCUqwS?YS5&#=b%9S9BQ5Z zsKTI_=R9#&8>9H0;<(aIvRIw)r>G~;#wTq2D@=%5T<#h29cUJx_DA2C!&)x^ry=vV z)PS2mAE2&M?ptU7>3R}rqlymKlxLwKe?IBcki(=mKNc*v5$tHwB6X8FHXJfq{~leB zV%%Y@Am=T9x%^$oVu=HI$O!+I;YNdfGCvqGNgzR`l<@#1_I7iz{AO5O8e6{jXP)hu zl;&Qh#A)~tR)j$%9e|J57K7ph*?rAos_b zYy+vJ$l+`3*XnQSn43=Fc(b)DeC>EZLjx7?EUGK)k7DjM1+bYT=BS`Dn6`-x9Ph$B zT8IPs0U23~`Az{?Rl6GAr*G_`!C^}n7l}hmfv3XIcYf3{T;+j&$>^$seyk#SgB9BKkH~XHD{SR$2f%wXn$L(xEC!OS0 z@vU%~EvvR^w?`zyd+&N;9w?>qisCn4P01G%unIVsX-p}SdKJqq7GQeU(6RN~N@ln% zJce;)ryUzU=K4aif5Ys>g^~oSjeq@%!*?pcn)1B&1t4P!sPjy!U;z0~>{3BZqrykM7QOa}`GoPr133$)QNS!J~F7J}~h z(T@T%FDHI$VFE2w0Q=4Pa>yU{(oG$A)N3sB>}O`2lYAd{H>E3t9+jcbzhPc&Q+r}< z`ks5cysDV2V%=fsA%?nw&6I(uZJM!0k(IqbtRaJRBdcnr@W>VWZ>p8aNb8A+9&CQF z&SKkP*pJV?g)Uu&laI(s2l6f7I?l9DJlrx}yvQ`9yLhnqvlKBjr8Tc#gSKrMdWguW z!S?Zk0-t744!lt`&nGdHPR~+Z9l$ckP}@qY@RiA>2itS?F8Uw~*N2=Oli6P&rArW} z@n19}#gI?4NnJFUsjnP~+v#kfX%ND{MftSSwU^&rZw*~J(;h#Tf+UA$08>Z%^R|qM zc0x0gEg>7i53oh~W$2mxh7f#-<98_rqPq1<7Fr>?Gk=ck+4D1ZUi~^DoEn9cU{n9b zU?UXlbKokMv_;R0XZde}9T^PC>U~6VzV0T(ohBLLJZ**42!3Rfl})PkedqUxKD-r| zgrMk$;o%LM;PYcD(>ucC@-$X#xK!oTn}g{qGaCL?-KH6HZ0{T?TNm53fn$4nQ-5O?_BqW7MfbdFY! z`}xTz0|fq*G0CL^Zu%t03@^MNgfhg}8P8591JOfyl^PVwIf~n3TQh6`{1Z&J0DA?uu94g{pp@TjnmGP2 zV*U*dyi~5U6d^2C^K#?*_tfbDcDAp~97|~xw*Lbm@<)uc??V%Ko@}1*tWlZ1S^ef< z7aaLtW>_;9_59h@BV4ES7rqJnr32k>mR6j`dA+vBAW~29$f*OS`-T-gHp&rf-bRUe z{mdHn`vJGH#S5SMei-}EMpQR;xhTzpQ>vAgxTF94A1p!a&3<$~Y=nQf*eqJoIqQ+B zoS!}`|4JSYtZ$Zw8PuWO+RL11Q!L;c=L9-cTT;+nKSn3WfC3IbzpSf9-T!G-9F)E061$F~3ZSl@#!{WzPzBMav%rYsoZ6vZ4(tGkMY z1k_7&3%zLj@B0$~bgFwO0dy4_?=sCOK#|hB-LLF%+h1>y0r}kW(nnazV;*m1uqpe z8o>`u8Q;x;Ww3QcTRXoHd&j%3&>_R!(pN7Yua0FF_Lj zs@x9nI-b!wUd1K+#Ofa7EL7Xfyic{V=On*;yMI@=5D?^!HZp+O-;N`@z7D#WQ%jD1i9|m5u@{8F2K&^v0*Ta z1$>KK%(kJk-|&;dXR<6TBt zZVVvMPSfP-tS~-Rvg<8WP1S$IICCFtTfR5MB+W%Xp3Gu?ajU!Q&vqv}Yv&Va8{(@6 z5^hb4Lh~dN(bB^Xs4)f=FMqc6^^4V_k-UwaY=>=z^0n{9>T(@Yt$^nx$2@<2&Is8F zAVFdz*e_Ybo3uI0MgwecDNkgu89$!m_4Ou^oOkqRLvGxgJ~`n1@Cjf03g=M{ruQGo zd%TaBN6VY{1!@6(8|7M#y>2v01NEX+1sR|69`2ID9P$xd^S4RRud^zzz8m{&-vp~s8t*|oSs73EX(N-wMC-frb+Be& z0`E%^T8$-5B`6XA0|}9{+u9F;v+!K6>gh{b7^wy`r6kIHe%~vEvD2Ts*;Z!2vHY{@ znGe0xWT7h2NUknDb4iOrIQ-xu$0>NU(!8udrS-0g{>r}+a~Jq^7}w^!>3?%DDdV=C zCReWHX*9jiV6}SggsHM0G^ga;+OL$20gMo3q?~nW_~_Ifem|%t66l>;gl(4cwXa(K z?2L3ZizUu0S<+V|JEmp-H3z2#Q{d_RGaW5<^PYUuhCil_?$=EauDR4dLZSn&pu($2 zy*;LqYg-^J?#BeD?~`VBSK$fdLwfOl8eg#xYZl(q?E!=#ZNr~}aEF7pxb9g6*N?9z zS$Zw+qJfDps6ulTqhDe5y^%sl6Ud*iUeZRa8V@#Y$W6iu^hoDEgg#6YR3BRsw{fW| zD@CyJP#=zT{P|Xg=HnaV%#544z%)c7`8rCW8Lp?PragTLAHNtKYlWPF+Q_8f!D^GheS}&{Jk0 zRa})>UT3d{XvdFz=ULVvqDB|Pg5I`iuW0-~A6{j#+Wd8Q#^?+W{4V7u| z^n(YL6`*W?_Zv72o30~JH6j=h1nj-}gBcVW(19v60_yjLh2)2agtFH2AAHya&=B7X z8^T9a*mC3ZgUHo)6wKUX4l6k6&J7#++6gW%444|+NNp*x@3_;sHnLHC<5#b!f4RlN zp*{<2dmKkQi>(mLccLRQIw(yK66d#pj$lpj04kW|r+{or4XK}tu!41}m0AIX^Jm{I z&EIaF@Y`B8JTniz%c9P@96@__(+#Vf28Pb6tv}DmghsZE{rbWvN(V%|xTy$Y$EJ4Z zW;2ZYFb+@KEi#}oObgNtaWpa?M8YF4JunipMlEA_lOV|l(LtQr)M0mL^?EcbeGyyn zdnQ(;A5mCd?CA`8fj)$`clyNTS z%2W&7m0Fv}3cBu4f0C%&`$}IzfPbE|jjT7CxjSG;{8b^+rzj3VoMHnLL280);xK@q zjwri*;o%g84D6CEfUtArL{`_IlZH?{q_yjd(d-A?-V-6#?SO~m?j3~w_emA-Uyj@# z0z1O?;0KMxPB9hRnDtZ<43awwoCE)L;j~+M&8fL|9LqsBEz6(M$jh}U=;%se>7IQ}#e6+!Y2&&k$ka%~kP|-sR6!Cpj z|4G{{?%%%Ichyr1x?CF)`AZeAGvLu=J#3**yo=eFuZ(e0AC*iP77ox3KUtMGS`PTS zI-e^3k-f;5ZTU6J6xf%Yxkl(m6gA0~%Lh_|Jc;xew(i&2zQaD&KgWW@LP;hqZGJ<) z&J~&}<6lmoa@c3Bqzk-V&sy8vrQf9iK8tBIzv8}5THG;)8}S&aM&nkj#4I&pn@3Fl z;!EEYcUm5+sdFkxP=ChcUyk)Pd;Y9)R^(8lU`K#4`&PnZG<&~)31v*B%@C`R|5wF0 zHJJGG<55uda!)kT2r3ae(vt{jT90E=BN3D#6a3(O$Z^B-CD*s+)_TGn^HJrd?@Ybv zB-Kh{3baX_5U%*?SuEloOlCYv;5*N)0aDJtrbbNjoIm?J4&!4dt z`%2b$Osdz{ooI47M~}Ucq#zJ*E?TqaO{*KR%uX<_iVab;$eE7@WRickKXKt~`)9v$ zrP;~cWewWPZ6~AGWXC7xr(fALI_4)*{zyWR)p+i|grNK%`DHXKDoyeAFL zn1JvMqMwC{N0zkUK?GZYcrNXd+2SQtd-t>jVOe%=Avp?E^zgA8KsVY7MUKPHrw_qv zq}xb#_BvaK_Q-0?gCr;qR_+w@6E4gQ9rAmr=njak;cbl=)AWk33f&0yZsie@#={h^$j%`ypZVc>**Zg4maO@xn#2 z#u>u0j!D6OdhX>!YxoTMxS#X$O}7&)IOiyf9kblLDjBVhlD$9!?*e|`;Y%ax)}9Wf z*P74gUbr9N%7wi)Jsl7j(9L|C6mC{=(5y};LHpLcm71NsxbQ-=45CfIBRBn9^Opg5 zVMqnMEgBTGvuw*O*W$=7Q-ghin`c&;6J(^}f5N4oKDxT%LnZYHYQ8bm*;FG~ajW~SLHXB+nyiS+ zRveSb7VMb7GoF$YS9AMqMWYz@YmB>l0Wgp1^PVmxa|&WyX|U5oH`Ao<1rBA|#a(G% zE|vZ$hS(0ySy$=!BG{CsFunVppYTmEEj47|5T2Dq6Pxq%I-06nyUN`c63taVwuxvE z*;IeJM8hKO`f)7Hvo`ZA~;!vs^C7dX}mYg1&#-ny*ZNKX_ER zd|-<4DOnXVEiP;9f#IFV(JLDtS+?X`=jYr=>>9dDE*%sg&C@ zPPswdw5R1Ke}nJ9%gN&htB!*zr!7m492@f`K|zQH={VoYg;rCAB*UDVVu5lJ!MJf~ zzb%oL{8IR1{NSdaXYlLbG+tbb{|(=H!MjpK<|P-=S@f&$*Wc4n`gMFOz4-QUpE|U% zN0C8B-@h}dF?b91((Y{^JhW!|Y-)}}e=yXAkgehKc}c_C@*z0Iv&(dfc17gef#}^e z&C@GV39+3Ty9QE0-1D(+^!NXh8BQ3xKLu&ojI?E%4=hc-iM>yUV9<&DIlZ2sef{y& z3Bykq@6QR@voTa45dnUFr2%-Q_c@PbzlDeLuJ;(#9Ou1AvS?}$Ch%P%=x93#aXt-( ztm}yOE>)3NNW5eL2ZHV>M}GA$Gu*!crA{2#z7KobK#E(Gq?fG32)r;zi+TN^a{5e_ir|Lo1%o$Cn{KTc0Oq6!K2HvO z+l;vrrxEFe(zEuk+NO%w-po$gwSYHu$6spvnfiQqZLDCda%ivu`|xS#egfrBmVPk4Z-TZ44TT8HM zHBRw=k`+k1R>sHti@2edtdH{+`;;Qgf9FL`VBd)jOfBRU z2l4t(hovlVF0sH`sgNl|JL|O-_p0q!?lPUXZwz&I`~6R(aK_DD>(38R&^CAucPlX( zj6Hb}dsJ;##ibq+GghxBDERPUO| zI@ziMnfC4}W|2R`WA=iseV%RBt|QV!!dKb!SPkuy{%KDT5op@GQ{p3FeQ0sg|8A za00cZ!#48N$m$MX0?p=nKZ1gZRsLK0n|s&`3>J3T!iFr(my>OgS)%9B_rPI^v?Pye zYUzKcHzN5;s!Si^-ymzf5pHTdIg8g%uT1!6d}-lqZTV2Atz96#%8O%}-;U&-!9Pu{ zeybEA$xsqrh1+uDoK4abR)sC_=!2Kmp+gi?kvuMcZ42p;N+4&BOkhW4L3$0^KgP$@ zFHpA-li(!hcgx}lgXns)z(9oz3r*yF{C6XMxgu;zxZMnx-gXmMp9!r5pSc6(CJ77! zX*UCizceX`tzvKE9VztVSgy7s)mTw{Xhe?ogL$MX z3CEJu0NrVV-6t@GZugQY#_skPI1VUh8~N@3p4b$%nKgh2QjvYzdBM)$SI;2HTc#>j>N)Z7wB5uD*Ak3^51s{p@ zek0WqIECCl|2o~!T&^eU)s{|n%noS#K@BiAAbqq6F6i1ErfIk6QG=4~!mFdApb;rNs`O~m-W074U)4Tanu&*wb^~ac# zf}LE6m0w;4K8A0DvoLP2p=4cWYB%T-0uPV699-C2X|m^rAz*Z_S_ouaDn+#a<2VoW zD!+3kFkUmvAO@YDW-A^vXsMm{eT0ym<2WDN!x$K|f)do^yz1j_!K^jG+x*FOWqC(C zz_|VD&f7%>in4Nmfv_x=(-8|BRk zMx%}+C=Gr6mCxCbU=4XtZ3fS36u<$enLvJ7e^CmGoJG-5%EFhyeApv6AnW)y{oaP#rHn zOt(?9KM!2K*%!t|a|>qO6D5uEE+!fERCf@tWswzX4Ca~H{;-X6j?OSqU_DS%4E58m z6Et0Wi`Ue|L!dZJjjey)Qgw|92({7e_<=8X_+)q>(Itc!~4{j2(oVs!yK%bWw+e^)-j4`!kUlZfl^fq9NYLcw!}i-*{a zOE^8Cu*qj53%fim53%IpwAN+G$!;m}STTrsM5p!eOzgh1^DRrdrGGkjpGF-^qg-ES zN6`J^S!PEXmr=?!7^&-0?@y!EB$SUtC9?6{0bW zPhXd5p4;ezWl!K>giGOf{}pDQuxz?WC*^zXLXyY%qWc4e>Y!TNhwrL`O0NVjx-15a zO`tq`u^pfQ^tXvLY|JUxWj9y0bUn+XEv#cN$g)yb#QzWDCck zNy&l&846kYAAV@d@cdYTV3L=OwQbQx1gL!7u`uawyUpta$F|K$`Ar9Eb&~pDl-Z|M z+V;KbK%qk4h2XQtu&C6%q=1~vzZRT$Gjl^|VcuEr z;)ULanD4YCmB-e2e_I)&#;RpSu8TD{LMk>Ecn6I1pq@+V#a*D{1roA7dB$twxC#07 z*ZzSQdYlx|m%#I3jFs~TA!!b-R^yK(_-$C?6c#^Q;ip~>lTA;MK%fXlBXAG^V|RKq zqQfd=%tMFVAht+n-R2OPO;pMX&dmu?;5yc(hrv zdK%yeL_HM%PGgr>lE-%)J00=n7@_dxSHnoiy1Id7Mo`bxgQVEU-96ZKY?tvyABS-i zUi^>$Sl;It__FcRn@1vg{mhJGWrFhM=EvUueF!V|-!s1+Be(te;<@mEfHxpej4L0s zxT|}AR0cRH`dcCJ@z(Z2o+t=X8r0FN&=wa&DHVq>{+VKT^fJdND?HbRv@{uZH$h_! z$M|ydS6rI#H5(AUK4m7pTzMi}e~?M1%PB>8heBSukR^+Mb~`%|8_Ni>qjv6Qen`4rG!JHxp){cE8-psdXXFm0x9iM1mb=*{)h>HPzG7celys# z2f%{mQcS>6ymez&W!aMU!siKg54dkQTP?Zv`*xyA5#rMLa;pjntpCBUN(mx1{n_-* zgF7oF3kM#a?O%~n;!mTzv47G>b{99U9?t(pA22Y2YMQ9Dm5_tqYY?Hi8hZO{`8!-B zK!K#nVt6!W^|H!~d@y=qpv4^@B1807kob-o2gYf)ky`F}jCzd~Ecl&4>L_ z!h5S%@Wj&X0N)vV&2+y98^aRC@p!T04U2;q_cgIt z6EnaHjFbjsDyJ)KP4EmL7@&`;2TpWm~{pE-0Ee*2@2QooI-=UZ4`<0Az8hheXi(`+eCM78%+ro{uIFP8hr4>U?JB* z6?j6&8gLxCMyUii6AHP@{k?g6a3IW8AOl$zKKwI1{UUV+6Fh)wK=w1C5>ceK425z( z=C{~<=FxA3p{lp-c(BiKk=1nz*gL7L+oc-~_9P=O5+*1j5CY5?|K==g)0V;SG}Fkq z+5FBl@9ISGg0(VJkPPqSgseo0LO%XiwRZow&TUZ6=V~wdiocXl1cf#-NAvMCtk=xu zgHM2uY>_Vz>zpeq2VM&~FGmw)?U3e9xo&Su^S;)A2}=8PS@!+c2ONU(nbDq!BSm!c zt-)f9jA;;_JjI!)+zmkb`{#sv^2kc1NMEy}Daz^|l@K>+)fc0~JBB_8uAVFIUw>{73K{%EhB;y!UDZan<6K#zh;IjpM4U^M z(QJ8nIo_^S`nI&!cfy509U29tLB&D(_Vt@;UB62c9R`f_+{70-qwo5Lzo@H|Kkm(X z0;pbF%Jj{^cO^-KV|#Kd;12jPX$GWnS-nq(!?$x@+wmWty!IK!X%MhJH@_qC>K^Ij z2217~aQCRL8q|BWd`e;~4DS~u(1ZHiB6T~OW!8YKI{lZ62i`~DD+GCWk zR@R@Omg~T8gtX%+{Tx0CO~z`j;27GeMVgbX1!A{lo;|6&gbP@5E4N2bZ&6))Q8aiA zD^#1|(oE>U^O>BqLz-YODPYG{>av;tcs0241;;c@*U0Q>BujpWbU|~&joG}6o-|LB zKl_XTww{9LW#)znj!6_`TN54CT%GB470uDtqUn-nEs_L`r`vko zhVv*6lkKX&gK_=H@5c6e^!0)97B}T351ma*0lrZDK+or2{JrLn&Q9d4 z)X8F-H7YlXx)dx*o9#;8s}rv0_0tjgYbnI{UaQu54;foC|RCnm_(_d+JtzisP6o4gC@P8nn#<_ymhIr#Zmvi*e9P1{A_SY*+gzeOC4i_V{ zzqL;dM+~%1gDCdalvDmDTdRdr(t`1k95X@nDac{(W>`!?|v7?4hj+rVO;9S_meIWe9rXtv)7lyCc2 zx^L^X@8oC?Umf?R1(G4=Z2H<(Dh3QP8bFC&r+$mkh9d zapN@LdrY0t-v?q-EHT^HAU8oAH&AliC20-CelU(34qmY-8b+o7HoFtwS0}o9K%0UC zkSi;hs%wo~oK_Ikwb#m147YkB*9sT~E%twM8@+a8zfs3_w}EEAFNB^FTb~t?U1BiA z4KU@)Ur#RCneX`{w^6JA<1wLZgTq)GQQ}%^(Pw%~qzt4G_X~+*->HnSd!hF}@@pQ=s zL_ojjLbY5}fr(Dy_iFMkP*)hvJU$h@DGe{-5{$o4enC&M?&Od|QO?>g?}25-K)spj zjXHncS7ff|#n!u2|Fig0-SD52UWYIM#^FKsu3X!0rd`AcqotgxY6`3AN7S1`JOPRG zXuflq3@w@Zw@UELQK^+J^UI8C505K*>mSs~y!x$Lv&QnaT(;d+?{bX-_#JK-nb|*h%<$l@Ux*~Tc-(g{%2V>!V;W3oJmp9 z9hf(7m-sNWkQ+H0ul*|15D-IQQ$eS%?PG9cJ<*}@g&JBTq47?xjtTPlm zyrVg6>{sG1o%3&!Xl`ZOmD%=ZbGEi31}8z5&h|DcYpdv|G+L!XNIY`;?i;y)Ko zZ#&G&Vc^(lSg(htdmta3Ys1msXbl&=-8DYUi5YAk!y7h9h(x0{{I*pU^f6Ke9tisipGfED_fl_g=9$EyhW zvsZ+Ak?rHkqIwV^(WfFOkrMXlXLy~=CJ5;K=|g!48n44Cb05U7`6L-hPfzg)Kg>A} zh1Y*9CA&IP?}6;!`*QJWhIO^XpN0Z7SF$2-vx~{ZnzB11gM`egA7t=b@$B%gPL<(* z)$aNAz;H=J?%LMq4x z5q3-01-6z0!8kv@i5Jaab_9LR77G8fm+*U=N0^3uw2aITn7nzYGq!T{mnS zQwHSb#V8-XIugtzIY!s+Os{oQzb^MYJ6S^dsBgI4e}RgHf1c5lTh#OWtKQ!p9tp)S zIIe_pWvAbs;s&Yxj2bJ4APq&D^^EZ`_iroh;K9`wH`7By&Nvl+AF#5ux0Q<);FFhX zIaR~!&{t^28D)apJ`bynsCC64ID6+W-whsr{)}g2|HeX&$!Ro)Jxc=CyT&OmZ)BEtj`p)` zsn@Xb)VtQ=>qTH(qqWsO8$6wyhNqsNKri`P+FrA;1v3)m`)X5#1ews;=q4$6^kDzX z8aEU@Er$p^;4Kw-&VkMCnQEP-wC=8IkiB0K;v@OLZ2Lvk5#9~6+p4FEK{rk@>uyZZ zJ~w<~R|j@Xhc{O>gEX46UA@KZ6B;?!_FdMUiRN%l%O?fTEh49F1&(p1RxT4iM+Ve$%EqY@i{FnRaCuhsmuJN zN2tGpH55dU8e!|^KV8)>D0^7pWZNRc$ztGT^5dzgE8Am2UJg3&zrh#O4xB_maFaBX zAe{W4$HA1mQlP4SY)|&vyz88QXgPk5kZ)1{t{db954+=9`xxi=KiQ&>XUA5lY$Mm6 z2W`gK#HPP_(Gn+^X3fHQVR6V&yO$uW<50V=)#O&IOC)aa2(3?Or} z8Ft2MOG1`RQKd^h%!AkU^5jU4w0B>fxbGHLLAJ>g#K$9(TSDT0Qb zQ7;KoZ8Ly3H|Xy>cUQt+4EQptUtENf1T3*Fs(e^Vjies0Zr zSN0(VRZl=E&89ljn75>w1F*@J6qhqEZFo6frbV?lJw(ZvBJugL0uRCi2S&SU9k~zc zMOyA8dK;?%GxSJN7><07wz|;h`cQ^3RYdPa%gXE6Q6(QpZ`D#H$lsawBgkc?WYVe6 zJGjB>Yi}iR^I+XKc-J85MKQT zFWJ2#_VwN1B475b{%4kEe|JV?dpeV>Slv{D;A3jH(f(K%rj`}Q?_?Pu$PwUs)NTFr z$Po~+1(?8h;~_H=LXnsLtwQ5Lt%d|0`a^ zKKds2u}^ivoH>fpx44fq`f>)+}E{xk3S5E*AZJ`)ye@9N1h0l`Samabr`n#7|+$ znn8nc_`6)aIBe`B5MEz7^)9hW_#CYjKsHXa@S&KHq-%Pj*8h#tg`cz&KnCf);yi#S zrA=!W3NKw6Sr@L%n3J!(cr~ znZASAj9%?%XIB!1!6*k!F^I2e{|*hTeK_7W#q6~?_XrvBVvCogRW=P&m4X9*Akats zf@!4hYKfYnZeyvX{?8^Dq@}n{XjWVGb&uC-5RQN^NZtk8^c(PWRfV#uICcNj{ZS;k zdUv#z&~AdMaB|klZ8^`eP`lsOVR}bskJxL&b94%4{eB@18CnK^sN>@icRo(%AT|&WyuQ9NWOryI=e+ z9fB*X`i-)huAZuLdwgV7B?a-_KAv`phrn<`;8a3vRAJbPX9RNfSgp*+N=`k=Sa5YM zdp1s5aNhA_fLyOZg?-Pn98gP<3O5~r)%9S3S0M_6wzC@KUrPrmR5pX0-8K9fPuO2O zaR>}yj=QVwexd)SV3~rV$sTM~*QfYyUQp4`eJGz6_T7wkqK6QB(NnkVp!|~M4|&`9 zcg!KG0=Rmd(?&E$iPcvH#NR9#z*S`FB@A>{yb=Y)?SNkSWuMjeI$hxly2Mp;leb@| z@(n%@M3=yv*a)xo0zGpq_w%(YYXm}NAb+OI#+WtLdL_Pwr0At(Zrwj$L`y|(paD9- z5+PuT8fdRFF+`p?P~xWNuEf2vmvb$T38Z1^Jb%Re%_8anZMfE>fMEF7rY}th%CpeN zqZLzfy^2UaCVKL(V*cm-g>LyHD;bihI&5YtjpgHEO@KUxIdwCdk=$6O2 zzl-hF6i6Makb}qMY+P$q5%3Y9Ys^*@nB;&`S};M{@EB&av{Xu*^vtbweKWk zjl-}}^uQPlnPz187&Ok#A6o$26WyQVb8mypxLr+&!OUSi^=pmEt^?jH_X~#{+=3PA zEZ6N>--8SYji(!mrhkf_`cz_avZ8U-NT92}M=qdz=gp1<{Lzo6Ui?_5>uJ?tud zLTmRW$(=<(Sq~S(@E?ovU-hE5TcKBeKaR~ZVF3ZqgUtv%(aEXcHRHAL3^imu(S;*; zeOJ~k;F>!39g{C!V4k{uS5eSvT4#Jd-#bU|x9qzclY}eQ-M@dY-jn((0nD6f@h}2& zcl1D6ggV8G%?Al3`LC2b(rc)1mk;z##j8uM4GMY(@NqJ1v$8+^$D@2sqy&W+Nm~A> zKZ69+;*7qtJ0H=tkB}{>2an_wDf(Rl?LD|y_5Qk4X%4)g+`F*LW6t?){pGE8g~t45 zUdfsK&)MHB-6hydz)+0J555#3{rd&e$DvuE?n>PMY9BZ|JL9|GJ?(awkIZFf)VT(F zg|jmXjc+p&e?MS!uxzcJ^%fNB(#HogE@j)hvVeZHO;RBxk-hw%q$1}0cLPs%8?)XI z5f(opx`Hm#=)||ofYF4vnFm~Y+^6l>T0&Q-=J6O}oKn|UZiemRJ1HA;ah3AxkY_h( zBIeP9(MxyDzRdV$<7SWO&8qYc^O&*yyy1ha{pFbUx%9NU(;c>T@A#c1)rB4;*| z5y;Wg3n%IVR!mOC`q$TmQW~VoTrjo9h^&%u56y zJ^x~w=K9TxzP|j`M+h@m3f%06ja3TfwOBFSe$iu%JxxPSNw$)&^@o5B0s7C&XKQZ0D_mXY08oIzt+ z?eaJ@J@3F70!$kgZ2>^ES`KI`pva*6_Y-vulY1@!%`P8Y{uumv<@ZYzh*4n2UznLJ z5OikQ^iUzp%p0sCn1Zc19Vt8I%X07gBDv!x>qcUWt;_o`NOyXUjHuV;1CqgB(hlOf zzufYcuIv9gNs*Iu)t1)k(QV4rcR=^{itVnNTm*?BgVh|yVP)(ly)#KaTL7e#kfP$~ zU8zV2QW=o+2arWZQQd{FKryozD!?sobu{n#@9Pu4y^(Liy+xUVc?%iJwd3W@x|s~x zTOO4a+SvF;-_=b#WNa_aa>zY$73nSHyQ~e(+`I%#<$)SVk?43TfSnxx5#Ai3p`q>3 zAL@j7fD({m?oj{UJ7#>#-%MHebCN9Tumddxw3P+D{U1#ncL-ne7ivFBEGX7-lZR5! zK@|I9Y@D*as?iYFC-?Qrjn`zJk z%*IR)U=VkvsR8wZU^RK{3+$DvcxPJXLojmLJu_n)x{>IYcr=jK1Ms!n#4-(Yw6G5| zL>aN#7aah&AsmfNKj{}oTR|1aOArt?y4{M7s-$)j!*iH*MtR}0ncVk*O)Z>f-Un+1 z0eJ<5%5UIXrlKGfdi{zVJCyWjV0t1oiI3**mP08>cx-P$IKX!ypKg2V# zD8q{Mx`IBrxMnx?gk$i7z&(sSMVV%A5+Vj~1YorF^*wY;Tb?pA!LaO{5Ef}^>Fo@K z71z^F=*&H!Ybt9ij0IZE1cx8kS7JsNfiWXK-!U-l1suFRlRw*%hit27b4W=^S#-bK zJ7z~lU;01~&^nG%bzZbZ??^rqlHJ^JW1Kohkc;Skju@Wm60e?N@ zc!f}r3Rj#FPoZD1_QqbZ1R?qs$Qer0RiYuC>8ixA+WFI$m;#>jw#Y5el}HW&=QPF$ z1g5m;=`_s}2Dwje`mBNpPWD@^_P>Oz_I`}U;pe1qxgsqS{?y=Lm<|B*uEWKXT@K~e zi;Q2VbfEIy>+fSOd`f1Z?)C9NQe+%`gU^8Y7^lgxdjqH5#sqK{SdBy~3}RO_gdkdpjF3G?=ist z4Vn(0#@Tb0xq<>OJ6)PvS~xNk)?*0(;0Rl8zBdlPt*hMxW)Qw0kR!3!CZD-H%>0mo zz~*wB*q_R4E?-f7^Qt!B^10(EQc<-^-a`=DzI%ws4--1|P&<(A!48NIjCI9v;F7j}p!pKk=kVnQ>+b`raX-tY5@@kB^yqxzQJtKEDh`;x3VcW z?wWnhKA|k|%-GPc)jRkE$|oLE*@F63l=|26qcZDPKa?O%g%8q~32zR|ThND2u$i82 zZoplnqJ+YrNDJo*!BUGWf}6(5FqwD>I4Rcv!_|88e|%8Jg1J_eZo9m%BWwryrR=<< zA5OBxcL@uW?tPVX3s$DLBKzw8E&MgwJV>VBy=2VkH)*f& zF!KW`oh_HafhF(2LJaYR+`!Ari;6#6C9V);5hc{+P!mx=jEHH#PI`1+^V*FIJsJiY%Z`gP~4eoY^G?>Jro z3R6$hWqzF*{dxAY^y;DV7vrgor^j>m{CX;rBqC=|YF+*n{BTX$wAEE7lRYd5h#m&) zYbGGv1Nt&x&+TR$sO(u4*-IvWUre5T47@b4QlXVaeKkep^UibyHG^`d3e}@eq85|; zFmP&tnLsItZBU7qILt^`uU&zGFVkZkt**EKChwqCDRCyO01Yov7Rp9nKec;t47}~B zfC5f*5Lp^9n-Ao8fSp-jM*Z9HGvNyWfUSjH!1~sn>UCV1w*#7~Zw&akvC&3Eu0{GY z^8e&XQ;ZrikF`zk(kp-yK z0%AWi_Z|aJCLa^Sslm$GwEz*nAl#Fe1hO5#rOJz2f&~E-DV{uqyvvK>(lpV}( zB!pT|!4~K(;OE$^+zba(aG3iP02bKX>_Ld%HZNuhkh*<{X{JEBh$P6sx})EV#N4lc zh>-XQh_J(Nzq--KZT3PpfKcWm=G(0=u7UXS!UN&0Wd+(pa^u+N3y z72FE%&U9J@q~8Izhi){1xNz(vFW>-RW*7A|k4#Z|)1JhcyNz%_no*=ZHG_8C59)cn zU29pwJR}6*-jU5Lu9v)@DJFGQ4( z4a`sbfzv~0L{99+fQ@}XaYvs@Y!~qyl!;|HLY~8e%6O8cEe*HChlq?J9}?akwXR@( z6#$QwW|X7^o_r82K$n7A-3GjECM?3>Vz@&xvxx2aO)@b&uP7xjRQ(0>o+MmH%Y>pW zdRddd8(8nr3+iI074NQ`SJD7ogjn4@XU5t;jxtU6pyXH(afu8I*mNd>D+uZ=lTS z3B1c1UIr^!1BkC!i`P#>uq{JY9>IvXxCsi-SCW+f++Z!{>)76~RD9BP|43il5;6c{ zyK1ClWOkT>&8cGRc)^DKBtzKBkGGUNkFq}RW?6oR4h0~vYutlRt0!_zXl_K`1s84G z&F!bgH?uIwpvQSc3?};|lhT(@4UTD`nxQY(%*rh@g4Oq1pTb6C9q7F&db1dCv|US5rZ+QCrQB=IKa&J`~(~$ zZZ>Q_4QF5@Uw zLpOQ89btbYa3}!O@!BMadJ6l=@I_42O3;Tkc^p;T^-SgG-Jc|m^#!y=}E9C%@vfBT2y zXUoy%TTq}kK8sDK9D>kIM7SDjAnUkGmjA4j@qDNv|Ni~Eaut*Q4 zI79}m#;RdqY^{?m@MfJTKn(%vg1Q<1~e4+E%s z__G&v%a|bTRL}c=0@LR=Db?%(yu7yXSA$P=-px`z2W7%Tn=#>dm6%{Dp6D0X4)cyr zS0aP&kccblaCSqu1`21qNx`Zvmn~|kx3%xqMxhQ$^s8=N8>;_}e)ux9%X3wtx$?xW z0UUO6M*&7IXOE0M68`YC8gat>9GBP(q5?V zld#?fU)=n4(eq*6gc%?y1YMB!K45{;QekKi@1ZudKo(#OM?WF zDv~^7gg7NhS$&67n!uJcyDKTqt7i*X8W|Zey32A9D*Q^mUgg`c$pi<)q2KR;OKCeg z8D9{BLA*W)u0{wiQDq&89>x!=!vzZ=wy~Qlf5HplA5i z>}`yoNYr#y8#)2cAETYqnRWN%f}ywBu|w01>D@0`3y3m0=7h&I9}6HxvxYelYU()A z)yxJ}Nd*OBvT5O-ZCuYup8Rt4oT4~gESVjK}YqG>nRVJDGGC7)DVg1SaiWQh$cAEY2;>K zgqB{N5vFU2bMp2ZbIyxQz56NES1Ci^iG{pRA~uj`$9f&33Qx2^wl-ESd3k=SGL5gh zjNP{M7y1SMT+HR0#Rul|!bJSQPyLg=nN$(tvq(f9iN?Yh_TnP+C%@!cXBdTHv`Hu* z<(L{1J9nj*!g}3Kcf4uCMnuZp#jp4GvW7*vQLaq{aafNiEs*+h?KvaUO#=f>Y#*h} z!->kN3`|B<^M1|0fbnBCm>8S+y^;|dDRpmav6YDD)917infp2)W;%n*NzI6$xxar9 zG%+qnH#!Xrt|bClM4POEkAk2snd?!9rNrtTRHpo^oF-kaso0@UIech(J#|`8fj~95 zcAN;s$M3H3;vXt;bL9e0YO0W-OHaJwLNK{i;>`Xp@KAPK^AnGM%;Qhzw8q2do{T5W zd5`B-nBjTd30K=O?fYf_oX5v2THU`ndS!E>8>3&}Ip`pLBqWAtCeJxz=A#h%hj!Ij zM&t#FF=%)Ma*5ejh9a~t$|MZ(?x$T9x!g7|@QB%g)V}znr|a*I zgsDwHihsBsvqzz6A>p5IEw4%MVHmVm0Z+tB;0bsXYXa#pCI6b}>ag|h+T$Z1{Zn%A z?iy*1&nurq@;nVpzG2#cIA`;q)We)BC3qmxVfuhHs?gwALFL=N-;5|%-A;AuIguW} zHT_-H{#nO<+CMq4nO9isB@9fC+PJ3M@-{fTsU3q^6L)r(^ka^cM^M-^`L#--boQ(8PZ zL^R@Q7(!!LS#9@P`mEWB4+pf-3RZ2G%}Eb7;avEmMf&HDVsaAJXMxs>?@4O>trxj$ z7?q!^j@}5>e?zZo#|v*b*4ky!Ld_0u$-<=@F1&0(!oqQ!6kL@1<6)0}huUbVm&uxz z__|S7@RPF{8ple*K9x#(InE@}WUgKcl-QR)h!Z(V*FUCh(7T1b0WWI>s2X<+6L#yo zaG^H$VnsW-p0=uMF#Ur=B1P}Tdi}yZd##WYZx=t~cpKqAMwOv=i*Md4bfM`u9mff8 z6uD!`Mdi!qDaa19qZSA&eK}$i`9f0i=-~vjzt&A>%2Hxj&o|`EB53 z4Ey26f1kma%U(o{nKr%DY}cm-VaL>*3*5{GG;s#hDVhuJpO}7~qd34yjFy^hg12qx zZJ2P5bCOs_@csqxe#FHfCKzK@RB0rc#v3&n$;k2|ln<~88pkZU*%X6ER%0rA=*}GN zMC0x8$;NA*54l1S)a;EiUcq%rEVq~4fn5KPIwhAK;Kh*-=zss8zp9Me&pgG>E=#se k2cB2H_J5xE|6e!21BqH&K1be|H0QR{s$=cF6f+;_sZH}=?#e`6p1h5aH9;h(6}J_<>3 zX1en|0c|Y`5F^vYXfz{rS{-H_@{j~d926wG1nG)f&e-t85qrhwo%j!Sh~WQy<^&*H zZZ&cjQbD6O(!p;EAV-P<401F+A9`=x*eY-B(sNX6QK0KlzpBAmtc*1MIb5OB!ua)=%Opy4i81z^g5)^ zq?J9vbt^?_Ql-|*b>-^DVa+WkYP?lg)-NA0UWz_BSJnjV6Rz$VrOQ# z3wZ`4n~=on$-tycYl^e%Um?UOo&aRP@Li^c-45eg>bLJztbF3G@?k!$9LFsS_#MvLd z+7~SchN1h^Ags9ItdM^d{GG(g=K;czY`HbygjwB$Ir6Eo@sm;HyK?aGeU}Atj^fk< zo|u2uRjy&)3H%*BX9Pl%oXR=@JW|u7(_xutFoj5|^Ojfg6jXkbpZ9)ZeZP4DvV_d1 zh>edOFi<$aqg$`D(g8LbU6>(eo;#+9&7u!#iaDnWq9Yu7(=#Da6K7{AR60~h&Y z=bQha{pb+HMx+vrYQ*g0?wGvCQ%(lUN^GkI@a9vqO*EEXbiuY4_LzIxjZG-Qt2B#| z4O+TJyj;sU=ll;_<%?Z!{*CU_f-rKOj}%|GV-%;xx^sXxBSnBRax6#dA)%76LDxtW zD4B%m2R!7-$HRzDDUNa82Q2W%+Ix;zcG($CdWQ=_(4`qWiaSJzZVFO+7<;QCMIV5)@@YT1xK@zAm%AZq7)xo zEIb>K!=eBmA0G!%qkUBREl@TRccs{FOAwRTUM2a51Tlz3OgmdHH#awz=;-KNqGnB1Dpd~={Y3P)GWs!xxx^rr-LLif^ZGBA Wo3g_nB8yc30000P8K*^ooa7EsbJ(O|cay!|d)Y)(5d)N}n*YEeuzI|KX=Ge`?rJ33P%r{H+c>nMB z`+xU)42iG!im&*JulR~=m6ercsH*Dpdc6}hO}mq2wxTF?{JOvj!L_jm_QIan8#=@k zbY*2_YDGmwozLfcmF4&R@ALdh{C-RYdtz_s09~L{Nl8hnrQn(QyLhvoUZTWhm0MO=6=33T>&JktjR zzM!BWoiBCPs;}WH4?0AJHPjl0~_dBR@ zO$jYtRYEu4;UTXl>V$U#OV|XsT&`*`*cV7u2kA0Edfmv!6?D@Cg+6nai)Jsh(}Q0w zrpLGC)5Zh2^wLi<=#8#adh1__bc{yO2^vl(S@w6OQ0=_3sFDB-Oh9!c>2J2#{{dKl zLP_>9jmF=s&}VC1G`G=4%f3=fYqwuRn-AsE%kQSs{y(MAk@E&f$Z?h*@!y>+9h5*v z4Ux~cpU8scL^TdDmRVsFz&X^E0dUgeCMxvkGljJEa1MR{OgjDW&neW_ohYD!K^`-K z>{~in4t$VI<8SeT=%^6@EMXJibUGIW6M$2>@g|jC{BZ`oO~d*H5}$#X<9O|x416wR zR&oQx6vc=4C1~#X)ku)gV=U0Yd6y(M3~Z zOaP+(bWTwn7ej*wG21$_iHipSl~j07y@O8EFhq7Cz#vmd5KHI`5eE+P`y2M<(WBoi zq&3eK(BXe3fh@6#jtdF&rL6@FOmvDJ0jSbo!iWBzL>(cNKJsxQO}WPj;DL-W`A!$L zv%-6SJCd#&qXnOuSi;I@i%c#BpgZ5qz;$%@5#o2BN&6pb*nTXFCQWsN%-9fsD11Ge z^sB!ciIXs${II3oPJp00YTRwd79wQ@*ZblQFTIl?3hy|X zi4=;;5a31Osw$0kzn2zFD9DiFsd~D6FL-YNm9n|zuSp>pn$pg8+!+?=7u490#tHfY$k+1l0Zj0)j-Em=p$KN~3cY+tEXqnkejn_|bGK z1K$(E+Pf2|x>g9YlmKiFwnGxIdQ+i^04OwLK^ZN5tQf$>XDh2T-K8Wh?D>t^rEtBa zPZZ;NA^Q!-=qk7ef^MJ!UAXeo_{>@Yu)*hfBlbU240xWEF`FSGwJ43#6cEAhPnqeY zHa7QAJ;7to=TjL2y|mw0F8ziyRrJQWRJZ}YkAneW))D}v@19>4G7GeEXE$wDx!&&f zDTo(O?Yuf*76(E2;|*g~+ILn=FJvW%k`;3REzkl!TOt9W#IlPa}7ruIXwFiL?evH0A0^$ZX)uWsZq66M= z!4@viCU6_=bRa-lM*xZvuJ?^pXcnW9Y2^aw2ft2(AIk)&!%vWybCji>N!@fXSK`Vz z4w|ZJ5dny_t2aw3?9a)73`k@rZYH(Yb{g5}6b+;Lhe~l!77-wG0V5U0Lf~$@ev!th zv4Ga@$fwuNruOK9D7esLxXUdfK)SroIdBRw1HZXB7qJ9^6_XfZ5>de~?tz57B`KPGKKUGh3qe+TfG0Vfo%FlVE*^%|-0egq zDyKW|0h`~VWIwdX&?!m;n8)HKkzW2fjlSA+4K;s|j3NX%KM;&oY$(EsnOfuL4(9}J zjLceS7m_qpu4n2ZCrxo6B-5%FOSp+wp4M;@> zz#_-RWM!_8w5ZPv!V&#eJ0#rOWZl3LHUXnXjVj_FcZFjGPz>Ov-dj#jHeD@4&{ufq z`JK)z5CeC>=dpfHs0&zzM6gDvs@mL-IMCx54FAS}L{;Q(yvFKOV#yT#9uA+04 z7uXz;nVD&4-+EY4lvd}~; literal 0 HcmV?d00001 diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_512x512.png b/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..22c2b158ef17842e30ab96f6a58ad62f87267d64 GIT binary patch literal 28480 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 Ux`&QSH)+6j=U%J)?OxIU2MX8fw*UYD literal 0 HcmV?d00001 From f02e45486e2dd413c8222d97b589fa294d028792 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 9 Sep 2022 22:18:52 +0200 Subject: [PATCH 002/181] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20ActiveP?= =?UTF-8?q?hpInstallation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Core/Actions.swift | 4 +- phpmon/Common/PHP/ActivePhpInstallation.swift | 46 ++++--------------- phpmon/Common/PHP/PHP Version/PhpEnv.swift | 2 +- .../PHP/PHP Version/PhpVersionNumber.swift | 6 ++- .../PHP/Switcher/InternalSwitcher.swift | 6 +-- phpmon/Domain/App/AppDelegate.swift | 9 +++- .../DomainList/Cells/DomainListPhpCell.swift | 2 +- .../Homebrew/HomebrewDiagnostics.swift | 4 +- .../Valet/Sites/ValetSite+Fake.swift | 2 +- .../Integrations/Valet/Sites/ValetSite.swift | 4 +- phpmon/Domain/Menu/MainMenu+Actions.swift | 2 +- phpmon/Domain/Menu/MainMenu+FixMyValet.swift | 8 ++-- phpmon/Domain/Menu/MainMenu+Startup.swift | 7 ++- phpmon/Domain/Menu/StatusMenu+Items.swift | 8 ++-- .../SwiftUI/Domains/VersionPopoverView.swift | 6 +-- 15 files changed, 52 insertions(+), 64 deletions(-) diff --git a/phpmon/Common/Core/Actions.swift b/phpmon/Common/Core/Actions.swift index 37107c0..a100e86 100644 --- a/phpmon/Common/Core/Actions.swift +++ b/phpmon/Common/Core/Actions.swift @@ -41,7 +41,7 @@ class Actions { ] PhpEnv.shared.availablePhpVersions.forEach { version in - let formula = version == PhpEnv.brewPhpVersion + let formula = version == PhpEnv.brewPhpAlias ? "php" : "php@\(version)" servicesCommands.append("\(Paths.brew) services stop \(formula)") @@ -144,7 +144,7 @@ class Actions { extensions and/or run `composer global update`. */ public static func fixMyValet(completed: @escaping () -> Void) { - InternalSwitcher().performSwitch(to: PhpEnv.brewPhpVersion, completion: { + InternalSwitcher().performSwitch(to: PhpEnv.brewPhpAlias, completion: { brew("services restart dnsmasq", sudo: true) brew("services restart php", sudo: true) brew("services restart nginx", sudo: true) diff --git a/phpmon/Common/PHP/ActivePhpInstallation.swift b/phpmon/Common/PHP/ActivePhpInstallation.swift index cb1a0e2..c6b0ce1 100644 --- a/phpmon/Common/PHP/ActivePhpInstallation.swift +++ b/phpmon/Common/PHP/ActivePhpInstallation.swift @@ -17,11 +17,12 @@ import Foundation Using `version.short` is advisable if you want to interact with Homebrew. */ class ActivePhpInstallation { - - var version: Version! + var version: PhpVersionNumber! var limits: Limits! var iniFiles: [PhpConfigurationFile] = [] + var hasErrorState: Bool = false + var extensions: [PhpExtension] { return iniFiles.flatMap { initFile in return initFile.extensions @@ -31,20 +32,20 @@ class ActivePhpInstallation { // MARK: - Computed var formula: String { - return (version.short == PhpEnv.brewPhpVersion) ? "php" : "php@\(version.short)" + return (version.short == PhpEnv.brewPhpAlias) ? "php" : "php@\(version.short)" } // MARK: - Initializer init() { // Show information about the current version - getVersion() + determineVersion() // Initialize the list of ini files that are loaded iniFiles = [] // If an error occurred, exit early - if version.error { + if self.hasErrorState { limits = Limits() return } @@ -81,26 +82,11 @@ class ActivePhpInstallation { When the app tries to retrieve the version, the installation is considered broken if the output is nothing, _or_ if the output contains the word "Warning" or "Error". In normal situations this should not be the case. */ - private func getVersion() { - self.version = Version() + private func determineVersion() { + let output = Command.execute(path: Paths.phpConfig, arguments: ["--version"], trimNewlines: true) - let version = Command.execute(path: Paths.phpConfig, arguments: ["--version"], trimNewlines: true) - - if version == "" || version.contains("Warning") || version.contains("Error") { - self.version.short = "💩 BROKEN" - self.version.long = "" - self.version.error = true - return - } - - // That's the long version - self.version.long = version - - // Next up, let's strip away the minor version number - let segments = self.version.long.components(separatedBy: ".") - - // Get the first two elements - self.version.short = segments[0...1].joined(separator: ".") + self.hasErrorState = (output == "" || output.contains("Warning") || output.contains("Error")) + self.version = PhpVersionNumber.make(from: output) } /** @@ -152,18 +138,6 @@ class ActivePhpInstallation { // MARK: - Structs - /** - Struct containing information about the version number of the current PHP installation. - Also includes information about whether the install is considered "broken" or not. - If an error was found in the terminal output, `error` is set to `true` and the installation - can be considered broken. (The app will display this as well.) - */ - struct Version { - var short = "???" - var long = "???" - var error = false - } - /** Struct containing information about the limits of the current PHP installation. Includes: memory limit, max upload size and max post size. diff --git a/phpmon/Common/PHP/PHP Version/PhpEnv.swift b/phpmon/Common/PHP/PHP Version/PhpEnv.swift index 4dc3e88..e2b747f 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnv.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnv.swift @@ -54,7 +54,7 @@ class PhpEnv { As such, we take that information from Homebrew. */ - static var brewPhpVersion: String { + static var brewPhpAlias: String { return Self.shared.homebrewPackage.version } diff --git a/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift b/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift index 4241648..398285c 100644 --- a/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift +++ b/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift @@ -106,7 +106,11 @@ public struct PhpVersionNumber: Equatable, Hashable { return patch ?? (strictFallback ? 0 : constraint?.patch ?? 999) } - public var homebrewVersion: String { + public var long: String { + return "\(major).\(minor).\(patch ?? 0)" + } + + public var short: String { return "\(major).\(minor)" } diff --git a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift index ee96726..8c72b86 100644 --- a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift +++ b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift @@ -57,7 +57,7 @@ class InternalSwitcher: PhpSwitcher { let isolated = Valet.shared.sites.filter { site in site.isolatedPhpVersion != nil }.map { site in - return site.isolatedPhpVersion!.versionNumber.homebrewVersion + return site.isolatedPhpVersion!.versionNumber.short } var versions: Set = [primary] @@ -95,14 +95,14 @@ class InternalSwitcher: PhpSwitcher { } func stopPhpVersion(_ version: String) { - let formula = (version == PhpEnv.brewPhpVersion) ? "php" : "php@\(version)" + let formula = (version == PhpEnv.brewPhpAlias) ? "php" : "php@\(version)" brew("unlink \(formula)") brew("services stop \(formula)", sudo: true) Log.info("Unlinked and stopped services for \(formula)") } func startPhpVersion(_ version: String, primary: Bool) { - let formula = (version == PhpEnv.brewPhpVersion) ? "php" : "php@\(version)" + let formula = (version == PhpEnv.brewPhpAlias) ? "php" : "php@\(version)" if primary { Log.info("\(formula) is the primary formula, linking and starting services...") diff --git a/phpmon/Domain/App/AppDelegate.swift b/phpmon/Domain/App/AppDelegate.swift index 96f5628..fd020bb 100644 --- a/phpmon/Domain/App/AppDelegate.swift +++ b/phpmon/Domain/App/AppDelegate.swift @@ -65,14 +65,19 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele override init() { logger.verbosity = .info #if DEBUG - // logger.verbosity = .performance + logger.verbosity = .performance #endif if CommandLine.arguments.contains("--v") { logger.verbosity = .performance Log.info("Extra verbose mode has been activated.") } Log.separator(as: .info) - Log.info("PHP MONITOR by Nico Verbruggen") + #if SPONSOR + Log.info("PHP MONITOR SE by Nico Verbruggen") + #else + Log.info("PHP MONITOR by Nico Verbruggen") + #endif + Log.info("Version \(App.version)") Log.separator(as: .info) self.sharedShell = Shell.user diff --git a/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift b/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift index 3cd0b99..a39d54b 100644 --- a/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift +++ b/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift @@ -59,7 +59,7 @@ class DomainListPhpCell: NSTableCellView, DomainListCellProtocol { } return PhpEnv.shared.validVersions(for: site.composerPhp).filter({ version in - version.homebrewVersion != PhpEnv.phpInstall.version.short + version.short != PhpEnv.phpInstall.version.short }) } diff --git a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift index 4d4aff0..c5ab628 100644 --- a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift +++ b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift @@ -89,13 +89,13 @@ class HomebrewDiagnostics { from: tapAlias.data(using: .utf8)! ).first! - if tapPhp.version != PhpEnv.brewPhpVersion { + if tapPhp.version != PhpEnv.brewPhpAlias { Log.warn("The `php` formula alias seems to be the different between the tap and core. " + "This could be a problem!") Log.info("Determining whether both of these versions are installed...") let bothInstalled = PhpEnv.shared.availablePhpVersions.contains(tapPhp.version) - && PhpEnv.shared.availablePhpVersions.contains(PhpEnv.brewPhpVersion) + && PhpEnv.shared.availablePhpVersions.contains(PhpEnv.brewPhpAlias) if bothInstalled { Log.warn("Both conflicting aliases seem to be installed, warning the user!") diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite+Fake.swift b/phpmon/Domain/Integrations/Valet/Sites/ValetSite+Fake.swift index 344f05a..98df3b3 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/ValetSite+Fake.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/ValetSite+Fake.swift @@ -38,7 +38,7 @@ extension ValetSite { self.composerPhpCompatibleWithLinked = self.composerPhp.split(separator: "|") .map { string in - let origin = self.isolatedPhpVersion?.versionNumber.homebrewVersion ?? PhpEnv.phpInstall.version.long + let origin = self.isolatedPhpVersion?.versionNumber.short ?? PhpEnv.phpInstall.version.long return !PhpVersionNumberCollection.make(from: [origin]) .matching(constraint: string.trimmingCharacters(in: .whitespacesAndNewlines)) .isEmpty diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift index e59282c..eac09c1 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift @@ -55,7 +55,7 @@ class ValetSite: DomainListable { /// Which version of PHP is actually used to serve this site. var servingPhpVersion: String { - return self.isolatedPhpVersion?.versionNumber.homebrewVersion + return self.isolatedPhpVersion?.versionNumber.short ?? PhpEnv.phpInstall.version.short } @@ -144,7 +144,7 @@ class ValetSite: DomainListable { // For example, for Laravel 8 projects the value is "^7.3|^8.0" self.composerPhpCompatibleWithLinked = self.composerPhp.split(separator: "|") .map { string in - let origin = self.isolatedPhpVersion?.versionNumber.homebrewVersion ?? PhpEnv.phpInstall.version.long + let origin = self.isolatedPhpVersion?.versionNumber.short ?? PhpEnv.phpInstall.version.long return !PhpVersionNumberCollection.make(from: [origin]) .matching(constraint: string.trimmingCharacters(in: .whitespacesAndNewlines)) .isEmpty diff --git a/phpmon/Domain/Menu/MainMenu+Actions.swift b/phpmon/Domain/Menu/MainMenu+Actions.swift index 6e2819f..36e1f08 100644 --- a/phpmon/Domain/Menu/MainMenu+Actions.swift +++ b/phpmon/Domain/Menu/MainMenu+Actions.swift @@ -208,7 +208,7 @@ extension MainMenu { } @objc func openActiveConfigFolder() { - if PhpEnv.phpInstall.version.error { + if PhpEnv.phpInstall.hasErrorState { Actions.openGenericPhpConfigFolder() return } diff --git a/phpmon/Domain/Menu/MainMenu+FixMyValet.swift b/phpmon/Domain/Menu/MainMenu+FixMyValet.swift index 00bd5a6..36f5434 100644 --- a/phpmon/Domain/Menu/MainMenu+FixMyValet.swift +++ b/phpmon/Domain/Menu/MainMenu+FixMyValet.swift @@ -14,7 +14,7 @@ extension MainMenu { @MainActor @objc func fixMyValet() { let previousVersion = PhpEnv.phpInstall.version.short - if !PhpEnv.shared.availablePhpVersions.contains(PhpEnv.brewPhpVersion) { + if !PhpEnv.shared.availablePhpVersions.contains(PhpEnv.brewPhpAlias) { presentAlertForMissingFormula() return } @@ -22,7 +22,7 @@ extension MainMenu { if !BetterAlert() .withInformation( title: "alert.fix_my_valet.title".localized, - subtitle: "alert.fix_my_valet.info".localized(PhpEnv.brewPhpVersion) + subtitle: "alert.fix_my_valet.info".localized(PhpEnv.brewPhpAlias) ) .withPrimary(text: "alert.fix_my_valet.ok".localized) .withSecondary(text: "alert.fix_my_valet.cancel".localized) @@ -33,7 +33,7 @@ extension MainMenu { Actions.fixMyValet { DispatchQueue.main.async { - if previousVersion == PhpEnv.brewPhpVersion { + if previousVersion == PhpEnv.brewPhpAlias { self.presentAlertForSameVersion() } else { self.presentAlertForDifferentVersion(version: previousVersion) @@ -74,7 +74,7 @@ extension MainMenu { alert.close(with: .alertSecondButtonReturn) MainMenu.shared.switchToPhpVersion(version) }) - .withSecondary(text: "alert.fix_my_valet_done.stay".localized(PhpEnv.brewPhpVersion)) + .withSecondary(text: "alert.fix_my_valet_done.stay".localized(PhpEnv.brewPhpAlias)) .withTertiary(text: "", action: { _ in NSWorkspace.shared.open(Constants.Urls.FrequentlyAskedQuestions) }) diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 6cc8e4b..4071bc0 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -90,7 +90,12 @@ extension MainMenu { // Update the stats Stats.incrementSuccessfulLaunchCount() - Stats.evaluateSponsorMessageShouldBeDisplayed() + + #if SPONSOR + Log.info("Sponsor encouragement messages are omitted in SE builds.") + #else + Stats.evaluateSponsorMessageShouldBeDisplayed() + #endif // Present first launch screen if needed if Stats.successfulLaunchCount == 0 && !isRunningSwiftUIPreview { diff --git a/phpmon/Domain/Menu/StatusMenu+Items.swift b/phpmon/Domain/Menu/StatusMenu+Items.swift index 2de18f1..46f6c0d 100644 --- a/phpmon/Domain/Menu/StatusMenu+Items.swift +++ b/phpmon/Domain/Menu/StatusMenu+Items.swift @@ -13,13 +13,13 @@ import Cocoa extension StatusMenu { func addPhpVersionMenuItems() { - if PhpEnv.phpInstall.version.error { + if PhpEnv.phpInstall.hasErrorState { let brokenMenuItems = ["mi_php_broken_1", "mi_php_broken_2", "mi_php_broken_3", "mi_php_broken_4"] return addItems(brokenMenuItems.map { NSMenuItem(title: $0.localized) }) } addItem(HeaderView.asMenuItem( - text: "\("mi_php_version".localized) \(PhpEnv.phpInstall.version.long)") + text: "\("mi_php_version".localized) \(PhpEnv.phpInstall.version.toString())") ) } @@ -58,7 +58,7 @@ extension StatusMenu { let versionString = long ? longVersion.toString() : shortVersion let action = #selector(MainMenu.switchToPhpVersion(sender:)) - let brew = (shortVersion == PhpEnv.brewPhpVersion) ? "php" : "php@\(shortVersion)" + let brew = (shortVersion == PhpEnv.brewPhpAlias) ? "php" : "php@\(shortVersion)" let menuItem = PhpMenuItem( title: "\("mi_php_switch".localized) \(versionString) (\(brew))", action: (shortVersion == PhpEnv.phpInstall.version.short) @@ -254,7 +254,7 @@ extension StatusMenu { NSMenuItem(title: "mi_view_onboarding".localized, action: #selector(MainMenu.showWelcomeTour)), NSMenuItem(title: "mi_fa_php_doctor".localized, action: #selector(MainMenu.openWarnings)), NSMenuItem.separator(), - NSMenuItem(title: "mi_fix_my_valet".localized(PhpEnv.brewPhpVersion), + NSMenuItem(title: "mi_fix_my_valet".localized(PhpEnv.brewPhpAlias), action: #selector(MainMenu.fixMyValet), toolTip: "mi_fix_my_valet_tooltip".localized), NSMenuItem(title: "mi_fix_brew_permissions".localized(), action: #selector(MainMenu.fixHomebrewPermissions), diff --git a/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift b/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift index 6509711..a16579e 100644 --- a/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift +++ b/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift @@ -34,8 +34,8 @@ struct VersionPopoverView: View { ) HStack { ForEach(validPhpVersions, id: \.self) { version in - Button("site_link.switch_to_php".localized(version.homebrewVersion), action: { - MainMenu.shared.switchToPhpVersion(version.homebrewVersion) + Button("site_link.switch_to_php".localized(version.short), action: { + MainMenu.shared.switchToPhpVersion(version.short) parent?.close() }) } @@ -88,7 +88,7 @@ struct VersionPopoverView: View { if site.isolatedPhpVersion != nil { information += "alert.composer_php_isolated.desc".localized( - site.isolatedPhpVersion!.versionNumber.homebrewVersion, + site.isolatedPhpVersion!.versionNumber.short, PhpEnv.phpInstall.version.short ) information += "\n\n" From 95729c5315b29786609b9135cb5f1cb82fb832ce Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 10 Sep 2022 21:21:58 +0200 Subject: [PATCH 003/181] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Single=20target,?= =?UTF-8?q?=20multiple=20configurations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 584 ++++++------------ ...r SE.xcscheme => PHP Monitor DEV.xcscheme} | 59 +- .../Contents.json | 0 .../icon_128x128.png | Bin .../icon_128x128@2x.png | Bin .../icon_16x16.png | Bin .../icon_16x16@2x.png | Bin .../icon_256x256.png | Bin .../icon_256x256@2x.png | Bin .../icon_32x32.png | Bin .../icon_32x32@2x.png | Bin .../icon_512x512.png | Bin .../icon_512x512@2x.png | Bin phpmon/Domain/App/AppDelegate.swift | 9 +- 14 files changed, 242 insertions(+), 410 deletions(-) rename PHP Monitor.xcodeproj/xcshareddata/xcschemes/{PHP Monitor SE.xcscheme => PHP Monitor DEV.xcscheme} (56%) rename phpmon/Assets.xcassets/{AppIconSE.appiconset => AppIconEA.appiconset}/Contents.json (100%) rename phpmon/Assets.xcassets/{AppIconSE.appiconset => AppIconEA.appiconset}/icon_128x128.png (100%) rename phpmon/Assets.xcassets/{AppIconSE.appiconset => AppIconEA.appiconset}/icon_128x128@2x.png (100%) rename phpmon/Assets.xcassets/{AppIconSE.appiconset => AppIconEA.appiconset}/icon_16x16.png (100%) rename phpmon/Assets.xcassets/{AppIconSE.appiconset => AppIconEA.appiconset}/icon_16x16@2x.png (100%) rename phpmon/Assets.xcassets/{AppIconSE.appiconset => AppIconEA.appiconset}/icon_256x256.png (100%) rename phpmon/Assets.xcassets/{AppIconSE.appiconset => AppIconEA.appiconset}/icon_256x256@2x.png (100%) rename phpmon/Assets.xcassets/{AppIconSE.appiconset => AppIconEA.appiconset}/icon_32x32.png (100%) rename phpmon/Assets.xcassets/{AppIconSE.appiconset => AppIconEA.appiconset}/icon_32x32@2x.png (100%) rename phpmon/Assets.xcassets/{AppIconSE.appiconset => AppIconEA.appiconset}/icon_512x512.png (100%) rename phpmon/Assets.xcassets/{AppIconSE.appiconset => AppIconEA.appiconset}/icon_512x512@2x.png (100%) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 57f6c0f..f2a817b 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -103,148 +103,6 @@ C42F26732805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; C42F26762805FEE200938AC7 /* nginx-secure-proxy.test in Resources */ = {isa = PBXBuildFile; fileRef = C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */; }; - C4358B1328CBCC2600121D18 /* WarningManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699EE28A2F2A30060FEB8 /* WarningManager.swift */; }; - C4358B1428CBCC2600121D18 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; }; - C4358B1528CBCC2600121D18 /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; }; - C4358B1628CBCC2600121D18 /* MainMenu+FixMyValet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */; }; - C4358B1728CBCC2600121D18 /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; }; - C4358B1828CBCC2600121D18 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* Shell.swift */; }; - C4358B1928CBCC2600121D18 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PreferencesWindowController.swift */; }; - C4358B1A28CBCC2600121D18 /* PhpConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */; }; - C4358B1B28CBCC2600121D18 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; }; - C4358B1C28CBCC2600121D18 /* Valet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F792754499000D44ED0 /* Valet.swift */; }; - C4358B1D28CBCC2600121D18 /* ValetProxy+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */; }; - C4358B1E28CBCC2600121D18 /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E628553117006F9937 /* ArrayExtension.swift */; }; - C4358B1F28CBCC2600121D18 /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; }; - C4358B2028CBCC2600121D18 /* AppDelegate+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */; }; - C4358B2128CBCC2600121D18 /* CreatedFromFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5489625728312FAD004F647A /* CreatedFromFile.swift */; }; - C4358B2228CBCC2600121D18 /* SelectPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA627B07A1300544CD5 /* SelectPreferenceView.swift */; }; - C4358B2328CBCC2600121D18 /* NoWarningsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C708C28AA7F7900E8D498 /* NoWarningsView.swift */; }; - C4358B2428CBCC2600121D18 /* BetterAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */; }; - C4358B2528CBCC2600121D18 /* NSWindowExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */; }; - C4358B2628CBCC2600121D18 /* ValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4205A7D27F4D21800191A39 /* ValetProxy.swift */; }; - C4358B2728CBCC2600121D18 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; }; - C4358B2828CBCC2600121D18 /* HotkeyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */; }; - C4358B2928CBCC2600121D18 /* PreferenceName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C450C8C528C919EC002A2B4B /* PreferenceName.swift */; }; - C4358B2A28CBCC2600121D18 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; - C4358B2B28CBCC2600121D18 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; }; - C4358B2C28CBCC2600121D18 /* AddProxyVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9F24A280B69E100DCD39A /* AddProxyVC.swift */; }; - C4358B2D28CBCC2600121D18 /* DomainListVC+ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41E87192763D42300161EE0 /* DomainListVC+ContextMenu.swift */; }; - C4358B2E28CBCC2600121D18 /* ActivePhpInstallation+Checks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */; }; - C4358B2F28CBCC2600121D18 /* PresetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C463E37F284930EE00422731 /* PresetHelper.swift */; }; - C4358B3028CBCC2600121D18 /* ValetSite+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */; }; - C4358B3128CBCC2600121D18 /* FakeSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8DE27F88AEB002D32A9 /* FakeSiteScanner.swift */; }; - C4358B3228CBCC2600121D18 /* SwiftUIHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */; }; - C4358B3328CBCC2600121D18 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */; }; - C4358B3428CBCC2600121D18 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; }; - C4358B3528CBCC2600121D18 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E428551F9B006F9937 /* HeaderView.swift */; }; - C4358B3628CBCC2600121D18 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; - C4358B3728CBCC2600121D18 /* ProgressVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44A874728905BB000498BC4 /* ProgressVC.swift */; }; - C4358B3828CBCC2600121D18 /* PMWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CCBA6B275C567B008C7055 /* PMWindowController.swift */; }; - C4358B3928CBCC2600121D18 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; }; - C4358B3A28CBCC2600121D18 /* DomainListNameCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F427E2582B0045BD4E /* DomainListNameCell.swift */; }; - C4358B3B28CBCC2600121D18 /* Preset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C5C9B2846A40600E28255 /* Preset.swift */; }; - C4358B3C28CBCC2600121D18 /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; }; - C4358B3D28CBCC2600121D18 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B609192853AAD300C95265 /* SectionHeaderView.swift */; }; - C4358B3E28CBCC2600121D18 /* DomainListPhpCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F627E258410045BD4E /* DomainListPhpCell.swift */; }; - C4358B3F28CBCC2600121D18 /* PreferencesWindowController+Hotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE7F288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift */; }; - C4358B4028CBCC2600121D18 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; }; - C4358B4128CBCC2600121D18 /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */; }; - C4358B4228CBCC2600121D18 /* DomainListKindCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */; }; - C4358B4328CBCC2600121D18 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CDA892288F1A71007CE25F /* Keys.swift */; }; - C4358B4428CBCC2600121D18 /* MainMenu+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F361602836BFD9003598CC /* MainMenu+Actions.swift */; }; - C4358B4528CBCC2600121D18 /* TerminalProgressWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */; }; - C4358B4628CBCC2600121D18 /* KeyCombo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AF27E4F51E003B9AD9 /* KeyCombo.swift */; }; - C4358B4728CBCC2600121D18 /* ProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E627F88B41002D32A9 /* ProxyScanner.swift */; }; - C4358B4828CBCC2600121D18 /* CustomPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */; }; - C4358B4928CBCC2600121D18 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Application.swift */; }; - C4358B4A28CBCC2600121D18 /* App+ActivationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */; }; - C4358B4B28CBCC2600121D18 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; }; - C4358B4C28CBCC2600121D18 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; }; - C4358B4D28CBCC2600121D18 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; }; - C4358B4E28CBCC2600121D18 /* EnvironmentCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */; }; - C4358B4F28CBCC2600121D18 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; }; - C4358B5028CBCC2600121D18 /* HomebrewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F30B02278E16BA00755FCE /* HomebrewService.swift */; }; - C4358B5128CBCC2600121D18 /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AD27E4F51E003B9AD9 /* Key.swift */; }; - C4358B5228CBCC2600121D18 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.swift */; }; - C4358B5328CBCC2600121D18 /* ValetSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E127F88B13002D32A9 /* ValetSiteScanner.swift */; }; - C4358B5428CBCC2600121D18 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; - C4358B5528CBCC2600121D18 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; }; - C4358B5628CBCC2600121D18 /* XibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9225CC804200CC7490 /* XibLoadable.swift */; }; - C4358B5728CBCC2600121D18 /* CheckboxPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD29276C8AA4004CE748 /* CheckboxPreferenceView.swift */; }; - C4358B5828CBCC2600121D18 /* Warning.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699F028A2F3150060FEB8 /* Warning.swift */; }; - C4358B5928CBCC2600121D18 /* HotKeysController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AC27E4F51E003B9AD9 /* HotKeysController.swift */; }; - C4358B5A28CBCC2600121D18 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; }; - C4358B5B28CBCC2600121D18 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; }; - C4358B5C28CBCC2600121D18 /* DomainListVC+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CA5EC2774F8EE00A2C80E /* DomainListVC+Actions.swift */; }; - C4358B5D28CBCC2600121D18 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; - C4358B5E28CBCC2600121D18 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; }; - C4358B5F28CBCC2600121D18 /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */; }; - C4358B6028CBCC2600121D18 /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; }; - C4358B6128CBCC2600121D18 /* MenuBarIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA927B0890D00544CD5 /* MenuBarIcons.swift */; }; - C4358B6228CBCC2600121D18 /* VersionPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */; }; - C4358B6328CBCC2600121D18 /* PhpConfigWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */; }; - C4358B6428CBCC2600121D18 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; }; - C4358B6528CBCC2600121D18 /* AppDelegate+InterApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */; }; - C4358B6628CBCC2600121D18 /* ValetProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C484437A2804BB560041A78A /* ValetProxyScanner.swift */; }; - C4358B6728CBCC2600121D18 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; }; - C4358B6828CBCC2600121D18 /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42759662627662800093CAE /* NSMenuExtension.swift */; }; - C4358B6928CBCC2600121D18 /* WarningListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDA928A2C49900CEAC97 /* WarningListView.swift */; }; - C4358B6A28CBCC2600121D18 /* DomainListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAE275A7A69003FCD53 /* DomainListVC.swift */; }; - C4358B6B28CBCC2600121D18 /* MainMenu+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */; }; - C4358B6C28CBCC2600121D18 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C1019A27C65C6F001FACC2 /* Process.swift */; }; - C4358B6D28CBCC2600121D18 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; }; - C4358B6E28CBCC2600121D18 /* DomainListTLSCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067FA27E25FD70045BD4E /* DomainListTLSCell.swift */; }; - C4358B6F28CBCC2600121D18 /* PMTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A81CA328C67101008DD9D1 /* PMTableView.swift */; }; - C4358B7028CBCC2600121D18 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; }; - C4358B7128CBCC2600121D18 /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; }; - C4358B7228CBCC2600121D18 /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; }; - C4358B7328CBCC2600121D18 /* SelectionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FE011028084FC200D1DE6D /* SelectionVC.swift */; }; - C4358B7428CBCC2600121D18 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4709CA128524B3400088BB8 /* StatsView.swift */; }; - C4358B7528CBCC2600121D18 /* AlertableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */; }; - C4358B7628CBCC2600121D18 /* Filesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4188988275FE8CB001EF227 /* Filesystem.swift */; }; - C4358B7728CBCC2600121D18 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B6091C2853AB9700C95265 /* ServicesView.swift */; }; - C4358B7828CBCC2600121D18 /* NSMenuItemExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */; }; - C4358B7928CBCC2600121D18 /* App+GlobalHotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */; }; - C4358B7A28CBCC2600121D18 /* InterAppHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EED88827A48778006D7272 /* InterAppHandler.swift */; }; - C4358B7B28CBCC2600121D18 /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; }; - C4358B7C28CBCC2600121D18 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; }; - C4358B7D28CBCC2600121D18 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; }; - C4358B7E28CBCC2600121D18 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; }; - C4358B7F28CBCC2600121D18 /* WarningsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDAC28A2DAC600CEAC97 /* WarningsWindowController.swift */; }; - C4358B8028CBCC2600121D18 /* ComposerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */; }; - C4358B8128CBCC2600121D18 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; }; - C4358B8228CBCC2600121D18 /* OnboardingWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE82288F1F9700FC478F /* OnboardingWindowController.swift */; }; - C4358B8328CBCC2600121D18 /* BetterAlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */; }; - C4358B8428CBCC2600121D18 /* VersionExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5635D276AB09000F12CCB /* VersionExtractor.swift */; }; - C4358B8528CBCC2600121D18 /* HotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AE27E4F51E003B9AD9 /* HotKey.swift */; }; - C4358B8628CBCC2600121D18 /* PhpHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D936C827E3EB6100BD69FE /* PhpHelper.swift */; }; - C4358B8728CBCC2600121D18 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; }; - C4358B8828CBCC2600121D18 /* DomainListTypeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F827E2585E0045BD4E /* DomainListTypeCell.swift */; }; - C4358B8928CBCC2600121D18 /* ModifierFlagsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0B027E4F51E003B9AD9 /* ModifierFlagsExtension.swift */; }; - C4358B8A28CBCC2600121D18 /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; }; - C4358B8B28CBCC2600121D18 /* NoDomainResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508AE28ADA23D008FAC1F /* NoDomainResultsView.swift */; }; - C4358B8C28CBCC2600121D18 /* ComposerJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D89BC52783C99400A02B68 /* ComposerJson.swift */; }; - C4358B8D28CBCC2600121D18 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; }; - C4358B8E28CBCC2600121D18 /* Shell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */; }; - C4358B8F28CBCC2600121D18 /* Xdebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42337A2281F19F000459A48 /* Xdebug.swift */; }; - C4358B9028CBCC2600121D18 /* AppDelegate+MenuOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */; }; - C4358B9128CBCC2600121D18 /* SiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A527E60D7A009F26CB /* SiteScanner.swift */; }; - C4358B9228CBCC2600121D18 /* DomainListWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* DomainListWindowController.swift */; }; - C4358B9328CBCC2600121D18 /* DomainListCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */; }; - C4358B9428CBCC2600121D18 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; }; - C4358B9528CBCC2600121D18 /* AddSiteVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4930849279F331F009C240B /* AddSiteVC.swift */; }; - C4358B9628CBCC2600121D18 /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DEB7D327A5D60B00834718 /* Stats.swift */; }; - C4358B9928CBCC2600121D18 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3A22B0098000E7CF16 /* Assets.xcassets */; }; - C4358B9A28CBCC2600121D18 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3C22B0098000E7CF16 /* Main.storyboard */; }; - C4358B9B28CBCC2600121D18 /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */; }; - C4358B9C28CBCC2600121D18 /* ProgressWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C44C1990276E44CB0072762D /* ProgressWindow.storyboard */; }; - C4358B9D28CBCC2600121D18 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = C4232EE42612526500158FC6 /* Credits.html */; }; - C4358B9E28CBCC2600121D18 /* SelectPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD25276C883F004CE748 /* SelectPreferenceView.xib */; }; - C4358B9F28CBCC2600121D18 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; - C4358BA028CBCC2600121D18 /* CheckboxPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C4068CA327B0780A00544CD5 /* CheckboxPreferenceView.xib */; }; - C4358BA128CBCC2600121D18 /* HotkeyPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD2C276C8D67004CE748 /* HotkeyPreferenceView.xib */; }; - C4358BA228CBCC2600121D18 /* InternetAccessPolicy.strings in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */; }; C43603A0275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */; }; C43603A1275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */; }; C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A1925D9CD1000591B77 /* Utility.swift */; }; @@ -528,7 +386,6 @@ C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Shell+PATH.swift"; sourceTree = ""; }; C42F26722805B4B400938AC7 /* DomainListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListable.swift; sourceTree = ""; }; C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-secure-proxy.test"; sourceTree = ""; }; - C4358BA728CBCC2600121D18 /* PHP Monitor SE.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PHP Monitor SE.app"; sourceTree = BUILT_PRODUCTS_DIR; }; C4358BA828CBCC2600121D18 /* PHP Monitor SE-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "PHP Monitor SE-Info.plist"; path = "/Users/nicoverbruggen/Code/phpmon/PHP Monitor SE-Info.plist"; sourceTree = ""; }; C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Notifications.swift"; sourceTree = ""; }; C43A8A1925D9CD1000591B77 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; @@ -651,13 +508,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - C4358B9728CBCC2600121D18 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; C4F7807625D7F84B000DBC97 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -821,7 +671,6 @@ children = ( C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */, C4F7807925D7F84B000DBC97 /* phpmon-tests.xctest */, - C4358BA728CBCC2600121D18 /* PHP Monitor SE.app */, ); name = Products; sourceTree = ""; @@ -1332,26 +1181,6 @@ productReference = C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */; productType = "com.apple.product-type.application"; }; - C4358B1128CBCC2600121D18 /* PHP Monitor SE */ = { - isa = PBXNativeTarget; - buildConfigurationList = C4358BA428CBCC2600121D18 /* Build configuration list for PBXNativeTarget "PHP Monitor SE" */; - buildPhases = ( - C4358B1228CBCC2600121D18 /* Sources */, - C4358B9728CBCC2600121D18 /* Frameworks */, - C4358B9828CBCC2600121D18 /* Resources */, - C4358BA328CBCC2600121D18 /* Run `swiftlint` */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "PHP Monitor SE"; - packageProductDependencies = ( - ); - productName = phpmon; - productReference = C4358BA728CBCC2600121D18 /* PHP Monitor SE.app */; - productType = "com.apple.product-type.application"; - }; C4F7807825D7F84B000DBC97 /* phpmon-tests */ = { isa = PBXNativeTarget; buildConfigurationList = C4F7808025D7F84B000DBC97 /* Build configuration list for PBXNativeTarget "phpmon-tests" */; @@ -1406,7 +1235,6 @@ projectRoot = ""; targets = ( C41C1B3222B0097F00E7CF16 /* PHP Monitor */, - C4358B1128CBCC2600121D18 /* PHP Monitor SE */, C4F7807825D7F84B000DBC97 /* phpmon-tests */, ); }; @@ -1430,23 +1258,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - C4358B9828CBCC2600121D18 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C4358B9928CBCC2600121D18 /* Assets.xcassets in Resources */, - C4358B9A28CBCC2600121D18 /* Main.storyboard in Resources */, - C4358B9B28CBCC2600121D18 /* InternetAccessPolicy.plist in Resources */, - C4358B9C28CBCC2600121D18 /* ProgressWindow.storyboard in Resources */, - C4358B9D28CBCC2600121D18 /* Credits.html in Resources */, - C4358B9E28CBCC2600121D18 /* SelectPreferenceView.xib in Resources */, - C4358B9F28CBCC2600121D18 /* Localizable.strings in Resources */, - C4358BA028CBCC2600121D18 /* CheckboxPreferenceView.xib in Resources */, - C4358BA128CBCC2600121D18 /* HotkeyPreferenceView.xib in Resources */, - C4358BA228CBCC2600121D18 /* InternetAccessPolicy.strings in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; C4F7807725D7F84B000DBC97 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1471,24 +1282,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - C4358BA328CBCC2600121D18 /* Run `swiftlint` */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Run `swiftlint`"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint > /dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; - }; C4F5FBCB28216985001065C5 /* Run `swiftlint` */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1649,145 +1442,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - C4358B1228CBCC2600121D18 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C4358B1328CBCC2600121D18 /* WarningManager.swift in Sources */, - C4358B1428CBCC2600121D18 /* PhpExtension.swift in Sources */, - C4358B1528CBCC2600121D18 /* Startup.swift in Sources */, - C4358B1628CBCC2600121D18 /* MainMenu+FixMyValet.swift in Sources */, - C4358B1728CBCC2600121D18 /* PhpVersionNumber.swift in Sources */, - C4358B1828CBCC2600121D18 /* Shell.swift in Sources */, - C4358B1928CBCC2600121D18 /* PreferencesWindowController.swift in Sources */, - C4358B1A28CBCC2600121D18 /* PhpConfigurationFile.swift in Sources */, - C4358B1B28CBCC2600121D18 /* DateExtension.swift in Sources */, - C4358B1C28CBCC2600121D18 /* Valet.swift in Sources */, - C4358B1D28CBCC2600121D18 /* ValetProxy+Fake.swift in Sources */, - C4358B1E28CBCC2600121D18 /* ArrayExtension.swift in Sources */, - C4358B1F28CBCC2600121D18 /* PrefsVC.swift in Sources */, - C4358B2028CBCC2600121D18 /* AppDelegate+Notifications.swift in Sources */, - C4358B2128CBCC2600121D18 /* CreatedFromFile.swift in Sources */, - C4358B2228CBCC2600121D18 /* SelectPreferenceView.swift in Sources */, - C4358B2328CBCC2600121D18 /* NoWarningsView.swift in Sources */, - C4358B2428CBCC2600121D18 /* BetterAlert.swift in Sources */, - C4358B2528CBCC2600121D18 /* NSWindowExtension.swift in Sources */, - C4358B2628CBCC2600121D18 /* ValetProxy.swift in Sources */, - C4358B2728CBCC2600121D18 /* App+ConfigWatch.swift in Sources */, - C4358B2828CBCC2600121D18 /* HotkeyPreferenceView.swift in Sources */, - C4358B2928CBCC2600121D18 /* PreferenceName.swift in Sources */, - C4358B2A28CBCC2600121D18 /* ValetSite.swift in Sources */, - C4358B2B28CBCC2600121D18 /* PhpInstallation.swift in Sources */, - C4358B2C28CBCC2600121D18 /* AddProxyVC.swift in Sources */, - C4358B2D28CBCC2600121D18 /* DomainListVC+ContextMenu.swift in Sources */, - C4358B2E28CBCC2600121D18 /* ActivePhpInstallation+Checks.swift in Sources */, - C4358B2F28CBCC2600121D18 /* PresetHelper.swift in Sources */, - C4358B3028CBCC2600121D18 /* ValetSite+Fake.swift in Sources */, - C4358B3128CBCC2600121D18 /* FakeSiteScanner.swift in Sources */, - C4358B3228CBCC2600121D18 /* SwiftUIHelper.swift in Sources */, - C4358B3328CBCC2600121D18 /* OnboardingView.swift in Sources */, - C4358B3428CBCC2600121D18 /* HomebrewDiagnostics.swift in Sources */, - C4358B3528CBCC2600121D18 /* HeaderView.swift in Sources */, - C4358B3628CBCC2600121D18 /* AppVersion.swift in Sources */, - C4358B3728CBCC2600121D18 /* ProgressVC.swift in Sources */, - C4358B3828CBCC2600121D18 /* PMWindowController.swift in Sources */, - C4358B3928CBCC2600121D18 /* Command.swift in Sources */, - C4358B3A28CBCC2600121D18 /* DomainListNameCell.swift in Sources */, - C4358B3B28CBCC2600121D18 /* Preset.swift in Sources */, - C4358B3C28CBCC2600121D18 /* GlobalKeybindPreference.swift in Sources */, - C4358B3D28CBCC2600121D18 /* SectionHeaderView.swift in Sources */, - C4358B3E28CBCC2600121D18 /* DomainListPhpCell.swift in Sources */, - C4358B3F28CBCC2600121D18 /* PreferencesWindowController+Hotkey.swift in Sources */, - C4358B4028CBCC2600121D18 /* Actions.swift in Sources */, - C4358B4128CBCC2600121D18 /* StatusMenu+Items.swift in Sources */, - C4358B4228CBCC2600121D18 /* DomainListKindCell.swift in Sources */, - C4358B4328CBCC2600121D18 /* Keys.swift in Sources */, - C4358B4428CBCC2600121D18 /* MainMenu+Actions.swift in Sources */, - C4358B4528CBCC2600121D18 /* TerminalProgressWindowController.swift in Sources */, - C4358B4628CBCC2600121D18 /* KeyCombo.swift in Sources */, - C4358B4728CBCC2600121D18 /* ProxyScanner.swift in Sources */, - C4358B4828CBCC2600121D18 /* CustomPrefs.swift in Sources */, - C4358B4928CBCC2600121D18 /* Application.swift in Sources */, - C4358B4A28CBCC2600121D18 /* App+ActivationPolicy.swift in Sources */, - C4358B4B28CBCC2600121D18 /* MainMenu+Switcher.swift in Sources */, - C4358B4C28CBCC2600121D18 /* PhpFrameworks.swift in Sources */, - C4358B4D28CBCC2600121D18 /* App.swift in Sources */, - C4358B4E28CBCC2600121D18 /* EnvironmentCheck.swift in Sources */, - C4358B4F28CBCC2600121D18 /* MenuBarImageGenerator.swift in Sources */, - C4358B5028CBCC2600121D18 /* HomebrewService.swift in Sources */, - C4358B5128CBCC2600121D18 /* Key.swift in Sources */, - C4358B5228CBCC2600121D18 /* WarningView.swift in Sources */, - C4358B5328CBCC2600121D18 /* ValetSiteScanner.swift in Sources */, - C4358B5428CBCC2600121D18 /* DomainListable.swift in Sources */, - C4358B5528CBCC2600121D18 /* Preferences.swift in Sources */, - C4358B5628CBCC2600121D18 /* XibLoadable.swift in Sources */, - C4358B5728CBCC2600121D18 /* CheckboxPreferenceView.swift in Sources */, - C4358B5828CBCC2600121D18 /* Warning.swift in Sources */, - C4358B5928CBCC2600121D18 /* HotKeysController.swift in Sources */, - C4358B5A28CBCC2600121D18 /* MainMenu.swift in Sources */, - C4358B5B28CBCC2600121D18 /* Logger.swift in Sources */, - C4358B5C28CBCC2600121D18 /* DomainListVC+Actions.swift in Sources */, - C4358B5D28CBCC2600121D18 /* AppUpdateChecker.swift in Sources */, - C4358B5E28CBCC2600121D18 /* HomebrewPackage.swift in Sources */, - C4358B5F28CBCC2600121D18 /* PhpSwitcher.swift in Sources */, - C4358B6028CBCC2600121D18 /* ServicesManager.swift in Sources */, - C4358B6128CBCC2600121D18 /* MenuBarIcons.swift in Sources */, - C4358B6228CBCC2600121D18 /* VersionPopoverView.swift in Sources */, - C4358B6328CBCC2600121D18 /* PhpConfigWatcher.swift in Sources */, - C4358B6428CBCC2600121D18 /* Helpers.swift in Sources */, - C4358B6528CBCC2600121D18 /* AppDelegate+InterApp.swift in Sources */, - C4358B6628CBCC2600121D18 /* ValetProxyScanner.swift in Sources */, - C4358B6728CBCC2600121D18 /* AppDelegate.swift in Sources */, - C4358B6828CBCC2600121D18 /* NSMenuExtension.swift in Sources */, - C4358B6928CBCC2600121D18 /* WarningListView.swift in Sources */, - C4358B6A28CBCC2600121D18 /* DomainListVC.swift in Sources */, - C4358B6B28CBCC2600121D18 /* MainMenu+Async.swift in Sources */, - C4358B6C28CBCC2600121D18 /* Process.swift in Sources */, - C4358B6D28CBCC2600121D18 /* Events.swift in Sources */, - C4358B6E28CBCC2600121D18 /* DomainListTLSCell.swift in Sources */, - C4358B6F28CBCC2600121D18 /* PMTableView.swift in Sources */, - C4358B7028CBCC2600121D18 /* Errors.swift in Sources */, - C4358B7128CBCC2600121D18 /* Paths.swift in Sources */, - C4358B7228CBCC2600121D18 /* ActivePhpInstallation.swift in Sources */, - C4358B7328CBCC2600121D18 /* SelectionVC.swift in Sources */, - C4358B7428CBCC2600121D18 /* StatsView.swift in Sources */, - C4358B7528CBCC2600121D18 /* AlertableError.swift in Sources */, - C4358B7628CBCC2600121D18 /* Filesystem.swift in Sources */, - C4358B7728CBCC2600121D18 /* ServicesView.swift in Sources */, - C4358B7828CBCC2600121D18 /* NSMenuItemExtension.swift in Sources */, - C4358B7928CBCC2600121D18 /* App+GlobalHotkey.swift in Sources */, - C4358B7A28CBCC2600121D18 /* InterAppHandler.swift in Sources */, - C4358B7B28CBCC2600121D18 /* PhpEnv.swift in Sources */, - C4358B7C28CBCC2600121D18 /* Alert.swift in Sources */, - C4358B7D28CBCC2600121D18 /* LocalNotification.swift in Sources */, - C4358B7E28CBCC2600121D18 /* NginxConfigurationFile.swift in Sources */, - C4358B7F28CBCC2600121D18 /* WarningsWindowController.swift in Sources */, - C4358B8028CBCC2600121D18 /* ComposerWindow.swift in Sources */, - C4358B8128CBCC2600121D18 /* InternalSwitcher.swift in Sources */, - C4358B8228CBCC2600121D18 /* OnboardingWindowController.swift in Sources */, - C4358B8328CBCC2600121D18 /* BetterAlertVC.swift in Sources */, - C4358B8428CBCC2600121D18 /* VersionExtractor.swift in Sources */, - C4358B8528CBCC2600121D18 /* HotKey.swift in Sources */, - C4358B8628CBCC2600121D18 /* PhpHelper.swift in Sources */, - C4358B8728CBCC2600121D18 /* StatusMenu.swift in Sources */, - C4358B8828CBCC2600121D18 /* DomainListTypeCell.swift in Sources */, - C4358B8928CBCC2600121D18 /* ModifierFlagsExtension.swift in Sources */, - C4358B8A28CBCC2600121D18 /* MainMenu+Startup.swift in Sources */, - C4358B8B28CBCC2600121D18 /* NoDomainResultsView.swift in Sources */, - C4358B8C28CBCC2600121D18 /* ComposerJson.swift in Sources */, - C4358B8D28CBCC2600121D18 /* StringExtension.swift in Sources */, - C4358B8E28CBCC2600121D18 /* Shell+PATH.swift in Sources */, - C4358B8F28CBCC2600121D18 /* Xdebug.swift in Sources */, - C4358B9028CBCC2600121D18 /* AppDelegate+MenuOutlets.swift in Sources */, - C4358B9128CBCC2600121D18 /* SiteScanner.swift in Sources */, - C4358B9228CBCC2600121D18 /* DomainListWindowController.swift in Sources */, - C4358B9328CBCC2600121D18 /* DomainListCellProtocol.swift in Sources */, - C4358B9428CBCC2600121D18 /* Constants.swift in Sources */, - C4358B9528CBCC2600121D18 /* AddSiteVC.swift in Sources */, - C4358B9628CBCC2600121D18 /* Stats.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; C4F7807525D7F84B000DBC97 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2087,11 +1741,12 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 999; + CURRENT_PROJECT_VERSION = 1000; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = phpmon/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -2114,11 +1769,12 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 999; + CURRENT_PROJECT_VERSION = 1000; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = phpmon/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -2132,63 +1788,216 @@ }; name = Release; }; - C4358BA528CBCC2600121D18 /* Debug */ = { + C4975D0728CD190C00FFB4E8 /* Release.Dev */ = { isa = XCBuildConfiguration; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIconSE; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AppColor; - CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 999; - DEBUG = YES; - DEVELOPMENT_TEAM = 8M54J5J787; - ENABLE_HARDENED_RUNTIME = YES; - INFOPLIST_FILE = "PHP Monitor SE-Info.plist"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.0; - OTHER_SWIFT_FLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.se; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "SPONSOR DEBUG"; - SWIFT_VERSION = 5.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; }; - name = Debug; + name = Release.Dev; }; - C4358BA628CBCC2600121D18 /* Release */ = { + C4975D0828CD190C00FFB4E8 /* Release.Dev */ = { isa = XCBuildConfiguration; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIconSE; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIconEA; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AppColor; CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 999; + CURRENT_PROJECT_VERSION = 1000; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; - INFOPLIST_FILE = "PHP Monitor SE-Info.plist"; + INFOPLIST_FILE = phpmon/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 6.0; - OTHER_SWIFT_FLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.se; + PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = SPONSOR; SWIFT_VERSION = 5.0; }; - name = Release; + name = Release.Dev; + }; + C4975D0928CD190C00FFB4E8 /* Release.Dev */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 8M54J5J787; + INFOPLIST_FILE = "phpmon-tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release.Dev; + }; + C4975D0A28CD193A00FFB4E8 /* Debug.Dev */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug.Dev; + }; + C4975D0B28CD193A00FFB4E8 /* Debug.Dev */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIconEA; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AppColor; + CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1000; + DEBUG = YES; + DEVELOPMENT_TEAM = 8M54J5J787; + ENABLE_HARDENED_RUNTIME = YES; + INFOPLIST_FILE = phpmon/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 6.0; + PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Debug.Dev; + }; + C4975D0C28CD193A00FFB4E8 /* Debug.Dev */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 8M54J5J787; + INFOPLIST_FILE = "phpmon-tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug.Dev; }; C4F7808125D7F84B000DBC97 /* Debug */ = { isa = XCBuildConfiguration; @@ -2235,7 +2044,9 @@ isa = XCConfigurationList; buildConfigurations = ( C41C1B4122B0098000E7CF16 /* Debug */, + C4975D0A28CD193A00FFB4E8 /* Debug.Dev */, C41C1B4222B0098000E7CF16 /* Release */, + C4975D0728CD190C00FFB4E8 /* Release.Dev */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -2244,16 +2055,9 @@ isa = XCConfigurationList; buildConfigurations = ( C41C1B4422B0098000E7CF16 /* Debug */, + C4975D0B28CD193A00FFB4E8 /* Debug.Dev */, C41C1B4522B0098000E7CF16 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - C4358BA428CBCC2600121D18 /* Build configuration list for PBXNativeTarget "PHP Monitor SE" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C4358BA528CBCC2600121D18 /* Debug */, - C4358BA628CBCC2600121D18 /* Release */, + C4975D0828CD190C00FFB4E8 /* Release.Dev */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -2262,7 +2066,9 @@ isa = XCConfigurationList; buildConfigurations = ( C4F7808125D7F84B000DBC97 /* Debug */, + C4975D0C28CD193A00FFB4E8 /* Debug.Dev */, C4F7808225D7F84B000DBC97 /* Release */, + C4975D0928CD190C00FFB4E8 /* Release.Dev */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor SE.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme similarity index 56% rename from PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor SE.xcscheme rename to PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index 1430354..de38b69 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor SE.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + buildConfiguration = "Debug.Dev"> diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/Contents.json b/phpmon/Assets.xcassets/AppIconEA.appiconset/Contents.json similarity index 100% rename from phpmon/Assets.xcassets/AppIconSE.appiconset/Contents.json rename to phpmon/Assets.xcassets/AppIconEA.appiconset/Contents.json diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_128x128.png b/phpmon/Assets.xcassets/AppIconEA.appiconset/icon_128x128.png similarity index 100% rename from phpmon/Assets.xcassets/AppIconSE.appiconset/icon_128x128.png rename to phpmon/Assets.xcassets/AppIconEA.appiconset/icon_128x128.png diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_128x128@2x.png b/phpmon/Assets.xcassets/AppIconEA.appiconset/icon_128x128@2x.png similarity index 100% rename from phpmon/Assets.xcassets/AppIconSE.appiconset/icon_128x128@2x.png rename to phpmon/Assets.xcassets/AppIconEA.appiconset/icon_128x128@2x.png diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_16x16.png b/phpmon/Assets.xcassets/AppIconEA.appiconset/icon_16x16.png similarity index 100% rename from phpmon/Assets.xcassets/AppIconSE.appiconset/icon_16x16.png rename to phpmon/Assets.xcassets/AppIconEA.appiconset/icon_16x16.png diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_16x16@2x.png b/phpmon/Assets.xcassets/AppIconEA.appiconset/icon_16x16@2x.png similarity index 100% rename from phpmon/Assets.xcassets/AppIconSE.appiconset/icon_16x16@2x.png rename to phpmon/Assets.xcassets/AppIconEA.appiconset/icon_16x16@2x.png diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_256x256.png b/phpmon/Assets.xcassets/AppIconEA.appiconset/icon_256x256.png similarity index 100% rename from phpmon/Assets.xcassets/AppIconSE.appiconset/icon_256x256.png rename to phpmon/Assets.xcassets/AppIconEA.appiconset/icon_256x256.png diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_256x256@2x.png b/phpmon/Assets.xcassets/AppIconEA.appiconset/icon_256x256@2x.png similarity index 100% rename from phpmon/Assets.xcassets/AppIconSE.appiconset/icon_256x256@2x.png rename to phpmon/Assets.xcassets/AppIconEA.appiconset/icon_256x256@2x.png diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_32x32.png b/phpmon/Assets.xcassets/AppIconEA.appiconset/icon_32x32.png similarity index 100% rename from phpmon/Assets.xcassets/AppIconSE.appiconset/icon_32x32.png rename to phpmon/Assets.xcassets/AppIconEA.appiconset/icon_32x32.png diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_32x32@2x.png b/phpmon/Assets.xcassets/AppIconEA.appiconset/icon_32x32@2x.png similarity index 100% rename from phpmon/Assets.xcassets/AppIconSE.appiconset/icon_32x32@2x.png rename to phpmon/Assets.xcassets/AppIconEA.appiconset/icon_32x32@2x.png diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_512x512.png b/phpmon/Assets.xcassets/AppIconEA.appiconset/icon_512x512.png similarity index 100% rename from phpmon/Assets.xcassets/AppIconSE.appiconset/icon_512x512.png rename to phpmon/Assets.xcassets/AppIconEA.appiconset/icon_512x512.png diff --git a/phpmon/Assets.xcassets/AppIconSE.appiconset/icon_512x512@2x.png b/phpmon/Assets.xcassets/AppIconEA.appiconset/icon_512x512@2x.png similarity index 100% rename from phpmon/Assets.xcassets/AppIconSE.appiconset/icon_512x512@2x.png rename to phpmon/Assets.xcassets/AppIconEA.appiconset/icon_512x512@2x.png diff --git a/phpmon/Domain/App/AppDelegate.swift b/phpmon/Domain/App/AppDelegate.swift index fd020bb..2cc0fb4 100644 --- a/phpmon/Domain/App/AppDelegate.swift +++ b/phpmon/Domain/App/AppDelegate.swift @@ -71,15 +71,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele logger.verbosity = .performance Log.info("Extra verbose mode has been activated.") } - Log.separator(as: .info) - #if SPONSOR - Log.info("PHP MONITOR SE by Nico Verbruggen") - #else - Log.info("PHP MONITOR by Nico Verbruggen") - #endif + Log.separator(as: .info) + Log.info("PHP MONITOR by Nico Verbruggen") Log.info("Version \(App.version)") Log.separator(as: .info) + self.sharedShell = Shell.user self.state = App.shared self.menu = MainMenu.shared From 61528cea466f8e5769226af51662736de693bab8 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 14 Sep 2022 19:05:14 +0200 Subject: [PATCH 004/181] =?UTF-8?q?=F0=9F=8F=97=20WIP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 6 ++++ phpmon/Domain/App/App.swift | 3 ++ phpmon/Domain/App/EnvironmentManager.swift | 34 ++++++++++++++++++++++ phpmon/Domain/Menu/MainMenu+Startup.swift | 2 ++ 4 files changed, 45 insertions(+) create mode 100644 phpmon/Domain/App/EnvironmentManager.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index cfb89ae..39844d6 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -191,6 +191,8 @@ C495F5B028A42E080087F70A /* EnvironmentCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */; }; C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PreferencesWindowController.swift */; }; C4998F0B2617633900B2526E /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PreferencesWindowController.swift */; }; + C4A6957628D23EE300A14CF8 /* EnvironmentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A6957528D23EE300A14CF8 /* EnvironmentManager.swift */; }; + C4A6957728D23EE300A14CF8 /* EnvironmentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A6957528D23EE300A14CF8 /* EnvironmentManager.swift */; }; C4A81CA428C67101008DD9D1 /* PMTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A81CA328C67101008DD9D1 /* PMTableView.swift */; }; C4A81CA528C67101008DD9D1 /* PMTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A81CA328C67101008DD9D1 /* PMTableView.swift */; }; C4AC51FC27E27F47008528CA /* DomainListKindCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */; }; @@ -432,6 +434,7 @@ C4930849279F331F009C240B /* AddSiteVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSiteVC.swift; sourceTree = ""; }; C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentCheck.swift; sourceTree = ""; }; C4998F092617633900B2526E /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; + C4A6957528D23EE300A14CF8 /* EnvironmentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentManager.swift; sourceTree = ""; }; C4A81CA328C67101008DD9D1 /* PMTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PMTableView.swift; sourceTree = ""; }; C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListKindCell.swift; sourceTree = ""; }; C4ACA38E25C754C100060C66 /* PhpExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtension.swift; sourceTree = ""; }; @@ -934,6 +937,7 @@ C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */, C40FE736282ABA4F00A302C2 /* AppVersion.swift */, C45E76132854A65300B4FE0C /* ServicesManager.swift */, + C4A6957528D23EE300A14CF8 /* EnvironmentManager.swift */, ); path = App; sourceTree = ""; @@ -1333,6 +1337,7 @@ C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */, C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */, C4D9F24B280B69E100DCD39A /* AddProxyVC.swift in Sources */, + C4A6957628D23EE300A14CF8 /* EnvironmentManager.swift in Sources */, C41E871A2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */, C40C7F2827721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */, C463E380284930EE00422731 /* PresetHelper.swift in Sources */, @@ -1578,6 +1583,7 @@ C4D9F24C280B69E100DCD39A /* AddProxyVC.swift in Sources */, C4B5853F2770FE3900DA4FBE /* Paths.swift in Sources */, C481F79A26164A7C004FBCFF /* Preferences.swift in Sources */, + C4A6957728D23EE300A14CF8 /* EnvironmentManager.swift in Sources */, C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */, C4A81CA528C67101008DD9D1 /* PMTableView.swift in Sources */, C4B585422770FE3900DA4FBE /* Shell.swift in Sources */, diff --git a/phpmon/Domain/App/App.swift b/phpmon/Domain/App/App.swift index df288d8..308b099 100644 --- a/phpmon/Domain/App/App.swift +++ b/phpmon/Domain/App/App.swift @@ -47,6 +47,9 @@ class App { // MARK: Variables + /** Technical information about the current environment. */ + var environment = EnvironmentManager() + /** The list of preferences that are currently active. */ var preferences: [PreferenceName: Bool]! diff --git a/phpmon/Domain/App/EnvironmentManager.swift b/phpmon/Domain/App/EnvironmentManager.swift new file mode 100644 index 0000000..7417e1c --- /dev/null +++ b/phpmon/Domain/App/EnvironmentManager.swift @@ -0,0 +1,34 @@ +// +// EnvironmentManager.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 14/09/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +public class EnvironmentManager { + var values: [EnvironmentProperty: Bool] = [:] + + public func process() async { + self.values[.hasValetInstalled] = !{ + let output = valet("--version", sudo: false) + // Failure condition #1: does not contain Laravel Valet + if !output.contains("Laravel Valet") { + return true + } + // Extract the version number + Valet.shared.version = VersionExtractor.from(output) + // Get the actual version + return Valet.shared.version == nil + }() // returns true if none of the failure conditions are met + + dump(self.values) + } +} + +public enum EnvironmentProperty { + case hasHomebrewInstalled + case hasValetInstalled +} diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 4071bc0..1b90d57 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -18,6 +18,8 @@ extension MainMenu { self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!) } + await App.shared.environment.process() + if await Startup().checkEnvironment() { self.onEnvironmentPass() } else { From c35e7781f476a1467e73c3119e4f48cbce58beaf Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 15 Sep 2022 17:36:13 +0200 Subject: [PATCH 005/181] =?UTF-8?q?=F0=9F=94=A5=20Remove=20unneeded=20plis?= =?UTF-8?q?t=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor SE-Info.plist | 49 --------------------------- PHP Monitor.xcodeproj/project.pbxproj | 2 -- 2 files changed, 51 deletions(-) delete mode 100644 PHP Monitor SE-Info.plist diff --git a/PHP Monitor SE-Info.plist b/PHP Monitor SE-Info.plist deleted file mode 100644 index 7918ee3..0000000 --- a/PHP Monitor SE-Info.plist +++ /dev/null @@ -1,49 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleURLTypes - - - CFBundleTypeRole - Viewer - CFBundleURLName - com.nicoverbruggen.phpmon - CFBundleURLSchemes - - phpmon - - - - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - LSApplicationCategoryType - public.app-category.utilities - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - LSUIElement - - NSHumanReadableCopyright - Copyright © 2019-2022 Nico Verbruggen. All rights reserved. - NSMainStoryboardFile - Main - NSPrincipalClass - NSApplication - - diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 39844d6..d509d82 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -388,7 +388,6 @@ C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Shell+PATH.swift"; sourceTree = ""; }; C42F26722805B4B400938AC7 /* DomainListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListable.swift; sourceTree = ""; }; C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-secure-proxy.test"; sourceTree = ""; }; - C4358BA828CBCC2600121D18 /* PHP Monitor SE-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "PHP Monitor SE-Info.plist"; path = "/Users/nicoverbruggen/Code/phpmon/PHP Monitor SE-Info.plist"; sourceTree = ""; }; C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Notifications.swift"; sourceTree = ""; }; C43A8A1925D9CD1000591B77 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; C43A8A1F25D9D1D700591B77 /* brew-formula.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "brew-formula.json"; sourceTree = ""; }; @@ -665,7 +664,6 @@ C4F7807A25D7F84B000DBC97 /* phpmon-tests */, C41C1B3422B0097F00E7CF16 /* Products */, C4D309E72770EF2F00958BCF /* Frameworks */, - C4358BA828CBCC2600121D18 /* PHP Monitor SE-Info.plist */, ); sourceTree = ""; }; From bb124bd0ee7090c7e0ec65478e9bc6094a9a3581 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 17 Sep 2022 23:16:49 +0200 Subject: [PATCH 006/181] =?UTF-8?q?=F0=9F=91=8C=20Cleanup=20and=20removal?= =?UTF-8?q?=20of=20unneeded=20dump?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/App/AppDelegate+MenuOutlets.swift | 2 +- phpmon/Domain/App/EnvironmentManager.swift | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/phpmon/Domain/App/AppDelegate+MenuOutlets.swift b/phpmon/Domain/App/AppDelegate+MenuOutlets.swift index 9461de7..acd41bc 100644 --- a/phpmon/Domain/App/AppDelegate+MenuOutlets.swift +++ b/phpmon/Domain/App/AppDelegate+MenuOutlets.swift @@ -37,7 +37,7 @@ extension AppDelegate { .window?.contentViewController as? DomainListVC if vc != nil { - // If the view exists, directly reload the list of sites + // If the view exists, directly reload the list of sites. vc!.reloadDomains() } else { // If the view does not exist, reload the cached data that was populated when the app initially launched. diff --git a/phpmon/Domain/App/EnvironmentManager.swift b/phpmon/Domain/App/EnvironmentManager.swift index 7417e1c..b7f05cd 100644 --- a/phpmon/Domain/App/EnvironmentManager.swift +++ b/phpmon/Domain/App/EnvironmentManager.swift @@ -14,17 +14,19 @@ public class EnvironmentManager { public func process() async { self.values[.hasValetInstalled] = !{ let output = valet("--version", sudo: false) + // Failure condition #1: does not contain Laravel Valet if !output.contains("Laravel Valet") { return true } + // Extract the version number Valet.shared.version = VersionExtractor.from(output) + // Get the actual version return Valet.shared.version == nil - }() // returns true if none of the failure conditions are met - dump(self.values) + }() // returns true if none of the failure conditions are met } } From fc27131ccac1caf93f4359746300c7ebc485c0f5 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sun, 18 Sep 2022 00:05:37 +0200 Subject: [PATCH 007/181] =?UTF-8?q?=F0=9F=91=8C=20Add=20support=20for=207.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.2", "7.1", "7.0"]).all + ) + + // Non-strict check (ignoring patch has no effect) + 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.2", "7.1", "7.0"]).all + ) + } + + func testCanCheckLessThanConstraints() throws { + XCTAssertEqual( + PhpVersionNumberCollection + .make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]) + .matching(constraint: "<7.2", strict: true), + PhpVersionNumberCollection + .make(from: ["7.1", "7.0"]).all + ) + + XCTAssertEqual( + PhpVersionNumberCollection + .make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]) + .matching(constraint: "<7.2.0", strict: true), + PhpVersionNumberCollection + .make(from: ["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.2", "7.1", "7.0"]).all + ) + + // Non-strict check (patch resolves to 7.2.999, which is bigger than 7.2.5) + 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.1", "7.0"]).all + ) + } } diff --git a/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift b/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift index 398285c..c262b2c 100644 --- a/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift +++ b/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift @@ -87,6 +87,14 @@ public struct PhpVersionNumberCollection: Equatable { return self.versions.filter { $0.isNewerThan(version, strict) } } + if let version = PhpVersionNumber.make(from: constraint, type: .smallerThanOrEqual) { + return self.versions.filter { $0.isSameAs(version, strict) || $0.isOlderThan(version, strict)} + } + + if let version = PhpVersionNumber.make(from: constraint, type: .smallerThan) { + return self.versions.filter { $0.isOlderThan(version, strict)} + } + return [] } } @@ -120,12 +128,8 @@ public struct PhpVersionNumber: Equatable, Hashable { case tildeVersionRange = #"^~(?\d+).(?\d+).?(?\d+)?\z"# case greaterThanOrEqual = #"^>=(?\d+).(?\d+).?(?\d+)?\z"# case greaterThan = #"^>(?\d+).(?\d+).?(?\d+)?\z"# - - // TODO: (6.0) Handle these cases (even though I suspect these are uncommon) - /* case smallerThanOrEqual = #"^<=(?\d+).(?\d+).?(?\d+)?\z"# case smallerThan = #"^<(?\d+).(?\d+).?(?\d+)?\z"# - */ } public static func parse(_ text: String) throws -> Self { @@ -179,6 +183,15 @@ public struct PhpVersionNumber: Equatable, Hashable { ) } + internal func isOlderThan(_ 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 && ( From 0d86f3ded68f1454c11a8f9e74e8525c84d39805 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sun, 18 Sep 2022 14:09:36 +0200 Subject: [PATCH 008/181] =?UTF-8?q?=F0=9F=93=9D=20Update=20README,=20contr?= =?UTF-8?q?ibution=20guidelines?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/contributing.md | 23 +++++++++++++++++++++++ .github/pull_request_template.md | 2 +- README.md | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 .github/contributing.md diff --git a/.github/contributing.md b/.github/contributing.md new file mode 100644 index 0000000..a1b1f4c --- /dev/null +++ b/.github/contributing.md @@ -0,0 +1,23 @@ +# Contribution Guidelines + +Thank you for your interest in contributing to PHP Monitor. + +I consider this project a bit of a nice side-project to my daily gig, so it is very much a personal affair where I love to tinker around. + +**While the code of the latest PHP Monitor release is public, many things are constantly in flux that may not be pushed to this repository yet.** + +I don't mean to be rude, but I don't want other people involved with the project beyond simply contributing a few small things here and there, as has been the case in the past. + +The extra mental overhead of having additional contributors to report to, whose code will need to be reviewed... it's a lot and it makes working on PHP Monitor less enjoyable for me. + +Plus, at this point, the majority of PHP Monitor's main functionality is also done. + +As a result, I may refer you to this file at some point. Again, I don't wish to be rude, but this general rule stands: + +**Making any changes in a fork and opening a pull request without opening an issue first will most likely result in your PR being closed without mercy.** + +To repeat, I am **not opposed** to small contributions and fixes, if they are **meaningful or insightful**. + +To learn more, please check out the [pull request template](/.github/pull_request_template.md) which contains more information about my contribution requirements. (This will also show up when you open a new PR.) + +Thank you for respecting this! \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 85293b0..3477d27 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -16,7 +16,7 @@ In short: It is usually best to *get in touch first* if you are making substanti ## About destination branches -Please keep in mind that `main` is reserved for the current code state of the latest release and should *never* be the destination branch unless a new release is happening. **Merge requests that target `main` will be closed without mercy.** +Please keep in mind that `main` is reserved for the current code state of the latest release and should *never* be the destination branch unless a new release is happening. **Pull requests that target `main` will be closed without mercy.** Usually, the best target is the stable `dev/x.x` branch that corresponds with the latest major version that is released. diff --git a/README.md b/README.md index 7575671..b5c73c8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ > **Note** -> 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, as this is something I make in my free time. **Thank you!** ⭐️ +> 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 [sponsoring](https://nicoverbruggen.be/sponsor) to support the project, as this is something I make in my free time. **Thank you!** ⭐️

PHP Monitor Logo

From 4494a0555f48320f55728042db7309ba8b40af14 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 20 Sep 2022 00:33:58 +0200 Subject: [PATCH 009/181] =?UTF-8?q?=F0=9F=9A=A7=20WIP:=20Shell=20rework?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 6 +++++ phpmon/Common/Core/NewShell.swift | 39 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 phpmon/Common/Core/NewShell.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index d509d82..3e8469f 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 03E36FE728D9219000636F7F /* NewShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* NewShell.swift */; }; + 03E36FE828D9219000636F7F /* NewShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* NewShell.swift */; }; 5420395926135DC100FB00FA /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; }; 5420395F2613607600FB00FA /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; }; 5489625828312FAD004F647A /* CreatedFromFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5489625728312FAD004F647A /* CreatedFromFile.swift */; }; @@ -322,6 +324,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 03E36FE628D9219000636F7F /* NewShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewShell.swift; sourceTree = ""; }; 5420395826135DC100FB00FA /* PrefsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsVC.swift; sourceTree = ""; }; 5420395E2613607600FB00FA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; 5489625728312FAD004F647A /* CreatedFromFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatedFromFile.swift; sourceTree = ""; }; @@ -647,6 +650,7 @@ C4C1019A27C65C6F001FACC2 /* Process.swift */, C40C7F2F27722E8D00DDDCDC /* Logger.swift */, C417DC73277614690015E6EE /* Helpers.swift */, + 03E36FE628D9219000636F7F /* NewShell.swift */, ); path = Core; sourceTree = ""; @@ -1386,6 +1390,7 @@ C41CA5ED2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */, C46E206D28299B3800D909D6 /* AppUpdateChecker.swift in Sources */, C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */, + 03E36FE728D9219000636F7F /* NewShell.swift in Sources */, C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */, C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */, C4068CAA27B0890D00544CD5 /* MenuBarIcons.swift in Sources */, @@ -1536,6 +1541,7 @@ C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */, 5489625928313231004F647A /* CreatedFromFile.swift in Sources */, 54D9E0B327E4F51E003B9AD9 /* HotKeysController.swift in Sources */, + 03E36FE828D9219000636F7F /* NewShell.swift in Sources */, C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */, C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */, C46E20702829D27F00D909D6 /* AppUpdaterCheckTest.swift in Sources */, diff --git a/phpmon/Common/Core/NewShell.swift b/phpmon/Common/Core/NewShell.swift new file mode 100644 index 0000000..2c7d69c --- /dev/null +++ b/phpmon/Common/Core/NewShell.swift @@ -0,0 +1,39 @@ +// +// NewShell.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 20/09/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class NewShell { + static var shared: Shellable! + + public func useTestable(_ expectations: [String: String]) { + Self.shared = TestableShell(expectations: expectations) + } +} + +protocol Shellable { + func pipe(_ command: String) -> String +} + +class SystemShell: Shellable { + func pipe(_ command: String) -> String { + return "shell output" + } +} + +class TestableShell: Shellable { + init(expectations: [String: String]) { + self.expectations = expectations + } + + var expectations: [String: String] = [:] + + func pipe(_ command: String) -> String { + return expectations[command] ?? "" + } +} From 3f25759d4fdbd497bb7e8d591c534d46089cc916 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 20 Sep 2022 20:49:29 +0200 Subject: [PATCH 010/181] =?UTF-8?q?=F0=9F=8F=97=20Fix=20SwiftLint,=20WIP?= =?UTF-8?q?=20shell=20rework?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 15 ++++++- phpmon-tests/Concord/ShellTest.swift | 32 ++++++++++++++ phpmon/Common/Core/NewShell.swift | 60 ++++++++++++++++++++++++--- 3 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 phpmon-tests/Concord/ShellTest.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 3e8469f..082bf9d 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -63,6 +63,7 @@ C40FE738282ABA4F00A302C2 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; C40FE73B282ABB2E00A302C2 /* AppVersionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE739282ABB2E00A302C2 /* AppVersionTest.swift */; }; C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; }; + C413E43528DA3EB100AE33C7 /* ShellTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C413E43428DA3EB100AE33C7 /* ShellTest.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 */; }; @@ -357,6 +358,7 @@ C40FE736282ABA4F00A302C2 /* AppVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersion.swift; sourceTree = ""; }; C40FE739282ABB2E00A302C2 /* AppVersionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionTest.swift; sourceTree = ""; }; C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackage.swift; sourceTree = ""; }; + C413E43428DA3EB100AE33C7 /* ShellTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellTest.swift; sourceTree = ""; }; C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpFrameworks.swift; sourceTree = ""; }; C415D3B62770F294005EF286 /* Actions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = ""; }; C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+InterApp.swift"; sourceTree = ""; }; @@ -655,6 +657,14 @@ path = Core; sourceTree = ""; }; + C413E43328DA3E8F00AE33C7 /* Concord */ = { + isa = PBXGroup; + children = ( + C413E43428DA3EB100AE33C7 /* ShellTest.swift */, + ); + path = Concord; + sourceTree = ""; + }; C41C1B2A22B0097F00E7CF16 = { isa = PBXGroup; children = ( @@ -1140,6 +1150,7 @@ C4F7807A25D7F84B000DBC97 /* phpmon-tests */ = { isa = PBXGroup; children = ( + C413E43328DA3E8F00AE33C7 /* Concord */, C4F7807D25D7F84B000DBC97 /* Info.plist */, C43A8A1925D9CD1000591B77 /* Utility.swift */, C40C7F1C27720E1400DDDCDC /* Test Files */, @@ -1290,6 +1301,7 @@ /* Begin PBXShellScriptBuildPhase section */ C4F5FBCB28216985001065C5 /* Run `swiftlint` */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -1460,6 +1472,7 @@ C41CA5EE2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */, C4FACE81288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift in Sources */, 54D9E0B727E4F51E003B9AD9 /* HotKey.swift in Sources */, + C413E43528DA3EB100AE33C7 /* ShellTest.swift in Sources */, C4205A7F27F4D21800191A39 /* ValetProxy.swift in Sources */, C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */, C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */, diff --git a/phpmon-tests/Concord/ShellTest.swift b/phpmon-tests/Concord/ShellTest.swift new file mode 100644 index 0000000..6467068 --- /dev/null +++ b/phpmon-tests/Concord/ShellTest.swift @@ -0,0 +1,32 @@ +// +// ShellTest.swift +// phpmon-tests +// +// Created by Nico Verbruggen on 20/09/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import XCTest + +class ShellTest: XCTestCase { + func test_default_shell_is_system_shell() { + XCTAssertTrue(NewShell.shared is SystemShell) + + XCTAssertTrue(NewShell.shared.syncPipe("php -v") + .contains("Copyright (c) The PHP Group")) + + let expectedPhpOutput = """ + PHP 8.1.10 (cli) (built: Sep 3 2022 12:09:27) (NTS) + Copyright (c) The PHP Group + Zend Engine v4.1.10, Copyright (c) Zend Technologies + with Zend OPcache v8.1.10, Copyright (c), by Zend Technologies + with Xdebug v3.1.4, Copyright (c) 2002-2022, by Derick Rethans + """ + + NewShell.useTestable([ + "php -v": expectedPhpOutput + ]) + + XCTAssertEqual(expectedPhpOutput, NewShell.shared.syncPipe("php -v")) + } +} diff --git a/phpmon/Common/Core/NewShell.swift b/phpmon/Common/Core/NewShell.swift index 2c7d69c..5ee43ea 100644 --- a/phpmon/Common/Core/NewShell.swift +++ b/phpmon/Common/Core/NewShell.swift @@ -9,20 +9,64 @@ import Foundation class NewShell { - static var shared: Shellable! + static var shared: Shellable = SystemShell() - public func useTestable(_ expectations: [String: String]) { + /// Uses a testable shell with predefined responses. You specify the terminal's output. + public static func useTestable(_ expectations: [String: String]) { Self.shared = TestableShell(expectations: expectations) } + + /// Reverts back to the system shell. You do not need to call this, only after using `useTestable()`. + public static func useSystem() { + Self.shared = SystemShell() + } } protocol Shellable { - func pipe(_ command: String) -> String + func syncPipe(_ command: String) -> String + func pipe(_ command: String) async -> String } class SystemShell: Shellable { - func pipe(_ command: String) -> String { - return "shell output" + public var launchPath: String = "/bin/sh" + + public var exports: String = "" + + private func getShellProcess(for command: String) -> Process { + var completeCommand = "" + + // Basic export (PATH) + completeCommand += "export PATH=\(Paths.binPath):$PATH && " + + // Put additional exports in between + if !self.exports.isEmpty { + completeCommand += "\(self.exports) && " + } + + completeCommand += command + + let task = Process() + task.launchPath = self.launchPath + task.arguments = ["--noprofile", "-norc", "--login", "-c", completeCommand] + return task + } + + func syncPipe(_ command: String) -> String { + let task = getShellProcess(for: command) + let pipe = Pipe() + + task.standardOutput = pipe + task.launch() + + return String( + data: pipe.fileHandleForReading.readDataToEndOfFile(), + encoding: .utf8 + ) ?? "" + } + + func pipe(_ command: String) async -> String { + // TODO + return "" } } @@ -33,7 +77,11 @@ class TestableShell: Shellable { var expectations: [String: String] = [:] - func pipe(_ command: String) -> String { + func pipe(_ command: String) async -> String { + return expectations[command] ?? "" + } + + func syncPipe(_ command: String) -> String { return expectations[command] ?? "" } } From 90a69338f7d642fe2b2d3a962924fa64be2289b2 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 20 Sep 2022 20:53:15 +0200 Subject: [PATCH 011/181] =?UTF-8?q?=F0=9F=8F=97=20Add=20additional=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon-tests/Concord/ShellTest.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/phpmon-tests/Concord/ShellTest.swift b/phpmon-tests/Concord/ShellTest.swift index 6467068..7cc2f8e 100644 --- a/phpmon-tests/Concord/ShellTest.swift +++ b/phpmon-tests/Concord/ShellTest.swift @@ -14,7 +14,9 @@ class ShellTest: XCTestCase { XCTAssertTrue(NewShell.shared.syncPipe("php -v") .contains("Copyright (c) The PHP Group")) + } + func test_we_can_predefine_responses_for_dummy_shell() { let expectedPhpOutput = """ PHP 8.1.10 (cli) (built: Sep 3 2022 12:09:27) (NTS) Copyright (c) The PHP Group @@ -27,6 +29,8 @@ class ShellTest: XCTestCase { "php -v": expectedPhpOutput ]) + XCTAssertTrue(NewShell.shared is TestableShell) + XCTAssertEqual(expectedPhpOutput, NewShell.shared.syncPipe("php -v")) } } From 39769d815f84397105602a9d15c450842961ef3b Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 21 Sep 2022 21:06:11 +0200 Subject: [PATCH 012/181] =?UTF-8?q?=F0=9F=9A=9B=20Move=20around=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 44 +++++++++++++++---- .../{Concord => Next}/ShellTest.swift | 10 ++--- phpmon/Next/NxtShell.swift | 23 ++++++++++ phpmon/Next/Shellable.swift | 14 ++++++ .../NewShell.swift => Next/SystemShell.swift} | 39 +--------------- phpmon/Next/TestableShell.swift | 25 +++++++++++ 6 files changed, 104 insertions(+), 51 deletions(-) rename phpmon-tests/{Concord => Next}/ShellTest.swift (76%) create mode 100644 phpmon/Next/NxtShell.swift create mode 100644 phpmon/Next/Shellable.swift rename phpmon/{Common/Core/NewShell.swift => Next/SystemShell.swift} (53%) create mode 100644 phpmon/Next/TestableShell.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 082bf9d..baa1c54 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -7,8 +7,8 @@ objects = { /* Begin PBXBuildFile section */ - 03E36FE728D9219000636F7F /* NewShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* NewShell.swift */; }; - 03E36FE828D9219000636F7F /* NewShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* NewShell.swift */; }; + 03E36FE728D9219000636F7F /* NxtShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* NxtShell.swift */; }; + 03E36FE828D9219000636F7F /* NxtShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* NxtShell.swift */; }; 5420395926135DC100FB00FA /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; }; 5420395F2613607600FB00FA /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; }; 5489625828312FAD004F647A /* CreatedFromFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5489625728312FAD004F647A /* CreatedFromFile.swift */; }; @@ -148,6 +148,12 @@ C46E206D28299B3800D909D6 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; C46E206E28299B3800D909D6 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; C46E20702829D27F00D909D6 /* AppUpdaterCheckTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206F2829D27F00D909D6 /* AppUpdaterCheckTest.swift */; }; + C46EBC4428DB95F0007ACC74 /* Shellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* Shellable.swift */; }; + C46EBC4528DB95F0007ACC74 /* Shellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* Shellable.swift */; }; + C46EBC4728DB9644007ACC74 /* SystemShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4628DB9644007ACC74 /* SystemShell.swift */; }; + C46EBC4828DB9644007ACC74 /* SystemShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4628DB9644007ACC74 /* SystemShell.swift */; }; + C46EBC4A28DB966A007ACC74 /* TestableShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4928DB966A007ACC74 /* TestableShell.swift */; }; + C46EBC4B28DB966A007ACC74 /* TestableShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4928DB966A007ACC74 /* TestableShell.swift */; }; C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; }; C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */; }; C46FA9892822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */; }; @@ -325,7 +331,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 03E36FE628D9219000636F7F /* NewShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewShell.swift; sourceTree = ""; }; + 03E36FE628D9219000636F7F /* NxtShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NxtShell.swift; sourceTree = ""; }; 5420395826135DC100FB00FA /* PrefsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsVC.swift; sourceTree = ""; }; 5420395E2613607600FB00FA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; 5489625728312FAD004F647A /* CreatedFromFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatedFromFile.swift; sourceTree = ""; }; @@ -418,6 +424,9 @@ C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListCellProtocol.swift; sourceTree = ""; }; C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateChecker.swift; sourceTree = ""; }; C46E206F2829D27F00D909D6 /* AppUpdaterCheckTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppUpdaterCheckTest.swift; sourceTree = ""; }; + C46EBC4328DB95F0007ACC74 /* Shellable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shellable.swift; sourceTree = ""; }; + C46EBC4628DB9644007ACC74 /* SystemShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemShell.swift; sourceTree = ""; }; + C46EBC4928DB966A007ACC74 /* TestableShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableShell.swift; sourceTree = ""; }; C46FA23E246C358E00944F05 /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = ""; }; C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigurationFile.swift; sourceTree = ""; }; C46FA98A2822F08F00D78807 /* PhpConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigurationTest.swift; sourceTree = ""; }; @@ -652,17 +661,16 @@ C4C1019A27C65C6F001FACC2 /* Process.swift */, C40C7F2F27722E8D00DDDCDC /* Logger.swift */, C417DC73277614690015E6EE /* Helpers.swift */, - 03E36FE628D9219000636F7F /* NewShell.swift */, ); path = Core; sourceTree = ""; }; - C413E43328DA3E8F00AE33C7 /* Concord */ = { + C413E43328DA3E8F00AE33C7 /* Next */ = { isa = PBXGroup; children = ( C413E43428DA3EB100AE33C7 /* ShellTest.swift */, ); - path = Concord; + path = Next; sourceTree = ""; }; C41C1B2A22B0097F00E7CF16 = { @@ -693,6 +701,7 @@ C41C1B3522B0097F00E7CF16 /* phpmon */ = { isa = PBXGroup; children = ( + C46EBC3F28DB9550007ACC74 /* Next */, C4B5853A2770FE2500DA4FBE /* Common */, C41E181722CB61EB0072CF09 /* Domain */, 54D9E0BE27E4F5C0003B9AD9 /* Vendor */, @@ -863,6 +872,17 @@ path = DomainList; sourceTree = ""; }; + C46EBC3F28DB9550007ACC74 /* Next */ = { + isa = PBXGroup; + children = ( + 03E36FE628D9219000636F7F /* NxtShell.swift */, + C46EBC4628DB9644007ACC74 /* SystemShell.swift */, + C46EBC4928DB966A007ACC74 /* TestableShell.swift */, + C46EBC4328DB95F0007ACC74 /* Shellable.swift */, + ); + path = Next; + sourceTree = ""; + }; C47331A0247093AC009A0597 /* Menu */ = { isa = PBXGroup; children = ( @@ -1150,7 +1170,7 @@ C4F7807A25D7F84B000DBC97 /* phpmon-tests */ = { isa = PBXGroup; children = ( - C413E43328DA3E8F00AE33C7 /* Concord */, + C413E43328DA3E8F00AE33C7 /* Next */, C4F7807D25D7F84B000DBC97 /* Info.plist */, C43A8A1925D9CD1000591B77 /* Utility.swift */, C40C7F1C27720E1400DDDCDC /* Test Files */, @@ -1376,6 +1396,7 @@ C4AC51FC27E27F47008528CA /* DomainListKindCell.swift in Sources */, C4CDA893288F1A71007CE25F /* Keys.swift in Sources */, C4F361612836BFD9003598CC /* MainMenu+Actions.swift in Sources */, + C46EBC4A28DB966A007ACC74 /* TestableShell.swift in Sources */, C44C198D276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */, 54D9E0B827E4F51E003B9AD9 /* KeyCombo.swift in Sources */, C4C0E8E727F88B41002D32A9 /* ProxyScanner.swift in Sources */, @@ -1386,6 +1407,7 @@ C415937F27A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */, C4811D2422D70A4700B5F6B3 /* App.swift in Sources */, C495F5AF28A42E080087F70A /* EnvironmentCheck.swift in Sources */, + C46EBC4428DB95F0007ACC74 /* Shellable.swift in Sources */, C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */, C4F30B03278E16BA00755FCE /* HomebrewService.swift in Sources */, 54D9E0B427E4F51E003B9AD9 /* Key.swift in Sources */, @@ -1402,9 +1424,10 @@ C41CA5ED2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */, C46E206D28299B3800D909D6 /* AppUpdateChecker.swift in Sources */, C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */, - 03E36FE728D9219000636F7F /* NewShell.swift in Sources */, + 03E36FE728D9219000636F7F /* NxtShell.swift in Sources */, C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */, C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */, + C46EBC4728DB9644007ACC74 /* SystemShell.swift in Sources */, C4068CAA27B0890D00544CD5 /* MenuBarIcons.swift in Sources */, C44264C02850BD2A007400F1 /* VersionPopoverView.swift in Sources */, C4C8E81B276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */, @@ -1475,6 +1498,7 @@ C413E43528DA3EB100AE33C7 /* ShellTest.swift in Sources */, C4205A7F27F4D21800191A39 /* ValetProxy.swift in Sources */, C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */, + C46EBC4528DB95F0007ACC74 /* Shellable.swift in Sources */, C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */, 54FCFD2B276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */, C415D3B82770F294005EF286 /* Actions.swift in Sources */, @@ -1554,7 +1578,7 @@ C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */, 5489625928313231004F647A /* CreatedFromFile.swift in Sources */, 54D9E0B327E4F51E003B9AD9 /* HotKeysController.swift in Sources */, - 03E36FE828D9219000636F7F /* NewShell.swift in Sources */, + 03E36FE828D9219000636F7F /* NxtShell.swift in Sources */, C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */, C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */, C46E20702829D27F00D909D6 /* AppUpdaterCheckTest.swift in Sources */, @@ -1589,6 +1613,7 @@ C4F780B725D80B5D000DBC97 /* App.swift in Sources */, C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */, C485707628BF455100539B36 /* SectionHeaderView.swift in Sources */, + C46EBC4828DB9644007ACC74 /* SystemShell.swift in Sources */, C4E4404727C56F4700D225E1 /* ValetSite.swift in Sources */, C44CCD4A27AFF3BC00CE40E5 /* MainMenu+Async.swift in Sources */, C449B4F327EE7FC600C47E8A /* DomainListTypeCell.swift in Sources */, @@ -1609,6 +1634,7 @@ C40C7F1F2772136000DDDCDC /* PhpEnv.swift in Sources */, C464ADB0275A7A6A003FCD53 /* DomainListVC.swift in Sources */, C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */, + C46EBC4B28DB966A007ACC74 /* TestableShell.swift in Sources */, C418898A275FE8CB001EF227 /* Filesystem.swift in Sources */, C40FE73B282ABB2E00A302C2 /* AppVersionTest.swift in Sources */, C4F780C625D80B75000DBC97 /* XibLoadable.swift in Sources */, diff --git a/phpmon-tests/Concord/ShellTest.swift b/phpmon-tests/Next/ShellTest.swift similarity index 76% rename from phpmon-tests/Concord/ShellTest.swift rename to phpmon-tests/Next/ShellTest.swift index 7cc2f8e..631c48a 100644 --- a/phpmon-tests/Concord/ShellTest.swift +++ b/phpmon-tests/Next/ShellTest.swift @@ -10,9 +10,9 @@ import XCTest class ShellTest: XCTestCase { func test_default_shell_is_system_shell() { - XCTAssertTrue(NewShell.shared is SystemShell) + XCTAssertTrue(NxtShell.shared is SystemShell) - XCTAssertTrue(NewShell.shared.syncPipe("php -v") + XCTAssertTrue(NxtShell.shared.syncPipe("php -v") .contains("Copyright (c) The PHP Group")) } @@ -25,12 +25,12 @@ class ShellTest: XCTestCase { with Xdebug v3.1.4, Copyright (c) 2002-2022, by Derick Rethans """ - NewShell.useTestable([ + NxtShell.useTestable([ "php -v": expectedPhpOutput ]) - XCTAssertTrue(NewShell.shared is TestableShell) + XCTAssertTrue(NxtShell.shared is TestableShell) - XCTAssertEqual(expectedPhpOutput, NewShell.shared.syncPipe("php -v")) + XCTAssertEqual(expectedPhpOutput, NxtShell.shared.syncPipe("php -v")) } } diff --git a/phpmon/Next/NxtShell.swift b/phpmon/Next/NxtShell.swift new file mode 100644 index 0000000..f924b7c --- /dev/null +++ b/phpmon/Next/NxtShell.swift @@ -0,0 +1,23 @@ +// +// Shell.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 20/09/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class NxtShell { + static var shared: Shellable = SystemShell() + + /// Uses a testable shell with predefined responses. You specify the terminal's output. + public static func useTestable(_ expectations: [String: String]) { + Self.shared = TestableShell(expectations: expectations) + } + + /// Reverts back to the system shell. You do not need to call this, only after using `useTestable()`. + public static func useSystem() { + Self.shared = SystemShell() + } +} diff --git a/phpmon/Next/Shellable.swift b/phpmon/Next/Shellable.swift new file mode 100644 index 0000000..b238a36 --- /dev/null +++ b/phpmon/Next/Shellable.swift @@ -0,0 +1,14 @@ +// +// Shellable.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 21/09/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +protocol Shellable { + func syncPipe(_ command: String) -> String + func pipe(_ command: String) async -> String +} diff --git a/phpmon/Common/Core/NewShell.swift b/phpmon/Next/SystemShell.swift similarity index 53% rename from phpmon/Common/Core/NewShell.swift rename to phpmon/Next/SystemShell.swift index 5ee43ea..7eb76ff 100644 --- a/phpmon/Common/Core/NewShell.swift +++ b/phpmon/Next/SystemShell.swift @@ -1,32 +1,13 @@ // -// NewShell.swift +// SystemShell.swift // PHP Monitor // -// Created by Nico Verbruggen on 20/09/2022. +// Created by Nico Verbruggen on 21/09/2022. // Copyright © 2022 Nico Verbruggen. All rights reserved. // import Foundation -class NewShell { - static var shared: Shellable = SystemShell() - - /// Uses a testable shell with predefined responses. You specify the terminal's output. - public static func useTestable(_ expectations: [String: String]) { - Self.shared = TestableShell(expectations: expectations) - } - - /// Reverts back to the system shell. You do not need to call this, only after using `useTestable()`. - public static func useSystem() { - Self.shared = SystemShell() - } -} - -protocol Shellable { - func syncPipe(_ command: String) -> String - func pipe(_ command: String) async -> String -} - class SystemShell: Shellable { public var launchPath: String = "/bin/sh" @@ -69,19 +50,3 @@ class SystemShell: Shellable { return "" } } - -class TestableShell: Shellable { - init(expectations: [String: String]) { - self.expectations = expectations - } - - var expectations: [String: String] = [:] - - func pipe(_ command: String) async -> String { - return expectations[command] ?? "" - } - - func syncPipe(_ command: String) -> String { - return expectations[command] ?? "" - } -} diff --git a/phpmon/Next/TestableShell.swift b/phpmon/Next/TestableShell.swift new file mode 100644 index 0000000..ccdd2af --- /dev/null +++ b/phpmon/Next/TestableShell.swift @@ -0,0 +1,25 @@ +// +// TestableShell.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 21/09/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class TestableShell: Shellable { + init(expectations: [String: String]) { + self.expectations = expectations + } + + var expectations: [String: String] = [:] + + func pipe(_ command: String) async -> String { + return expectations[command] ?? "" + } + + func syncPipe(_ command: String) -> String { + return expectations[command] ?? "" + } + } From 1d396202db1c3d2a8f42908a772319daad8dcf38 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 21 Sep 2022 21:31:00 +0200 Subject: [PATCH 013/181] =?UTF-8?q?=F0=9F=91=8C=20Add=20PATH=20to=20System?= =?UTF-8?q?Shell?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon-tests/Next/ShellTest.swift | 7 +++++++ phpmon/Next/SystemShell.swift | 23 +++++++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/phpmon-tests/Next/ShellTest.swift b/phpmon-tests/Next/ShellTest.swift index 631c48a..98d24c0 100644 --- a/phpmon-tests/Next/ShellTest.swift +++ b/phpmon-tests/Next/ShellTest.swift @@ -16,6 +16,13 @@ class ShellTest: XCTestCase { .contains("Copyright (c) The PHP Group")) } + func test_system_shell_has_path() { + let systemShell = NxtShell.shared as! SystemShell + + XCTAssertTrue(systemShell.PATH.contains(":/usr/local/bin")) + XCTAssertTrue(systemShell.PATH.contains(":/usr/bin")) + } + func test_we_can_predefine_responses_for_dummy_shell() { let expectedPhpOutput = """ PHP 8.1.10 (cli) (built: Sep 3 2022 12:09:27) (NTS) diff --git a/phpmon/Next/SystemShell.swift b/phpmon/Next/SystemShell.swift index 7eb76ff..0011b91 100644 --- a/phpmon/Next/SystemShell.swift +++ b/phpmon/Next/SystemShell.swift @@ -11,7 +11,24 @@ import Foundation class SystemShell: Shellable { public var launchPath: String = "/bin/sh" - public var exports: String = "" + public var PATH: String = { + let task = Process() + task.launchPath = "/bin/zsh" + + // We need an interactive shell so the user's PATH is loaded in correctly + task.arguments = ["--login", "-ilc", "echo $PATH"] + + let pipe = Pipe() + task.standardOutput = pipe + task.launch() + + return String( + data: pipe.fileHandleForReading.readDataToEndOfFile(), + encoding: String.Encoding.utf8 + ) ?? "" + }() + + var exports: String = "" private func getShellProcess(for command: String) -> Process { var completeCommand = "" @@ -19,7 +36,7 @@ class SystemShell: Shellable { // Basic export (PATH) completeCommand += "export PATH=\(Paths.binPath):$PATH && " - // Put additional exports in between + // Put additional exports (as defined by the user) in between if !self.exports.isEmpty { completeCommand += "\(self.exports) && " } @@ -32,6 +49,8 @@ class SystemShell: Shellable { return task } + // MARK: - Shellable Protocol + func syncPipe(_ command: String) -> String { let task = getShellProcess(for: command) let pipe = Pipe() From e871a004901663797886f0a196ce1f8ee638a002 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 21 Sep 2022 21:42:03 +0200 Subject: [PATCH 014/181] =?UTF-8?q?=F0=9F=91=8C=20Add=20additional=20comme?= =?UTF-8?q?ntary=20to=20new=20shell=20classes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 12 +++++- .../Shell+PATH.swift | 0 .../{Core => Pending Removal}/Shell.swift | 2 + phpmon/Domain/Preferences/CustomPrefs.swift | 1 + phpmon/Next/SystemShell.swift | 41 ++++++++++++++++--- 5 files changed, 49 insertions(+), 7 deletions(-) rename phpmon/Common/{Core => Pending Removal}/Shell+PATH.swift (100%) rename phpmon/Common/{Core => Pending Removal}/Shell.swift (96%) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index baa1c54..455a64c 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -656,8 +656,6 @@ C4EC1E72279DFCF40010F296 /* Events.swift */, C4B5853D2770FE3900DA4FBE /* Command.swift */, C4B5853B2770FE3900DA4FBE /* Paths.swift */, - C4B5853C2770FE3900DA4FBE /* Shell.swift */, - C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */, C4C1019A27C65C6F001FACC2 /* Process.swift */, C40C7F2F27722E8D00DDDCDC /* Logger.swift */, C417DC73277614690015E6EE /* Helpers.swift */, @@ -883,6 +881,15 @@ path = Next; sourceTree = ""; }; + C46EBC4C28DB9F43007ACC74 /* Pending Removal */ = { + isa = PBXGroup; + children = ( + C4B5853C2770FE3900DA4FBE /* Shell.swift */, + C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */, + ); + path = "Pending Removal"; + sourceTree = ""; + }; C47331A0247093AC009A0597 /* Menu */ = { isa = PBXGroup; children = ( @@ -977,6 +984,7 @@ C4B5853A2770FE2500DA4FBE /* Common */ = { isa = PBXGroup; children = ( + C46EBC4C28DB9F43007ACC74 /* Pending Removal */, C40C7F2127721F7300DDDCDC /* Core */, 54B20EDF263AA22C00D3250E /* PHP */, C44CCD4327AFE93300CE40E5 /* Errors */, diff --git a/phpmon/Common/Core/Shell+PATH.swift b/phpmon/Common/Pending Removal/Shell+PATH.swift similarity index 100% rename from phpmon/Common/Core/Shell+PATH.swift rename to phpmon/Common/Pending Removal/Shell+PATH.swift diff --git a/phpmon/Common/Core/Shell.swift b/phpmon/Common/Pending Removal/Shell.swift similarity index 96% rename from phpmon/Common/Core/Shell.swift rename to phpmon/Common/Pending Removal/Shell.swift index 132309e..ba4ccde 100644 --- a/phpmon/Common/Core/Shell.swift +++ b/phpmon/Common/Pending Removal/Shell.swift @@ -7,6 +7,8 @@ import Cocoa +// TODO: Enable this to see where deprecations and replacements are needed. +// @available(*, deprecated, message: "Use the new replacement `NxtShell` instead") public class Shell { // MARK: - Invoke static functions diff --git a/phpmon/Domain/Preferences/CustomPrefs.swift b/phpmon/Domain/Preferences/CustomPrefs.swift index 22d2c24..56b091b 100644 --- a/phpmon/Domain/Preferences/CustomPrefs.swift +++ b/phpmon/Domain/Preferences/CustomPrefs.swift @@ -26,6 +26,7 @@ struct CustomPrefs: Decodable { return self.environmentVariables != nil && !self.environmentVariables!.keys.isEmpty } + @available(*, deprecated, message: "Use `setCustomEnvironmentVariables` instead") public func getEnvironmentVariables() -> String { return self.environmentVariables!.map { (key, value) in return "export \(key)=\(value)" diff --git a/phpmon/Next/SystemShell.swift b/phpmon/Next/SystemShell.swift index 0011b91..f232828 100644 --- a/phpmon/Next/SystemShell.swift +++ b/phpmon/Next/SystemShell.swift @@ -9,9 +9,26 @@ import Foundation class SystemShell: Shellable { - public var launchPath: String = "/bin/sh" + /** + The launch path of the terminal in question that is used. + On macOS, we use /bin/sh since it's pretty fast. + */ + private(set) var launchPath: String = "/bin/sh" - public var PATH: String = { + /** + For some commands, we need to know what's in the user's PATH. + The entire PATH is retrieved here, so we can set the PATH in our own terminal as necessary. + */ + private(set) var PATH: String = { return SystemShell.getPath() }() + + /** + Exports are additional environment variables set by the user via the custom configuration. + These are populated when the configuration file is being loaded. + */ + private(set) var exports: String = "" + + /** Retrieves the user's PATH by opening an interactive shell and echoing $PATH. */ + private static func getPath() -> String { let task = Process() task.launchPath = "/bin/zsh" @@ -26,10 +43,12 @@ class SystemShell: Shellable { data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: String.Encoding.utf8 ) ?? "" - }() - - var exports: String = "" + } + /** + Create a process that will run the required shell with the appropriate arguments. + This process still needs to be started, or one can attach output handlers. + */ private func getShellProcess(for command: String) -> Process { var completeCommand = "" @@ -49,6 +68,18 @@ class SystemShell: Shellable { return task } + // MARK: - Public API + + /** + Set custom environment variables. + These will be exported when a command is executed. + */ + public func setCustomEnvironmentVariables(_ variables: [String: String]) { + self.exports = variables.map { (key, value) in + return "export \(key)=\(value)" + }.joined(separator: "&&") + } + // MARK: - Shellable Protocol func syncPipe(_ command: String) -> String { From 5399bddfebd98329beda1abf7baa6ea3af1ff601 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 26 Sep 2022 20:37:24 +0200 Subject: [PATCH 015/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Shell=20rework?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon-tests/Next/ShellTest.swift | 11 ++++++- phpmon/Next/NxtShell.swift | 3 +- phpmon/Next/Shellable.swift | 1 + phpmon/Next/TestableShell.swift | 53 ++++++++++++++++++++++++++++--- 4 files changed, 61 insertions(+), 7 deletions(-) diff --git a/phpmon-tests/Next/ShellTest.swift b/phpmon-tests/Next/ShellTest.swift index 98d24c0..e7b937a 100644 --- a/phpmon-tests/Next/ShellTest.swift +++ b/phpmon-tests/Next/ShellTest.swift @@ -32,12 +32,21 @@ class ShellTest: XCTestCase { with Xdebug v3.1.4, Copyright (c) 2002-2022, by Derick Rethans """ + let slowVersionOutput = FakeTerminalOutput( + output: expectedPhpOutput, + duration: 1000, + isError: false + ) + NxtShell.useTestable([ - "php -v": expectedPhpOutput + "php -v": expectedPhpOutput, + "php --version": slowVersionOutput ]) XCTAssertTrue(NxtShell.shared is TestableShell) XCTAssertEqual(expectedPhpOutput, NxtShell.shared.syncPipe("php -v")) + + XCTAssertEqual(expectedPhpOutput, NxtShell.shared.syncPipe("php --version")) } } diff --git a/phpmon/Next/NxtShell.swift b/phpmon/Next/NxtShell.swift index f924b7c..540d289 100644 --- a/phpmon/Next/NxtShell.swift +++ b/phpmon/Next/NxtShell.swift @@ -12,7 +12,8 @@ class NxtShell { static var shared: Shellable = SystemShell() /// Uses a testable shell with predefined responses. You specify the terminal's output. - public static func useTestable(_ expectations: [String: String]) { + /// they also work with simple String objects. + public static func useTestable(_ expectations: [String: OutputsToShell]) { Self.shared = TestableShell(expectations: expectations) } diff --git a/phpmon/Next/Shellable.swift b/phpmon/Next/Shellable.swift index b238a36..441c846 100644 --- a/phpmon/Next/Shellable.swift +++ b/phpmon/Next/Shellable.swift @@ -9,6 +9,7 @@ import Foundation protocol Shellable { + // TODO: Rework this so it supports listening for updates (when piping) and func syncPipe(_ command: String) -> String func pipe(_ command: String) async -> String } diff --git a/phpmon/Next/TestableShell.swift b/phpmon/Next/TestableShell.swift index ccdd2af..a210b7e 100644 --- a/phpmon/Next/TestableShell.swift +++ b/phpmon/Next/TestableShell.swift @@ -8,18 +8,61 @@ import Foundation -class TestableShell: Shellable { - init(expectations: [String: String]) { +public class TestableShell: Shellable { + + public typealias Input = String + + init(expectations: [Input: OutputsToShell]) { self.expectations = expectations } - var expectations: [String: String] = [:] + var expectations: [Input: OutputsToShell] = [:] func pipe(_ command: String) async -> String { - return expectations[command] ?? "" + // TODO: Deal with the duration and output to error + return expectations[command]?.getOutputAsString() ?? "" } func syncPipe(_ command: String) -> String { - return expectations[command] ?? "" + // TODO: Deal with the duration and output to error + return expectations[command]?.getOutputAsString() ?? "" } +} + +protocol OutputsToShell { + func getOutputAsString() -> String + func getDuration() -> Int + func outputsToError() -> Bool +} + +struct FakeTerminalOutput: OutputsToShell { + var output: String + var duration: Int + var isError: Bool + + func getOutputAsString() -> String { + return output } + + func getDuration() -> Int { + return duration + } + + func outputsToError() -> Bool { + return isError + } +} + +extension String: OutputsToShell { + func getOutputAsString() -> String { + return self + } + + func getDuration() -> Int { + return 100 + } + + func outputsToError() -> Bool { + return false + } +} From 348356941036fdc7d0ffe720773f095dcc458f5a Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 27 Sep 2022 20:26:11 +0200 Subject: [PATCH 016/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Shell=20rework?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 36 +++++++++---------- phpmon-tests/Next/ShellTest.swift | 14 ++++---- .../Parsers/HomebrewPackageTest.swift | 4 +-- phpmon/Common/Core/Actions.swift | 2 +- phpmon/Common/Core/Helpers.swift | 10 +++--- phpmon/Common/Core/Paths.swift | 2 +- phpmon/Common/Helpers/Application.swift | 4 +-- phpmon/Common/PHP/ActivePhpInstallation.swift | 2 +- phpmon/Common/PHP/PHP Version/PhpEnv.swift | 4 +-- phpmon/Common/PHP/PHP Version/PhpHelper.swift | 10 +++--- .../PHP/Switcher/InternalSwitcher.swift | 2 +- ...hell+PATH.swift => LegacyShell+PATH.swift} | 2 +- .../{Shell.swift => LegacyShell.swift} | 16 ++++----- phpmon/Domain/App/AppDelegate.swift | 4 +-- phpmon/Domain/App/AppUpdateChecker.swift | 2 +- phpmon/Domain/App/ServicesManager.swift | 4 +-- phpmon/Domain/App/Startup.swift | 8 ++--- phpmon/Domain/DomainList/AddProxyVC.swift | 2 +- phpmon/Domain/DomainList/AddSiteVC.swift | 2 +- .../DomainList/DomainListVC+Actions.swift | 16 ++++----- .../Composer/ComposerWindow.swift | 2 +- .../Homebrew/HomebrewDiagnostics.swift | 6 ++-- phpmon/Domain/Preferences/CustomPrefs.swift | 6 ++-- phpmon/Domain/Presets/Preset.swift | 2 +- phpmon/Domain/Warnings/WarningManager.swift | 4 +-- .../{NxtShell.swift => ActiveShell.swift} | 6 +++- phpmon/Next/Shellable.swift | 8 +++-- 27 files changed, 93 insertions(+), 87 deletions(-) rename phpmon/Common/Pending Removal/{Shell+PATH.swift => LegacyShell+PATH.swift} (97%) rename phpmon/Common/Pending Removal/{Shell.swift => LegacyShell.swift} (92%) rename phpmon/Next/{NxtShell.swift => ActiveShell.swift} (90%) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 455a64c..34501f1 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -7,8 +7,8 @@ objects = { /* Begin PBXBuildFile section */ - 03E36FE728D9219000636F7F /* NxtShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* NxtShell.swift */; }; - 03E36FE828D9219000636F7F /* NxtShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* NxtShell.swift */; }; + 03E36FE728D9219000636F7F /* ActiveShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* ActiveShell.swift */; }; + 03E36FE828D9219000636F7F /* ActiveShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* ActiveShell.swift */; }; 5420395926135DC100FB00FA /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; }; 5420395F2613607600FB00FA /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; }; 5489625828312FAD004F647A /* CreatedFromFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5489625728312FAD004F647A /* CreatedFromFile.swift */; }; @@ -101,8 +101,8 @@ C42CFB1627DFDE7900862737 /* nginx-site.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1527DFDE7900862737 /* nginx-site.test */; }; C42CFB1827DFDFDC00862737 /* nginx-site-isolated.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */; }; C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */; }; - C42E3BF428A9BF5100AFECFC /* Shell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */; }; - C42E3BF528A9BF5100AFECFC /* Shell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */; }; + C42E3BF428A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */; }; + C42E3BF528A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */; }; C42F26732805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; C42F26762805FEE200938AC7 /* nginx-secure-proxy.test in Resources */ = {isa = PBXBuildFile; fileRef = C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */; }; @@ -216,8 +216,8 @@ C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B56360276AB0A500F12CCB /* VersionExtractorTest.swift */; }; C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; }; C4B5853F2770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; }; - C4B585412770FE3900DA4FBE /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* Shell.swift */; }; - C4B585422770FE3900DA4FBE /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* Shell.swift */; }; + C4B585412770FE3900DA4FBE /* LegacyShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */; }; + C4B585422770FE3900DA4FBE /* LegacyShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */; }; C4B585442770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; }; C4B585452770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; }; C4B6091A2853AAD300C95265 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B609192853AAD300C95265 /* SectionHeaderView.swift */; }; @@ -331,7 +331,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 03E36FE628D9219000636F7F /* NxtShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NxtShell.swift; sourceTree = ""; }; + 03E36FE628D9219000636F7F /* ActiveShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveShell.swift; sourceTree = ""; }; 5420395826135DC100FB00FA /* PrefsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsVC.swift; sourceTree = ""; }; 5420395E2613607600FB00FA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; 5489625728312FAD004F647A /* CreatedFromFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatedFromFile.swift; sourceTree = ""; }; @@ -396,7 +396,7 @@ C42CFB1527DFDE7900862737 /* nginx-site.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site.test"; sourceTree = ""; }; C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site-isolated.test"; sourceTree = ""; }; C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationTest.swift; sourceTree = ""; }; - C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Shell+PATH.swift"; sourceTree = ""; }; + C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LegacyShell+PATH.swift"; sourceTree = ""; }; C42F26722805B4B400938AC7 /* DomainListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListable.swift; sourceTree = ""; }; C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-secure-proxy.test"; sourceTree = ""; }; C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Notifications.swift"; sourceTree = ""; }; @@ -458,7 +458,7 @@ C4B5635D276AB09000F12CCB /* VersionExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionExtractor.swift; sourceTree = ""; }; C4B56360276AB0A500F12CCB /* VersionExtractorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionExtractorTest.swift; sourceTree = ""; }; C4B5853B2770FE3900DA4FBE /* Paths.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Paths.swift; sourceTree = ""; }; - C4B5853C2770FE3900DA4FBE /* Shell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Shell.swift; sourceTree = ""; }; + C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyShell.swift; sourceTree = ""; }; C4B5853D2770FE3900DA4FBE /* Command.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = ""; }; C4B609192853AAD300C95265 /* SectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderView.swift; sourceTree = ""; }; C4B6091C2853AB9700C95265 /* ServicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesView.swift; sourceTree = ""; }; @@ -873,7 +873,7 @@ C46EBC3F28DB9550007ACC74 /* Next */ = { isa = PBXGroup; children = ( - 03E36FE628D9219000636F7F /* NxtShell.swift */, + 03E36FE628D9219000636F7F /* ActiveShell.swift */, C46EBC4628DB9644007ACC74 /* SystemShell.swift */, C46EBC4928DB966A007ACC74 /* TestableShell.swift */, C46EBC4328DB95F0007ACC74 /* Shellable.swift */, @@ -884,8 +884,8 @@ C46EBC4C28DB9F43007ACC74 /* Pending Removal */ = { isa = PBXGroup; children = ( - C4B5853C2770FE3900DA4FBE /* Shell.swift */, - C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */, + C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */, + C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */, ); path = "Pending Removal"; sourceTree = ""; @@ -1358,7 +1358,7 @@ C4D8016622B1584700C6DA1B /* Startup.swift in Sources */, C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */, C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */, - C4B585412770FE3900DA4FBE /* Shell.swift in Sources */, + C4B585412770FE3900DA4FBE /* LegacyShell.swift in Sources */, C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */, C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */, C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */, @@ -1432,7 +1432,7 @@ C41CA5ED2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */, C46E206D28299B3800D909D6 /* AppUpdateChecker.swift in Sources */, C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */, - 03E36FE728D9219000636F7F /* NxtShell.swift in Sources */, + 03E36FE728D9219000636F7F /* ActiveShell.swift in Sources */, C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */, C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */, C46EBC4728DB9644007ACC74 /* SystemShell.swift in Sources */, @@ -1481,7 +1481,7 @@ C40508AF28ADA23D008FAC1F /* NoDomainResultsView.swift in Sources */, C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */, C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */, - C42E3BF428A9BF5100AFECFC /* Shell+PATH.swift in Sources */, + C42E3BF428A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */, C42337A3281F19F000459A48 /* Xdebug.swift in Sources */, C4B97B75275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */, C41C02A627E60D7A009F26CB /* SiteScanner.swift in Sources */, @@ -1534,7 +1534,7 @@ C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */, C43A8A2425D9D20D00591B77 /* HomebrewPackageTest.swift in Sources */, C485707928BF456C00539B36 /* ArrayExtension.swift in Sources */, - C42E3BF528A9BF5100AFECFC /* Shell+PATH.swift in Sources */, + C42E3BF528A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */, C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */, C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */, C4F319C927B034A500AFF46F /* Stats.swift in Sources */, @@ -1586,7 +1586,7 @@ C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */, 5489625928313231004F647A /* CreatedFromFile.swift in Sources */, 54D9E0B327E4F51E003B9AD9 /* HotKeysController.swift in Sources */, - 03E36FE828D9219000636F7F /* NxtShell.swift in Sources */, + 03E36FE828D9219000636F7F /* ActiveShell.swift in Sources */, C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */, C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */, C46E20702829D27F00D909D6 /* AppUpdaterCheckTest.swift in Sources */, @@ -1636,7 +1636,7 @@ C4A6957728D23EE300A14CF8 /* EnvironmentManager.swift in Sources */, C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */, C4A81CA528C67101008DD9D1 /* PMTableView.swift in Sources */, - C4B585422770FE3900DA4FBE /* Shell.swift in Sources */, + C4B585422770FE3900DA4FBE /* LegacyShell.swift in Sources */, C45E76152854A65300B4FE0C /* ServicesManager.swift in Sources */, C464ADAD275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */, C40C7F1F2772136000DDDCDC /* PhpEnv.swift in Sources */, diff --git a/phpmon-tests/Next/ShellTest.swift b/phpmon-tests/Next/ShellTest.swift index e7b937a..7ff4c09 100644 --- a/phpmon-tests/Next/ShellTest.swift +++ b/phpmon-tests/Next/ShellTest.swift @@ -10,14 +10,14 @@ import XCTest class ShellTest: XCTestCase { func test_default_shell_is_system_shell() { - XCTAssertTrue(NxtShell.shared is SystemShell) + XCTAssertTrue(Shell is SystemShell) - XCTAssertTrue(NxtShell.shared.syncPipe("php -v") + XCTAssertTrue(Shell.syncPipe("php -v") .contains("Copyright (c) The PHP Group")) } func test_system_shell_has_path() { - let systemShell = NxtShell.shared as! SystemShell + let systemShell = Shell as! SystemShell XCTAssertTrue(systemShell.PATH.contains(":/usr/local/bin")) XCTAssertTrue(systemShell.PATH.contains(":/usr/bin")) @@ -38,15 +38,15 @@ class ShellTest: XCTestCase { isError: false ) - NxtShell.useTestable([ + ActiveShell.useTestable([ "php -v": expectedPhpOutput, "php --version": slowVersionOutput ]) - XCTAssertTrue(NxtShell.shared is TestableShell) + XCTAssertTrue(Shell is TestableShell) - XCTAssertEqual(expectedPhpOutput, NxtShell.shared.syncPipe("php -v")) + XCTAssertEqual(expectedPhpOutput, Shell.syncPipe("php -v")) - XCTAssertEqual(expectedPhpOutput, NxtShell.shared.syncPipe("php --version")) + XCTAssertEqual(expectedPhpOutput, Shell.syncPipe("php --version")) } } diff --git a/phpmon-tests/Parsers/HomebrewPackageTest.swift b/phpmon-tests/Parsers/HomebrewPackageTest.swift index 02f2fdf..0f9644a 100644 --- a/phpmon-tests/Parsers/HomebrewPackageTest.swift +++ b/phpmon-tests/Parsers/HomebrewPackageTest.swift @@ -56,7 +56,7 @@ class HomebrewPackageTest: XCTestCase { func testCanParseServicesJsonFromCliOutput() throws { let services = try! JSONDecoder().decode( [HomebrewService].self, - from: Shell.pipe( + from: LegacyShell.pipe( "sudo \(Paths.brew) services info --all --json", requiresPath: true ).data(using: .utf8)! @@ -77,7 +77,7 @@ class HomebrewPackageTest: XCTestCase { func testCanLoadExtensionJsonFromCliOutput() throws { let package = try! JSONDecoder().decode( [HomebrewPackage].self, - from: Shell.pipe("\(Paths.brew) info php --json", requiresPath: true).data(using: .utf8)! + from: LegacyShell.pipe("\(Paths.brew) info php --json", requiresPath: true).data(using: .utf8)! ).first! XCTAssertTrue(package.name == "php") diff --git a/phpmon/Common/Core/Actions.swift b/phpmon/Common/Core/Actions.swift index a100e86..30131ee 100644 --- a/phpmon/Common/Core/Actions.swift +++ b/phpmon/Common/Core/Actions.swift @@ -124,7 +124,7 @@ class Actions { try! " /tmp/phpmon_phpinfo.html") + LegacyShell.run("\(Paths.binPath)/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html") return URL(string: "file:///private/tmp/phpmon_phpinfo.html")! } diff --git a/phpmon/Common/Core/Helpers.swift b/phpmon/Common/Core/Helpers.swift index 8d122c5..43fa168 100644 --- a/phpmon/Common/Core/Helpers.swift +++ b/phpmon/Common/Core/Helpers.swift @@ -12,14 +12,14 @@ Runs a `valet` command. Defaults to running as superuser. */ func valet(_ command: String, sudo: Bool = true) -> String { - return Shell.pipe("\(sudo ? "sudo " : "")" + "\(Paths.valet) \(command)", requiresPath: true) + return LegacyShell.pipe("\(sudo ? "sudo " : "")" + "\(Paths.valet) \(command)", requiresPath: true) } /** Runs a `brew` command. Can run as superuser. */ func brew(_ command: String, sudo: Bool = false) { - Shell.run("\(sudo ? "sudo " : "")" + "\(Paths.brew) \(command)") + LegacyShell.run("\(sudo ? "sudo " : "")" + "\(Paths.brew) \(command)") } /** @@ -33,9 +33,9 @@ func sed(file: String, original: String, replacement: String) { // Check if gsed exists; it is able to follow symlinks, // which we want to do to toggle the extension if Filesystem.fileExists("\(Paths.binPath)/gsed") { - Shell.run("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)") + LegacyShell.run("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)") } else { - Shell.run("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)") + LegacyShell.run("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)") } } @@ -43,7 +43,7 @@ func sed(file: String, original: String, replacement: String) { Uses `grep` to determine whether a particular query string can be found in a particular file. */ func grepContains(file: String, query: String) -> Bool { - return Shell.pipe(""" + return LegacyShell.pipe(""" grep -q '\(query)' \(file); [ $? -eq 0 ] && echo "YES" || echo "NO" """) .trimmingCharacters(in: .whitespacesAndNewlines) diff --git a/phpmon/Common/Core/Paths.swift b/phpmon/Common/Core/Paths.swift index 89b5d43..a4a5233 100644 --- a/phpmon/Common/Core/Paths.swift +++ b/phpmon/Common/Core/Paths.swift @@ -21,7 +21,7 @@ public class Paths { init() { baseDir = App.architecture != "x86_64" ? .opt : .usr - userName = String(Shell.pipe("id -un").split(separator: "\n")[0]) + userName = String(LegacyShell.pipe("id -un").split(separator: "\n")[0]) } public func detectBinaryPaths() { diff --git a/phpmon/Common/Helpers/Application.swift b/phpmon/Common/Helpers/Application.swift index ad0e854..ae50d34 100644 --- a/phpmon/Common/Helpers/Application.swift +++ b/phpmon/Common/Helpers/Application.swift @@ -34,13 +34,13 @@ class Application { (This will open the app if it isn't open yet.) */ @objc public func openDirectory(file: String) { - return Shell.run("/usr/bin/open -a \"\(name)\" \"\(file)\"") + return LegacyShell.run("/usr/bin/open -a \"\(name)\" \"\(file)\"") } /** Checks if the app is installed. */ func isInstalled() -> Bool { // If this script does not complain, the app exists! - return Shell.user.executeSynchronously( + return LegacyShell.user.executeSynchronously( "/usr/bin/open -Ra \"\(name)\"", requiresPath: false ).task.terminationStatus == 0 diff --git a/phpmon/Common/PHP/ActivePhpInstallation.swift b/phpmon/Common/PHP/ActivePhpInstallation.swift index c6b0ce1..b3cd72d 100644 --- a/phpmon/Common/PHP/ActivePhpInstallation.swift +++ b/phpmon/Common/PHP/ActivePhpInstallation.swift @@ -129,7 +129,7 @@ class ActivePhpInstallation { if self.version.short == "5.6" { // The main PHP config file should contain `valet.sock` and then we're probably fine? let fileName = "\(Paths.etcPath)/php/5.6/php-fpm.conf" - return Shell.pipe("cat \(fileName)").contains("valet.sock") + return LegacyShell.pipe("cat \(fileName)").contains("valet.sock") } // Make sure to check if valet-fpm.conf exists. If it does, we should be fine :) diff --git a/phpmon/Common/PHP/PHP Version/PhpEnv.swift b/phpmon/Common/PHP/PHP Version/PhpEnv.swift index e2b747f..fcbe844 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnv.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnv.swift @@ -15,7 +15,7 @@ class PhpEnv { init() { self.currentInstall = ActivePhpInstallation() - let brewPhpAlias = Shell.pipe("\(Paths.brew) info php --json") + let brewPhpAlias = LegacyShell.pipe("\(Paths.brew) info php --json") self.homebrewPackage = try! JSONDecoder().decode( [HomebrewPackage].self, @@ -84,7 +84,7 @@ class PhpEnv { Detects which versions of PHP are installed. */ public func detectPhpVersions() -> [String] { - let files = Shell.pipe("ls \(Paths.optPath) | grep php@") + let files = LegacyShell.pipe("ls \(Paths.optPath) | grep php@") var versionsOnly = extractPhpVersions(from: files.components(separatedBy: "\n")) diff --git a/phpmon/Common/PHP/PHP Version/PhpHelper.swift b/phpmon/Common/PHP/PHP Version/PhpHelper.swift index 9775edd..d40736a 100644 --- a/phpmon/Common/PHP/PHP Version/PhpHelper.swift +++ b/phpmon/Common/PHP/PHP Version/PhpHelper.swift @@ -20,13 +20,13 @@ class PhpHelper { let destination = "\(Paths.homePath)/.config/phpmon/bin/pm\(dotless)" // Check if the ~/.config/phpmon/bin directory is in the PATH - let inPath = Shell.user.PATH.contains("\(Paths.homePath)/.config/phpmon/bin") + let inPath = LegacyShell.user.PATH.contains("\(Paths.homePath)/.config/phpmon/bin") // Check if we can create symlinks (`/usr/local/bin` must be writable) let canWriteSymlinks = FileManager.default.isWritableFile(atPath: "/usr/local/bin/") do { - Shell.run("mkdir -p ~/.config/phpmon/bin") + LegacyShell.run("mkdir -p ~/.config/phpmon/bin") if FileManager.default.fileExists(atPath: destination) { let contents = try String(contentsOfFile: destination) @@ -61,7 +61,7 @@ class PhpHelper { ) // Make sure the file is executable - Shell.run("chmod +x \(destination)") + LegacyShell.run("chmod +x \(destination)") // Create a symlink if the folder is not in the PATH if !inPath { @@ -86,13 +86,13 @@ class PhpHelper { if !Filesystem.fileExists(destination) { Log.info("Creating new symlink: \(destination)") - Shell.run("ln -s \(source) \(destination)") + LegacyShell.run("ln -s \(source) \(destination)") return } if !Filesystem.fileIsSymlink(destination) { Log.info("Overwriting existing file with new symlink: \(destination)") - Shell.run("ln -fs \(source) \(destination)") + LegacyShell.run("ln -fs \(source) \(destination)") return } diff --git a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift index 8c72b86..b125140 100644 --- a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift +++ b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift @@ -115,7 +115,7 @@ class InternalSwitcher: PhpSwitcher { if Valet.enabled(feature: .isolatedSites) && primary { let socketVersion = version.replacingOccurrences(of: ".", with: "") - Shell.run("ln -sF ~/.config/valet/valet\(socketVersion).sock ~/.config/valet/valet.sock") + LegacyShell.run("ln -sF ~/.config/valet/valet\(socketVersion).sock ~/.config/valet/valet.sock") Log.info("Symlinked new socket version (valet\(socketVersion).sock → valet.sock).") } diff --git a/phpmon/Common/Pending Removal/Shell+PATH.swift b/phpmon/Common/Pending Removal/LegacyShell+PATH.swift similarity index 97% rename from phpmon/Common/Pending Removal/Shell+PATH.swift rename to phpmon/Common/Pending Removal/LegacyShell+PATH.swift index 7f78c98..3d7e2ee 100644 --- a/phpmon/Common/Pending Removal/Shell+PATH.swift +++ b/phpmon/Common/Pending Removal/LegacyShell+PATH.swift @@ -8,7 +8,7 @@ import Foundation -extension Shell { +extension LegacyShell { var PATH: String { let task = Process() diff --git a/phpmon/Common/Pending Removal/Shell.swift b/phpmon/Common/Pending Removal/LegacyShell.swift similarity index 92% rename from phpmon/Common/Pending Removal/Shell.swift rename to phpmon/Common/Pending Removal/LegacyShell.swift index ba4ccde..97250b5 100644 --- a/phpmon/Common/Pending Removal/Shell.swift +++ b/phpmon/Common/Pending Removal/LegacyShell.swift @@ -8,8 +8,8 @@ import Cocoa // TODO: Enable this to see where deprecations and replacements are needed. -// @available(*, deprecated, message: "Use the new replacement `NxtShell` instead") -public class Shell { +// @available(*, deprecated, message: "Use the new replacement `Shell` instead") +public class LegacyShell { // MARK: - Invoke static functions @@ -17,14 +17,14 @@ public class Shell { _ command: String, requiresPath: Bool = false ) { - Shell.user.run(command, requiresPath: requiresPath) + LegacyShell.user.run(command, requiresPath: requiresPath) } public static func pipe( _ command: String, requiresPath: Bool = false ) -> String { - return Shell.user.pipe(command, requiresPath: requiresPath) + return LegacyShell.user.pipe(command, requiresPath: requiresPath) } // MARK: - Singleton @@ -40,7 +40,7 @@ public class Shell { /** Singleton to access a user shell (with --login) */ - public static let user = Shell() + public static let user = LegacyShell() /** Runs a shell command without using the output. @@ -54,7 +54,7 @@ public class Shell { requiresPath: Bool = false ) { // Equivalent of piping to /dev/null; don't do anything with the string - _ = Shell.pipe(command, requiresPath: requiresPath) + _ = LegacyShell.pipe(command, requiresPath: requiresPath) } /** @@ -85,7 +85,7 @@ public class Shell { public func executeSynchronously( _ command: String, requiresPath: Bool = false - ) -> Shell.Output { + ) -> LegacyShell.Output { let outputPipe = Pipe() let errorPipe = Pipe() @@ -96,7 +96,7 @@ public class Shell { task.launch() task.waitUntilExit() - let output = Shell.Output( + let output = LegacyShell.Output( standardOutput: String( data: outputPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8 diff --git a/phpmon/Domain/App/AppDelegate.swift b/phpmon/Domain/App/AppDelegate.swift index 2cc0fb4..f817de0 100644 --- a/phpmon/Domain/App/AppDelegate.swift +++ b/phpmon/Domain/App/AppDelegate.swift @@ -18,7 +18,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele (invoked by PHP Monitor) shell commands. It is used to invoke all commands in this application. */ - let sharedShell: Shell + let sharedShell: LegacyShell /** The App singleton contains information about the state of @@ -77,7 +77,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele Log.info("Version \(App.version)") Log.separator(as: .info) - self.sharedShell = Shell.user + self.sharedShell = LegacyShell.user self.state = App.shared self.menu = MainMenu.shared self.paths = Paths.shared diff --git a/phpmon/Domain/App/AppUpdateChecker.swift b/phpmon/Domain/App/AppUpdateChecker.swift index 9914e15..b2d614d 100644 --- a/phpmon/Domain/App/AppUpdateChecker.swift +++ b/phpmon/Domain/App/AppUpdateChecker.swift @@ -32,7 +32,7 @@ class AppUpdateChecker { command = "curl -s --max-time 5" } - return Shell.pipe( + return LegacyShell.pipe( "\(command) '\(caskFile)' | grep version" ) } diff --git a/phpmon/Domain/App/ServicesManager.swift b/phpmon/Domain/App/ServicesManager.swift index 1eb73be..d274764 100644 --- a/phpmon/Domain/App/ServicesManager.swift +++ b/phpmon/Domain/App/ServicesManager.swift @@ -24,7 +24,7 @@ class ServicesManager: ObservableObject { ] DispatchQueue.global(qos: .background).async { - let data = Shell + let data = LegacyShell .pipe("sudo \(Paths.brew) services info --all --json", requiresPath: true) .data(using: .utf8)! @@ -44,7 +44,7 @@ class ServicesManager: ObservableObject { } DispatchQueue.global(qos: .background).async { - let data = Shell + let data = LegacyShell .pipe("\(Paths.brew) services info --all --json", requiresPath: true) .data(using: .utf8)! diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index 8d80154..0f49448 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -115,7 +115,7 @@ class Startup { // Make sure we can detect one or more PHP installations. // ================================================================================= EnvironmentCheck( - command: { return !Shell.pipe("ls \(Paths.optPath) | grep php").contains("php") }, + command: { return !LegacyShell.pipe("ls \(Paths.optPath) | grep php").contains("php") }, name: "`ls \(Paths.optPath) | grep php` returned php result", titleText: "startup.errors.php_opt.title".localized, subtitleText: "startup.errors.php_opt.subtitle".localized( @@ -143,14 +143,14 @@ class Startup { // functioning correctly. Let the user know that they need to run `valet trust`. // ================================================================================= EnvironmentCheck( - command: { return !Shell.pipe("cat /private/etc/sudoers.d/brew").contains(Paths.brew) }, + command: { return !LegacyShell.pipe("cat /private/etc/sudoers.d/brew").contains(Paths.brew) }, name: "`/private/etc/sudoers.d/brew` contains brew", titleText: "startup.errors.sudoers_brew.title".localized, subtitleText: "startup.errors.sudoers_brew.subtitle".localized, descriptionText: "startup.errors.sudoers_brew.desc".localized ), EnvironmentCheck( - command: { return !Shell.pipe("cat /private/etc/sudoers.d/valet").contains(Paths.valet) }, + command: { return !LegacyShell.pipe("cat /private/etc/sudoers.d/valet").contains(Paths.valet) }, name: "`/private/etc/sudoers.d/valet` contains valet", titleText: "startup.errors.sudoers_valet.title".localized, subtitleText: "startup.errors.sudoers_valet.subtitle".localized, @@ -202,7 +202,7 @@ class Startup { command: { return App.architecture == "x86_64" && FileManager.default.fileExists(atPath: "/usr/local/bin/which") - && Shell.pipe("which node", requiresPath: false) + && LegacyShell.pipe("which node", requiresPath: false) .contains("env: node: No such file or directory") }, name: "`env: node` issue does not apply", diff --git a/phpmon/Domain/DomainList/AddProxyVC.swift b/phpmon/Domain/DomainList/AddProxyVC.swift index bd93779..0733fd1 100644 --- a/phpmon/Domain/DomainList/AddProxyVC.swift +++ b/phpmon/Domain/DomainList/AddProxyVC.swift @@ -72,7 +72,7 @@ class AddProxyVC: NSViewController, NSTextFieldDelegate { App.shared.domainListWindowController?.contentVC.setUIBusy() DispatchQueue.global(qos: .userInitiated).async { - Shell.run("\(Paths.valet) proxy \(domain) \(proxyName)\(secure)", requiresPath: true) + LegacyShell.run("\(Paths.valet) proxy \(domain) \(proxyName)\(secure)", requiresPath: true) Actions.restartNginx() DispatchQueue.main.async { diff --git a/phpmon/Domain/DomainList/AddSiteVC.swift b/phpmon/Domain/DomainList/AddSiteVC.swift index 7a0193e..8275d27 100644 --- a/phpmon/Domain/DomainList/AddSiteVC.swift +++ b/phpmon/Domain/DomainList/AddSiteVC.swift @@ -71,7 +71,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate { // Adding `valet links` is a workaround for Valet malforming the config.json file // TODO: I will have to investigate and report this behaviour if possible - Shell.run("cd '\(path)' && \(Paths.valet) link '\(name)' && valet links", requiresPath: true) + LegacyShell.run("cd '\(path)' && \(Paths.valet) link '\(name)' && valet links", requiresPath: true) dismissView(outcome: .OK) diff --git a/phpmon/Domain/DomainList/DomainListVC+Actions.swift b/phpmon/Domain/DomainList/DomainListVC+Actions.swift index 48a7a97..d90a491 100644 --- a/phpmon/Domain/DomainList/DomainListVC+Actions.swift +++ b/phpmon/Domain/DomainList/DomainListVC+Actions.swift @@ -25,11 +25,11 @@ extension DomainListVC { self.waitAndExecute { // 1. Remove the original proxy - Shell.run("\(Paths.valet) unproxy \(selectedProxy.domain)", requiresPath: true) + LegacyShell.run("\(Paths.valet) unproxy \(selectedProxy.domain)", requiresPath: true) // 2. Add a new proxy, which is either secured/unsecured let secure = originalSecureStatus ? "" : " --secure" - Shell.run("\(Paths.valet) proxy \(selectedProxy.domain) \(selectedProxy.target)\(secure)", + LegacyShell.run("\(Paths.valet) proxy \(selectedProxy.domain) \(selectedProxy.target)\(secure)", requiresPath: true) // 3. Restart nginx @@ -50,7 +50,7 @@ extension DomainListVC { let command = "cd '\(selectedSite.absolutePath)' && sudo \(Paths.valet) \(action) && exit;" waitAndExecute { - Shell.run(command, requiresPath: true) + LegacyShell.run(command, requiresPath: true) } completion: { [self] in selectedSite.determineSecured() if selectedSite.secured == originalSecureStatus { @@ -100,11 +100,11 @@ extension DomainListVC { } @objc func openInFinder() { - Shell.run("open '\(selectedSite!.absolutePath)'") + LegacyShell.run("open '\(selectedSite!.absolutePath)'") } @objc func openInTerminal() { - Shell.run("open -b com.apple.terminal '\(selectedSite!.absolutePath)'") + LegacyShell.run("open -b com.apple.terminal '\(selectedSite!.absolutePath)'") } @objc func openWithEditor(sender: EditorMenuItem) { @@ -157,7 +157,7 @@ extension DomainListVC { style: .critical, onFirstButtonPressed: { self.waitAndExecute { - Shell.run("valet unlink '\(site.name)'", requiresPath: true) + LegacyShell.run("valet unlink '\(site.name)'", requiresPath: true) } completion: { self.reloadDomains() } @@ -179,7 +179,7 @@ extension DomainListVC { style: .critical, onFirstButtonPressed: { self.waitAndExecute { - Shell.run("valet unproxy '\(proxy.domain)'", requiresPath: true) + LegacyShell.run("valet unproxy '\(proxy.domain)'", requiresPath: true) } completion: { self.reloadDomains() } @@ -191,7 +191,7 @@ extension DomainListVC { let rowToReload = tableView.selectedRow waitAndExecute { - Shell.run(command, requiresPath: true) + LegacyShell.run(command, requiresPath: true) } completion: { [self] in beforeCellReload() tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0, 1, 2, 3, 4]) diff --git a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift index 2868636..2762cd2 100644 --- a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift +++ b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift @@ -43,7 +43,7 @@ class ComposerWindow { window?.setType(info: true) DispatchQueue.global(qos: .userInitiated).async { [self] in - let task = Shell.user.createTask( + let task = LegacyShell.user.createTask( for: "\(Paths.composer!) global update", requiresPath: true ) diff --git a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift index c5ab628..b77fc92 100644 --- a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift +++ b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift @@ -14,7 +14,7 @@ class HomebrewDiagnostics { Determines the Homebrew taps the user has installed. */ public static var installedTaps: [String] = { - return Shell + return LegacyShell .pipe("\(Paths.brew) tap") .split(separator: "\n") .map { string in @@ -75,7 +75,7 @@ class HomebrewDiagnostics { Check if the alias conflict as documented in `checkForCaskConflict` actually occurred. */ private static func hasAliasConflict() -> Bool { - let tapAlias = Shell.pipe("\(Paths.brew) info shivammathur/php/php --json") + let tapAlias = LegacyShell.pipe("\(Paths.brew) info shivammathur/php/php --json") if tapAlias.contains("brew tap shivammathur/php") || tapAlias.contains("Error") { Log.info("The user does not appear to have tapped: shivammathur/php") @@ -134,7 +134,7 @@ class HomebrewDiagnostics { public static func cannotLoadService(_ name: String = "nginx") -> Bool { let serviceInfo = try? JSONDecoder().decode( [HomebrewService].self, - from: Shell.pipe( + from: LegacyShell.pipe( "sudo \(Paths.brew) services info \(name) --json", requiresPath: true ).data(using: .utf8)! diff --git a/phpmon/Domain/Preferences/CustomPrefs.swift b/phpmon/Domain/Preferences/CustomPrefs.swift index 731ada5..12f6b82 100644 --- a/phpmon/Domain/Preferences/CustomPrefs.swift +++ b/phpmon/Domain/Preferences/CustomPrefs.swift @@ -44,7 +44,7 @@ struct CustomPrefs: Decodable { extension Preferences { func loadCustomPreferences() { // Ensure the configuration directory is created if missing - Shell.run("mkdir -p ~/.config/phpmon") + LegacyShell.run("mkdir -p ~/.config/phpmon") // Move the legacy file moveOutdatedConfigurationFile() @@ -63,7 +63,7 @@ extension Preferences { func moveOutdatedConfigurationFile() { if Filesystem.fileExists("~/.phpmon.conf.json") && !Filesystem.fileExists("~/.config/phpmon/config.json") { Log.info("An outdated configuration file was found. Moving it...") - Shell.run("cp ~/.phpmon.conf.json ~/.config/phpmon/config.json") + LegacyShell.run("cp ~/.phpmon.conf.json ~/.config/phpmon/config.json") Log.info("The configuration file was copied successfully!") } } @@ -87,7 +87,7 @@ extension Preferences { if customPreferences.hasEnvironmentVariables() { Log.info("Configuring the additional exports...") - Shell.user.exports = customPreferences.getEnvironmentVariables() + LegacyShell.user.exports = customPreferences.getEnvironmentVariables() } } catch { Log.warn("The ~/.config/phpmon/config.json file seems to be missing or malformed.") diff --git a/phpmon/Domain/Presets/Preset.swift b/phpmon/Domain/Presets/Preset.swift index b5d931f..f88d20a 100644 --- a/phpmon/Domain/Presets/Preset.swift +++ b/phpmon/Domain/Presets/Preset.swift @@ -260,7 +260,7 @@ struct Preset: Codable, Equatable { private func persistRevert() { let data = try! JSONEncoder().encode(self.revertSnapshot) - Shell.run("mkdir -p ~/.config/phpmon") + LegacyShell.run("mkdir -p ~/.config/phpmon") try! String(data: data, encoding: .utf8)! .write( diff --git a/phpmon/Domain/Warnings/WarningManager.swift b/phpmon/Domain/Warnings/WarningManager.swift index 2211899..7c88a92 100644 --- a/phpmon/Domain/Warnings/WarningManager.swift +++ b/phpmon/Domain/Warnings/WarningManager.swift @@ -22,7 +22,7 @@ class WarningManager { public let evaluations: [Warning] = [ Warning( command: { - return Shell.pipe("sysctl -n sysctl.proc_translated") + return LegacyShell.pipe("sysctl -n sysctl.proc_translated") .trimmingCharacters(in: .whitespacesAndNewlines) == "1" }, name: "Running PHP Monitor with Rosetta on M1", @@ -32,7 +32,7 @@ class WarningManager { ), Warning( command: { - return !Shell.user.PATH.contains("\(Paths.homePath)/.config/phpmon/bin") && + return !LegacyShell.user.PATH.contains("\(Paths.homePath)/.config/phpmon/bin") && !FileManager.default.isWritableFile(atPath: "/usr/local/bin/") }, name: "Helpers cannot be symlinked and not in PATH", diff --git a/phpmon/Next/NxtShell.swift b/phpmon/Next/ActiveShell.swift similarity index 90% rename from phpmon/Next/NxtShell.swift rename to phpmon/Next/ActiveShell.swift index 540d289..e523ac5 100644 --- a/phpmon/Next/NxtShell.swift +++ b/phpmon/Next/ActiveShell.swift @@ -8,7 +8,11 @@ import Foundation -class NxtShell { +var Shell: Shellable { + return ActiveShell.shared +} + +class ActiveShell { static var shared: Shellable = SystemShell() /// Uses a testable shell with predefined responses. You specify the terminal's output. diff --git a/phpmon/Next/Shellable.swift b/phpmon/Next/Shellable.swift index 441c846..bb930a9 100644 --- a/phpmon/Next/Shellable.swift +++ b/phpmon/Next/Shellable.swift @@ -9,7 +9,9 @@ import Foundation protocol Shellable { - // TODO: Rework this so it supports listening for updates (when piping) and - func syncPipe(_ command: String) -> String - func pipe(_ command: String) async -> String + typealias Output = String + + func syncPipe(_ command: String) -> Output + + func pipe(_ command: String) async -> Output } From a59efb7fceb51dc3dfd5bcfc78cbfa06a5936fa9 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 27 Sep 2022 22:27:33 +0200 Subject: [PATCH 017/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Shell=20rework?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon-tests/Next/ShellTest.swift | 13 ++++++---- phpmon/Next/Shellable.swift | 37 ++++++++++++++++++++++++---- phpmon/Next/SystemShell.swift | 40 ++++++++++++++++++++++++------- phpmon/Next/TestableShell.swift | 22 ++++++++++++----- 4 files changed, 88 insertions(+), 24 deletions(-) diff --git a/phpmon-tests/Next/ShellTest.swift b/phpmon-tests/Next/ShellTest.swift index 7ff4c09..28575e8 100644 --- a/phpmon-tests/Next/ShellTest.swift +++ b/phpmon-tests/Next/ShellTest.swift @@ -12,8 +12,7 @@ class ShellTest: XCTestCase { func test_default_shell_is_system_shell() { XCTAssertTrue(Shell is SystemShell) - XCTAssertTrue(Shell.syncPipe("php -v") - .contains("Copyright (c) The PHP Group")) + XCTAssertTrue(Shell.sync("php -v").output.contains("Copyright (c) The PHP Group")) } func test_system_shell_has_path() { @@ -45,8 +44,14 @@ class ShellTest: XCTestCase { XCTAssertTrue(Shell is TestableShell) - XCTAssertEqual(expectedPhpOutput, Shell.syncPipe("php -v")) + XCTAssertEqual(expectedPhpOutput, Shell.sync("php -v").output) - XCTAssertEqual(expectedPhpOutput, Shell.syncPipe("php --version")) + XCTAssertEqual(expectedPhpOutput, Shell.sync("php --version").output) + } + + func test_unrecognized_commands_output_stderr() { + ActiveShell.useTestable([:]) + + XCTAssertEqual("Unexpected Command", Shell.sync("unrecognized command").output) } } diff --git a/phpmon/Next/Shellable.swift b/phpmon/Next/Shellable.swift index bb930a9..a418a32 100644 --- a/phpmon/Next/Shellable.swift +++ b/phpmon/Next/Shellable.swift @@ -8,10 +8,37 @@ import Foundation -protocol Shellable { - typealias Output = String +struct ShellOutput: CustomStringConvertible { + var output: String + var isError: Bool - func syncPipe(_ command: String) -> Output - - func pipe(_ command: String) async -> Output + var description: String { + return output + } +} + +protocol Shellable { + /** + Run a command synchronously. Waits until the command is done. + */ + func sync(_ command: String) -> ShellOutput + + /** + Run a command asynchronously. + */ + func pipe(_ command: String) async -> ShellOutput + + /** + Run a command asynchronously, without returning the output of the command. + */ + func quiet(_ command: String) async + + /** + Attach to a given command and listen for progress updates. + Any data that ends up in standard out or standard error becomes available. + */ + func attach( + _ command: String, + didReceiveOutput: @escaping (ShellOutput) -> Void + ) async -> ShellOutput } diff --git a/phpmon/Next/SystemShell.swift b/phpmon/Next/SystemShell.swift index f232828..867a6c4 100644 --- a/phpmon/Next/SystemShell.swift +++ b/phpmon/Next/SystemShell.swift @@ -82,21 +82,43 @@ class SystemShell: Shellable { // MARK: - Shellable Protocol - func syncPipe(_ command: String) -> String { + func sync(_ command: String) -> ShellOutput { let task = getShellProcess(for: command) - let pipe = Pipe() - task.standardOutput = pipe + let outputPipe = Pipe() + let errorPipe = Pipe() + + task.standardOutput = outputPipe + task.standardError = errorPipe + task.waitUntilExit() task.launch() - return String( - data: pipe.fileHandleForReading.readDataToEndOfFile(), + let stdOut = String( + data: outputPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8 - ) ?? "" + )! + + let stdErr = String( + data: errorPipe.fileHandleForReading.readDataToEndOfFile(), + encoding: .utf8 + )! + + if stdErr.lengthOfBytes(using: .utf8) > 0 { + return ShellOutput(output: stdErr, isError: true) + } + + return ShellOutput(output: stdOut, isError: false) } - func pipe(_ command: String) async -> String { - // TODO - return "" + func pipe(_ command: String) async -> ShellOutput { + return sync(command) + } + + func quiet(_ command: String) async { + _ = await self.pipe(command) + } + + func attach(_ command: String, didReceiveOutput: @escaping (ShellOutput) -> Void) async -> ShellOutput { + return sync(command) } } diff --git a/phpmon/Next/TestableShell.swift b/phpmon/Next/TestableShell.swift index a210b7e..715133c 100644 --- a/phpmon/Next/TestableShell.swift +++ b/phpmon/Next/TestableShell.swift @@ -18,14 +18,24 @@ public class TestableShell: Shellable { var expectations: [Input: OutputsToShell] = [:] - func pipe(_ command: String) async -> String { - // TODO: Deal with the duration and output to error - return expectations[command]?.getOutputAsString() ?? "" + func quiet(_ command: String) async { + return } - func syncPipe(_ command: String) -> String { - // TODO: Deal with the duration and output to error - return expectations[command]?.getOutputAsString() ?? "" + func pipe(_ command: String) async -> ShellOutput { + self.sync(command) + } + + func attach(_ command: String, didReceiveOutput: @escaping (ShellOutput) -> Void) async -> ShellOutput { + self.sync(command) + } + + func sync(_ command: String) -> ShellOutput { + guard let expectation = expectations[command] else { + return ShellOutput(output: "Unexpected Command", isError: true) + } + + return ShellOutput(output: expectation.getOutputAsString(), isError: expectation.outputsToError()) } } From 513a86ec398aa6b1ab7d6068879e41a2711fbf48 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 28 Sep 2022 18:24:01 +0200 Subject: [PATCH 018/181] =?UTF-8?q?=F0=9F=90=9B=20Fix=20an=20issue=20with?= =?UTF-8?q?=20missing=20separator=20item?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/Menu/StatusMenu+Items.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/phpmon/Domain/Menu/StatusMenu+Items.swift b/phpmon/Domain/Menu/StatusMenu+Items.swift index c022bdd..561a4e3 100644 --- a/phpmon/Domain/Menu/StatusMenu+Items.swift +++ b/phpmon/Domain/Menu/StatusMenu+Items.swift @@ -212,6 +212,7 @@ extension StatusMenu { func addXdebugMenuItem() { if !Xdebug.enabled { + addItem(NSMenuItem.separator()) return } From bbac2632a2267726e5fc96f7acf46c0111f5eea0 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 28 Sep 2022 21:28:51 +0200 Subject: [PATCH 019/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Much=20improved?= =?UTF-8?q?=20Shell=20protocol?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 12 ++-- .../{ShellTest.swift => FakeShellTest.swift} | 15 +---- phpmon-tests/Next/SystemShellTest.swift | 57 +++++++++++++++++++ phpmon/Next/Shellable.swift | 31 ++++++++-- phpmon/Next/SystemShell.swift | 44 +++++++++++++- phpmon/Next/TestableShell.swift | 7 ++- 6 files changed, 138 insertions(+), 28 deletions(-) rename phpmon-tests/Next/{ShellTest.swift => FakeShellTest.swift} (74%) create mode 100644 phpmon-tests/Next/SystemShellTest.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 34501f1..fcceb87 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -63,9 +63,10 @@ C40FE738282ABA4F00A302C2 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; C40FE73B282ABB2E00A302C2 /* AppVersionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE739282ABB2E00A302C2 /* AppVersionTest.swift */; }; C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; }; - C413E43528DA3EB100AE33C7 /* ShellTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C413E43428DA3EB100AE33C7 /* ShellTest.swift */; }; + C413E43528DA3EB100AE33C7 /* FakeShellTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C413E43428DA3EB100AE33C7 /* FakeShellTest.swift */; }; C415937F27A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; }; C415938027A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; }; + C4159AF728E4D40400545349 /* SystemShellTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4159AF628E4D40400545349 /* SystemShellTest.swift */; }; C415D3B72770F294005EF286 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; }; C415D3B82770F294005EF286 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; }; C415D3E82770F692005EF286 /* AppDelegate+InterApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */; }; @@ -364,8 +365,9 @@ C40FE736282ABA4F00A302C2 /* AppVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersion.swift; sourceTree = ""; }; C40FE739282ABB2E00A302C2 /* AppVersionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionTest.swift; sourceTree = ""; }; C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackage.swift; sourceTree = ""; }; - C413E43428DA3EB100AE33C7 /* ShellTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellTest.swift; sourceTree = ""; }; + C413E43428DA3EB100AE33C7 /* FakeShellTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeShellTest.swift; sourceTree = ""; }; C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpFrameworks.swift; sourceTree = ""; }; + C4159AF628E4D40400545349 /* SystemShellTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemShellTest.swift; sourceTree = ""; }; C415D3B62770F294005EF286 /* Actions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = ""; }; C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+InterApp.swift"; sourceTree = ""; }; C4168F4427ADB4A3003B6C39 /* DEVELOPER.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = DEVELOPER.md; sourceTree = ""; }; @@ -666,7 +668,8 @@ C413E43328DA3E8F00AE33C7 /* Next */ = { isa = PBXGroup; children = ( - C413E43428DA3EB100AE33C7 /* ShellTest.swift */, + C413E43428DA3EB100AE33C7 /* FakeShellTest.swift */, + C4159AF628E4D40400545349 /* SystemShellTest.swift */, ); path = Next; sourceTree = ""; @@ -1503,7 +1506,7 @@ C41CA5EE2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */, C4FACE81288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift in Sources */, 54D9E0B727E4F51E003B9AD9 /* HotKey.swift in Sources */, - C413E43528DA3EB100AE33C7 /* ShellTest.swift in Sources */, + C413E43528DA3EB100AE33C7 /* FakeShellTest.swift in Sources */, C4205A7F27F4D21800191A39 /* ValetProxy.swift in Sources */, C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */, C46EBC4528DB95F0007ACC74 /* Shellable.swift in Sources */, @@ -1557,6 +1560,7 @@ C4FC21B128391F8E00D368BB /* MainMenu+Actions.swift in Sources */, 54D9E0B927E4F51E003B9AD9 /* KeyCombo.swift in Sources */, C4EED88A27A48778006D7272 /* InterAppHandler.swift in Sources */, + C4159AF728E4D40400545349 /* SystemShellTest.swift in Sources */, C450C8C728C919EC002A2B4B /* PreferenceName.swift in Sources */, C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */, C485707B28BF458900539B36 /* VersionPopoverView.swift in Sources */, diff --git a/phpmon-tests/Next/ShellTest.swift b/phpmon-tests/Next/FakeShellTest.swift similarity index 74% rename from phpmon-tests/Next/ShellTest.swift rename to phpmon-tests/Next/FakeShellTest.swift index 28575e8..57510a6 100644 --- a/phpmon-tests/Next/ShellTest.swift +++ b/phpmon-tests/Next/FakeShellTest.swift @@ -8,20 +8,7 @@ import XCTest -class ShellTest: XCTestCase { - func test_default_shell_is_system_shell() { - XCTAssertTrue(Shell is SystemShell) - - XCTAssertTrue(Shell.sync("php -v").output.contains("Copyright (c) The PHP Group")) - } - - func test_system_shell_has_path() { - let systemShell = Shell as! SystemShell - - XCTAssertTrue(systemShell.PATH.contains(":/usr/local/bin")) - XCTAssertTrue(systemShell.PATH.contains(":/usr/bin")) - } - +class FakeShellTest: XCTestCase { func test_we_can_predefine_responses_for_dummy_shell() { let expectedPhpOutput = """ PHP 8.1.10 (cli) (built: Sep 3 2022 12:09:27) (NTS) diff --git a/phpmon-tests/Next/SystemShellTest.swift b/phpmon-tests/Next/SystemShellTest.swift new file mode 100644 index 0000000..4ddde43 --- /dev/null +++ b/phpmon-tests/Next/SystemShellTest.swift @@ -0,0 +1,57 @@ +// +// SystemShellTest.swift +// phpmon-tests +// +// Created by Nico Verbruggen on 28/09/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import XCTest + +class SystemShellTest: XCTestCase { + func test_system_shell_is_default() { + XCTAssertTrue(Shell is SystemShell) + + XCTAssertTrue(Shell.sync("php -v").output.contains("Copyright (c) The PHP Group")) + } + + func test_system_shell_has_path() { + let systemShell = Shell as! SystemShell + + XCTAssertTrue(systemShell.PATH.contains(":/usr/local/bin")) + XCTAssertTrue(systemShell.PATH.contains(":/usr/bin")) + } + + func test_system_shell_can_buffer_output() async { + var bits: [String] = [] + + let shellOutput = try! await Shell.attach( + "php -r \"echo 'Hello world' . PHP_EOL; usleep(200); echo 'Goodbye world';\"", + didReceiveOutput: { incoming in + bits.append(incoming.output) + }, + withTimeout: 2.0 + ) + + XCTAssertTrue(bits.contains("Hello world\n")) + XCTAssertTrue(bits.contains("Goodbye world")) + XCTAssertEqual("Hello world\nGoodbye world", shellOutput.output) + } + + func test_system_shell_can_timeout_and_throw_error() async { + let expectation = XCTestExpectation(description: #function) + + do { + _ = try await Shell.attach( + "php -r \"sleep(1);\"", + didReceiveOutput: { _ in }, + withTimeout: 0.1 + ) + } catch { + XCTAssertEqual(error as? ShellError, ShellError.timedOut) + expectation.fulfill() + } + + wait(for: [expectation], timeout: 3.0) + } +} diff --git a/phpmon/Next/Shellable.swift b/phpmon/Next/Shellable.swift index a418a32..ceb653f 100644 --- a/phpmon/Next/Shellable.swift +++ b/phpmon/Next/Shellable.swift @@ -11,34 +11,55 @@ import Foundation struct ShellOutput: CustomStringConvertible { var output: String var isError: Bool - var description: String { return output } + + static func out(_ output: String) -> ShellOutput { + return ShellOutput(output: output, isError: false) + } + + static func err(_ output: String) -> ShellOutput { + return ShellOutput(output: output, isError: true) + } } protocol Shellable { /** Run a command synchronously. Waits until the command is done. + Returns the most relevant output (prefers error output if it exists). */ func sync(_ command: String) -> ShellOutput /** Run a command asynchronously. + Returns the most relevant output (prefers error output if it exists). */ func pipe(_ command: String) async -> ShellOutput /** Run a command asynchronously, without returning the output of the command. + Returns the most relevant output (prefers error output if it exists). */ func quiet(_ command: String) async /** - Attach to a given command and listen for progress updates. - Any data that ends up in standard out or standard error becomes available. + Runs a command asynchronously, and fires closure with `stdout` or `stderr` data as it comes in. + + You can specify how long this task should run. + The process will always be terminated after the specified time interval. + (Whether it is complete or not.) + + Unlike `sync`, `pipe` and `quiet`, you can capture both `stdout` and `stderr` with this mechanism. + The end result is still the most relevant output (where error output is preferred if it exists). */ func attach( _ command: String, - didReceiveOutput: @escaping (ShellOutput) -> Void - ) async -> ShellOutput + didReceiveOutput: @escaping (ShellOutput) -> Void, + withTimeout timeout: TimeInterval + ) async throws -> ShellOutput +} + +enum ShellError: Error { + case timedOut } diff --git a/phpmon/Next/SystemShell.swift b/phpmon/Next/SystemShell.swift index 867a6c4..93672f4 100644 --- a/phpmon/Next/SystemShell.swift +++ b/phpmon/Next/SystemShell.swift @@ -90,8 +90,8 @@ class SystemShell: Shellable { task.standardOutput = outputPipe task.standardError = errorPipe - task.waitUntilExit() task.launch() + task.waitUntilExit() let stdOut = String( data: outputPipe.fileHandleForReading.readDataToEndOfFile(), @@ -118,7 +118,45 @@ class SystemShell: Shellable { _ = await self.pipe(command) } - func attach(_ command: String, didReceiveOutput: @escaping (ShellOutput) -> Void) async -> ShellOutput { - return sync(command) + func attach( + _ command: String, + didReceiveOutput: @escaping (ShellOutput) -> Void, + withTimeout timeout: TimeInterval = 5.0 + ) async throws -> ShellOutput { + let task = getShellProcess(for: command) + + var allOut: String = "" + var allErr: String = "" + + task.listen { stdOut in + allOut += stdOut; didReceiveOutput(.out(stdOut)) + } didReceiveStandardErrorData: { stdErr in + allErr += stdErr; didReceiveOutput(.err(stdErr)) + } + + return try await withCheckedThrowingContinuation({ continuation in + var timer: Timer? + + task.terminationHandler = { process in + process.haltListening() + + timer?.invalidate() + + if !allErr.isEmpty { + return continuation.resume(returning: .err(allErr)) + } + + return continuation.resume(returning: .out(allOut)) + } + + timer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { _ in + task.terminationHandler = nil + task.terminate() + return continuation.resume(throwing: ShellError.timedOut) + } + + task.launch() + task.waitUntilExit() + }) } } diff --git a/phpmon/Next/TestableShell.swift b/phpmon/Next/TestableShell.swift index 715133c..0ef9ebb 100644 --- a/phpmon/Next/TestableShell.swift +++ b/phpmon/Next/TestableShell.swift @@ -9,7 +9,6 @@ import Foundation public class TestableShell: Shellable { - public typealias Input = String init(expectations: [Input: OutputsToShell]) { @@ -26,7 +25,11 @@ public class TestableShell: Shellable { self.sync(command) } - func attach(_ command: String, didReceiveOutput: @escaping (ShellOutput) -> Void) async -> ShellOutput { + func attach( + _ command: String, + didReceiveOutput: @escaping (ShellOutput) -> Void, + withTimeout timeout: TimeInterval + ) async throws -> ShellOutput { self.sync(command) } From 99da32892126f18f6d2479bca51df6a014ce9b75 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 28 Sep 2022 21:43:18 +0200 Subject: [PATCH 020/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Even=20better=20S?= =?UTF-8?q?hell=20functionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon-tests/Next/FakeShellTest.swift | 10 +++++++--- phpmon-tests/Next/SystemShellTest.swift | 14 ++++++++++---- phpmon/Next/Shellable.swift | 19 ++++++++++--------- phpmon/Next/SystemShell.swift | 6 +----- phpmon/Next/TestableShell.swift | 5 +++-- 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/phpmon-tests/Next/FakeShellTest.swift b/phpmon-tests/Next/FakeShellTest.swift index 57510a6..03b91a2 100644 --- a/phpmon-tests/Next/FakeShellTest.swift +++ b/phpmon-tests/Next/FakeShellTest.swift @@ -31,14 +31,18 @@ class FakeShellTest: XCTestCase { XCTAssertTrue(Shell is TestableShell) - XCTAssertEqual(expectedPhpOutput, Shell.sync("php -v").output) + XCTAssertEqual(expectedPhpOutput, Shell.sync("php -v").out) - XCTAssertEqual(expectedPhpOutput, Shell.sync("php --version").output) + XCTAssertEqual(expectedPhpOutput, Shell.sync("php --version").out) } func test_unrecognized_commands_output_stderr() { ActiveShell.useTestable([:]) - XCTAssertEqual("Unexpected Command", Shell.sync("unrecognized command").output) + let output = Shell.sync("unrecognized command") + + XCTAssertTrue(output.hasError) + XCTAssertEqual("Unexpected Command", output.err) + XCTAssertEqual("", output.out) } } diff --git a/phpmon-tests/Next/SystemShellTest.swift b/phpmon-tests/Next/SystemShellTest.swift index 4ddde43..bab6967 100644 --- a/phpmon-tests/Next/SystemShellTest.swift +++ b/phpmon-tests/Next/SystemShellTest.swift @@ -9,10 +9,16 @@ import XCTest class SystemShellTest: XCTestCase { + + override class func setUp() { + // Reset to the default shell + ActiveShell.useSystem() + } + func test_system_shell_is_default() { XCTAssertTrue(Shell is SystemShell) - XCTAssertTrue(Shell.sync("php -v").output.contains("Copyright (c) The PHP Group")) + XCTAssertTrue(Shell.sync("php -v").out.contains("Copyright (c) The PHP Group")) } func test_system_shell_has_path() { @@ -28,14 +34,14 @@ class SystemShellTest: XCTestCase { let shellOutput = try! await Shell.attach( "php -r \"echo 'Hello world' . PHP_EOL; usleep(200); echo 'Goodbye world';\"", didReceiveOutput: { incoming in - bits.append(incoming.output) + bits.append(incoming.out) }, withTimeout: 2.0 ) XCTAssertTrue(bits.contains("Hello world\n")) XCTAssertTrue(bits.contains("Goodbye world")) - XCTAssertEqual("Hello world\nGoodbye world", shellOutput.output) + XCTAssertEqual("Hello world\nGoodbye world", shellOutput.out) } func test_system_shell_can_timeout_and_throw_error() async { @@ -52,6 +58,6 @@ class SystemShellTest: XCTestCase { expectation.fulfill() } - wait(for: [expectation], timeout: 3.0) + wait(for: [expectation], timeout: 5.0) } } diff --git a/phpmon/Next/Shellable.swift b/phpmon/Next/Shellable.swift index ceb653f..3c50b75 100644 --- a/phpmon/Next/Shellable.swift +++ b/phpmon/Next/Shellable.swift @@ -8,19 +8,20 @@ import Foundation -struct ShellOutput: CustomStringConvertible { - var output: String - var isError: Bool - var description: String { - return output +struct ShellOutput { + var out: String + var err: String + + var hasError: Bool { + return err.lengthOfBytes(using: .utf8) > 0 } - static func out(_ output: String) -> ShellOutput { - return ShellOutput(output: output, isError: false) + static func out(_ out: String?, _ err: String? = nil) -> ShellOutput { + return ShellOutput(out: out ?? "", err: err ?? "") } - static func err(_ output: String) -> ShellOutput { - return ShellOutput(output: output, isError: true) + static func err(_ err: String?) -> ShellOutput { + return ShellOutput(out: "", err: err ?? "") } } diff --git a/phpmon/Next/SystemShell.swift b/phpmon/Next/SystemShell.swift index 93672f4..1901d55 100644 --- a/phpmon/Next/SystemShell.swift +++ b/phpmon/Next/SystemShell.swift @@ -103,11 +103,7 @@ class SystemShell: Shellable { encoding: .utf8 )! - if stdErr.lengthOfBytes(using: .utf8) > 0 { - return ShellOutput(output: stdErr, isError: true) - } - - return ShellOutput(output: stdOut, isError: false) + return .out(stdOut, stdErr) } func pipe(_ command: String) async -> ShellOutput { diff --git a/phpmon/Next/TestableShell.swift b/phpmon/Next/TestableShell.swift index 0ef9ebb..41a6fd2 100644 --- a/phpmon/Next/TestableShell.swift +++ b/phpmon/Next/TestableShell.swift @@ -35,10 +35,11 @@ public class TestableShell: Shellable { func sync(_ command: String) -> ShellOutput { guard let expectation = expectations[command] else { - return ShellOutput(output: "Unexpected Command", isError: true) + return .err("Unexpected Command") } - return ShellOutput(output: expectation.getOutputAsString(), isError: expectation.outputsToError()) + let output = expectation.getOutputAsString() + return expectation.outputsToError() ? .err(output) : .out(output) } } From 4c11fae5419968227d859dfade47bf0819dabcd3 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 28 Sep 2022 22:18:16 +0200 Subject: [PATCH 021/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Run=20shell=20com?= =?UTF-8?q?mands=20in=20parallel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon-tests/Next/SystemShellTest.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/phpmon-tests/Next/SystemShellTest.swift b/phpmon-tests/Next/SystemShellTest.swift index bab6967..8e411bc 100644 --- a/phpmon-tests/Next/SystemShellTest.swift +++ b/phpmon-tests/Next/SystemShellTest.swift @@ -60,4 +60,18 @@ class SystemShellTest: XCTestCase { wait(for: [expectation], timeout: 5.0) } + + func test_system_processes_run_in_parallel() async { + let expectation = XCTestExpectation(description: #function) + + let thing = { + await Shell.quiet("php -r \"usleep(700);\"") + await Shell.quiet("php -r \"usleep(700);\"") + await Shell.quiet("php -r \"usleep(700);\"") + expectation.fulfill() + } + + await thing() + wait(for: [expectation], timeout: 1.0) + } } From ffffcad84b9e966b2eddbdf851cff0f006b1d2cc Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 29 Sep 2022 18:50:40 +0200 Subject: [PATCH 022/181] =?UTF-8?q?=F0=9F=90=9B=20Fix=20ComposerWindow=20d?= =?UTF-8?q?einit=20not=20firing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Helpers/PMWindowController.swift | 2 +- phpmon/Domain/DomainList/DomainListVC.swift | 2 +- .../Integrations/Composer/ComposerWindow.swift | 14 ++++++++++---- phpmon/Domain/Notice/BetterAlertVC.swift | 2 +- phpmon/Domain/Preferences/PrefsVC.swift | 2 +- phpmon/Domain/Progress/ProgressVC.swift | 2 +- .../TerminalProgressWindowController.swift | 2 +- phpmon/Domain/Watcher/PhpConfigWatcher.swift | 2 +- 8 files changed, 17 insertions(+), 11 deletions(-) diff --git a/phpmon/Common/Helpers/PMWindowController.swift b/phpmon/Common/Helpers/PMWindowController.swift index f9e75b4..24684dc 100644 --- a/phpmon/Common/Helpers/PMWindowController.swift +++ b/phpmon/Common/Helpers/PMWindowController.swift @@ -30,7 +30,7 @@ class PMWindowController: NSWindowController, NSWindowDelegate { } deinit { - Log.perf("Window controller '\(windowName)' was deinitialized") + Log.perf("deinit: \(String(describing: self)).\(#function)") } } diff --git a/phpmon/Domain/DomainList/DomainListVC.swift b/phpmon/Domain/DomainList/DomainListVC.swift index 7c0f531..d3a26cd 100644 --- a/phpmon/Domain/DomainList/DomainListVC.swift +++ b/phpmon/Domain/DomainList/DomainListVC.swift @@ -292,6 +292,6 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource // MARK: - Deinitialization deinit { - Log.perf("DomainListVC deallocated") + Log.perf("deinit: \(String(describing: self)).\(#function)") } } diff --git a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift index 2762cd2..90faa60 100644 --- a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift +++ b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift @@ -52,15 +52,15 @@ class ComposerWindow { } task.listen( - didReceiveStandardOutputData: { string in + didReceiveStandardOutputData: { [weak self] string in DispatchQueue.main.async { - self.window?.addToConsole(string) + self?.window?.addToConsole(string) } // Log.perf("\(string.trimmingCharacters(in: .newlines))") }, - didReceiveStandardErrorData: { string in + didReceiveStandardErrorData: { [weak self] string in DispatchQueue.main.async { - self.window?.addToConsole(string) + self?.window?.addToConsole(string) } // Log.perf("\(string.trimmingCharacters(in: .newlines))") } @@ -91,6 +91,7 @@ class ComposerWindow { } window = nil removeBusyStatus() + menu = nil completion(true) } } @@ -103,6 +104,7 @@ class ComposerWindow { window?.progressView?.labelDescription.stringValue = "alert.composer_failure.info".localized window = nil removeBusyStatus() + menu = nil completion(false) } } @@ -128,4 +130,8 @@ class ComposerWindow { .withPrimary(text: "OK") .show() } + + deinit { + Log.perf("deinit: \(String(describing: self)).\(#function)") + } } diff --git a/phpmon/Domain/Notice/BetterAlertVC.swift b/phpmon/Domain/Notice/BetterAlertVC.swift index 9f4797f..c6c3bdc 100644 --- a/phpmon/Domain/Notice/BetterAlertVC.swift +++ b/phpmon/Domain/Notice/BetterAlertVC.swift @@ -47,7 +47,7 @@ class BetterAlertVC: NSViewController { } deinit { - Log.perf("A BetterAlert has been deinitialized.") + Log.perf("deinit: \(String(describing: self)).\(#function)") } // MARK: Outlet Actions diff --git a/phpmon/Domain/Preferences/PrefsVC.swift b/phpmon/Domain/Preferences/PrefsVC.swift index 47179b0..346f6ec 100644 --- a/phpmon/Domain/Preferences/PrefsVC.swift +++ b/phpmon/Domain/Preferences/PrefsVC.swift @@ -25,7 +25,7 @@ class GenericPreferenceVC: NSViewController { // MARK: - Deinitialization deinit { - Log.perf("PrefsVC deallocated") + Log.perf("deinit: \(String(describing: self)).\(#function)") } func getDynamicIconPV() -> NSView { diff --git a/phpmon/Domain/Progress/ProgressVC.swift b/phpmon/Domain/Progress/ProgressVC.swift index 2b77bb2..9fc8f2c 100644 --- a/phpmon/Domain/Progress/ProgressVC.swift +++ b/phpmon/Domain/Progress/ProgressVC.swift @@ -18,7 +18,7 @@ class ProgressViewController: NSViewController { @IBOutlet weak var imageViewType: NSImageView! deinit { - Log.perf("Deinitializing ProgressViewController") + Log.perf("deinit: \(String(describing: self)).\(#function)") } } diff --git a/phpmon/Domain/Progress/TerminalProgressWindowController.swift b/phpmon/Domain/Progress/TerminalProgressWindowController.swift index c7c05ea..d60f7bc 100644 --- a/phpmon/Domain/Progress/TerminalProgressWindowController.swift +++ b/phpmon/Domain/Progress/TerminalProgressWindowController.swift @@ -56,7 +56,7 @@ class TerminalProgressWindowController: NSWindowController, NSWindowDelegate { } deinit { - Log.perf("Deinitializing ProgressWindowController") + Log.perf("deinit: \(String(describing: self)).\(#function)") } } diff --git a/phpmon/Domain/Watcher/PhpConfigWatcher.swift b/phpmon/Domain/Watcher/PhpConfigWatcher.swift index b1b1e45..cc6d231 100644 --- a/phpmon/Domain/Watcher/PhpConfigWatcher.swift +++ b/phpmon/Domain/Watcher/PhpConfigWatcher.swift @@ -68,7 +68,7 @@ class PhpConfigWatcher { } deinit { - Log.perf("A PhpConfigWatcher has been deinitialized.") + Log.perf("deinit: \(String(describing: self)).\(#function)") } } From 5ebafdb4e319fb99a0a606fd24ea0980c5757663 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 29 Sep 2022 19:08:40 +0200 Subject: [PATCH 023/181] =?UTF-8?q?=F0=9F=91=8C=20Shell=20tweaks,=20fix=20?= =?UTF-8?q?ComposerWindow=20async=20issue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 6 +++ phpmon-tests/Next/SystemShellTest.swift | 2 +- .../Extensions/TimeIntervalExtension.swift | 15 ++++++ .../Composer/ComposerWindow.swift | 51 +++++++++---------- phpmon/Domain/Menu/MainMenu+Actions.swift | 2 +- phpmon/Next/Shellable.swift | 2 +- phpmon/Next/SystemShell.swift | 6 +-- phpmon/Next/TestableShell.swift | 4 +- 8 files changed, 53 insertions(+), 35 deletions(-) create mode 100644 phpmon/Common/Extensions/TimeIntervalExtension.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index fcceb87..4ad3a28 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -125,6 +125,8 @@ C449B4F427EE7FC800C47E8A /* DomainListKindCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */; }; C44A874828905BB000498BC4 /* ProgressVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44A874728905BB000498BC4 /* ProgressVC.swift */; }; C44A874928905BB000498BC4 /* ProgressVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44A874728905BB000498BC4 /* ProgressVC.swift */; }; + C44B3A4628E5C70100718CB1 /* TimeIntervalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */; }; + C44B3A4728E5C70100718CB1 /* TimeIntervalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */; }; C44C198D276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */; }; C44C198E276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */; }; C44C1991276E44CB0072762D /* ProgressWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C44C1990276E44CB0072762D /* ProgressWindow.storyboard */; }; @@ -412,6 +414,7 @@ C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIHelper.swift; sourceTree = ""; }; C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionPopoverView.swift; sourceTree = ""; }; C44A874728905BB000498BC4 /* ProgressVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressVC.swift; sourceTree = ""; }; + C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeIntervalExtension.swift; sourceTree = ""; }; C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalProgressWindowController.swift; sourceTree = ""; }; C44C1990276E44CB0072762D /* ProgressWindow.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ProgressWindow.storyboard; sourceTree = ""; }; C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertableError.swift; sourceTree = ""; }; @@ -1202,6 +1205,7 @@ C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */, C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */, C4EB53E628553117006F9937 /* ArrayExtension.swift */, + C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */, ); path = Extensions; sourceTree = ""; @@ -1388,6 +1392,7 @@ C463E380284930EE00422731 /* PresetHelper.swift in Sources */, C41C02A927E61A65009F26CB /* ValetSite+Fake.swift in Sources */, C4C0E8DF27F88AEB002D32A9 /* FakeSiteScanner.swift in Sources */, + C44B3A4628E5C70100718CB1 /* TimeIntervalExtension.swift in Sources */, C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */, C4E9D2C02878B336008FFDAD /* OnboardingView.swift in Sources */, C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */, @@ -1510,6 +1515,7 @@ C4205A7F27F4D21800191A39 /* ValetProxy.swift in Sources */, C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */, C46EBC4528DB95F0007ACC74 /* Shellable.swift in Sources */, + C44B3A4728E5C70100718CB1 /* TimeIntervalExtension.swift in Sources */, C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */, 54FCFD2B276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */, C415D3B82770F294005EF286 /* Actions.swift in Sources */, diff --git a/phpmon-tests/Next/SystemShellTest.swift b/phpmon-tests/Next/SystemShellTest.swift index 8e411bc..28530ff 100644 --- a/phpmon-tests/Next/SystemShellTest.swift +++ b/phpmon-tests/Next/SystemShellTest.swift @@ -31,7 +31,7 @@ class SystemShellTest: XCTestCase { func test_system_shell_can_buffer_output() async { var bits: [String] = [] - let shellOutput = try! await Shell.attach( + let (_, shellOutput) = try! await Shell.attach( "php -r \"echo 'Hello world' . PHP_EOL; usleep(200); echo 'Goodbye world';\"", didReceiveOutput: { incoming in bits.append(incoming.out) diff --git a/phpmon/Common/Extensions/TimeIntervalExtension.swift b/phpmon/Common/Extensions/TimeIntervalExtension.swift new file mode 100644 index 0000000..19d3854 --- /dev/null +++ b/phpmon/Common/Extensions/TimeIntervalExtension.swift @@ -0,0 +1,15 @@ +// +// TimeExtension.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 29/09/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +extension TimeInterval { + public static func minutes(_ amount: Int) -> TimeInterval { + return Double(amount * 60) + } +} diff --git a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift index 90faa60..3822789 100644 --- a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift +++ b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift @@ -8,8 +8,7 @@ import Foundation -class ComposerWindow { - +@MainActor class ComposerWindow { private var menu: MainMenu? private var shouldNotify: Bool! = nil private var completion: ((Bool) -> Void)! = nil @@ -42,39 +41,37 @@ class ComposerWindow { window?.setType(info: true) - DispatchQueue.global(qos: .userInitiated).async { [self] in - let task = LegacyShell.user.createTask( - for: "\(Paths.composer!) global update", requiresPath: true - ) + Task { await performComposerUpdate() } + } + + private func performComposerUpdate() async { + do { + let command = "\(Paths.composer!) global update" DispatchQueue.main.async { - self.window?.addToConsole("\(Paths.composer!) global update\n") + self.window?.addToConsole("\(command)\n") } - task.listen( - didReceiveStandardOutputData: { [weak self] string in - DispatchQueue.main.async { - self?.window?.addToConsole(string) + let (process, _) = try await Shell.attach( + command, + didReceiveOutput: { [weak self] output in + if output.hasError { + DispatchQueue.main.async { self?.window?.addToConsole(output.err) } + } + if !output.out.isEmpty { + DispatchQueue.main.async { self?.window?.addToConsole(output.out) } } - // Log.perf("\(string.trimmingCharacters(in: .newlines))") }, - didReceiveStandardErrorData: { [weak self] string in - DispatchQueue.main.async { - self?.window?.addToConsole(string) - } - // Log.perf("\(string.trimmingCharacters(in: .newlines))") - } + withTimeout: .minutes(5) ) - task.launch() - task.waitUntilExit() - task.haltListening() - - if task.terminationStatus <= 0 { - composerUpdateSucceeded() - } else { - composerUpdateFailed() - } + if process.terminationStatus <= 0 { + composerUpdateSucceeded() + } else { + composerUpdateFailed() + } + } catch { + composerUpdateFailed() } } diff --git a/phpmon/Domain/Menu/MainMenu+Actions.swift b/phpmon/Domain/Menu/MainMenu+Actions.swift index 36e1f08..3f696b6 100644 --- a/phpmon/Domain/Menu/MainMenu+Actions.swift +++ b/phpmon/Domain/Menu/MainMenu+Actions.swift @@ -200,7 +200,7 @@ extension MainMenu { } } - @objc func updateGlobalComposerDependencies() { + @MainActor @objc func updateGlobalComposerDependencies() { ComposerWindow().updateGlobalDependencies( notify: true, completion: { _ in } diff --git a/phpmon/Next/Shellable.swift b/phpmon/Next/Shellable.swift index 3c50b75..40bc8d2 100644 --- a/phpmon/Next/Shellable.swift +++ b/phpmon/Next/Shellable.swift @@ -58,7 +58,7 @@ protocol Shellable { _ command: String, didReceiveOutput: @escaping (ShellOutput) -> Void, withTimeout timeout: TimeInterval - ) async throws -> ShellOutput + ) async throws -> (Process, ShellOutput) } enum ShellError: Error { diff --git a/phpmon/Next/SystemShell.swift b/phpmon/Next/SystemShell.swift index 1901d55..dfab315 100644 --- a/phpmon/Next/SystemShell.swift +++ b/phpmon/Next/SystemShell.swift @@ -118,7 +118,7 @@ class SystemShell: Shellable { _ command: String, didReceiveOutput: @escaping (ShellOutput) -> Void, withTimeout timeout: TimeInterval = 5.0 - ) async throws -> ShellOutput { + ) async throws -> (Process, ShellOutput) { let task = getShellProcess(for: command) var allOut: String = "" @@ -139,10 +139,10 @@ class SystemShell: Shellable { timer?.invalidate() if !allErr.isEmpty { - return continuation.resume(returning: .err(allErr)) + return continuation.resume(returning: (process, .err(allErr))) } - return continuation.resume(returning: .out(allOut)) + return continuation.resume(returning: (process, .out(allOut))) } timer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { _ in diff --git a/phpmon/Next/TestableShell.swift b/phpmon/Next/TestableShell.swift index 41a6fd2..cb3f962 100644 --- a/phpmon/Next/TestableShell.swift +++ b/phpmon/Next/TestableShell.swift @@ -29,8 +29,8 @@ public class TestableShell: Shellable { _ command: String, didReceiveOutput: @escaping (ShellOutput) -> Void, withTimeout timeout: TimeInterval - ) async throws -> ShellOutput { - self.sync(command) + ) async throws -> (Process, ShellOutput) { + return (Process(), self.sync(command)) } func sync(_ command: String) -> ShellOutput { From 572330eaa17eb82fe8a9bc39963dc0bad7ad338e Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 30 Sep 2022 23:44:16 +0200 Subject: [PATCH 024/181] =?UTF-8?q?=F0=9F=91=8C=20Remove=20reference=20to?= =?UTF-8?q?=20singleton?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Integrations/Composer/ComposerWindow.swift | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift index 3822789..d53c003 100644 --- a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift +++ b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift @@ -9,7 +9,6 @@ import Foundation @MainActor class ComposerWindow { - private var menu: MainMenu? private var shouldNotify: Bool! = nil private var completion: ((Bool) -> Void)! = nil private var window: TerminalProgressWindowController? @@ -18,7 +17,6 @@ import Foundation Updates the global dependencies and runs the completion callback when done. */ func updateGlobalDependencies(notify: Bool, completion: @escaping (Bool) -> Void) { - self.menu = MainMenu.shared self.shouldNotify = notify self.completion = completion @@ -31,8 +29,8 @@ import Foundation } PhpEnv.shared.isBusy = true - menu?.setBusyImage() - menu?.rebuild() + MainMenu.shared.setBusyImage() + MainMenu.shared.rebuild() window = TerminalProgressWindowController.display( title: "alert.composer_progress.title".localized, @@ -88,7 +86,6 @@ import Foundation } window = nil removeBusyStatus() - menu = nil completion(true) } } @@ -101,7 +98,6 @@ import Foundation window?.progressView?.labelDescription.stringValue = "alert.composer_failure.info".localized window = nil removeBusyStatus() - menu = nil completion(false) } } @@ -110,8 +106,8 @@ import Foundation private func removeBusyStatus() { PhpEnv.shared.isBusy = false - DispatchQueue.main.async { [self] in - menu?.updatePhpVersionInStatusBar() + DispatchQueue.main.async { + MainMenu.shared.updatePhpVersionInStatusBar() } } From 86eb2954891954e82e5a50f9757495c8dbaf4bd4 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 30 Sep 2022 23:45:32 +0200 Subject: [PATCH 025/181] =?UTF-8?q?=F0=9F=91=8C=20Add=20`runComposerUpdate?= =?UTF-8?q?ShellCommand`=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Composer/ComposerWindow.swift | 53 ++++++++++--------- .../TerminalProgressWindowController.swift | 12 +++-- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift index d53c003..6386793 100644 --- a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift +++ b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift @@ -44,35 +44,40 @@ import Foundation private func performComposerUpdate() async { do { - let command = "\(Paths.composer!) global update" - - DispatchQueue.main.async { - self.window?.addToConsole("\(command)\n") - } - - let (process, _) = try await Shell.attach( - command, - didReceiveOutput: { [weak self] output in - if output.hasError { - DispatchQueue.main.async { self?.window?.addToConsole(output.err) } - } - if !output.out.isEmpty { - DispatchQueue.main.async { self?.window?.addToConsole(output.out) } - } - }, - withTimeout: .minutes(5) - ) - - if process.terminationStatus <= 0 { - composerUpdateSucceeded() - } else { - composerUpdateFailed() - } + try await runComposerUpdateShellCommand() } catch { composerUpdateFailed() } } + private func runComposerUpdateShellCommand() async throws { + let command = "\(Paths.composer!) global update" + + self.window?.addToConsole("\(command)\n") + + let (process, _) = try await Shell.attach( + command, + didReceiveOutput: { [weak self] output in + guard let window = self?.window else { return } + + if output.hasError { + window.addToConsole(output.err) + } + + if !output.out.isEmpty { + window.addToConsole(output.out) + } + }, + withTimeout: .minutes(5) + ) + + if process.terminationStatus <= 0 { + composerUpdateSucceeded() + } else { + composerUpdateFailed() + } + } + private func composerUpdateSucceeded() { // Closing the window should happen after a slight delay DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [self] in diff --git a/phpmon/Domain/Progress/TerminalProgressWindowController.swift b/phpmon/Domain/Progress/TerminalProgressWindowController.swift index d60f7bc..55e4790 100644 --- a/phpmon/Domain/Progress/TerminalProgressWindowController.swift +++ b/phpmon/Domain/Progress/TerminalProgressWindowController.swift @@ -35,12 +35,14 @@ class TerminalProgressWindowController: NSWindowController, NSWindowDelegate { } public func addToConsole(_ string: String) { - guard let textView = self.progressView?.textView else { - return - } + DispatchQueue.main.async { + guard let textView = self.progressView?.textView else { + return + } - textView.string += string - textView.scrollToEndOfDocument(nil) + textView.string += string + textView.scrollToEndOfDocument(nil) + } } public func setType(info: Bool = true) { From c9a5cd3a9f329ccfdcd3fbc9c74a1f3d9418a324 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 3 Oct 2022 19:27:00 +0200 Subject: [PATCH 026/181] =?UTF-8?q?=F0=9F=8F=97=20WIP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Next/TestableShell.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/phpmon/Next/TestableShell.swift b/phpmon/Next/TestableShell.swift index cb3f962..ece3f3e 100644 --- a/phpmon/Next/TestableShell.swift +++ b/phpmon/Next/TestableShell.swift @@ -43,6 +43,12 @@ public class TestableShell: Shellable { } } +// TODO: Test env shell output should be modeled differently +// So the possible outcome is either: +// 1. Immediate with almost zero delay `.instant("string")` +// 2. Delayed but then all at once: `.delay(300, "string")` +// 3. A stream of data spread over multiple seconds: `.multiple([.delay(300, "hello"), .delay(300, "bye")])` + protocol OutputsToShell { func getOutputAsString() -> String func getDuration() -> Int From c26c49134099511c2aeadefc9c51309d5a8c4c89 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 3 Oct 2022 22:27:50 +0200 Subject: [PATCH 027/181] =?UTF-8?q?=F0=9F=8F=97=20WIP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon-tests/Next/FakeShellTest.swift | 14 +++++ phpmon/Next/ActiveShell.swift | 5 +- phpmon/Next/TestableShell.swift | 79 ++++++++++++++++----------- 3 files changed, 62 insertions(+), 36 deletions(-) diff --git a/phpmon-tests/Next/FakeShellTest.swift b/phpmon-tests/Next/FakeShellTest.swift index 03b91a2..dedd2b1 100644 --- a/phpmon-tests/Next/FakeShellTest.swift +++ b/phpmon-tests/Next/FakeShellTest.swift @@ -9,6 +9,19 @@ import XCTest class FakeShellTest: XCTestCase { + + func test_fake_shell_output_can_be_declared() { + let greeting = BatchFakeShellOutput(items: [ + .instant("Hello world"), + .delayed(0.3, "Goodbye world") + ]) + + let output = greeting.outputInstantaneously() + + XCTAssertEqual("Hello world\nGoodbye world", output.out) + } + + /* func test_we_can_predefine_responses_for_dummy_shell() { let expectedPhpOutput = """ PHP 8.1.10 (cli) (built: Sep 3 2022 12:09:27) (NTS) @@ -45,4 +58,5 @@ class FakeShellTest: XCTestCase { XCTAssertEqual("Unexpected Command", output.err) XCTAssertEqual("", output.out) } + */ } diff --git a/phpmon/Next/ActiveShell.swift b/phpmon/Next/ActiveShell.swift index e523ac5..4210338 100644 --- a/phpmon/Next/ActiveShell.swift +++ b/phpmon/Next/ActiveShell.swift @@ -15,13 +15,10 @@ var Shell: Shellable { class ActiveShell { static var shared: Shellable = SystemShell() - /// Uses a testable shell with predefined responses. You specify the terminal's output. - /// they also work with simple String objects. - public static func useTestable(_ expectations: [String: OutputsToShell]) { + public static func useTestable(_ expectations: [String: BatchFakeShellOutput]) { Self.shared = TestableShell(expectations: expectations) } - /// Reverts back to the system shell. You do not need to call this, only after using `useTestable()`. public static func useSystem() { Self.shared = SystemShell() } diff --git a/phpmon/Next/TestableShell.swift b/phpmon/Next/TestableShell.swift index ece3f3e..11ee354 100644 --- a/phpmon/Next/TestableShell.swift +++ b/phpmon/Next/TestableShell.swift @@ -11,11 +11,11 @@ import Foundation public class TestableShell: Shellable { public typealias Input = String - init(expectations: [Input: OutputsToShell]) { + init(expectations: [Input: BatchFakeShellOutput]) { self.expectations = expectations } - var expectations: [Input: OutputsToShell] = [:] + var expectations: [Input: BatchFakeShellOutput] = [:] func quiet(_ command: String) async { return @@ -37,9 +37,7 @@ public class TestableShell: Shellable { guard let expectation = expectations[command] else { return .err("Unexpected Command") } - - let output = expectation.getOutputAsString() - return expectation.outputsToError() ? .err(output) : .out(output) + return ShellOutput(out: "", err: "") } } @@ -49,40 +47,57 @@ public class TestableShell: Shellable { // 2. Delayed but then all at once: `.delay(300, "string")` // 3. A stream of data spread over multiple seconds: `.multiple([.delay(300, "hello"), .delay(300, "bye")])` -protocol OutputsToShell { - func getOutputAsString() -> String - func getDuration() -> Int - func outputsToError() -> Bool -} +struct FakeShellOutput { + let delay: TimeInterval + let output: ShellOutput -struct FakeTerminalOutput: OutputsToShell { - var output: String - var duration: Int - var isError: Bool - - func getOutputAsString() -> String { - return output + static func instant(_ stdOut: String, _ stdErr: String? = nil) -> FakeShellOutput { + return FakeShellOutput(delay: 0, output: ShellOutput(out: stdOut, err: stdErr ?? "")) } - func getDuration() -> Int { - return duration - } - - func outputsToError() -> Bool { - return isError + static func delayed(_ delay: TimeInterval, _ stdOut: String, _ stdErr: String? = nil) -> FakeShellOutput { + return FakeShellOutput(delay: delay, output: ShellOutput(out: stdOut, err: stdErr ?? "")) } } -extension String: OutputsToShell { - func getOutputAsString() -> String { - return self +struct BatchFakeShellOutput { + var items: [FakeShellOutput] + + /** + Outputs the fake shell output as expected. + */ + public func output( + didReceiveOutput: @escaping (ShellOutput) -> Void, + ignoreDelay: Bool = false + ) async -> ShellOutput { + var allOut: String = "" + var allErr: String = "" + + Task { + self.items.forEach { fakeShellOutput in + let delay = UInt64(fakeShellOutput.delay * 1_000_000_000) + try await Task.sleep(nanoseconds: delay) + + allOut += fakeShellOutput.output.out + + if fakeShellOutput.output.hasError { + allErr += fakeShellOutput.output.err + } + } + } + + return ShellOutput( + out: allOut, + err: allErr + ) } - func getDuration() -> Int { - return 100 - } - - func outputsToError() -> Bool { - return false + /** + For testing purposes (and speed) we may omit the delay, regardless of its timespan. + */ + public func outputInstantaneously( + didReceiveOutput: @escaping (ShellOutput) -> Void + ) -> ShellOutput { + self.output(didReceiveOutput: didReceiveOutput, ignoreDelay: true) } } From 953ccb37927a8afea2414510aae154f854f62e82 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 4 Oct 2022 17:57:05 +0200 Subject: [PATCH 028/181] =?UTF-8?q?=F0=9F=8F=97=20WIP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon-tests/Next/FakeShellTest.swift | 49 ++++------------- phpmon-tests/Next/SystemShellTest.swift | 6 +-- .../Composer/ComposerWindow.swift | 11 +--- phpmon/Next/Shellable.swift | 6 ++- phpmon/Next/SystemShell.swift | 20 ++++--- phpmon/Next/TestableShell.swift | 53 ++++++++----------- 6 files changed, 52 insertions(+), 93 deletions(-) diff --git a/phpmon-tests/Next/FakeShellTest.swift b/phpmon-tests/Next/FakeShellTest.swift index dedd2b1..f7f1fd8 100644 --- a/phpmon-tests/Next/FakeShellTest.swift +++ b/phpmon-tests/Next/FakeShellTest.swift @@ -9,54 +9,27 @@ import XCTest class FakeShellTest: XCTestCase { - - func test_fake_shell_output_can_be_declared() { + func test_fake_shell_output_can_be_declared() async { let greeting = BatchFakeShellOutput(items: [ - .instant("Hello world"), + .instant("Hello world\n"), .delayed(0.3, "Goodbye world") ]) - let output = greeting.outputInstantaneously() + let output = await greeting.outputInstantaneously() XCTAssertEqual("Hello world\nGoodbye world", output.out) } - /* - func test_we_can_predefine_responses_for_dummy_shell() { - let expectedPhpOutput = """ - PHP 8.1.10 (cli) (built: Sep 3 2022 12:09:27) (NTS) - Copyright (c) The PHP Group - Zend Engine v4.1.10, Copyright (c) Zend Technologies - with Zend OPcache v8.1.10, Copyright (c), by Zend Technologies - with Xdebug v3.1.4, Copyright (c) 2002-2022, by Derick Rethans - """ - - let slowVersionOutput = FakeTerminalOutput( - output: expectedPhpOutput, - duration: 1000, - isError: false - ) - - ActiveShell.useTestable([ - "php -v": expectedPhpOutput, - "php --version": slowVersionOutput + func test_fake_shell_can_output_in_realtime() async { + let greeting = BatchFakeShellOutput(items: [ + .instant("Hello world\n"), + .delayed(2, "Goodbye world") ]) - XCTAssertTrue(Shell is TestableShell) + let output = await greeting.output(didReceiveOutput: { output, _ in + print(output) + }) - XCTAssertEqual(expectedPhpOutput, Shell.sync("php -v").out) - - XCTAssertEqual(expectedPhpOutput, Shell.sync("php --version").out) + XCTAssertEqual("Hello world\nGoodbye world", output.out) } - - func test_unrecognized_commands_output_stderr() { - ActiveShell.useTestable([:]) - - let output = Shell.sync("unrecognized command") - - XCTAssertTrue(output.hasError) - XCTAssertEqual("Unexpected Command", output.err) - XCTAssertEqual("", output.out) - } - */ } diff --git a/phpmon-tests/Next/SystemShellTest.swift b/phpmon-tests/Next/SystemShellTest.swift index 28530ff..3d04221 100644 --- a/phpmon-tests/Next/SystemShellTest.swift +++ b/phpmon-tests/Next/SystemShellTest.swift @@ -33,8 +33,8 @@ class SystemShellTest: XCTestCase { let (_, shellOutput) = try! await Shell.attach( "php -r \"echo 'Hello world' . PHP_EOL; usleep(200); echo 'Goodbye world';\"", - didReceiveOutput: { incoming in - bits.append(incoming.out) + didReceiveOutput: { incoming, _ in + bits.append(incoming) }, withTimeout: 2.0 ) @@ -50,7 +50,7 @@ class SystemShellTest: XCTestCase { do { _ = try await Shell.attach( "php -r \"sleep(1);\"", - didReceiveOutput: { _ in }, + didReceiveOutput: { _, _ in }, withTimeout: 0.1 ) } catch { diff --git a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift index 6386793..81a67a3 100644 --- a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift +++ b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift @@ -57,16 +57,9 @@ import Foundation let (process, _) = try await Shell.attach( command, - didReceiveOutput: { [weak self] output in + didReceiveOutput: { [weak self] (incoming, _) in guard let window = self?.window else { return } - - if output.hasError { - window.addToConsole(output.err) - } - - if !output.out.isEmpty { - window.addToConsole(output.out) - } + window.addToConsole(incoming) }, withTimeout: .minutes(5) ) diff --git a/phpmon/Next/Shellable.swift b/phpmon/Next/Shellable.swift index 40bc8d2..816fba1 100644 --- a/phpmon/Next/Shellable.swift +++ b/phpmon/Next/Shellable.swift @@ -8,6 +8,10 @@ import Foundation +enum ShellStream { + case stdOut, stdErr, stdIn +} + struct ShellOutput { var out: String var err: String @@ -56,7 +60,7 @@ protocol Shellable { */ func attach( _ command: String, - didReceiveOutput: @escaping (ShellOutput) -> Void, + didReceiveOutput: @escaping (String, ShellStream) -> Void, withTimeout timeout: TimeInterval ) async throws -> (Process, ShellOutput) } diff --git a/phpmon/Next/SystemShell.swift b/phpmon/Next/SystemShell.swift index dfab315..28f3637 100644 --- a/phpmon/Next/SystemShell.swift +++ b/phpmon/Next/SystemShell.swift @@ -116,18 +116,16 @@ class SystemShell: Shellable { func attach( _ command: String, - didReceiveOutput: @escaping (ShellOutput) -> Void, + didReceiveOutput: @escaping (String, ShellStream) -> Void, withTimeout timeout: TimeInterval = 5.0 ) async throws -> (Process, ShellOutput) { let task = getShellProcess(for: command) + var output = ShellOutput(out: "", err: "") - var allOut: String = "" - var allErr: String = "" - - task.listen { stdOut in - allOut += stdOut; didReceiveOutput(.out(stdOut)) - } didReceiveStandardErrorData: { stdErr in - allErr += stdErr; didReceiveOutput(.err(stdErr)) + task.listen { incoming in + output.out += incoming; didReceiveOutput(incoming, .stdOut) + } didReceiveStandardErrorData: { incoming in + output.err += incoming; didReceiveOutput(incoming, .stdErr) } return try await withCheckedThrowingContinuation({ continuation in @@ -138,11 +136,11 @@ class SystemShell: Shellable { timer?.invalidate() - if !allErr.isEmpty { - return continuation.resume(returning: (process, .err(allErr))) + if !output.err.isEmpty { + return continuation.resume(returning: (process, .err(output.err))) } - return continuation.resume(returning: (process, .out(allOut))) + return continuation.resume(returning: (process, .out(output.out))) } timer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { _ in diff --git a/phpmon/Next/TestableShell.swift b/phpmon/Next/TestableShell.swift index 11ee354..3605779 100644 --- a/phpmon/Next/TestableShell.swift +++ b/phpmon/Next/TestableShell.swift @@ -27,7 +27,7 @@ public class TestableShell: Shellable { func attach( _ command: String, - didReceiveOutput: @escaping (ShellOutput) -> Void, + didReceiveOutput: @escaping (String, ShellStream) -> Void, withTimeout timeout: TimeInterval ) async throws -> (Process, ShellOutput) { return (Process(), self.sync(command)) @@ -41,22 +41,17 @@ public class TestableShell: Shellable { } } -// TODO: Test env shell output should be modeled differently -// So the possible outcome is either: -// 1. Immediate with almost zero delay `.instant("string")` -// 2. Delayed but then all at once: `.delay(300, "string")` -// 3. A stream of data spread over multiple seconds: `.multiple([.delay(300, "hello"), .delay(300, "bye")])` - struct FakeShellOutput { let delay: TimeInterval - let output: ShellOutput + let output: String + let stream: ShellStream - static func instant(_ stdOut: String, _ stdErr: String? = nil) -> FakeShellOutput { - return FakeShellOutput(delay: 0, output: ShellOutput(out: stdOut, err: stdErr ?? "")) + static func instant(_ output: String, _ stream: ShellStream = .stdOut) -> FakeShellOutput { + return FakeShellOutput(delay: 0, output: output, stream: stream) } - static func delayed(_ delay: TimeInterval, _ stdOut: String, _ stdErr: String? = nil) -> FakeShellOutput { - return FakeShellOutput(delay: delay, output: ShellOutput(out: stdOut, err: stdErr ?? "")) + static func delayed(_ delay: TimeInterval, _ output: String, _ stream: ShellStream = .stdOut) -> FakeShellOutput { + return FakeShellOutput(delay: delay, output: output, stream: stream) } } @@ -67,37 +62,33 @@ struct BatchFakeShellOutput { Outputs the fake shell output as expected. */ public func output( - didReceiveOutput: @escaping (ShellOutput) -> Void, + didReceiveOutput: @escaping (String, ShellStream) -> Void, ignoreDelay: Bool = false ) async -> ShellOutput { - var allOut: String = "" - var allErr: String = "" + var output = ShellOutput(out: "", err: "") - Task { - self.items.forEach { fakeShellOutput in - let delay = UInt64(fakeShellOutput.delay * 1_000_000_000) - try await Task.sleep(nanoseconds: delay) + for item in items { + if !ignoreDelay { + let delay = UInt64(item.delay * 1_000_000_000) + try! await Task.sleep(nanoseconds: delay) + } - allOut += fakeShellOutput.output.out - - if fakeShellOutput.output.hasError { - allErr += fakeShellOutput.output.err - } + if item.stream == .stdErr { + output.err += item.output + } else if item.stream == .stdOut { + output.out += item.output } } - return ShellOutput( - out: allOut, - err: allErr - ) + return output } /** For testing purposes (and speed) we may omit the delay, regardless of its timespan. */ public func outputInstantaneously( - didReceiveOutput: @escaping (ShellOutput) -> Void - ) -> ShellOutput { - self.output(didReceiveOutput: didReceiveOutput, ignoreDelay: true) + didReceiveOutput: @escaping (String, ShellStream) -> Void = { _, _ in } + ) async -> ShellOutput { + return await self.output(didReceiveOutput: didReceiveOutput, ignoreDelay: true) } } From 8a6139d5e7d73c405cd09a4c1b30eabb90af9ded Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 4 Oct 2022 18:40:41 +0200 Subject: [PATCH 029/181] =?UTF-8?q?=F0=9F=8F=97=20Remove=20synchronous=20t?= =?UTF-8?q?erminal=20commands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon-tests/Next/SystemShellTest.swift | 6 ++++-- .../Domain/SwiftUI/Common/SwiftUIHelper.swift | 6 ++++++ phpmon/Next/Shellable.swift | 6 ------ phpmon/Next/SystemShell.swift | 6 +----- phpmon/Next/TestableShell.swift | 18 ++++++++++-------- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/phpmon-tests/Next/SystemShellTest.swift b/phpmon-tests/Next/SystemShellTest.swift index 3d04221..207586b 100644 --- a/phpmon-tests/Next/SystemShellTest.swift +++ b/phpmon-tests/Next/SystemShellTest.swift @@ -15,10 +15,12 @@ class SystemShellTest: XCTestCase { ActiveShell.useSystem() } - func test_system_shell_is_default() { + func test_system_shell_is_default() async { XCTAssertTrue(Shell is SystemShell) - XCTAssertTrue(Shell.sync("php -v").out.contains("Copyright (c) The PHP Group")) + let output = await Shell.pipe("php -v") + + XCTAssertTrue(output.out.contains("Copyright (c) The PHP Group")) } func test_system_shell_has_path() { diff --git a/phpmon/Domain/SwiftUI/Common/SwiftUIHelper.swift b/phpmon/Domain/SwiftUI/Common/SwiftUIHelper.swift index 3598836..e553b7e 100644 --- a/phpmon/Domain/SwiftUI/Common/SwiftUIHelper.swift +++ b/phpmon/Domain/SwiftUI/Common/SwiftUIHelper.swift @@ -9,6 +9,12 @@ import Foundation import SwiftUI +var isRunningTests: Bool { + let environment = ProcessInfo.processInfo.environment + return environment["TEST_MODE"] != nil + || environment["XCTestConfigurationFilePath"] != nil +} + var isRunningSwiftUIPreview: Bool { #if DEBUG // If running SwiftUI *and* when debugging diff --git a/phpmon/Next/Shellable.swift b/phpmon/Next/Shellable.swift index 816fba1..0b4f78b 100644 --- a/phpmon/Next/Shellable.swift +++ b/phpmon/Next/Shellable.swift @@ -30,12 +30,6 @@ struct ShellOutput { } protocol Shellable { - /** - Run a command synchronously. Waits until the command is done. - Returns the most relevant output (prefers error output if it exists). - */ - func sync(_ command: String) -> ShellOutput - /** Run a command asynchronously. Returns the most relevant output (prefers error output if it exists). diff --git a/phpmon/Next/SystemShell.swift b/phpmon/Next/SystemShell.swift index 28f3637..ec8c3b8 100644 --- a/phpmon/Next/SystemShell.swift +++ b/phpmon/Next/SystemShell.swift @@ -82,7 +82,7 @@ class SystemShell: Shellable { // MARK: - Shellable Protocol - func sync(_ command: String) -> ShellOutput { + func pipe(_ command: String) async -> ShellOutput { let task = getShellProcess(for: command) let outputPipe = Pipe() @@ -106,10 +106,6 @@ class SystemShell: Shellable { return .out(stdOut, stdErr) } - func pipe(_ command: String) async -> ShellOutput { - return sync(command) - } - func quiet(_ command: String) async { _ = await self.pipe(command) } diff --git a/phpmon/Next/TestableShell.swift b/phpmon/Next/TestableShell.swift index 3605779..c2b5311 100644 --- a/phpmon/Next/TestableShell.swift +++ b/phpmon/Next/TestableShell.swift @@ -18,11 +18,12 @@ public class TestableShell: Shellable { var expectations: [Input: BatchFakeShellOutput] = [:] func quiet(_ command: String) async { - return + _ = try! await self.attach(command, didReceiveOutput: { _, _ in }, withTimeout: 60) } func pipe(_ command: String) async -> ShellOutput { - self.sync(command) + let (_, output) = try! await self.attach(command, didReceiveOutput: { _, _ in }, withTimeout: 60) + return output } func attach( @@ -30,14 +31,15 @@ public class TestableShell: Shellable { didReceiveOutput: @escaping (String, ShellStream) -> Void, withTimeout timeout: TimeInterval ) async throws -> (Process, ShellOutput) { - return (Process(), self.sync(command)) - } - - func sync(_ command: String) -> ShellOutput { guard let expectation = expectations[command] else { - return .err("Unexpected Command") + return (Process(), .err("No Expected Output")) } - return ShellOutput(out: "", err: "") + + let output = await expectation.output(didReceiveOutput: { output, type in + didReceiveOutput(output, type) + }, ignoreDelay: isRunningTests) + + return (Process(), output) } } From 2c25bcbdb5a51331bf6379fb1b2ad41130e9ba15 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 4 Oct 2022 19:00:33 +0200 Subject: [PATCH 030/181] =?UTF-8?q?=F0=9F=8F=97=20Ensure=20Shellable=20has?= =?UTF-8?q?=20PATH?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon-tests/Next/FakeShellTest.swift | 6 ++++++ phpmon/Domain/Warnings/WarningManager.swift | 4 ++-- phpmon/Next/Shellable.swift | 10 ++++++++++ phpmon/Next/TestableShell.swift | 8 +++++--- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/phpmon-tests/Next/FakeShellTest.swift b/phpmon-tests/Next/FakeShellTest.swift index f7f1fd8..993cf5d 100644 --- a/phpmon-tests/Next/FakeShellTest.swift +++ b/phpmon-tests/Next/FakeShellTest.swift @@ -32,4 +32,10 @@ class FakeShellTest: XCTestCase { XCTAssertEqual("Hello world\nGoodbye world", output.out) } + + func test_fake_shell_has_path() { + ActiveShell.useTestable([:]) + + XCTAssertEqual(Shell.PATH, "/usr/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin") + } } diff --git a/phpmon/Domain/Warnings/WarningManager.swift b/phpmon/Domain/Warnings/WarningManager.swift index 7c88a92..b0b8b1a 100644 --- a/phpmon/Domain/Warnings/WarningManager.swift +++ b/phpmon/Domain/Warnings/WarningManager.swift @@ -22,7 +22,7 @@ class WarningManager { public let evaluations: [Warning] = [ Warning( command: { - return LegacyShell.pipe("sysctl -n sysctl.proc_translated") + return await Shell.pipe("sysctl -n sysctl.proc_translated").out .trimmingCharacters(in: .whitespacesAndNewlines) == "1" }, name: "Running PHP Monitor with Rosetta on M1", @@ -32,7 +32,7 @@ class WarningManager { ), Warning( command: { - return !LegacyShell.user.PATH.contains("\(Paths.homePath)/.config/phpmon/bin") && + return !Shell.PATH.contains("\(Paths.homePath)/.config/phpmon/bin") && !FileManager.default.isWritableFile(atPath: "/usr/local/bin/") }, name: "Helpers cannot be symlinked and not in PATH", diff --git a/phpmon/Next/Shellable.swift b/phpmon/Next/Shellable.swift index 0b4f78b..e2eee2a 100644 --- a/phpmon/Next/Shellable.swift +++ b/phpmon/Next/Shellable.swift @@ -30,9 +30,19 @@ struct ShellOutput { } protocol Shellable { + /** + The PATH for the current shell. + */ + var PATH: String { get } + /** Run a command asynchronously. Returns the most relevant output (prefers error output if it exists). + + Common usage: + ``` + let output = await Shell.pipe("php -v") + ``` */ func pipe(_ command: String) async -> ShellOutput diff --git a/phpmon/Next/TestableShell.swift b/phpmon/Next/TestableShell.swift index c2b5311..eaad914 100644 --- a/phpmon/Next/TestableShell.swift +++ b/phpmon/Next/TestableShell.swift @@ -9,13 +9,15 @@ import Foundation public class TestableShell: Shellable { - public typealias Input = String + var PATH: String { + return "/usr/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin" + } - init(expectations: [Input: BatchFakeShellOutput]) { + init(expectations: [String: BatchFakeShellOutput]) { self.expectations = expectations } - var expectations: [Input: BatchFakeShellOutput] = [:] + var expectations: [String: BatchFakeShellOutput] = [:] func quiet(_ command: String) async { _ = try! await self.attach(command, didReceiveOutput: { _, _ in }, withTimeout: 60) From 0b33116eb0c3a243ff916b21cafe60a9bccc7375 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 4 Oct 2022 19:39:34 +0200 Subject: [PATCH 031/181] =?UTF-8?q?=F0=9F=8F=97=20Fake=20shell=20in=20use?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 6 ++++++ phpmon/Domain/App/AppDelegate.swift | 2 ++ phpmon/Domain/App/Startup.swift | 14 ++++++------- phpmon/Next/TestableShell.swift | 8 ++++++++ phpmon/Next/Testables.swift | 29 +++++++++++++++++++++++++++ 5 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 phpmon/Next/Testables.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 4ad3a28..d9c5848 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -59,6 +59,8 @@ C40C7F2927721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */; }; C40C7F3027722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; }; C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; }; + C40F505528ECA64E004AD45B /* Testables.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40F505428ECA64E004AD45B /* Testables.swift */; }; + C40F505628ECA64E004AD45B /* Testables.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40F505428ECA64E004AD45B /* Testables.swift */; }; C40FE737282ABA4F00A302C2 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; C40FE738282ABA4F00A302C2 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; C40FE73B282ABB2E00A302C2 /* AppVersionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE739282ABB2E00A302C2 /* AppVersionTest.swift */; }; @@ -364,6 +366,7 @@ C40C7F1D2772136000DDDCDC /* PhpEnv.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpEnv.swift; sourceTree = ""; }; C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ActivePhpInstallation+Checks.swift"; sourceTree = ""; }; C40C7F2F27722E8D00DDDCDC /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + C40F505428ECA64E004AD45B /* Testables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Testables.swift; sourceTree = ""; }; C40FE736282ABA4F00A302C2 /* AppVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersion.swift; sourceTree = ""; }; C40FE739282ABB2E00A302C2 /* AppVersionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionTest.swift; sourceTree = ""; }; C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackage.swift; sourceTree = ""; }; @@ -883,6 +886,7 @@ C46EBC4628DB9644007ACC74 /* SystemShell.swift */, C46EBC4928DB966A007ACC74 /* TestableShell.swift */, C46EBC4328DB95F0007ACC74 /* Shellable.swift */, + C40F505428ECA64E004AD45B /* Testables.swift */, ); path = Next; sourceTree = ""; @@ -1488,6 +1492,7 @@ C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */, C40508AF28ADA23D008FAC1F /* NoDomainResultsView.swift in Sources */, C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */, + C40F505528ECA64E004AD45B /* Testables.swift in Sources */, C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */, C42E3BF428A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */, C42337A3281F19F000459A48 /* Xdebug.swift in Sources */, @@ -1615,6 +1620,7 @@ C44C198E276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */, C485707828BF456300539B36 /* Warning.swift in Sources */, C415938027A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */, + C40F505628ECA64E004AD45B /* Testables.swift in Sources */, C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */, C449B4F227EE7FC400C47E8A /* DomainListPhpCell.swift in Sources */, C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */, diff --git a/phpmon/Domain/App/AppDelegate.swift b/phpmon/Domain/App/AppDelegate.swift index f817de0..14fa164 100644 --- a/phpmon/Domain/App/AppDelegate.swift +++ b/phpmon/Domain/App/AppDelegate.swift @@ -66,6 +66,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele logger.verbosity = .info #if DEBUG logger.verbosity = .performance + // TODO: Enable to fake broken setup during testing + ActiveShell.useTestable(Testables.broken) #endif if CommandLine.arguments.contains("--v") { logger.verbosity = .performance diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index 0f49448..82e306c 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -115,7 +115,7 @@ class Startup { // Make sure we can detect one or more PHP installations. // ================================================================================= EnvironmentCheck( - command: { return !LegacyShell.pipe("ls \(Paths.optPath) | grep php").contains("php") }, + command: { return await !Shell.pipe("ls \(Paths.optPath) | grep php").out.contains("php") }, name: "`ls \(Paths.optPath) | grep php` returned php result", titleText: "startup.errors.php_opt.title".localized, subtitleText: "startup.errors.php_opt.subtitle".localized( @@ -143,14 +143,14 @@ class Startup { // functioning correctly. Let the user know that they need to run `valet trust`. // ================================================================================= EnvironmentCheck( - command: { return !LegacyShell.pipe("cat /private/etc/sudoers.d/brew").contains(Paths.brew) }, + command: { return await !Shell.pipe("cat /private/etc/sudoers.d/brew").out.contains(Paths.brew) }, name: "`/private/etc/sudoers.d/brew` contains brew", titleText: "startup.errors.sudoers_brew.title".localized, subtitleText: "startup.errors.sudoers_brew.subtitle".localized, descriptionText: "startup.errors.sudoers_brew.desc".localized ), EnvironmentCheck( - command: { return !LegacyShell.pipe("cat /private/etc/sudoers.d/valet").contains(Paths.valet) }, + command: { return await !Shell.pipe("cat /private/etc/sudoers.d/valet").out.contains(Paths.valet) }, name: "`/private/etc/sudoers.d/valet` contains valet", titleText: "startup.errors.sudoers_valet.title".localized, subtitleText: "startup.errors.sudoers_valet.subtitle".localized, @@ -200,10 +200,10 @@ class Startup { // ================================================================================= EnvironmentCheck( command: { + let nodePath = await Shell.pipe("which node").out return App.architecture == "x86_64" && FileManager.default.fileExists(atPath: "/usr/local/bin/which") - && LegacyShell.pipe("which node", requiresPath: false) - .contains("env: node: No such file or directory") + && nodePath.contains("env: node: No such file or directory") }, name: "`env: node` issue does not apply", titleText: "startup.errors.which_alias_issue.title".localized, @@ -215,7 +215,7 @@ class Startup { // ================================================================================= EnvironmentCheck( command: { - return valet("--version", sudo: false) + return await Shell.pipe("valet --version").out .contains("Composer detected issues in your platform") }, name: "`no global composer issues", @@ -228,7 +228,7 @@ class Startup { // ================================================================================= EnvironmentCheck( command: { - let output = valet("--version", sudo: false) + let output = await Shell.pipe("valet --version").out // Failure condition #1: does not contain Laravel Valet if !output.contains("Laravel Valet") { return true diff --git a/phpmon/Next/TestableShell.swift b/phpmon/Next/TestableShell.swift index eaad914..d934a1a 100644 --- a/phpmon/Next/TestableShell.swift +++ b/phpmon/Next/TestableShell.swift @@ -62,6 +62,14 @@ struct FakeShellOutput { struct BatchFakeShellOutput { var items: [FakeShellOutput] + static func with(_ items: [FakeShellOutput]) -> BatchFakeShellOutput { + return BatchFakeShellOutput(items: items) + } + + static func instant(_ output: String, _ stream: ShellStream = .stdOut) -> BatchFakeShellOutput { + return BatchFakeShellOutput(items: [.instant(output, stream)]) + } + /** Outputs the fake shell output as expected. */ diff --git a/phpmon/Next/Testables.swift b/phpmon/Next/Testables.swift new file mode 100644 index 0000000..f53d357 --- /dev/null +++ b/phpmon/Next/Testables.swift @@ -0,0 +1,29 @@ +// +// Testables.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 04/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +// swiftlint:disable colon trailing_comma +class Testables { + + typealias Configuration = [String: BatchFakeShellOutput] + + // TODO: Complete broken configuration setup + static var broken: Configuration { + return [ + "php -v" : .instant(""), + "ls /opt/homebrew/opt | grep php" : .instant(""), + ] + } + + // TODO: All expected, correct Terminal responses + static var working: Configuration { + return [:] + } + +} From 1fd7db15a7f70d23b60cc0b6acb570505dc93704 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 4 Oct 2022 23:42:43 +0200 Subject: [PATCH 032/181] =?UTF-8?q?=F0=9F=8F=97=20Testable=20terminal=20ou?= =?UTF-8?q?tput?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 6 ++ .../Common/Pending Removal/LegacyShell.swift | 2 + phpmon/Domain/App/AppDelegate.swift | 8 +- phpmon/Next/TestableFilesystem.swift | 24 +++++ phpmon/Next/TestableShell.swift | 8 ++ phpmon/Next/Testables.swift | 96 ++++++++++++++++--- 6 files changed, 129 insertions(+), 15 deletions(-) create mode 100644 phpmon/Next/TestableFilesystem.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index d9c5848..936ebe3 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -211,6 +211,8 @@ C4A81CA528C67101008DD9D1 /* PMTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A81CA328C67101008DD9D1 /* PMTableView.swift */; }; C4AC51FC27E27F47008528CA /* DomainListKindCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */; }; C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; }; + C4AD38B228ECD9D300FA8D83 /* TestableFilesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFilesystem.swift */; }; + C4AD38B328ECD9D300FA8D83 /* TestableFilesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFilesystem.swift */; }; C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AF9F70275445FF00D44ED0 /* valet-config.json */; }; C4AF9F78275447F100D44ED0 /* ValetConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F76275447F100D44ED0 /* ValetConfigurationTest.swift */; }; C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F792754499000D44ED0 /* Valet.swift */; }; @@ -459,6 +461,7 @@ C4A81CA328C67101008DD9D1 /* PMTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PMTableView.swift; sourceTree = ""; }; C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListKindCell.swift; sourceTree = ""; }; C4ACA38E25C754C100060C66 /* PhpExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtension.swift; sourceTree = ""; }; + C4AD38B128ECD9D300FA8D83 /* TestableFilesystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableFilesystem.swift; sourceTree = ""; }; C4AF9F70275445FF00D44ED0 /* valet-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "valet-config.json"; sourceTree = ""; }; C4AF9F76275447F100D44ED0 /* ValetConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetConfigurationTest.swift; sourceTree = ""; }; C4AF9F792754499000D44ED0 /* Valet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Valet.swift; sourceTree = ""; }; @@ -887,6 +890,7 @@ C46EBC4928DB966A007ACC74 /* TestableShell.swift */, C46EBC4328DB95F0007ACC74 /* Shellable.swift */, C40F505428ECA64E004AD45B /* Testables.swift */, + C4AD38B128ECD9D300FA8D83 /* TestableFilesystem.swift */, ); path = Next; sourceTree = ""; @@ -1400,6 +1404,7 @@ C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */, C4E9D2C02878B336008FFDAD /* OnboardingView.swift in Sources */, C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */, + C4AD38B228ECD9D300FA8D83 /* TestableFilesystem.swift in Sources */, C4EB53E528551F9B006F9937 /* HeaderView.swift in Sources */, C40FE737282ABA4F00A302C2 /* AppVersion.swift in Sources */, C44A874828905BB000498BC4 /* ProgressVC.swift in Sources */, @@ -1588,6 +1593,7 @@ C41E871B2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */, C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */, C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */, + C4AD38B328ECD9D300FA8D83 /* TestableFilesystem.swift in Sources */, C40C5C9D2846A40600E28255 /* Preset.swift in Sources */, C4F30B09278E1A0E00755FCE /* CustomPrefs.swift in Sources */, C40FE738282ABA4F00A302C2 /* AppVersion.swift in Sources */, diff --git a/phpmon/Common/Pending Removal/LegacyShell.swift b/phpmon/Common/Pending Removal/LegacyShell.swift index 97250b5..cfc454b 100644 --- a/phpmon/Common/Pending Removal/LegacyShell.swift +++ b/phpmon/Common/Pending Removal/LegacyShell.swift @@ -121,6 +121,8 @@ public class LegacyShell { public func createTask(for command: String, requiresPath: Bool) -> Process { var completeCommand = "" + Log.info("LEGACY COMMAND: \(command)") + if requiresPath { // Basic export (PATH) completeCommand += "export PATH=\(Paths.binPath):$PATH && " diff --git a/phpmon/Domain/App/AppDelegate.swift b/phpmon/Domain/App/AppDelegate.swift index 14fa164..8348b12 100644 --- a/phpmon/Domain/App/AppDelegate.swift +++ b/phpmon/Domain/App/AppDelegate.swift @@ -65,9 +65,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele override init() { logger.verbosity = .info #if DEBUG - logger.verbosity = .performance - // TODO: Enable to fake broken setup during testing - ActiveShell.useTestable(Testables.broken) + logger.verbosity = .performance + + // TODO: Enable to fake broken setup during testing + ActiveShell.useTestable(Testables.working.shellOutput) + #endif if CommandLine.arguments.contains("--v") { logger.verbosity = .performance diff --git a/phpmon/Next/TestableFilesystem.swift b/phpmon/Next/TestableFilesystem.swift new file mode 100644 index 0000000..0999dc1 --- /dev/null +++ b/phpmon/Next/TestableFilesystem.swift @@ -0,0 +1,24 @@ +// +// TestableFilesystem.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 04/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class TestableFilesystem {} + +enum FakeFileType { + case binary, text, directory, symlink +} + +struct FakeFile { + var type: FakeFileType + var content: String? + + public static func fake(_ type: FakeFileType, _ content: String? = nil) -> FakeFile { + return FakeFile(type: type, content: content) + } +} diff --git a/phpmon/Next/TestableShell.swift b/phpmon/Next/TestableShell.swift index d934a1a..85eadd2 100644 --- a/phpmon/Next/TestableShell.swift +++ b/phpmon/Next/TestableShell.swift @@ -70,6 +70,14 @@ struct BatchFakeShellOutput { return BatchFakeShellOutput(items: [.instant(output, stream)]) } + static func delayed( + _ delay: TimeInterval, + _ output: String, + _ stream: ShellStream = .stdOut + ) -> BatchFakeShellOutput { + return BatchFakeShellOutput(items: [.delayed(delay, output, stream)]) + } + /** Outputs the fake shell output as expected. */ diff --git a/phpmon/Next/Testables.swift b/phpmon/Next/Testables.swift index f53d357..921f0aa 100644 --- a/phpmon/Next/Testables.swift +++ b/phpmon/Next/Testables.swift @@ -8,22 +8,94 @@ import Foundation +// +struct TestableConfiguration { + let architecture: String + let filesystem: [String: FakeFile] + let shellOutput: [String: BatchFakeShellOutput] +} + // swiftlint:disable colon trailing_comma class Testables { - - typealias Configuration = [String: BatchFakeShellOutput] - - // TODO: Complete broken configuration setup - static var broken: Configuration { - return [ - "php -v" : .instant(""), - "ls /opt/homebrew/opt | grep php" : .instant(""), - ] + static var broken: TestableConfiguration { + return TestableConfiguration( + architecture: "arm64", + filesystem: [:], + shellOutput: [ + "id -un" : .instant("username"), + "php -v" : .instant(""), + "ls /opt/homebrew/opt | grep php" : .instant(""), + ] + ) } // TODO: All expected, correct Terminal responses - static var working: Configuration { - return [:] + static var working: TestableConfiguration { + return TestableConfiguration( + architecture: "arm64", + filesystem: [ + "/opt/homebrew/brew" + : .fake(.binary), + "/opt/homebrew/opt/php" + : .fake(.symlink, "/opt/homebrew/Cellar/php/8.1.10_1"), + "/opt/homebrew/Cellar/php/8.1.10_1" + : .fake(.directory), + "/opt/homebrew/Cellar/php/8.1.10_1/bin/php" + : .fake(.binary), + "/opt/homebrew/Cellar/php/8.1.10_1/bin/php-config" + : .fake(.binary) + ], + shellOutput: [ + "id -un" + : .instant("username"), + "which node" + : .instant("/opt/homebrew/bin/node"), + "php -v" + : .instant(""" + PHP 8.1.10 (cli) (built: Sep 3 2022 12:09:27) (NTS) + Copyright (c) The PHP Group + Zend Engine v4.1.10, Copyright (c) Zend Technologies + with Zend OPcache v8.1.10, Copyright (c), by Zend Technologies + """), + "ls /opt/homebrew/opt | grep php" + : .instant("php"), + "sudo /opt/homebrew/bin/brew services info nginx --json" + : .delayed(0.2, """ + [ + { + "name": "nginx", + "service_name": "homebrew.mxcl.nginx", + "running": true, + "loaded": true, + "schedulable": false, + "pid": 133, + "exit_code": 0, + "user": "root", + "status": "started", + "file": "/Library/LaunchDaemons/homebrew.mxcl.nginx.plist", + "command": "/opt/homebrew/opt/nginx/bin/nginx -g daemon off;", + "working_dir": "/opt/homebrew", + "root_dir": null, + "log_path": null, + "error_log_path": null, + "interval": null, + "cron": null + } + ] + """), + "cat /private/etc/sudoers.d/brew" + : .instant(""" + Cmnd_Alias BREW = /opt/homebrew/bin/brew * + %admin ALL=(root) NOPASSWD:SETENV: BREW + """), + "cat /private/etc/sudoers.d/valet" + : .instant(""" + Cmnd_Alias VALET = /opt/homebrew/bin/valet * + %admin ALL=(root) NOPASSWD:SETENV: VALET + """), + "valet --version" + : .instant("Laravel Valet 3.1.11") + ] + ) } - } From 3267dc8addc6af4882e00ddea4e8df24dbd563bb Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 5 Oct 2022 00:38:17 +0200 Subject: [PATCH 033/181] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Use=20new=20`Shell?= =?UTF-8?q?.pipe`=20to=20replace=20legacy=20shell?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 14 +- phpmon/Common/Core/Paths.swift | 8 +- phpmon/Common/PHP/PHP Version/PhpEnv.swift | 14 +- phpmon/Domain/App/EnvironmentManager.swift | 4 +- phpmon/Domain/App/Startup.swift | 5 +- .../Homebrew/HomebrewDiagnostics.swift | 24 +- phpmon/Domain/Integrations/Valet/Valet.swift | 6 +- phpmon/Domain/Menu/MainMenu+Switcher.swift | 8 +- phpmon/Next/Testables.swift | 219 +++++++++++++++--- 9 files changed, 243 insertions(+), 59 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 936ebe3..9eed9bf 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -59,7 +59,6 @@ C40C7F2927721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */; }; C40C7F3027722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; }; C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; }; - C40F505528ECA64E004AD45B /* Testables.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40F505428ECA64E004AD45B /* Testables.swift */; }; C40F505628ECA64E004AD45B /* Testables.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40F505428ECA64E004AD45B /* Testables.swift */; }; C40FE737282ABA4F00A302C2 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; C40FE738282ABA4F00A302C2 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; @@ -213,6 +212,8 @@ C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; }; C4AD38B228ECD9D300FA8D83 /* TestableFilesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFilesystem.swift */; }; C4AD38B328ECD9D300FA8D83 /* TestableFilesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFilesystem.swift */; }; + C4AD38B528ECE2DB00FA8D83 /* brew-formula.json in Resources */ = {isa = PBXBuildFile; fileRef = C43A8A1F25D9D1D700591B77 /* brew-formula.json */; }; + C4AD38B628ECE56D00FA8D83 /* Testables.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40F505428ECA64E004AD45B /* Testables.swift */; }; C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AF9F70275445FF00D44ED0 /* valet-config.json */; }; C4AF9F78275447F100D44ED0 /* ValetConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F76275447F100D44ED0 /* ValetConfigurationTest.swift */; }; C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F792754499000D44ED0 /* Valet.swift */; }; @@ -711,6 +712,7 @@ C41C1B3522B0097F00E7CF16 /* phpmon */ = { isa = PBXGroup; children = ( + C4AD38B428ECE1E100FA8D83 /* Tests */, C46EBC3F28DB9550007ACC74 /* Next */, C4B5853A2770FE2500DA4FBE /* Common */, C41E181722CB61EB0072CF09 /* Domain */, @@ -943,6 +945,13 @@ path = "PHP Version"; sourceTree = ""; }; + C4AD38B428ECE1E100FA8D83 /* Tests */ = { + isa = PBXGroup; + children = ( + ); + path = Tests; + sourceTree = ""; + }; C4AF9F6A275445C900D44ED0 /* Valet */ = { isa = PBXGroup; children = ( @@ -1313,6 +1322,7 @@ 54FCFD26276C883F004CE748 /* SelectPreferenceView.xib in Resources */, C473319F2470923A009A0597 /* Localizable.strings in Resources */, C4068CA427B0780A00544CD5 /* CheckboxPreferenceView.xib in Resources */, + C4AD38B528ECE2DB00FA8D83 /* brew-formula.json in Resources */, 54FCFD2D276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */, C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */, ); @@ -1453,6 +1463,7 @@ C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */, C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */, C46EBC4728DB9644007ACC74 /* SystemShell.swift in Sources */, + C4AD38B628ECE56D00FA8D83 /* Testables.swift in Sources */, C4068CAA27B0890D00544CD5 /* MenuBarIcons.swift in Sources */, C44264C02850BD2A007400F1 /* VersionPopoverView.swift in Sources */, C4C8E81B276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */, @@ -1497,7 +1508,6 @@ C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */, C40508AF28ADA23D008FAC1F /* NoDomainResultsView.swift in Sources */, C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */, - C40F505528ECA64E004AD45B /* Testables.swift in Sources */, C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */, C42E3BF428A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */, C42337A3281F19F000459A48 /* Xdebug.swift in Sources */, diff --git a/phpmon/Common/Core/Paths.swift b/phpmon/Common/Core/Paths.swift index a4a5233..ac34932 100644 --- a/phpmon/Common/Core/Paths.swift +++ b/phpmon/Common/Core/Paths.swift @@ -17,11 +17,15 @@ public class Paths { internal var baseDir: Paths.HomebrewDir - private var userName: String + private var userName: String! = nil init() { baseDir = App.architecture != "x86_64" ? .opt : .usr - userName = String(LegacyShell.pipe("id -un").split(separator: "\n")[0]) + + Task { + let output = await Shell.pipe("id -un").out + userName = String(output.split(separator: "\n")[0]) + } } public func detectBinaryPaths() { diff --git a/phpmon/Common/PHP/PHP Version/PhpEnv.swift b/phpmon/Common/PHP/PHP Version/PhpEnv.swift index fcbe844..1a00fef 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnv.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnv.swift @@ -15,14 +15,16 @@ class PhpEnv { init() { self.currentInstall = ActivePhpInstallation() - let brewPhpAlias = LegacyShell.pipe("\(Paths.brew) info php --json") + Task { + let brewPhpAlias = await Shell.pipe("\(Paths.brew) info php --json").out - self.homebrewPackage = try! JSONDecoder().decode( - [HomebrewPackage].self, - from: brewPhpAlias.data(using: .utf8)! - ).first! + self.homebrewPackage = try! JSONDecoder().decode( + [HomebrewPackage].self, + from: brewPhpAlias.data(using: .utf8)! + ).first! - Log.info("When on your system, the `php` formula means version \(homebrewPackage.version)!") + Log.info("When on your system, the `php` formula means version \(homebrewPackage.version)!") + } } // MARK: - Properties diff --git a/phpmon/Domain/App/EnvironmentManager.swift b/phpmon/Domain/App/EnvironmentManager.swift index b7f05cd..effcc9d 100644 --- a/phpmon/Domain/App/EnvironmentManager.swift +++ b/phpmon/Domain/App/EnvironmentManager.swift @@ -12,8 +12,8 @@ public class EnvironmentManager { var values: [EnvironmentProperty: Bool] = [:] public func process() async { - self.values[.hasValetInstalled] = !{ - let output = valet("--version", sudo: false) + self.values[.hasValetInstalled] = await !{ + let output = await Shell.pipe("valet --version").out // Failure condition #1: does not contain Laravel Valet if !output.contains("Laravel Valet") { diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index 82e306c..5aaf2c7 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -160,7 +160,10 @@ class Startup { // Verify if the Homebrew services are running (as root). // ================================================================================= EnvironmentCheck( - command: { return HomebrewDiagnostics.cannotLoadService() }, + command: { + await HomebrewDiagnostics.loadInstalledTaps() + return await HomebrewDiagnostics.cannotLoadService("nginx") + }, name: "`sudo \(Paths.brew) services info` JSON loaded", titleText: "startup.errors.services_json_error.title".localized, subtitleText: "startup.errors.services_json_error.subtitle".localized, diff --git a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift index b77fc92..e9dc210 100644 --- a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift +++ b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift @@ -9,18 +9,23 @@ import Foundation class HomebrewDiagnostics { - /** Determines the Homebrew taps the user has installed. */ - public static var installedTaps: [String] = { - return LegacyShell + public static var installedTaps: [String] = [] + + /** + Load which taps are installed. + */ + public static func loadInstalledTaps() async { + installedTaps = await Shell .pipe("\(Paths.brew) tap") + .out .split(separator: "\n") .map { string in return String(string) } - }() + } /** Determines whether the PHP Monitor Cask is installed. @@ -131,13 +136,14 @@ class HomebrewDiagnostics { 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 { + public static func cannotLoadService(_ name: String) async -> Bool { + let nginxJson = await Shell + .pipe("sudo \(Paths.brew) services info \(name) --json") + .out + let serviceInfo = try? JSONDecoder().decode( [HomebrewService].self, - from: LegacyShell.pipe( - "sudo \(Paths.brew) services info \(name) --json", - requiresPath: true - ).data(using: .utf8)! + from: nginxJson.data(using: .utf8)! ) return serviceInfo == nil diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 6aaafea..f023662 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -183,9 +183,9 @@ class Valet { } } - public func hasPlatformIssues() -> Bool { - return valet("--version", sudo: false) - .contains("Composer detected issues in your platform") + public func hasPlatformIssues() async -> Bool { + return await Shell.pipe("valet --version") + .out.contains("Composer detected issues in your platform") } /** diff --git a/phpmon/Domain/Menu/MainMenu+Switcher.swift b/phpmon/Domain/Menu/MainMenu+Switcher.swift index 66c61d2..fc55377 100644 --- a/phpmon/Domain/Menu/MainMenu+Switcher.swift +++ b/phpmon/Domain/Menu/MainMenu+Switcher.swift @@ -53,9 +53,11 @@ extension MainMenu { } @MainActor private func checkForPlatformIssues() { - if Valet.shared.hasPlatformIssues() { - Log.info("Composer platform issue(s) detected.") - self.suggestFixMyComposer() + Task { + if await Valet.shared.hasPlatformIssues() { + Log.info("Composer platform issue(s) detected.") + self.suggestFixMyComposer() + } } } diff --git a/phpmon/Next/Testables.swift b/phpmon/Next/Testables.swift index 921f0aa..9795cce 100644 --- a/phpmon/Next/Testables.swift +++ b/phpmon/Next/Testables.swift @@ -8,7 +8,6 @@ import Foundation -// struct TestableConfiguration { let architecture: String let filesystem: [String: FakeFile] @@ -51,38 +50,11 @@ class Testables { "which node" : .instant("/opt/homebrew/bin/node"), "php -v" - : .instant(""" - PHP 8.1.10 (cli) (built: Sep 3 2022 12:09:27) (NTS) - Copyright (c) The PHP Group - Zend Engine v4.1.10, Copyright (c) Zend Technologies - with Zend OPcache v8.1.10, Copyright (c), by Zend Technologies - """), + : .instant(ShellStrings.phpVersion), "ls /opt/homebrew/opt | grep php" : .instant("php"), "sudo /opt/homebrew/bin/brew services info nginx --json" - : .delayed(0.2, """ - [ - { - "name": "nginx", - "service_name": "homebrew.mxcl.nginx", - "running": true, - "loaded": true, - "schedulable": false, - "pid": 133, - "exit_code": 0, - "user": "root", - "status": "started", - "file": "/Library/LaunchDaemons/homebrew.mxcl.nginx.plist", - "command": "/opt/homebrew/opt/nginx/bin/nginx -g daemon off;", - "working_dir": "/opt/homebrew", - "root_dir": null, - "log_path": null, - "error_log_path": null, - "interval": null, - "cron": null - } - ] - """), + : .delayed(0.2, ShellStrings.nginxJson), "cat /private/etc/sudoers.d/brew" : .instant(""" Cmnd_Alias BREW = /opt/homebrew/bin/brew * @@ -94,8 +66,193 @@ class Testables { %admin ALL=(root) NOPASSWD:SETENV: VALET """), "valet --version" - : .instant("Laravel Valet 3.1.11") + : .instant("Laravel Valet 3.1.11"), + "/opt/homebrew/bin/brew tap" + : .instant(""" + homebrew/cask + homebrew/core + homebrew/services + nicoverbruggen/cask + shivammathur/php + """), + "/opt/homebrew/bin/brew info php --json" + : .instant(ShellStrings.brewJson) ] ) } } + +struct ShellStrings { + + static let phpVersion = """ + PHP 8.1.10 (cli) (built: Sep 3 2022 12:09:27) (NTS) + Copyright (c) The PHP Group + Zend Engine v4.1.10, Copyright (c) Zend Technologies + with Zend OPcache v8.1.10, Copyright (c), by Zend Technologies + """ + + static let nginxJson = """ + [ + { + "name": "nginx", + "service_name": "homebrew.mxcl.nginx", + "running": true, + "loaded": true, + "schedulable": false, + "pid": 133, + "exit_code": 0, + "user": "root", + "status": "started", + "file": "/Library/LaunchDaemons/homebrew.mxcl.nginx.plist", + "command": "/opt/homebrew/opt/nginx/bin/nginx -g daemon off;", + "working_dir": "/opt/homebrew", + "root_dir": null, + "log_path": null, + "error_log_path": null, + "interval": null, + "cron": null + } + ] + """ + + static let brewJson = """ + [ + { + "name":"php", + "full_name":"php", + "tap":"homebrew/core", + "oldname":null, + "aliases":[ + "php@8.0" + ], + "versioned_formulae":[ + "php@7.4", + "php@7.3", + "php@7.2" + ], + "desc":"General-purpose scripting language", + "license":"PHP-3.01", + "homepage":"https://www.php.net/", + "versions":{ + "stable":"8.0.2", + "head":"HEAD", + "bottle":true + }, + "urls":{ + "stable":{ + "url":"https://www.php.net/distributions/php-8.0.2.tar.xz", + "tag":null, + "revision":null + } + }, + "revision":0, + "version_scheme":0, + "bottle":{ + "stable":{ + "rebuild":0, + "cellar":"/opt/homebrew/Cellar", + "prefix":"/opt/homebrew", + "root_url":"https://homebrew.bintray.com/bottles", + "files":{ + "arm64_big_sur":{ + "url":"https://homebrew.bintray.com/bottles/php-8.0.2.arm64_big_sur.bottle.tar.gz", + "sha256":"cbefa1db73d08b9af4593a44512b8d727e43033ee8517736bae5f16315501b12" + }, + "big_sur":{ + "url":"https://homebrew.bintray.com/bottles/php-8.0.2.big_sur.bottle.tar.gz", + "sha256":"6857142e12254b15da4e74c2986dd24faca57dac8d467b04621db349e277dd63" + }, + "catalina":{ + "url":"https://homebrew.bintray.com/bottles/php-8.0.2.catalina.bottle.tar.gz", + "sha256":"b651611134c18f93fdf121a4277b51b197a896a19ccb8020289b4e19e0638349" + }, + "mojave":{ + "url":"https://homebrew.bintray.com/bottles/php-8.0.2.mojave.bottle.tar.gz", + "sha256":"9583a51fcc6f804aadbb14e18f770d4fb4973deaed6ddc4770342e62974ffbca" + } + } + } + }, + "keg_only":false, + "bottle_disabled":false, + "options":[ + + ], + "build_dependencies":[ + "httpd", + "pkg-config" + ], + "dependencies":[ + "apr", + "apr-util", + "argon2", + "aspell", + "autoconf", + "curl", + "freetds", + "gd", + "gettext", + "glib", + "gmp", + "icu4c", + "krb5", + "libffi", + "libpq", + "libsodium", + "libzip", + "oniguruma", + "openldap", + "openssl@1.1", + "pcre2", + "sqlite", + "tidy-html5", + "unixodbc" + ], + "recommended_dependencies":[ + + ], + "optional_dependencies":[ + + ], + "uses_from_macos":[ + { + "xz":"build" + }, + "bzip2", + "libedit", + "libxml2", + "libxslt", + "zlib" + ], + "requirements":[ + + ], + "conflicts_with":[ + + ], + "installed":[ + { + "version": "8.1.10_1", + "used_options": [ + + ], + "built_as_bottle": true, + "poured_from_bottle": true, + "runtime_dependencies": [], + "installed_as_dependency": false, + "installed_on_request": true + } + ], + "linked_keg":"8.0.2", + "pinned":false, + "outdated":false, + "deprecated":false, + "deprecation_date":null, + "deprecation_reason":null, + "disabled":false, + "disable_date":null, + "disable_reason":null + } + ] + """ +} From fb1ca60240ed03f945e55416bb5e3b470c542b97 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 5 Oct 2022 01:06:52 +0200 Subject: [PATCH 034/181] =?UTF-8?q?=E2=9C=85=20Fix=20tests,=20new=20shell?= =?UTF-8?q?=20in=20various=20places?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Parsers/HomebrewPackageTest.swift | 4 +- .../Test Files/brew/brew-formula.json | 411 ++++++++++-------- phpmon/Common/PHP/PHP Version/PhpEnv.swift | 10 +- phpmon/Domain/App/Startup.swift | 4 +- .../Homebrew/HomebrewDiagnostics.swift | 10 +- phpmon/Domain/Menu/MainMenu+Startup.swift | 3 + phpmon/Next/Testables.swift | 152 +------ 7 files changed, 268 insertions(+), 326 deletions(-) diff --git a/phpmon-tests/Parsers/HomebrewPackageTest.swift b/phpmon-tests/Parsers/HomebrewPackageTest.swift index 0f9644a..5bd30a2 100644 --- a/phpmon-tests/Parsers/HomebrewPackageTest.swift +++ b/phpmon-tests/Parsers/HomebrewPackageTest.swift @@ -25,9 +25,9 @@ class HomebrewPackageTest: XCTestCase { XCTAssertEqual(package.name, "php") XCTAssertEqual(package.full_name, "php") - XCTAssertEqual(package.aliases.first!, "php@8.0") + XCTAssertEqual(package.aliases.first!, "php@8.1") XCTAssertEqual(package.installed.contains(where: { installed in - installed.version.starts(with: "8.0") + installed.version.starts(with: "8.1") }), true) } diff --git a/phpmon-tests/Test Files/brew/brew-formula.json b/phpmon-tests/Test Files/brew/brew-formula.json index d9499e4..1029156 100644 --- a/phpmon-tests/Test Files/brew/brew-formula.json +++ b/phpmon-tests/Test Files/brew/brew-formula.json @@ -1,70 +1,83 @@ [ { - "name":"php", - "full_name":"php", - "tap":"homebrew/core", - "oldname":null, - "aliases":[ - "php@8.0" + "name": "php", + "full_name": "php", + "tap": "homebrew/core", + "oldname": null, + "aliases": [ + "php@8.1" ], - "versioned_formulae":[ + "versioned_formulae": [ + "php@8.0", "php@7.4", "php@7.3", "php@7.2" ], - "desc":"General-purpose scripting language", - "license":"PHP-3.01", - "homepage":"https://www.php.net/", - "versions":{ - "stable":"8.0.2", - "head":"HEAD", - "bottle":true + "desc": "General-purpose scripting language", + "license": "PHP-3.01", + "homepage": "https://www.php.net/", + "versions": { + "stable": "8.1.10", + "head": "HEAD", + "bottle": true }, - "urls":{ - "stable":{ - "url":"https://www.php.net/distributions/php-8.0.2.tar.xz", - "tag":null, - "revision":null + "urls": { + "stable": { + "url": "https://www.php.net/distributions/php-8.1.10.tar.xz", + "tag": null, + "revision": null } }, - "revision":0, - "version_scheme":0, - "bottle":{ - "stable":{ - "rebuild":0, - "cellar":"/opt/homebrew/Cellar", - "prefix":"/opt/homebrew", - "root_url":"https://homebrew.bintray.com/bottles", - "files":{ - "arm64_big_sur":{ - "url":"https://homebrew.bintray.com/bottles/php-8.0.2.arm64_big_sur.bottle.tar.gz", - "sha256":"cbefa1db73d08b9af4593a44512b8d727e43033ee8517736bae5f16315501b12" + "revision": 1, + "version_scheme": 0, + "bottle": { + "stable": { + "rebuild": 0, + "root_url": "https://ghcr.io/v2/homebrew/core", + "files": { + "arm64_monterey": { + "cellar": "/opt/homebrew/Cellar", + "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:dcee33c9f445db3026a7e867805eb8f6d82e9e5599599b8c6cd8645475f7961c", + "sha256": "dcee33c9f445db3026a7e867805eb8f6d82e9e5599599b8c6cd8645475f7961c" }, - "big_sur":{ - "url":"https://homebrew.bintray.com/bottles/php-8.0.2.big_sur.bottle.tar.gz", - "sha256":"6857142e12254b15da4e74c2986dd24faca57dac8d467b04621db349e277dd63" + "arm64_big_sur": { + "cellar": "/opt/homebrew/Cellar", + "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:e0590064cd32f2baa4102fa49c80056f3886a0a89aec0589d0134ecbf0e7923e", + "sha256": "e0590064cd32f2baa4102fa49c80056f3886a0a89aec0589d0134ecbf0e7923e" }, - "catalina":{ - "url":"https://homebrew.bintray.com/bottles/php-8.0.2.catalina.bottle.tar.gz", - "sha256":"b651611134c18f93fdf121a4277b51b197a896a19ccb8020289b4e19e0638349" + "monterey": { + "cellar": "/usr/local/Cellar", + "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:62481320613b19c6ff310bf6ed50c7d2a2253cdbf403af12ec97bccd8a97a84c", + "sha256": "62481320613b19c6ff310bf6ed50c7d2a2253cdbf403af12ec97bccd8a97a84c" }, - "mojave":{ - "url":"https://homebrew.bintray.com/bottles/php-8.0.2.mojave.bottle.tar.gz", - "sha256":"9583a51fcc6f804aadbb14e18f770d4fb4973deaed6ddc4770342e62974ffbca" + "big_sur": { + "cellar": "/usr/local/Cellar", + "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:b34d96f7aad3c580a7cbdaadb8054fb9b6872111a5eec8e1bcb4a529970c8e03", + "sha256": "b34d96f7aad3c580a7cbdaadb8054fb9b6872111a5eec8e1bcb4a529970c8e03" + }, + "catalina": { + "cellar": "/usr/local/Cellar", + "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:cc0b85dcfdd60e1d8d7fa74c9f53be5d249d068835dbc7a81edacb7a076b6c76", + "sha256": "cc0b85dcfdd60e1d8d7fa74c9f53be5d249d068835dbc7a81edacb7a076b6c76" + }, + "x86_64_linux": { + "cellar": "/home/linuxbrew/.linuxbrew/Cellar", + "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:b934a5a4ad2d29b629f83962b57f638a654801d1ba21ba659a42da2e5afe3fae", + "sha256": "b934a5a4ad2d29b629f83962b57f638a654801d1ba21ba659a42da2e5afe3fae" } } } }, - "keg_only":false, - "bottle_disabled":false, - "options":[ - + "keg_only": false, + "keg_only_reason": null, + "options": [ + ], - "build_dependencies":[ + "build_dependencies": [ "httpd", "pkg-config" ], - "dependencies":[ + "dependencies": [ "apr", "apr-util", "argon2", @@ -74,11 +87,9 @@ "freetds", "gd", "gettext", - "glib", "gmp", "icu4c", "krb5", - "libffi", "libpq", "libsodium", "libzip", @@ -90,243 +101,295 @@ "tidy-html5", "unixodbc" ], - "recommended_dependencies":[ - + "test_dependencies": [ + "httpd" ], - "optional_dependencies":[ - + "recommended_dependencies": [ + ], - "uses_from_macos":[ + "optional_dependencies": [ + + ], + "uses_from_macos": [ { - "xz":"build" + "xz": "build" }, "bzip2", "libedit", + "libffi", "libxml2", "libxslt", "zlib" ], - "requirements":[ - + "requirements": [ + ], - "conflicts_with":[ - + "conflicts_with": [ + ], - "caveats":"To enable PHP in Apache add the following to httpd.conf and restart Apache:\n LoadModule php_module $(brew --prefix)/opt/php/lib/httpd/modules/libphp.so\n\n \n SetHandler application/x-httpd-php\n \n\nFinally, check DirectoryIndex includes index.php\n DirectoryIndex index.php index.html\n\nThe php.ini and php-fpm.ini file can be found in:\n $(brew --prefix)/etc/php/8.0/\n", - "installed":[ + "caveats": "To enable PHP in Apache add the following to httpd.conf and restart Apache:\n LoadModule php_module $(brew --prefix)/opt/php/lib/httpd/modules/libphp.so\n\n \n SetHandler application/x-httpd-php\n \n\nFinally, check DirectoryIndex includes index.php\n DirectoryIndex index.php index.html\n\nThe php.ini and php-fpm.ini file can be found in:\n $(brew --prefix)/etc/php/8.1/\n", + "installed": [ { - "version":"8.0.2", - "used_options":[ - + "version": "8.1.10_1", + "used_options": [ + ], - "built_as_bottle":true, - "poured_from_bottle":true, - "runtime_dependencies":[ + "built_as_bottle": true, + "poured_from_bottle": true, + "runtime_dependencies": [ { - "full_name":"apr", - "version":"1.7.0" + "full_name": "apr", + "version": "1.7.0", + "declared_directly": true }, { - "full_name":"openssl@1.1", - "version":"1.1.1i" + "full_name": "ca-certificates", + "version": "2022-07-19", + "declared_directly": false }, { - "full_name":"apr-util", - "version":"1.6.1" + "full_name": "openssl@1.1", + "version": "1.1.1q", + "declared_directly": true }, { - "full_name":"argon2", - "version":"20190702" + "full_name": "apr-util", + "version": "1.6.1", + "declared_directly": true }, { - "full_name":"aspell", - "version":"0.60.8" + "full_name": "argon2", + "version": "20190702", + "declared_directly": true }, { - "full_name":"autoconf", - "version":"2.69" + "full_name": "aspell", + "version": "0.60.8", + "declared_directly": true }, { - "full_name":"brotli", - "version":"1.0.9" + "full_name": "m4", + "version": "1.4.19", + "declared_directly": false }, { - "full_name":"gettext", - "version":"0.21" + "full_name": "autoconf", + "version": "2.71", + "declared_directly": true }, { - "full_name":"libunistring", - "version":"0.9.10" + "full_name": "brotli", + "version": "1.0.9", + "declared_directly": false }, { - "full_name":"libidn2", - "version":"2.3.0" + "full_name": "gettext", + "version": "0.21", + "declared_directly": true }, { - "full_name":"libmetalink", - "version":"0.1.3" + "full_name": "libunistring", + "version": "1.0", + "declared_directly": false }, { - "full_name":"libssh2", - "version":"1.9.0" + "full_name": "libidn2", + "version": "2.3.3", + "declared_directly": false }, { - "full_name":"c-ares", - "version":"1.17.1" + "full_name": "libnghttp2", + "version": "1.49.0", + "declared_directly": false }, { - "full_name":"jemalloc", - "version":"5.2.1" + "full_name": "libssh2", + "version": "1.10.0", + "declared_directly": false }, { - "full_name":"libev", - "version":"4.33" + "full_name": "openldap", + "version": "2.6.3", + "declared_directly": true }, { - "full_name":"nghttp2", - "version":"1.43.0" + "full_name": "rtmpdump", + "version": "2.4+20151223", + "declared_directly": false }, { - "full_name":"openldap", - "version":"2.4.57" + "full_name": "lz4", + "version": "1.9.4", + "declared_directly": false }, { - "full_name":"rtmpdump", - "version":"2.4+20151223" + "full_name": "xz", + "version": "5.2.6", + "declared_directly": false }, { - "full_name":"zstd", - "version":"1.4.8" + "full_name": "zstd", + "version": "1.5.2", + "declared_directly": false }, { - "full_name":"curl", - "version":"7.75.0" + "full_name": "curl", + "version": "7.85.0", + "declared_directly": true }, { - "full_name":"libtool", - "version":"2.4.6" + "full_name": "libtool", + "version": "2.4.7", + "declared_directly": false }, { - "full_name":"unixodbc", - "version":"2.3.9" + "full_name": "unixodbc", + "version": "2.3.11", + "declared_directly": true }, { - "full_name":"freetds", - "version":"1.2.18" + "full_name": "freetds", + "version": "1.3.13", + "declared_directly": true }, { - "full_name":"libpng", - "version":"1.6.37" + "full_name": "libpng", + "version": "1.6.37", + "declared_directly": false }, { - "full_name":"freetype", - "version":"2.10.4" + "full_name": "freetype", + "version": "2.12.1", + "declared_directly": false }, { - "full_name":"fontconfig", - "version":"2.13.1" + "full_name": "fontconfig", + "version": "2.14.0", + "declared_directly": false }, { - "full_name":"jpeg", - "version":"9d" + "full_name": "jpeg-turbo", + "version": "2.1.4", + "declared_directly": false }, { - "full_name":"libtiff", - "version":"4.2.0" + "full_name": "giflib", + "version": "5.2.1", + "declared_directly": false }, { - "full_name":"webp", - "version":"1.2.0" + "full_name": "imath", + "version": "3.1.5", + "declared_directly": false }, { - "full_name":"gd", - "version":"2.3.1" + "full_name": "openexr", + "version": "3.1.5", + "declared_directly": false }, { - "full_name":"libffi", - "version":"3.3" + "full_name": "libtiff", + "version": "4.4.0", + "declared_directly": false }, { - "full_name":"pcre", - "version":"8.44" + "full_name": "webp", + "version": "1.2.4", + "declared_directly": false }, { - "full_name":"gdbm", - "version":"1.18.1" + "full_name": "jpeg-xl", + "version": "0.6.1", + "declared_directly": false }, { - "full_name":"readline", - "version":"8.1" + "full_name": "libvmaf", + "version": "2.3.1", + "declared_directly": false }, { - "full_name":"sqlite", - "version":"3.34.0" + "full_name": "aom", + "version": "3.4.0", + "declared_directly": false }, { - "full_name":"tcl-tk", - "version":"8.6.11" + "full_name": "libavif", + "version": "0.10.1", + "declared_directly": false }, { - "full_name":"xz", - "version":"5.2.5" + "full_name": "gd", + "version": "2.3.3", + "declared_directly": true }, { - "full_name":"python@3.9", - "version":"3.9.1" + "full_name": "gmp", + "version": "6.2.1", + "declared_directly": true }, { - "full_name":"glib", - "version":"2.66.6" + "full_name": "icu4c", + "version": "71.1", + "declared_directly": true }, { - "full_name":"gmp", - "version":"6.2.1" + "full_name": "krb5", + "version": "1.20", + "declared_directly": true }, { - "full_name":"icu4c", - "version":"67.1" + "full_name": "libpq", + "version": "14.5", + "declared_directly": true }, { - "full_name":"krb5", - "version":"1.19" + "full_name": "libsodium", + "version": "1.0.18", + "declared_directly": true }, { - "full_name":"libpq", - "version":"13.1" + "full_name": "libzip", + "version": "1.9.2", + "declared_directly": true }, { - "full_name":"libsodium", - "version":"1.0.18" + "full_name": "oniguruma", + "version": "6.9.8", + "declared_directly": true }, { - "full_name":"libzip", - "version":"1.7.3" + "full_name": "pcre2", + "version": "10.40", + "declared_directly": true }, { - "full_name":"oniguruma", - "version":"6.9.6" + "full_name": "readline", + "version": "8.1.2", + "declared_directly": false }, { - "full_name":"pcre2", - "version":"10.36" + "full_name": "sqlite", + "version": "3.39.2", + "declared_directly": true }, { - "full_name":"tidy-html5", - "version":"5.6.0" + "full_name": "tidy-html5", + "version": "5.8.0", + "declared_directly": true } ], - "installed_as_dependency":false, - "installed_on_request":true + "installed_as_dependency": false, + "installed_on_request": true } ], - "linked_keg":"8.0.2", - "pinned":false, - "outdated":false, - "deprecated":false, - "deprecation_date":null, - "deprecation_reason":null, - "disabled":false, - "disable_date":null, - "disable_reason":null + "linked_keg": "8.1.10_1", + "pinned": false, + "outdated": false, + "deprecated": false, + "deprecation_date": null, + "deprecation_reason": null, + "disabled": false, + "disable_date": null, + "disable_reason": null } -] + ] diff --git a/phpmon/Common/PHP/PHP Version/PhpEnv.swift b/phpmon/Common/PHP/PHP Version/PhpEnv.swift index 1a00fef..8eb7be3 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnv.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnv.swift @@ -14,7 +14,9 @@ class PhpEnv { init() { self.currentInstall = ActivePhpInstallation() + } + func determinePhpAlias() { Task { let brewPhpAlias = await Shell.pipe("\(Paths.brew) info php --json").out @@ -23,7 +25,7 @@ class PhpEnv { from: brewPhpAlias.data(using: .utf8)! ).first! - Log.info("When on your system, the `php` formula means version \(homebrewPackage.version)!") + Log.info("[BREW] On your system, the `php` formula means version \(homebrewPackage.version)!") } } @@ -79,14 +81,14 @@ class PhpEnv { } public static func detectPhpVersions() { - _ = Self.shared.detectPhpVersions() + Task { await Self.shared.detectPhpVersions() } } /** Detects which versions of PHP are installed. */ - public func detectPhpVersions() -> [String] { - let files = LegacyShell.pipe("ls \(Paths.optPath) | grep php@") + public func detectPhpVersions() async -> [String] { + let files = await Shell.pipe("ls \(Paths.optPath) | grep php@").out var versionsOnly = extractPhpVersions(from: files.components(separatedBy: "\n")) diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index 5aaf2c7..c6b2b03 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -115,7 +115,9 @@ class Startup { // Make sure we can detect one or more PHP installations. // ================================================================================= EnvironmentCheck( - command: { return await !Shell.pipe("ls \(Paths.optPath) | grep php").out.contains("php") }, + command: { + return await !Shell.pipe("ls \(Paths.optPath) | grep php").out.contains("php") + }, name: "`ls \(Paths.optPath) | grep php` returned php result", titleText: "startup.errors.php_opt.title".localized, subtitleText: "startup.errors.php_opt.subtitle".localized( diff --git a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift index e9dc210..39ac784 100644 --- a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift +++ b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift @@ -42,8 +42,10 @@ class HomebrewDiagnostics { This check only needs to be performed if the `shivammathur/php` tap is active. */ public static func checkForCaskConflict() { - if hasAliasConflict() { - presentAlertAboutConflict() + Task { + if await hasAliasConflict() { + presentAlertAboutConflict() + } } } @@ -79,8 +81,8 @@ class HomebrewDiagnostics { /** Check if the alias conflict as documented in `checkForCaskConflict` actually occurred. */ - private static func hasAliasConflict() -> Bool { - let tapAlias = LegacyShell.pipe("\(Paths.brew) info shivammathur/php/php --json") + private static func hasAliasConflict() async -> Bool { + let tapAlias = await Shell.pipe("brew info shivammathur/php/php --json").out if tapAlias.contains("brew tap shivammathur/php") || tapAlias.contains("Error") { Log.info("The user does not appear to have tapped: shivammathur/php") diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 728622d..efb89a2 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -31,6 +31,9 @@ extension MainMenu { When the environment is all clear and the app can run, let's go. */ private func onEnvironmentPass() { + // Determine what the `php` formula is aliased to + PhpEnv.shared.determinePhpAlias() + // Determine install method Log.info(HomebrewDiagnostics.customCaskInstalled ? "The app has probably been installed via Homebrew Cask." diff --git a/phpmon/Next/Testables.swift b/phpmon/Next/Testables.swift index 9795cce..982ca7d 100644 --- a/phpmon/Next/Testables.swift +++ b/phpmon/Next/Testables.swift @@ -53,6 +53,8 @@ class Testables { : .instant(ShellStrings.phpVersion), "ls /opt/homebrew/opt | grep php" : .instant("php"), + "ls /opt/homebrew/opt | grep php@" + : .instant("php@8.1"), "sudo /opt/homebrew/bin/brew services info nginx --json" : .delayed(0.2, ShellStrings.nginxJson), "cat /private/etc/sudoers.d/brew" @@ -76,7 +78,9 @@ class Testables { shivammathur/php """), "/opt/homebrew/bin/brew info php --json" - : .instant(ShellStrings.brewJson) + : .instant(ShellStrings.brewJson), + "brew info shivammathur/php/php --json" + : .instant("Error: No available formula with the name \"shivammathur/php/php\".") ] ) } @@ -115,144 +119,10 @@ struct ShellStrings { ] """ - static let brewJson = """ - [ - { - "name":"php", - "full_name":"php", - "tap":"homebrew/core", - "oldname":null, - "aliases":[ - "php@8.0" - ], - "versioned_formulae":[ - "php@7.4", - "php@7.3", - "php@7.2" - ], - "desc":"General-purpose scripting language", - "license":"PHP-3.01", - "homepage":"https://www.php.net/", - "versions":{ - "stable":"8.0.2", - "head":"HEAD", - "bottle":true - }, - "urls":{ - "stable":{ - "url":"https://www.php.net/distributions/php-8.0.2.tar.xz", - "tag":null, - "revision":null - } - }, - "revision":0, - "version_scheme":0, - "bottle":{ - "stable":{ - "rebuild":0, - "cellar":"/opt/homebrew/Cellar", - "prefix":"/opt/homebrew", - "root_url":"https://homebrew.bintray.com/bottles", - "files":{ - "arm64_big_sur":{ - "url":"https://homebrew.bintray.com/bottles/php-8.0.2.arm64_big_sur.bottle.tar.gz", - "sha256":"cbefa1db73d08b9af4593a44512b8d727e43033ee8517736bae5f16315501b12" - }, - "big_sur":{ - "url":"https://homebrew.bintray.com/bottles/php-8.0.2.big_sur.bottle.tar.gz", - "sha256":"6857142e12254b15da4e74c2986dd24faca57dac8d467b04621db349e277dd63" - }, - "catalina":{ - "url":"https://homebrew.bintray.com/bottles/php-8.0.2.catalina.bottle.tar.gz", - "sha256":"b651611134c18f93fdf121a4277b51b197a896a19ccb8020289b4e19e0638349" - }, - "mojave":{ - "url":"https://homebrew.bintray.com/bottles/php-8.0.2.mojave.bottle.tar.gz", - "sha256":"9583a51fcc6f804aadbb14e18f770d4fb4973deaed6ddc4770342e62974ffbca" - } - } - } - }, - "keg_only":false, - "bottle_disabled":false, - "options":[ - - ], - "build_dependencies":[ - "httpd", - "pkg-config" - ], - "dependencies":[ - "apr", - "apr-util", - "argon2", - "aspell", - "autoconf", - "curl", - "freetds", - "gd", - "gettext", - "glib", - "gmp", - "icu4c", - "krb5", - "libffi", - "libpq", - "libsodium", - "libzip", - "oniguruma", - "openldap", - "openssl@1.1", - "pcre2", - "sqlite", - "tidy-html5", - "unixodbc" - ], - "recommended_dependencies":[ - - ], - "optional_dependencies":[ - - ], - "uses_from_macos":[ - { - "xz":"build" - }, - "bzip2", - "libedit", - "libxml2", - "libxslt", - "zlib" - ], - "requirements":[ - - ], - "conflicts_with":[ - - ], - "installed":[ - { - "version": "8.1.10_1", - "used_options": [ - - ], - "built_as_bottle": true, - "poured_from_bottle": true, - "runtime_dependencies": [], - "installed_as_dependency": false, - "installed_on_request": true - } - ], - "linked_keg":"8.0.2", - "pinned":false, - "outdated":false, - "deprecated":false, - "deprecation_date":null, - "deprecation_reason":null, - "disabled":false, - "disable_date":null, - "disable_reason":null - } - ] - """ + static let brewJson: String = { + return try! String(contentsOf: Bundle.main.url( + forResource: "brew-formula", + withExtension: "json" + )!, encoding: .utf8) + }() } From 108ae05c1d991b7001fd22166b178bae8d088ce0 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 5 Oct 2022 22:38:54 +0200 Subject: [PATCH 035/181] =?UTF-8?q?=F0=9F=8F=97=20Use=20new=20shell=20when?= =?UTF-8?q?=20parsing=20apps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Helpers/Application.swift | 37 +++++++++++++------ phpmon/Common/PHP/PHP Version/PhpHelper.swift | 4 +- .../DomainList/DomainListVC+Actions.swift | 4 +- phpmon/Domain/Menu/MainMenu+Startup.swift | 31 ++++++++++------ phpmon/Next/Testables.swift | 14 ++++++- 5 files changed, 62 insertions(+), 28 deletions(-) diff --git a/phpmon/Common/Helpers/Application.swift b/phpmon/Common/Helpers/Application.swift index ae50d34..0f1b1fe 100644 --- a/phpmon/Common/Helpers/Application.swift +++ b/phpmon/Common/Helpers/Application.swift @@ -33,31 +33,46 @@ class Application { Attempt to open a specific directory in the app of choice. (This will open the app if it isn't open yet.) */ - @objc public func openDirectory(file: String) { - return LegacyShell.run("/usr/bin/open -a \"\(name)\" \"\(file)\"") + @objc public func openDirectory(file: String) async { + return await Shell.quiet("/usr/bin/open -a \"\(name)\" \"\(file)\"") } /** Checks if the app is installed. */ - func isInstalled() -> Bool { - // If this script does not complain, the app exists! - return LegacyShell.user.executeSynchronously( + func isInstalled() async -> Bool { + + let (process, output) = try! await Shell.attach( "/usr/bin/open -Ra \"\(name)\"", - requiresPath: false - ).task.terminationStatus == 0 + didReceiveOutput: { _, _ in }, + withTimeout: 2.0 + ) + + if Shell is TestableShell { + // When testing, check the error output (must not be empty) + return !output.hasError + } else { + // If this script does not complain, the app exists! + return process.terminationStatus == 0 + } } /** Detect which apps are available to open a specific directory. */ - static public func detectPresetApplications() -> [Application] { - return [ + static public func detectPresetApplications() async -> [Application] { + var detected: [Application] = [] + + let detectable = [ Application("PhpStorm", .editor), Application("Visual Studio Code", .editor), Application("Sublime Text", .editor), Application("Sublime Merge", .git_gui), Application("iTerm", .terminal) - ].filter { - return $0.isInstalled() + ] + + for app in detectable where await app.isInstalled() { + detected.append(app) } + + return detected } } diff --git a/phpmon/Common/PHP/PHP Version/PhpHelper.swift b/phpmon/Common/PHP/PHP Version/PhpHelper.swift index d40736a..1165fa1 100644 --- a/phpmon/Common/PHP/PHP Version/PhpHelper.swift +++ b/phpmon/Common/PHP/PHP Version/PhpHelper.swift @@ -26,9 +26,9 @@ class PhpHelper { let canWriteSymlinks = FileManager.default.isWritableFile(atPath: "/usr/local/bin/") do { - LegacyShell.run("mkdir -p ~/.config/phpmon/bin") + Task { await Shell.quiet("mkdir -p ~/.config/phpmon/bin") } - if FileManager.default.fileExists(atPath: destination) { + if Filesystem.fileExists(destination) { let contents = try String(contentsOfFile: destination) if !contents.contains(keyPhrase) { Log.info("The file at '\(destination)' already exists and was not generated by PHP Monitor " diff --git a/phpmon/Domain/DomainList/DomainListVC+Actions.swift b/phpmon/Domain/DomainList/DomainListVC+Actions.swift index d90a491..23f17ed 100644 --- a/phpmon/Domain/DomainList/DomainListVC+Actions.swift +++ b/phpmon/Domain/DomainList/DomainListVC+Actions.swift @@ -107,9 +107,9 @@ extension DomainListVC { LegacyShell.run("open -b com.apple.terminal '\(selectedSite!.absolutePath)'") } - @objc func openWithEditor(sender: EditorMenuItem) { + @objc func openWithEditor(sender: EditorMenuItem) async { guard let editor = sender.editor else { return } - editor.openDirectory(file: selectedSite!.absolutePath) + await editor.openDirectory(file: selectedSite!.absolutePath) } @objc func isolateSite(sender: PhpMenuItem) { diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index efb89a2..901f14e 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -161,22 +161,29 @@ extension MainMenu { Detect which applications are installed that can be used to open a domain's source directory. */ private func detectApplications() { - Log.info("Detecting applications...") + Task { + Log.info("Detecting applications...") - App.shared.detectedApplications = Application.detectPresetApplications() + App.shared.detectedApplications = await Application.detectPresetApplications() - let customApps = Preferences.custom.scanApps?.map { appName in - return Application(appName, .user_supplied) - }.filter { app in - return app.isInstalled() - } ?? [] + let customApps = Preferences.custom.scanApps?.map { appName in + return Application(appName, .user_supplied) + } ?? [] - App.shared.detectedApplications.append(contentsOf: customApps) + var detectedCustomApps: [Application] = [] - let appNames = App.shared.detectedApplications.map { app in - return app.name + for app in customApps where await app.isInstalled() { + detectedCustomApps.append(app) + } + + App.shared.detectedApplications + .append(contentsOf: detectedCustomApps) + + let appNames = App.shared.detectedApplications.map { app in + return app.name + } + + Log.info("Detected applications: \(appNames)") } - - Log.info("Detected applications: \(appNames)") } } diff --git a/phpmon/Next/Testables.swift b/phpmon/Next/Testables.swift index 982ca7d..efa800e 100644 --- a/phpmon/Next/Testables.swift +++ b/phpmon/Next/Testables.swift @@ -77,10 +77,22 @@ class Testables { nicoverbruggen/cask shivammathur/php """), + "mkdir -p ~/.config/phpmon" + : .instant(""), "/opt/homebrew/bin/brew info php --json" : .instant(ShellStrings.brewJson), "brew info shivammathur/php/php --json" - : .instant("Error: No available formula with the name \"shivammathur/php/php\".") + : .instant("Error: No available formula with the name \"shivammathur/php/php\"."), + "/usr/bin/open -Ra \"PhpStorm\"" + : .instant("Unable to find application named 'PhpStorm'", .stdErr), + "/usr/bin/open -Ra \"Visual Studio Code\"" + : .instant("Unable to find application named 'Visual Studio Code'", .stdErr), + "/usr/bin/open -Ra \"Sublime Text\"" + : .instant("Unable to find application named 'Sublime Text'", .stdErr), + "/usr/bin/open -Ra \"Sublime Merge\"" + : .instant("Unable to find application named 'Sublime Merge'", .stdErr), + "/usr/bin/open -Ra \"iTerm\"" + : .instant("Unable to find application named 'iTerm'", .stdErr) ] ) } From ad41e3891e19e41e3442d5168ec8a716d94fbf6a Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 5 Oct 2022 22:39:36 +0200 Subject: [PATCH 036/181] =?UTF-8?q?=F0=9F=8F=97=20Mark=20old=20Shell=20as?= =?UTF-8?q?=20deprecated=20from=20now=20on?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Pending Removal/LegacyShell.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpmon/Common/Pending Removal/LegacyShell.swift b/phpmon/Common/Pending Removal/LegacyShell.swift index cfc454b..f364d76 100644 --- a/phpmon/Common/Pending Removal/LegacyShell.swift +++ b/phpmon/Common/Pending Removal/LegacyShell.swift @@ -8,7 +8,7 @@ import Cocoa // TODO: Enable this to see where deprecations and replacements are needed. -// @available(*, deprecated, message: "Use the new replacement `Shell` instead") +@available(*, deprecated, message: "Use the new replacement `Shell` instead") public class LegacyShell { // MARK: - Invoke static functions From e2ab7f08edcc787bed69173205c4886ddd1316cb Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 6 Oct 2022 22:55:05 +0200 Subject: [PATCH 037/181] =?UTF-8?q?=F0=9F=8F=97=20Remove=20LegacyShell=20e?= =?UTF-8?q?ntirely?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 20 -- phpmon/Common/Core/Actions.swift | 64 +++---- phpmon/Common/Core/Helpers.swift | 20 +- phpmon/Common/Core/Paths.swift | 8 +- phpmon/Common/PHP/ActivePhpInstallation.swift | 5 +- phpmon/Common/PHP/PHP Version/PhpEnv.swift | 28 +-- phpmon/Common/PHP/PHP Version/PhpHelper.swift | 108 +++++------ phpmon/Common/PHP/PhpExtension.swift | 4 +- .../PHP/Switcher/InternalSwitcher.swift | 46 ++--- .../Pending Removal/LegacyShell+PATH.swift | 33 ---- .../Common/Pending Removal/LegacyShell.swift | 175 ------------------ .../Domain/App/AppDelegate+MenuOutlets.swift | 18 +- phpmon/Domain/App/AppDelegate.swift | 16 +- phpmon/Domain/App/AppUpdateChecker.swift | 10 +- phpmon/Domain/App/InterAppHandler.swift | 8 +- phpmon/Domain/App/ServicesManager.swift | 53 +++--- phpmon/Domain/DomainList/AddProxyVC.swift | 6 +- phpmon/Domain/DomainList/AddSiteVC.swift | 8 +- .../DomainList/DomainListVC+Actions.swift | 27 ++- phpmon/Domain/DomainList/DomainListVC.swift | 23 ++- .../DomainListWindowController.swift | 4 +- .../Composer/ComposerWindow.swift | 9 +- .../Homebrew/HomebrewDiagnostics.swift | 10 +- phpmon/Domain/Integrations/Valet/Valet.swift | 6 +- phpmon/Domain/Menu/MainMenu+Actions.swift | 55 +++--- phpmon/Domain/Menu/MainMenu+Async.swift | 2 +- phpmon/Domain/Menu/MainMenu+Startup.swift | 21 +-- phpmon/Domain/Menu/MainMenu+Switcher.swift | 80 ++++---- phpmon/Domain/Menu/MainMenu.swift | 14 +- phpmon/Domain/Notice/BetterAlertVC.swift | 1 - .../PHP/ActivePhpInstallation+Checks.swift | 8 +- phpmon/Domain/Preferences/CustomPrefs.swift | 16 +- phpmon/Domain/Preferences/Preferences.swift | 5 +- phpmon/Domain/Presets/Preset.swift | 14 +- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 14 +- phpmon/Next/SystemShell.swift | 2 +- phpmon/Next/TestableShell.swift | 2 + 37 files changed, 359 insertions(+), 584 deletions(-) delete mode 100644 phpmon/Common/Pending Removal/LegacyShell+PATH.swift delete mode 100644 phpmon/Common/Pending Removal/LegacyShell.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 9eed9bf..deb4580 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -103,8 +103,6 @@ C42CFB1627DFDE7900862737 /* nginx-site.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1527DFDE7900862737 /* nginx-site.test */; }; C42CFB1827DFDFDC00862737 /* nginx-site-isolated.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */; }; C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */; }; - C42E3BF428A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */; }; - C42E3BF528A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */; }; C42F26732805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; C42F26762805FEE200938AC7 /* nginx-secure-proxy.test in Resources */ = {isa = PBXBuildFile; fileRef = C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */; }; @@ -224,8 +222,6 @@ C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B56360276AB0A500F12CCB /* VersionExtractorTest.swift */; }; C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; }; C4B5853F2770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; }; - C4B585412770FE3900DA4FBE /* LegacyShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */; }; - C4B585422770FE3900DA4FBE /* LegacyShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */; }; C4B585442770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; }; C4B585452770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; }; C4B6091A2853AAD300C95265 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B609192853AAD300C95265 /* SectionHeaderView.swift */; }; @@ -406,7 +402,6 @@ C42CFB1527DFDE7900862737 /* nginx-site.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site.test"; sourceTree = ""; }; C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site-isolated.test"; sourceTree = ""; }; C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationTest.swift; sourceTree = ""; }; - C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LegacyShell+PATH.swift"; sourceTree = ""; }; C42F26722805B4B400938AC7 /* DomainListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListable.swift; sourceTree = ""; }; C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-secure-proxy.test"; sourceTree = ""; }; C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Notifications.swift"; sourceTree = ""; }; @@ -470,7 +465,6 @@ C4B5635D276AB09000F12CCB /* VersionExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionExtractor.swift; sourceTree = ""; }; C4B56360276AB0A500F12CCB /* VersionExtractorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionExtractorTest.swift; sourceTree = ""; }; C4B5853B2770FE3900DA4FBE /* Paths.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Paths.swift; sourceTree = ""; }; - C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyShell.swift; sourceTree = ""; }; C4B5853D2770FE3900DA4FBE /* Command.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = ""; }; C4B609192853AAD300C95265 /* SectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderView.swift; sourceTree = ""; }; C4B6091C2853AB9700C95265 /* ServicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesView.swift; sourceTree = ""; }; @@ -897,15 +891,6 @@ path = Next; sourceTree = ""; }; - C46EBC4C28DB9F43007ACC74 /* Pending Removal */ = { - isa = PBXGroup; - children = ( - C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */, - C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */, - ); - path = "Pending Removal"; - sourceTree = ""; - }; C47331A0247093AC009A0597 /* Menu */ = { isa = PBXGroup; children = ( @@ -1007,7 +992,6 @@ C4B5853A2770FE2500DA4FBE /* Common */ = { isa = PBXGroup; children = ( - C46EBC4C28DB9F43007ACC74 /* Pending Removal */, C40C7F2127721F7300DDDCDC /* Core */, 54B20EDF263AA22C00D3250E /* PHP */, C44CCD4327AFE93300CE40E5 /* Errors */, @@ -1383,7 +1367,6 @@ C4D8016622B1584700C6DA1B /* Startup.swift in Sources */, C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */, C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */, - C4B585412770FE3900DA4FBE /* LegacyShell.swift in Sources */, C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */, C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */, C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */, @@ -1509,7 +1492,6 @@ C40508AF28ADA23D008FAC1F /* NoDomainResultsView.swift in Sources */, C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */, C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */, - C42E3BF428A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */, C42337A3281F19F000459A48 /* Xdebug.swift in Sources */, C4B97B75275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */, C41C02A627E60D7A009F26CB /* SiteScanner.swift in Sources */, @@ -1563,7 +1545,6 @@ C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */, C43A8A2425D9D20D00591B77 /* HomebrewPackageTest.swift in Sources */, C485707928BF456C00539B36 /* ArrayExtension.swift in Sources */, - C42E3BF528A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */, C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */, C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */, C4F319C927B034A500AFF46F /* Stats.swift in Sources */, @@ -1668,7 +1649,6 @@ C4A6957728D23EE300A14CF8 /* EnvironmentManager.swift in Sources */, C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */, C4A81CA528C67101008DD9D1 /* PMTableView.swift in Sources */, - C4B585422770FE3900DA4FBE /* LegacyShell.swift in Sources */, C45E76152854A65300B4FE0C /* ServicesManager.swift in Sources */, C464ADAD275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */, C40C7F1F2772136000DDDCDC /* PhpEnv.swift in Sources */, diff --git a/phpmon/Common/Core/Actions.swift b/phpmon/Common/Core/Actions.swift index 30131ee..0960a79 100644 --- a/phpmon/Common/Core/Actions.swift +++ b/phpmon/Common/Core/Actions.swift @@ -12,22 +12,22 @@ class Actions { // MARK: - Services - public static func restartPhpFpm() { - brew("services restart \(PhpEnv.phpInstall.formula)", sudo: true) + public static func restartPhpFpm() async { + await brew("services restart \(PhpEnv.phpInstall.formula)", sudo: true) } - public static func restartNginx() { - brew("services restart nginx", sudo: true) + public static func restartNginx() async { + await brew("services restart nginx", sudo: true) } - public static func restartDnsMasq() { - brew("services restart dnsmasq", sudo: true) + public static func restartDnsMasq() async { + await brew("services restart dnsmasq", sudo: true) } - public static func stopValetServices() { - brew("services stop \(PhpEnv.phpInstall.formula)", sudo: true) - brew("services stop nginx", sudo: true) - brew("services stop dnsmasq", sudo: true) + public static func stopValetServices() async { + await brew("services stop \(PhpEnv.phpInstall.formula)", sudo: true) + await brew("services stop nginx", sudo: true) + await brew("services stop dnsmasq", sudo: true) } public static func fixHomebrewPermissions() throws { @@ -65,26 +65,20 @@ class Actions { } // MARK: - Third Party Services - public static func stopService(name: String, completion: @escaping () -> Void) { - DispatchQueue.global(qos: .userInitiated).async { - brew("services stop \(name)", sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name }) - ServicesManager.loadHomebrewServices(completed: { - DispatchQueue.main.async { - completion() - } - }) - } + public static func stopService(name: String) async { + await brew( + "services stop \(name)", + sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name } + ) + await ServicesManager.loadHomebrewServices() } - public static func startService(name: String, completion: @escaping () -> Void) { - DispatchQueue.global(qos: .userInitiated).async { - brew("services start \(name)", sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name }) - ServicesManager.loadHomebrewServices(completed: { - DispatchQueue.main.async { - completion() - } - }) - } + public static func startService(name: String) async { + await brew( + "services start \(name)", + sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name } + ) + await ServicesManager.loadHomebrewServices() } // MARK: - Finding Config Files @@ -119,12 +113,12 @@ class Actions { // MARK: - Other Actions - public static func createTempPhpInfoFile() -> URL { + public static func createTempPhpInfoFile() async -> URL { // Write a file called `phpmon_phpinfo.php` to /tmp try! " /tmp/phpmon_phpinfo.html") + await Shell.quiet("\(Paths.binPath)/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html") return URL(string: "file:///private/tmp/phpmon_phpinfo.html")! } @@ -145,10 +139,12 @@ class Actions { */ public static func fixMyValet(completed: @escaping () -> Void) { InternalSwitcher().performSwitch(to: PhpEnv.brewPhpAlias, completion: { - brew("services restart dnsmasq", sudo: true) - brew("services restart php", sudo: true) - brew("services restart nginx", sudo: true) - completed() + Task { // restart all services and fire callback upon completion + await brew("services restart dnsmasq", sudo: true) + await brew("services restart php", sudo: true) + await brew("services restart nginx", sudo: true) + completed() + } }) } } diff --git a/phpmon/Common/Core/Helpers.swift b/phpmon/Common/Core/Helpers.swift index 43fa168..ba20a29 100644 --- a/phpmon/Common/Core/Helpers.swift +++ b/phpmon/Common/Core/Helpers.swift @@ -11,21 +11,21 @@ /** Runs a `valet` command. Defaults to running as superuser. */ -func valet(_ command: String, sudo: Bool = true) -> String { - return LegacyShell.pipe("\(sudo ? "sudo " : "")" + "\(Paths.valet) \(command)", requiresPath: true) +func valet(_ command: String, sudo: Bool = true) async -> String { + return await Shell.pipe("\(sudo ? "sudo " : "")" + "\(Paths.valet) \(command)").out } /** Runs a `brew` command. Can run as superuser. */ -func brew(_ command: String, sudo: Bool = false) { - LegacyShell.run("\(sudo ? "sudo " : "")" + "\(Paths.brew) \(command)") +func brew(_ command: String, sudo: Bool = false) async { + await Shell.quiet("\(sudo ? "sudo " : "")" + "\(Paths.brew) \(command)") } /** Runs `sed` in order to replace all occurrences of a string in a specific file with another. */ -func sed(file: String, original: String, replacement: String) { +func sed(file: String, original: String, replacement: String) async { // Escape slashes (or `sed` won't work) let e_original = original.replacingOccurrences(of: "/", with: "\\/") let e_replacement = replacement.replacingOccurrences(of: "/", with: "\\/") @@ -33,19 +33,19 @@ func sed(file: String, original: String, replacement: String) { // Check if gsed exists; it is able to follow symlinks, // which we want to do to toggle the extension if Filesystem.fileExists("\(Paths.binPath)/gsed") { - LegacyShell.run("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)") + await Shell.quiet("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)") } else { - LegacyShell.run("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)") + await Shell.quiet("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)") } } /** Uses `grep` to determine whether a particular query string can be found in a particular file. */ -func grepContains(file: String, query: String) -> Bool { - return LegacyShell.pipe(""" +func grepContains(file: String, query: String) async -> Bool { + return await Shell.pipe(""" grep -q '\(query)' \(file); [ $? -eq 0 ] && echo "YES" || echo "NO" - """) + """).out .trimmingCharacters(in: .whitespacesAndNewlines) .contains("YES") } diff --git a/phpmon/Common/Core/Paths.swift b/phpmon/Common/Core/Paths.swift index ac34932..bdf7610 100644 --- a/phpmon/Common/Core/Paths.swift +++ b/phpmon/Common/Core/Paths.swift @@ -21,11 +21,11 @@ public class Paths { init() { baseDir = App.architecture != "x86_64" ? .opt : .usr + } - Task { - let output = await Shell.pipe("id -un").out - userName = String(output.split(separator: "\n")[0]) - } + public func loadUser() async { + let output = await Shell.pipe("id -un").out + userName = String(output.split(separator: "\n")[0]) } public func detectBinaryPaths() { diff --git a/phpmon/Common/PHP/ActivePhpInstallation.swift b/phpmon/Common/PHP/ActivePhpInstallation.swift index b3cd72d..00a0c4a 100644 --- a/phpmon/Common/PHP/ActivePhpInstallation.swift +++ b/phpmon/Common/PHP/ActivePhpInstallation.swift @@ -125,11 +125,12 @@ class ActivePhpInstallation { versions of PHP, we can just check for the existence of the `valet-fpm.conf` file. If the check here fails, that means that Valet won't work properly. */ - func checkPhpFpmStatus() -> Bool { + func checkPhpFpmStatus() async -> Bool { if self.version.short == "5.6" { // The main PHP config file should contain `valet.sock` and then we're probably fine? let fileName = "\(Paths.etcPath)/php/5.6/php-fpm.conf" - return LegacyShell.pipe("cat \(fileName)").contains("valet.sock") + return await Shell.pipe("cat \(fileName)").out + .contains("valet.sock") } // Make sure to check if valet-fpm.conf exists. If it does, we should be fine :) diff --git a/phpmon/Common/PHP/PHP Version/PhpEnv.swift b/phpmon/Common/PHP/PHP Version/PhpEnv.swift index 8eb7be3..7d78d5f 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnv.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnv.swift @@ -16,17 +16,15 @@ class PhpEnv { self.currentInstall = ActivePhpInstallation() } - func determinePhpAlias() { - Task { - let brewPhpAlias = await Shell.pipe("\(Paths.brew) info php --json").out + func determinePhpAlias() async { + let brewPhpAlias = await Shell.pipe("\(Paths.brew) info php --json").out - self.homebrewPackage = try! JSONDecoder().decode( - [HomebrewPackage].self, - from: brewPhpAlias.data(using: .utf8)! - ).first! + self.homebrewPackage = try! JSONDecoder().decode( + [HomebrewPackage].self, + from: brewPhpAlias.data(using: .utf8)! + ).first! - Log.info("[BREW] On your system, the `php` formula means version \(homebrewPackage.version)!") - } + Log.info("[BREW] On your system, the `php` formula means version \(homebrewPackage.version)!") } // MARK: - Properties @@ -80,8 +78,8 @@ class PhpEnv { return InternalSwitcher() } - public static func detectPhpVersions() { - Task { await Self.shared.detectPhpVersions() } + public static func detectPhpVersions() async { + _ = await Self.shared.detectPhpVersions() } /** @@ -90,7 +88,7 @@ class PhpEnv { public func detectPhpVersions() async -> [String] { let files = await Shell.pipe("ls \(Paths.optPath) | grep php@").out - var versionsOnly = extractPhpVersions(from: files.components(separatedBy: "\n")) + var versionsOnly = await extractPhpVersions(from: files.components(separatedBy: "\n")) // Make sure the aliased version is detected // The user may have `php` installed, but not e.g. `php@8.0` @@ -128,7 +126,7 @@ class PhpEnv { from versions: [String], checkBinaries: Bool = true, generateHelpers: Bool = true - ) -> [String] { + ) async -> [String] { var output: [String] = [] var supported = Constants.SupportedPhpVersions @@ -153,7 +151,9 @@ class PhpEnv { } if generateHelpers { - output.forEach { PhpHelper.generate(for: $0) } + for item in output { + await PhpHelper.generate(for: item) + } } return output diff --git a/phpmon/Common/PHP/PHP Version/PhpHelper.swift b/phpmon/Common/PHP/PHP Version/PhpHelper.swift index 1165fa1..b0a8064 100644 --- a/phpmon/Common/PHP/PHP Version/PhpHelper.swift +++ b/phpmon/Common/PHP/PHP Version/PhpHelper.swift @@ -12,7 +12,7 @@ class PhpHelper { static let keyPhrase = "This file was automatically generated by PHP Monitor." - public static func generate(for version: String) { + public static func generate(for version: String) async { // Take the PHP version (e.g. "7.2") and generate a dotless version let dotless = version.replacingOccurrences(of: ".", with: "") @@ -20,79 +20,81 @@ class PhpHelper { let destination = "\(Paths.homePath)/.config/phpmon/bin/pm\(dotless)" // Check if the ~/.config/phpmon/bin directory is in the PATH - let inPath = LegacyShell.user.PATH.contains("\(Paths.homePath)/.config/phpmon/bin") + let inPath = Shell.PATH.contains("\(Paths.homePath)/.config/phpmon/bin") // Check if we can create symlinks (`/usr/local/bin` must be writable) let canWriteSymlinks = FileManager.default.isWritableFile(atPath: "/usr/local/bin/") - do { - Task { await Shell.quiet("mkdir -p ~/.config/phpmon/bin") } + Task { // Create the appropriate folders and check if the files exist + do { + await Shell.quiet("mkdir -p ~/.config/phpmon/bin") - if Filesystem.fileExists(destination) { - let contents = try String(contentsOfFile: destination) - if !contents.contains(keyPhrase) { - Log.info("The file at '\(destination)' already exists and was not generated by PHP Monitor " - + "(or is unreadable). Not updating this file.") - return - } - } - - // Let's follow the symlink to the PHP binary folder - let path = URL(fileURLWithPath: "\(Paths.optPath)/php@\(version)/bin") - .resolvingSymlinksInPath().path - - // The contents of the script! - let script = """ - #!/bin/zsh - # \(keyPhrase) - # It reflects the location of PHP \(version)'s binaries on your system. - # Usage: . pm\(dotless) - [[ $ZSH_EVAL_CONTEXT =~ :file$ ]] \\ - && echo "PHP Monitor has enabled this terminal to use PHP \(version)." \\ - || echo "You must run '. pm\(dotless)' (or 'source pm\(dotless)') instead!"; - export PATH=\(path):$PATH - """ - - // Write to the destination - try script.write( - to: URL(fileURLWithPath: destination), - atomically: true, - encoding: String.Encoding.utf8 - ) - - // Make sure the file is executable - LegacyShell.run("chmod +x \(destination)") - - // Create a symlink if the folder is not in the PATH - if !inPath { - // First, check if we can create symlinks at all - if !canWriteSymlinks { - Log.err("PHP Monitor does not have permission to symlink `/usr/local/bin/\(dotless)`.") - return + if Filesystem.fileExists(destination) { + let contents = try String(contentsOfFile: destination) + if !contents.contains(keyPhrase) { + Log.info("The file at '\(destination)' already exists and was not generated by PHP Monitor " + + "(or is unreadable). Not updating this file.") + return + } } - // Write the symlink - self.createSymlink(dotless) + // Let's follow the symlink to the PHP binary folder + let path = URL(fileURLWithPath: "\(Paths.optPath)/php@\(version)/bin") + .resolvingSymlinksInPath().path + + // The contents of the script! + let script = """ + #!/bin/zsh + # \(keyPhrase) + # It reflects the location of PHP \(version)'s binaries on your system. + # Usage: . pm\(dotless) + [[ $ZSH_EVAL_CONTEXT =~ :file$ ]] \\ + && echo "PHP Monitor has enabled this terminal to use PHP \(version)." \\ + || echo "You must run '. pm\(dotless)' (or 'source pm\(dotless)') instead!"; + export PATH=\(path):$PATH + """ + + // Write to the destination + try script.write( + to: URL(fileURLWithPath: destination), + atomically: true, + encoding: String.Encoding.utf8 + ) + + // Make sure the file is executable + await Shell.quiet("chmod +x \(destination)") + + // Create a symlink if the folder is not in the PATH + if !inPath { + // First, check if we can create symlinks at all + if !canWriteSymlinks { + Log.err("PHP Monitor does not have permission to symlink `/usr/local/bin/\(dotless)`.") + return + } + + // Write the symlink + await self.createSymlink(dotless) + } + } catch { + print(error) + Log.err("Could not write PHP Monitor helper for PHP \(version) to \(destination))") } - } catch { - print(error) - Log.err("Could not write PHP Monitor helper for PHP \(version) to \(destination))") } } - private static func createSymlink(_ dotless: String) { + private static func createSymlink(_ dotless: String) async { let source = "\(Paths.homePath)/.config/phpmon/bin/pm\(dotless)" let destination = "/usr/local/bin/pm\(dotless)" if !Filesystem.fileExists(destination) { Log.info("Creating new symlink: \(destination)") - LegacyShell.run("ln -s \(source) \(destination)") + await Shell.quiet("ln -s \(source) \(destination)") return } if !Filesystem.fileIsSymlink(destination) { Log.info("Overwriting existing file with new symlink: \(destination)") - LegacyShell.run("ln -fs \(source) \(destination)") + await Shell.quiet("ln -fs \(source) \(destination)") return } diff --git a/phpmon/Common/PHP/PhpExtension.swift b/phpmon/Common/PHP/PhpExtension.swift index 84a63a9..b6e7d7e 100644 --- a/phpmon/Common/PHP/PhpExtension.swift +++ b/phpmon/Common/PHP/PhpExtension.swift @@ -75,14 +75,14 @@ class PhpExtension { This simply toggles the extension in the .ini file. You may need to restart the other services in order for this change to apply. */ - func toggle() { + func toggle() async { let newLine = enabled // DISABLED: Commented out line ? "; \(line)" // ENABLED: Line where the comment delimiter (;) is removed : line.replacingOccurrences(of: "; ", with: "") - sed(file: file, original: line, replacement: newLine) + await sed(file: file, original: line, replacement: newLine) enabled.toggle() } diff --git a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift index b125140..58a1d67 100644 --- a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift +++ b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift @@ -19,6 +19,8 @@ class InternalSwitcher: PhpSwitcher { Please note that depending on which version is installed, the version that is switched to may or may not be identical to `php` (without @version). + + TODO: Use `async` and use structured concurrency: https://www.hackingwithswift.com/swift/5.5/structured-concurrency */ func performSwitch(to version: String, completion: @escaping () -> Void) { Log.info("Switching to \(version), unlinking all versions...") @@ -30,26 +32,28 @@ class InternalSwitcher: PhpSwitcher { PhpEnv.shared.availablePhpVersions.forEach { (available) in group.enter() - DispatchQueue.global(qos: .userInitiated).async { - self.disableDefaultPhpFpmPool(available) - self.stopPhpVersion(available) + Task { // TODO: Use structured concurrency + await self.disableDefaultPhpFpmPool(available) + await self.stopPhpVersion(available) group.leave() } } group.notify(queue: .global(qos: .userInitiated)) { - Log.info("All versions have been unlinked!") - Log.info("Linking the new version!") + Task { // TODO: Use structured concurrency + Log.info("All versions have been unlinked!") + Log.info("Linking the new version!") - for formula in versions { - self.startPhpVersion(formula, primary: (version == formula)) + for formula in versions { + await self.startPhpVersion(formula, primary: (version == formula)) + } + + Log.info("Restarting nginx, just to be sure!") + await brew("services restart nginx", sudo: true) + + Log.info("The new version(s) have been linked!") + completion() } - - Log.info("Restarting nginx, just to be sure!") - brew("services restart nginx", sudo: true) - - Log.info("The new version(s) have been linked!") - completion() } } @@ -74,7 +78,7 @@ class InternalSwitcher: PhpSwitcher { return FileManager.default.fileExists(atPath: pool) } - func disableDefaultPhpFpmPool(_ version: String) { + func disableDefaultPhpFpmPool(_ version: String) async { let pool = "\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf" if FileManager.default.fileExists(atPath: pool) { Log.info("A default `www.conf` file was found in the php-fpm.d directory for PHP \(version).") @@ -94,28 +98,28 @@ class InternalSwitcher: PhpSwitcher { } } - func stopPhpVersion(_ version: String) { + func stopPhpVersion(_ version: String) async { let formula = (version == PhpEnv.brewPhpAlias) ? "php" : "php@\(version)" - brew("unlink \(formula)") - brew("services stop \(formula)", sudo: true) + await brew("unlink \(formula)") + await brew("services stop \(formula)", sudo: true) Log.info("Unlinked and stopped services for \(formula)") } - func startPhpVersion(_ version: String, primary: Bool) { + func startPhpVersion(_ version: String, primary: Bool) async { let formula = (version == PhpEnv.brewPhpAlias) ? "php" : "php@\(version)" if primary { Log.info("\(formula) is the primary formula, linking and starting services...") - brew("link \(formula) --overwrite --force") + await brew("link \(formula) --overwrite --force") } else { Log.info("\(formula) is an isolated PHP version, starting services only...") } - brew("services start \(formula)", sudo: true) + await brew("services start \(formula)", sudo: true) if Valet.enabled(feature: .isolatedSites) && primary { let socketVersion = version.replacingOccurrences(of: ".", with: "") - LegacyShell.run("ln -sF ~/.config/valet/valet\(socketVersion).sock ~/.config/valet/valet.sock") + await Shell.quiet("ln -sF ~/.config/valet/valet\(socketVersion).sock ~/.config/valet/valet.sock") Log.info("Symlinked new socket version (valet\(socketVersion).sock → valet.sock).") } diff --git a/phpmon/Common/Pending Removal/LegacyShell+PATH.swift b/phpmon/Common/Pending Removal/LegacyShell+PATH.swift deleted file mode 100644 index 3d7e2ee..0000000 --- a/phpmon/Common/Pending Removal/LegacyShell+PATH.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// Shell+PATH.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 15/08/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. -// - -import Foundation - -extension LegacyShell { - - var PATH: String { - let task = Process() - task.launchPath = "/bin/zsh" - - let command = Filesystem.fileExists("~/.zshrc") - // source the user's .zshrc file if it exists to complete $PATH - ? ". ~/.zshrc && echo $PATH" - // otherwise, non-interactive mode is sufficient - : "echo $PATH" - - task.arguments = ["--login", "-lc", command] - - let pipe = Pipe() - task.standardOutput = pipe - task.launch() - - let data = pipe.fileHandleForReading.readDataToEndOfFile() - - return String(data: data, encoding: String.Encoding.utf8) ?? "" - } -} diff --git a/phpmon/Common/Pending Removal/LegacyShell.swift b/phpmon/Common/Pending Removal/LegacyShell.swift deleted file mode 100644 index f364d76..0000000 --- a/phpmon/Common/Pending Removal/LegacyShell.swift +++ /dev/null @@ -1,175 +0,0 @@ -// -// Shell.swift -// PHP Monitor -// -// Copyright © 2022 Nico Verbruggen. All rights reserved. -// - -import Cocoa - -// TODO: Enable this to see where deprecations and replacements are needed. -@available(*, deprecated, message: "Use the new replacement `Shell` instead") -public class LegacyShell { - - // MARK: - Invoke static functions - - public static func run( - _ command: String, - requiresPath: Bool = false - ) { - LegacyShell.user.run(command, requiresPath: requiresPath) - } - - public static func pipe( - _ command: String, - requiresPath: Bool = false - ) -> String { - return LegacyShell.user.pipe(command, requiresPath: requiresPath) - } - - // MARK: - Singleton - - /** - We now require macOS 11, so no need to detect which terminal to use. - */ - public var shell: String = "/bin/sh" - - /** Additional exports that are sent if `requiresPath` is set to true. */ - public var exports: String = "" - - /** - Singleton to access a user shell (with --login) - */ - public static let user = LegacyShell() - - /** - Runs a shell command without using the output. - Uses the default shell. - - - Parameter command: The command to run - - Parameter requiresPath: By default, the PATH is not resolved but some binaries might require this - */ - private func run( - _ command: String, - requiresPath: Bool = false - ) { - // Equivalent of piping to /dev/null; don't do anything with the string - _ = LegacyShell.pipe(command, requiresPath: requiresPath) - } - - /** - Runs a shell command and returns the output. - - - Parameter command: The command to run - - Parameter requiresPath: By default, the PATH is not resolved but some binaries might require this - */ - private func pipe( - _ command: String, - requiresPath: Bool = false - ) -> String { - let shellOutput = self.executeSynchronously(command, requiresPath: requiresPath) - let hasError = ( - shellOutput.standardOutput == "" - && shellOutput.errorOutput.lengthOfBytes(using: .utf8) > 0 - ) - return !hasError ? shellOutput.standardOutput : shellOutput.errorOutput - } - - /** - Runs the command and returns a `ShellOutput` object, which contains info about the process. - - - Parameter command: The command to run - - Parameter requiresPath: By default, the PATH is not resolved but some binaries might require this - - Parameter waitUntilExit: Waits for the command to complete before returning the `ShellOutput` - */ - public func executeSynchronously( - _ command: String, - requiresPath: Bool = false - ) -> LegacyShell.Output { - - let outputPipe = Pipe() - let errorPipe = Pipe() - - let task = self.createTask(for: command, requiresPath: requiresPath) - task.standardOutput = outputPipe - task.standardError = errorPipe - task.launch() - task.waitUntilExit() - - let output = LegacyShell.Output( - standardOutput: String( - data: outputPipe.fileHandleForReading.readDataToEndOfFile(), - encoding: .utf8 - )!, - errorOutput: String( - data: errorPipe.fileHandleForReading.readDataToEndOfFile(), - encoding: .utf8 - )!, - task: task - ) - - if CommandLine.arguments.contains("--v") { - log(task: task, output: output) - } - - return output - } - - /** - Creates a new process with the correct PATH and shell. - */ - public func createTask(for command: String, requiresPath: Bool) -> Process { - var completeCommand = "" - - Log.info("LEGACY COMMAND: \(command)") - - if requiresPath { - // Basic export (PATH) - completeCommand += "export PATH=\(Paths.binPath):$PATH && " - - // Put additional exports in between - if !self.exports.isEmpty { - completeCommand += "\(self.exports) && " - } - } - - completeCommand += command - - let task = Process() - task.launchPath = self.shell - task.arguments = ["--noprofile", "-norc", "--login", "-c", completeCommand] - - return task - } - - /** - Verbose logging for PHP Monitor's synchronous shell output. - */ - private func log(task: Process, output: Output) { - Log.info("") - Log.info("==== COMMAND ====") - Log.info("") - Log.info("\(self.shell) \(task.arguments?.joined(separator: " ") ?? "")") - Log.info("") - Log.info("==== OUTPUT ====") - Log.info("") - dump(output) - Log.info("") - Log.info("==== END OUTPUT ====") - Log.info("") - } - - public class Output { - public let standardOutput: String - public let errorOutput: String - public let task: Process - - init(standardOutput: String, - errorOutput: String, - task: Process) { - self.standardOutput = standardOutput - self.errorOutput = errorOutput - self.task = task - } - } -} diff --git a/phpmon/Domain/App/AppDelegate+MenuOutlets.swift b/phpmon/Domain/App/AppDelegate+MenuOutlets.swift index acd41bc..0d30925 100644 --- a/phpmon/Domain/App/AppDelegate+MenuOutlets.swift +++ b/phpmon/Domain/App/AppDelegate+MenuOutlets.swift @@ -33,15 +33,17 @@ extension AppDelegate { } @IBAction func reloadDomainListPressed(_ sender: Any) { - let vc = App.shared.domainListWindowController? - .window?.contentViewController as? DomainListVC + Task { // Reload domains + let vc = App.shared.domainListWindowController? + .window?.contentViewController as? DomainListVC - if vc != nil { - // If the view exists, directly reload the list of sites. - vc!.reloadDomains() - } else { - // If the view does not exist, reload the cached data that was populated when the app initially launched. - Valet.shared.reloadSites() + if vc != nil { + // If the view exists, directly reload the list of sites. + await vc!.reloadDomains() + } else { + // If the view does not exist, reload the cached data that was populated when the app launched. + await Valet.shared.reloadSites() + } } } diff --git a/phpmon/Domain/App/AppDelegate.swift b/phpmon/Domain/App/AppDelegate.swift index 8348b12..c34fc74 100644 --- a/phpmon/Domain/App/AppDelegate.swift +++ b/phpmon/Domain/App/AppDelegate.swift @@ -13,13 +13,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele // MARK: - Variables - /** - The Shell singleton that keeps track of the history of all - (invoked by PHP Monitor) shell commands. It is used to - invoke all commands in this application. - */ - let sharedShell: LegacyShell - /** The App singleton contains information about the state of the application and global variables. @@ -68,7 +61,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele logger.verbosity = .performance // TODO: Enable to fake broken setup during testing - ActiveShell.useTestable(Testables.working.shellOutput) + // ActiveShell.useTestable(Testables.working.shellOutput) #endif if CommandLine.arguments.contains("--v") { @@ -81,7 +74,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele Log.info("Version \(App.version)") Log.separator(as: .info) - self.sharedShell = LegacyShell.user self.state = App.shared self.menu = MainMenu.shared self.paths = Paths.shared @@ -103,8 +95,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele func applicationDidFinishLaunching(_ aNotification: Notification) { // Make sure notifications will work setupNotifications() - // Make sure the menu performs its initial checks - Task { await menu.startup() } + Task { // Make sure the menu performs its initial checks + await paths.loadUser() + await menu.startup() + } } } diff --git a/phpmon/Domain/App/AppUpdateChecker.swift b/phpmon/Domain/App/AppUpdateChecker.swift index b2d614d..c25ec1f 100644 --- a/phpmon/Domain/App/AppUpdateChecker.swift +++ b/phpmon/Domain/App/AppUpdateChecker.swift @@ -21,7 +21,7 @@ class AppUpdateChecker { public static func retrieveVersionFromCask( _ initiatedFromBackground: Bool = true - ) -> String { + ) async -> String { let caskFile = App.version.contains("-dev") ? Constants.Urls.DevBuildCaskFile.absoluteString : Constants.Urls.StableBuildCaskFile.absoluteString @@ -32,14 +32,14 @@ class AppUpdateChecker { command = "curl -s --max-time 5" } - return LegacyShell.pipe( + return await Shell.pipe( "\(command) '\(caskFile)' | grep version" - ) + ).out } public static func checkIfNewerVersionIsAvailable( initiatedFromBackground: Bool = true - ) { + ) async { if initiatedFromBackground { if !Preferences.isEnabled(.automaticBackgroundUpdateCheck) { Log.info("Automatic updates are disabled. No check will be performed.") @@ -49,7 +49,7 @@ class AppUpdateChecker { Log.info("Automatic updates are enabled, a check will be performed.") } - let versionString = retrieveVersionFromCask(initiatedFromBackground) + let versionString = await retrieveVersionFromCask(initiatedFromBackground) guard let onlineVersion = AppVersion.from(versionString) else { Log.err("We couldn't check for updates!") diff --git a/phpmon/Domain/App/InterAppHandler.swift b/phpmon/Domain/App/InterAppHandler.swift index 73e6477..5c0b365 100644 --- a/phpmon/Domain/App/InterAppHandler.swift +++ b/phpmon/Domain/App/InterAppHandler.swift @@ -26,10 +26,14 @@ class InterApp { DomainListVC.show() }), InterApp.Action(command: "services/stop", action: { _ in - MainMenu.shared.stopValetServices() + Task { // Stopping services as standalone task + await MainMenu.shared.stopValetServices() + } }), InterApp.Action(command: "services/restart/all", action: { _ in - MainMenu.shared.restartValetServices() + Task { // Restarting services as standalone task + await MainMenu.shared.restartValetServices() + } }), InterApp.Action(command: "services/restart/nginx", action: { _ in MainMenu.shared.restartNginx() diff --git a/phpmon/Domain/App/ServicesManager.swift b/phpmon/Domain/App/ServicesManager.swift index d274764..00d4fd0 100644 --- a/phpmon/Domain/App/ServicesManager.swift +++ b/phpmon/Domain/App/ServicesManager.swift @@ -16,53 +16,44 @@ class ServicesManager: ObservableObject { @Published var rootServices: [String: HomebrewService] = [:] @Published var userServices: [String: HomebrewService] = [:] - public static func loadHomebrewServices(completed: (() -> Void)? = nil) { + public static func loadHomebrewServices() async { let rootServiceNames = [ PhpEnv.phpInstall.formula, "nginx", "dnsmasq" ] - DispatchQueue.global(qos: .background).async { - let data = LegacyShell - .pipe("sudo \(Paths.brew) services info --all --json", requiresPath: true) - .data(using: .utf8)! + let normalJson = await Shell + .pipe("sudo \(Paths.brew) services info --all --json") + .out + .data(using: .utf8)! - let services = try! JSONDecoder() - .decode([HomebrewService].self, from: data) - .filter({ return rootServiceNames.contains($0.name) }) + let normalServices = try! JSONDecoder() + .decode([HomebrewService].self, from: normalJson) + .filter({ return rootServiceNames.contains($0.name) }) - DispatchQueue.main.async { - ServicesManager.shared.rootServices = Dictionary( - uniqueKeysWithValues: services.map { ($0.name, $0) } - ) - } + DispatchQueue.main.async { + ServicesManager.shared.rootServices = Dictionary( + uniqueKeysWithValues: normalServices.map { ($0.name, $0) } + ) } guard let userServiceNames = Preferences.custom.services else { return } - DispatchQueue.global(qos: .background).async { - let data = LegacyShell - .pipe("\(Paths.brew) services info --all --json", requiresPath: true) - .data(using: .utf8)! + let rootJson = await Shell + .pipe("\(Paths.brew) services info --all --json") + .out + .data(using: .utf8)! - let services = try! JSONDecoder() - .decode([HomebrewService].self, from: data) - .filter({ return userServiceNames.contains($0.name) }) + let rootServices = try! JSONDecoder() + .decode([HomebrewService].self, from: rootJson) + .filter({ return userServiceNames.contains($0.name) }) - DispatchQueue.main.async { - ServicesManager.shared.userServices = Dictionary( - uniqueKeysWithValues: services.map { ($0.name, $0) } - ) - completed?() - } - } - } - - func loadData() { - Self.loadHomebrewServices() + ServicesManager.shared.userServices = Dictionary( + uniqueKeysWithValues: rootServices.map { ($0.name, $0) } + ) } /** diff --git a/phpmon/Domain/DomainList/AddProxyVC.swift b/phpmon/Domain/DomainList/AddProxyVC.swift index 0733fd1..d926e64 100644 --- a/phpmon/Domain/DomainList/AddProxyVC.swift +++ b/phpmon/Domain/DomainList/AddProxyVC.swift @@ -71,9 +71,9 @@ class AddProxyVC: NSViewController, NSTextFieldDelegate { App.shared.domainListWindowController?.contentVC.setUIBusy() - DispatchQueue.global(qos: .userInitiated).async { - LegacyShell.run("\(Paths.valet) proxy \(domain) \(proxyName)\(secure)", requiresPath: true) - Actions.restartNginx() + Task { + await Shell.quiet("\(Paths.valet) proxy \(domain) \(proxyName)\(secure)") + await Actions.restartNginx() DispatchQueue.main.async { App.shared.domainListWindowController?.contentVC.setUINotBusy() diff --git a/phpmon/Domain/DomainList/AddSiteVC.swift b/phpmon/Domain/DomainList/AddSiteVC.swift index 8275d27..1af3f1d 100644 --- a/phpmon/Domain/DomainList/AddSiteVC.swift +++ b/phpmon/Domain/DomainList/AddSiteVC.swift @@ -51,7 +51,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate { // MARK: - Outlet Interactions - @IBAction func pressedCreateLink(_ sender: Any) { + @IBAction func pressedCreateLink(_ sender: Any) async { let path = pathControl.url!.path let name = inputDomainName.stringValue @@ -71,7 +71,9 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate { // Adding `valet links` is a workaround for Valet malforming the config.json file // TODO: I will have to investigate and report this behaviour if possible - LegacyShell.run("cd '\(path)' && \(Paths.valet) link '\(name)' && valet links", requiresPath: true) + Task { + await Shell.quiet("cd '\(path)' && \(Paths.valet) link '\(name)' && valet links") + } dismissView(outcome: .OK) @@ -81,7 +83,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate { .searchField.stringValue = "" // Add the new item and scrolls to it - App.shared.domainListWindowController? + await App.shared.domainListWindowController? .contentVC .addedNewSite( name: name, diff --git a/phpmon/Domain/DomainList/DomainListVC+Actions.swift b/phpmon/Domain/DomainList/DomainListVC+Actions.swift index 23f17ed..59b4823 100644 --- a/phpmon/Domain/DomainList/DomainListVC+Actions.swift +++ b/phpmon/Domain/DomainList/DomainListVC+Actions.swift @@ -25,15 +25,14 @@ extension DomainListVC { self.waitAndExecute { // 1. Remove the original proxy - LegacyShell.run("\(Paths.valet) unproxy \(selectedProxy.domain)", requiresPath: true) + await Shell.quiet("\(Paths.valet) unproxy \(selectedProxy.domain)") // 2. Add a new proxy, which is either secured/unsecured let secure = originalSecureStatus ? "" : " --secure" - LegacyShell.run("\(Paths.valet) proxy \(selectedProxy.domain) \(selectedProxy.target)\(secure)", - requiresPath: true) + await Shell.quiet("\(Paths.valet) proxy \(selectedProxy.domain) \(selectedProxy.target)\(secure)") // 3. Restart nginx - Actions.restartNginx() + await Actions.restartNginx() // 4. Reload site list DispatchQueue.main.async { @@ -50,7 +49,7 @@ extension DomainListVC { let command = "cd '\(selectedSite.absolutePath)' && sudo \(Paths.valet) \(action) && exit;" waitAndExecute { - LegacyShell.run(command, requiresPath: true) + await Shell.quiet(command) } completion: { [self] in selectedSite.determineSecured() if selectedSite.secured == originalSecureStatus { @@ -99,12 +98,12 @@ extension DomainListVC { NSWorkspace.shared.open(url) } - @objc func openInFinder() { - LegacyShell.run("open '\(selectedSite!.absolutePath)'") + @objc func openInFinder() async { + await Shell.quiet("open '\(selectedSite!.absolutePath)'") } - @objc func openInTerminal() { - LegacyShell.run("open -b com.apple.terminal '\(selectedSite!.absolutePath)'") + @objc func openInTerminal() async { + await Shell.quiet("open -b com.apple.terminal '\(selectedSite!.absolutePath)'") } @objc func openWithEditor(sender: EditorMenuItem) async { @@ -157,9 +156,9 @@ extension DomainListVC { style: .critical, onFirstButtonPressed: { self.waitAndExecute { - LegacyShell.run("valet unlink '\(site.name)'", requiresPath: true) + Task { await Shell.quiet("valet unlink '\(site.name)'") } } completion: { - self.reloadDomains() + Task { await self.reloadDomains() } } } ) @@ -179,9 +178,9 @@ extension DomainListVC { style: .critical, onFirstButtonPressed: { self.waitAndExecute { - LegacyShell.run("valet unproxy '\(proxy.domain)'", requiresPath: true) + Task { await Shell.quiet("valet unproxy '\(proxy.domain)'") } } completion: { - self.reloadDomains() + Task { await self.reloadDomains() } } } ) @@ -191,7 +190,7 @@ extension DomainListVC { let rowToReload = tableView.selectedRow waitAndExecute { - LegacyShell.run(command, requiresPath: true) + await Shell.quiet(command) } completion: { [self] in beforeCellReload() tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0, 1, 2, 3, 4]) diff --git a/phpmon/Domain/DomainList/DomainListVC.swift b/phpmon/Domain/DomainList/DomainListVC.swift index d3a26cd..b482b27 100644 --- a/phpmon/Domain/DomainList/DomainListVC.swift +++ b/phpmon/Domain/DomainList/DomainListVC.swift @@ -97,7 +97,7 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource domains = Valet.getDomainListable() searchedFor(text: lastSearchedFor) } else { - reloadDomains() + Task { await reloadDomains() } } } @@ -107,7 +107,7 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource Disables the UI so the user cannot interact with it. Also shows a spinner to indicate that we're busy. */ - public func setUIBusy() { + @MainActor public func setUIBusy() { // If it takes more than 0.5s to set the UI to not busy, show a spinner timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: { _ in self.progressIndicator.startAnimation(true) @@ -121,7 +121,7 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource /** Re-enables the UI so the user can interact with it. */ - public func setUINotBusy() { + @MainActor public func setUINotBusy() { timer?.invalidate() progressIndicator.stopAnimation(nil) tableView.alphaValue = 1.0 @@ -136,12 +136,11 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource - Parameter execute: Callback of the work that needs to happen. - Parameter completion: Callback that is fired when the work is done. */ - internal func waitAndExecute(_ execute: @escaping () -> Void, completion: @escaping () -> Void = {}) { - setUIBusy() - DispatchQueue.global(qos: .userInitiated).async { [unowned self] in - execute() + internal func waitAndExecute(_ execute: @escaping () async -> Void, completion: @escaping () -> Void = {}) { + Task { + setUIBusy() + await execute() - // For a smoother animation, expect at least a 0.2 second delay DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [self] in completion() setUINotBusy() @@ -151,9 +150,9 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource // MARK: - Site Data Loading - func reloadDomains() { + func reloadDomains() async { waitAndExecute { - Valet.shared.reloadSites() + await Valet.shared.reloadSites() } completion: { [self] in domains = Valet.shared.sites searchedFor(text: lastSearchedFor) @@ -177,9 +176,9 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource self.domains = descriptor.ascending ? sorted.reversed() : sorted } - func addedNewSite(name: String, secure: Bool) { + func addedNewSite(name: String, secure: Bool) async { waitAndExecute { - Valet.shared.reloadSites() + await Valet.shared.reloadSites() } completion: { [self] in find(name, secure) } diff --git a/phpmon/Domain/DomainList/DomainListWindowController.swift b/phpmon/Domain/DomainList/DomainListWindowController.swift index df92f75..947bb54 100644 --- a/phpmon/Domain/DomainList/DomainListWindowController.swift +++ b/phpmon/Domain/DomainList/DomainListWindowController.swift @@ -51,7 +51,9 @@ class DomainListWindowController: PMWindowController, NSSearchFieldDelegate, NST // MARK: - Reload functionality @IBAction func pressedReload(_ sender: Any?) { - contentVC.reloadDomains() + Task { + await contentVC.reloadDomains() + } } @IBAction func pressedAddLink(_ sender: Any?) { diff --git a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift index 81a67a3..010959f 100644 --- a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift +++ b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift @@ -21,10 +21,9 @@ import Foundation self.completion = completion Paths.shared.detectBinaryPaths() + if Paths.composer == nil { - DispatchQueue.main.async { - self.presentMissingAlert() - } + self.presentMissingAlert() return } @@ -39,7 +38,9 @@ import Foundation window?.setType(info: true) - Task { await performComposerUpdate() } + Task { // Start the Composer global update as a separate task + await performComposerUpdate() + } } private func performComposerUpdate() async { diff --git a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift index 39ac784..a424bf8 100644 --- a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift +++ b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift @@ -42,7 +42,7 @@ class HomebrewDiagnostics { This check only needs to be performed if the `shivammathur/php` tap is active. */ public static func checkForCaskConflict() { - Task { + Task { // Check if there's a conflict if await hasAliasConflict() { presentAlertAboutConflict() } @@ -72,9 +72,11 @@ class HomebrewDiagnostics { } versions.forEach { version in - switcher.disableDefaultPhpFpmPool(version) - switcher.stopPhpVersion(version) - switcher.startPhpVersion(version, primary: version == primary) + Task { // Fix each pool concurrently (but perform the tasks sequentially) + await switcher.disableDefaultPhpFpmPool(version) + await switcher.stopPhpVersion(version) + await switcher.startPhpVersion(version, primary: version == primary) + } } } diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index f023662..9dcea5e 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -116,15 +116,15 @@ class Valet { Starts the preload of sites. In order to make sure PHP Monitor can correctly handle all PHP versions including isolation, it needs to know about all sites. */ - public func startPreloadingSites() { - self.reloadSites() + public func startPreloadingSites() async { + await self.reloadSites() } /** 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() async { loadConfiguration() if isBusy { diff --git a/phpmon/Domain/Menu/MainMenu+Actions.swift b/phpmon/Domain/Menu/MainMenu+Actions.swift index 3f696b6..0e66a57 100644 --- a/phpmon/Domain/Menu/MainMenu+Actions.swift +++ b/phpmon/Domain/Menu/MainMenu+Actions.swift @@ -42,17 +42,17 @@ extension MainMenu { } @objc func restartPhpFpm() { - asyncExecution { - Actions.restartPhpFpm() + Task { // Simple restart service + await Actions.restartPhpFpm() } } - @objc func restartValetServices() { - asyncExecution { - Actions.restartDnsMasq() - Actions.restartPhpFpm() - Actions.restartNginx() - } success: { + @MainActor @objc func restartValetServices() { + Task { // Restart services and show notification + await Actions.restartDnsMasq() + await Actions.restartPhpFpm() + await Actions.restartNginx() + LocalNotification.send( title: "notification.services_restarted".localized, subtitle: "notification.services_restarted_desc".localized, @@ -61,10 +61,10 @@ extension MainMenu { } } - @objc func stopValetServices() { - asyncExecution { - Actions.stopValetServices() - } success: { + @MainActor @objc func stopValetServices() { + Task { // Stop services and show notification + await Actions.stopValetServices() + LocalNotification.send( title: "notification.services_stopped".localized, subtitle: "notification.services_stopped_desc".localized, @@ -74,14 +74,14 @@ extension MainMenu { } @objc func restartNginx() { - asyncExecution { - Actions.restartNginx() + Task { + await Actions.restartNginx() } } @objc func restartDnsMasq() { - asyncExecution { - Actions.restartDnsMasq() + Task { + await Actions.restartDnsMasq() } } @@ -134,18 +134,18 @@ extension MainMenu { } @objc func toggleExtension(sender: ExtensionMenuItem) { - asyncExecution { - sender.phpExtension?.toggle() + Task { + await sender.phpExtension?.toggle() if Preferences.isEnabled(.autoServiceRestartAfterExtensionToggle) { - Actions.restartPhpFpm() + await Actions.restartPhpFpm() } } } private func performRollback() { - asyncExecution { - PresetHelper.rollbackPreset?.apply() + Task { + await PresetHelper.rollbackPreset?.apply() PresetHelper.rollbackPreset = nil MainMenu.shared.rebuild() } @@ -171,8 +171,8 @@ extension MainMenu { } @objc func togglePreset(sender: PresetMenuItem) { - asyncExecution { - sender.preset?.apply() + Task { + await sender.preset?.apply() } } @@ -191,12 +191,11 @@ extension MainMenu { } @objc func openPhpInfo() { - var url: URL? - asyncWithBusyUI { - url = Actions.createTempPhpInfoFile() - } completion: { - if url != nil { NSWorkspace.shared.open(url!) } + Task { + let url = await Actions.createTempPhpInfoFile() + NSWorkspace.shared.open(url) + } } } diff --git a/phpmon/Domain/Menu/MainMenu+Async.swift b/phpmon/Domain/Menu/MainMenu+Async.swift index 8c82f3c..b81c9f3 100644 --- a/phpmon/Domain/Menu/MainMenu+Async.swift +++ b/phpmon/Domain/Menu/MainMenu+Async.swift @@ -74,7 +74,7 @@ extension MainMenu { } if behaviours.contains(.broadcastServicesUpdate) { - ServicesManager.shared.loadData() + Task { await ServicesManager.loadHomebrewServices() } } if error != nil { diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 901f14e..c20b650 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -21,18 +21,18 @@ extension MainMenu { await App.shared.environment.process() if await Startup().checkEnvironment() { - self.onEnvironmentPass() + await self.onEnvironmentPass() } else { - self.onEnvironmentFail() + await self.onEnvironmentFail() } } /** When the environment is all clear and the app can run, let's go. */ - private func onEnvironmentPass() { + private func onEnvironmentPass() async { // Determine what the `php` formula is aliased to - PhpEnv.shared.determinePhpAlias() + await PhpEnv.shared.determinePhpAlias() // Determine install method Log.info(HomebrewDiagnostics.customCaskInstalled @@ -49,7 +49,7 @@ extension MainMenu { Valet.shared.validateVersion() // Actually detect the PHP versions - PhpEnv.detectPhpVersions() + await PhpEnv.detectPhpVersions() // Check for an alias conflict HomebrewDiagnostics.checkForCaskConflict() @@ -79,7 +79,7 @@ extension MainMenu { App.shared.loadGlobalHotkey() // Preload sites - Valet.shared.startPreloadingSites() + await Valet.shared.startPreloadingSites() // After preloading sites, check for PHP-FPM pool conflicts HomebrewDiagnostics.checkForPhpFpmPoolConflicts() @@ -88,7 +88,7 @@ extension MainMenu { Valet.notifyAboutUnsupportedTLD() // Find out which services are active - ServicesManager.shared.loadData() + await ServicesManager.loadHomebrewServices() // Start the background refresh timer startSharedTimer() @@ -111,9 +111,7 @@ extension MainMenu { } // Check for updates - DispatchQueue.global(qos: .utility).async { - AppUpdateChecker.checkIfNewerVersionIsAvailable() - } + await AppUpdateChecker.checkIfNewerVersionIsAvailable() // We are ready! Log.info("PHP Monitor is ready to serve!") @@ -122,9 +120,8 @@ extension MainMenu { /** When the environment is not OK, present an alert to inform the user. */ - private func onEnvironmentFail() { + private func onEnvironmentFail() async { DispatchQueue.main.async { [self] in - BetterAlert() .withInformation( title: "alert.cannot_start.title".localized, diff --git a/phpmon/Domain/Menu/MainMenu+Switcher.swift b/phpmon/Domain/Menu/MainMenu+Switcher.swift index fc55377..e8427a0 100644 --- a/phpmon/Domain/Menu/MainMenu+Switcher.swift +++ b/phpmon/Domain/Menu/MainMenu+Switcher.swift @@ -19,36 +19,38 @@ extension MainMenu { PhpEnv.shared.isBusy = false // Reload the site list - self.reloadDomainListData() + Task { + await self.reloadDomainListData() - // Perform UI updates on main thread - DispatchQueue.main.async { [self] in - updatePhpVersionInStatusBar() - rebuild() + // Perform UI updates on main thread + DispatchQueue.main.async { [self] in + updatePhpVersionInStatusBar() + rebuild() - if !PhpEnv.shared.validate(version) { - self.suggestFixMyValet(failed: version) - return + if !PhpEnv.shared.validate(version) { + self.suggestFixMyValet(failed: version) + return + } + + // Run composer updates + if Preferences.isEnabled(.autoComposerGlobalUpdateAfterSwitch) { + ComposerWindow().updateGlobalDependencies( + notify: false, + completion: { _ in + self.notifyAboutVersionChange(to: version) + } + ) + } else { + self.notifyAboutVersionChange(to: version) + } + + // Check if Valet still works correctly + self.checkForPlatformIssues() + + // Update stats + Stats.incrementSuccessfulSwitchCount() + Stats.evaluateSponsorMessageShouldBeDisplayed() } - - // Run composer updates - if Preferences.isEnabled(.autoComposerGlobalUpdateAfterSwitch) { - ComposerWindow().updateGlobalDependencies( - notify: false, - completion: { _ in - self.notifyAboutVersionChange(to: version) - } - ) - } else { - self.notifyAboutVersionChange(to: version) - } - - // Check if Valet still works correctly - self.checkForPlatformIssues() - - // Update stats - Stats.incrementSuccessfulSwitchCount() - Stats.evaluateSponsorMessageShouldBeDisplayed() } } @@ -102,25 +104,21 @@ extension MainMenu { .show() } - private func reloadDomainListData() { + private func reloadDomainListData() async { if let window = App.shared.domainListWindowController { - DispatchQueue.main.async { - window.contentVC.reloadDomains() - } + await window.contentVC.reloadDomains() } else { - Valet.shared.reloadSites() + await Valet.shared.reloadSites() } } - private func notifyAboutVersionChange(to version: String) { - DispatchQueue.main.async { - LocalNotification.send( - title: String(format: "notification.version_changed_title".localized, version), - subtitle: String(format: "notification.version_changed_desc".localized, version), - preference: .notifyAboutVersionChange - ) + @MainActor private func notifyAboutVersionChange(to version: String) { + LocalNotification.send( + title: String(format: "notification.version_changed_title".localized, version), + subtitle: String(format: "notification.version_changed_desc".localized, version), + preference: .notifyAboutVersionChange + ) - PhpEnv.phpInstall.notifyAboutBrokenPhpFpm() - } + Task { await PhpEnv.phpInstall.notifyAboutBrokenPhpFpm() } } } diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index f1ca90b..e41fcfc 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -103,11 +103,11 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate Reloads the menu in the foreground. This mimics the exact behaviours of `asyncExecution` as set in the method below. */ - @objc func reloadPhpMonitorMenuInForeground() { + @MainActor @objc func reloadPhpMonitorMenuInForeground() async { refreshActiveInstallation() refreshIcon() rebuild(async: false) - ServicesManager.shared.loadData() + await ServicesManager.loadHomebrewServices() } /** @@ -185,10 +185,8 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate NSApplication.shared.terminate(nil) } - @objc func checkForUpdates() { - DispatchQueue.global(qos: .userInitiated).async { - AppUpdateChecker.checkIfNewerVersionIsAvailable(initiatedFromBackground: false) - } + @objc func checkForUpdates() async { + await AppUpdateChecker.checkIfNewerVersionIsAvailable(initiatedFromBackground: false) } // MARK: - Menu Delegate @@ -196,7 +194,9 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate func menuWillOpen(_ menu: NSMenu) { // Make sure the shortcut key does not trigger this when the menu is open App.shared.shortcutHotkey?.isPaused = true - ServicesManager.shared.loadData() + Task { + await ServicesManager.loadHomebrewServices() + } } func menuDidClose(_ menu: NSMenu) { diff --git a/phpmon/Domain/Notice/BetterAlertVC.swift b/phpmon/Domain/Notice/BetterAlertVC.swift index c6c3bdc..aad5ff4 100644 --- a/phpmon/Domain/Notice/BetterAlertVC.swift +++ b/phpmon/Domain/Notice/BetterAlertVC.swift @@ -74,5 +74,4 @@ class BetterAlertVC: NSViewController { self.view.window?.close() NSApplication.shared.stopModal(withCode: code) } - } diff --git a/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift b/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift index fb30e73..c126f20 100644 --- a/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift +++ b/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift @@ -19,7 +19,13 @@ extension ActivePhpInstallation { This method actively presents a modal if said checks fails, so don't call this method too many times. */ public func notifyAboutBrokenPhpFpm() { - if !self.checkPhpFpmStatus() { + Task { + let fpmStatusConfiguredCorrectly = await self.checkPhpFpmStatus() + + if fpmStatusConfiguredCorrectly { + return + } + DispatchQueue.main.async { BetterAlert() .withInformation( diff --git a/phpmon/Domain/Preferences/CustomPrefs.swift b/phpmon/Domain/Preferences/CustomPrefs.swift index 12f6b82..66e323f 100644 --- a/phpmon/Domain/Preferences/CustomPrefs.swift +++ b/phpmon/Domain/Preferences/CustomPrefs.swift @@ -26,7 +26,7 @@ struct CustomPrefs: Decodable { return self.environmentVariables != nil && !self.environmentVariables!.keys.isEmpty } - @available(*, deprecated, message: "Use `setCustomEnvironmentVariables` instead") + // TODO: Rework this public func getEnvironmentVariables() -> String { return self.environmentVariables!.map { (key, value) in return "export \(key)=\(value)" @@ -42,12 +42,12 @@ struct CustomPrefs: Decodable { } extension Preferences { - func loadCustomPreferences() { + func loadCustomPreferences() async { // Ensure the configuration directory is created if missing - LegacyShell.run("mkdir -p ~/.config/phpmon") + await Shell.quiet("mkdir -p ~/.config/phpmon") // Move the legacy file - moveOutdatedConfigurationFile() + await moveOutdatedConfigurationFile() // Attempt to load the file if it exists let url = URL(fileURLWithPath: "\(Paths.homePath)/.config/phpmon/config.json") @@ -60,10 +60,10 @@ extension Preferences { } } - func moveOutdatedConfigurationFile() { + func moveOutdatedConfigurationFile() async { if Filesystem.fileExists("~/.phpmon.conf.json") && !Filesystem.fileExists("~/.config/phpmon/config.json") { Log.info("An outdated configuration file was found. Moving it...") - LegacyShell.run("cp ~/.phpmon.conf.json ~/.config/phpmon/config.json") + await Shell.quiet("cp ~/.phpmon.conf.json ~/.config/phpmon/config.json") Log.info("The configuration file was copied successfully!") } } @@ -87,7 +87,9 @@ extension Preferences { if customPreferences.hasEnvironmentVariables() { Log.info("Configuring the additional exports...") - LegacyShell.user.exports = customPreferences.getEnvironmentVariables() + if let shell = Shell as? SystemShell { + shell.exports = customPreferences.getEnvironmentVariables() + } } } catch { Log.warn("The ~/.config/phpmon/config.json file seems to be missing or malformed.") diff --git a/phpmon/Domain/Preferences/Preferences.swift b/phpmon/Domain/Preferences/Preferences.swift index d60b9f0..02a5e1e 100644 --- a/phpmon/Domain/Preferences/Preferences.swift +++ b/phpmon/Domain/Preferences/Preferences.swift @@ -27,7 +27,10 @@ class Preferences { services: [], environmentVariables: [:] ) - loadCustomPreferences() + + Task { + await loadCustomPreferences() + } } // MARK: - First Time Run diff --git a/phpmon/Domain/Presets/Preset.swift b/phpmon/Domain/Presets/Preset.swift index f88d20a..bd7eeae 100644 --- a/phpmon/Domain/Presets/Preset.swift +++ b/phpmon/Domain/Presets/Preset.swift @@ -67,19 +67,19 @@ struct Preset: Codable, Equatable { /** Applies a given preset. */ - public func apply() { + public func apply() async { Task { // Was this a rollback? let wasRollback = (self.name == "AutomaticRevertSnapshot") // Save the preset that would revert this preset - self.persistRevert() + await self.persistRevert() // Apply the PHP version if is considered a valid version if self.version != nil { if await !switchToPhpVersionIfValid() { PresetHelper.rollbackPreset = nil - Actions.restartPhpFpm() + await Actions.restartPhpFpm() return } } @@ -94,7 +94,7 @@ struct Preset: Codable, Equatable { for foundExt in PhpEnv.phpInstall.extensions where foundExt.name == ext.key && foundExt.enabled != ext.value { Log.info("Toggling extension \(foundExt.name) in \(foundExt.file)") - foundExt.toggle() + await foundExt.toggle() break } } @@ -103,7 +103,7 @@ struct Preset: Codable, Equatable { PresetHelper.loadRollbackPresetFromFile() // Restart PHP FPM process (also reloads menu, which will show the preset rollback) - Actions.restartPhpFpm() + await Actions.restartPhpFpm() // Show the correct notification if wasRollback { @@ -257,10 +257,10 @@ struct Preset: Codable, Equatable { /** Persists the revert as a JSON file, so it can be read from a file after restarting PHP Monitor. */ - private func persistRevert() { + private func persistRevert() async { let data = try! JSONEncoder().encode(self.revertSnapshot) - LegacyShell.run("mkdir -p ~/.config/phpmon") + await Shell.quiet("mkdir -p ~/.config/phpmon") try! String(data: data, encoding: .utf8)! .write( diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index db87a04..1b09703 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -91,15 +91,13 @@ struct CheckmarkView: View { return nil } - public func toggleService() { + public func toggleService() async { if active()! { - Actions.stopService(name: serviceName, completion: { - busy = false - }) + await Actions.stopService(name: serviceName) + busy = false } else { - Actions.startService(name: serviceName, completion: { - busy = false - }) + await Actions.startService(name: serviceName) + busy = false } } @@ -121,7 +119,7 @@ struct CheckmarkView: View { } else { Button { busy = true - toggleService() + Task { await toggleService() } } label: { Image(systemName: active()! ? "checkmark" : "xmark") .resizable() diff --git a/phpmon/Next/SystemShell.swift b/phpmon/Next/SystemShell.swift index ec8c3b8..17a214a 100644 --- a/phpmon/Next/SystemShell.swift +++ b/phpmon/Next/SystemShell.swift @@ -25,7 +25,7 @@ class SystemShell: Shellable { Exports are additional environment variables set by the user via the custom configuration. These are populated when the configuration file is being loaded. */ - private(set) var exports: String = "" + var exports: String = "" /** Retrieves the user's PATH by opening an interactive shell and echoing $PATH. */ private static func getPath() -> String { diff --git a/phpmon/Next/TestableShell.swift b/phpmon/Next/TestableShell.swift index 85eadd2..1a97a10 100644 --- a/phpmon/Next/TestableShell.swift +++ b/phpmon/Next/TestableShell.swift @@ -33,6 +33,8 @@ public class TestableShell: Shellable { didReceiveOutput: @escaping (String, ShellStream) -> Void, withTimeout timeout: TimeInterval ) async throws -> (Process, ShellOutput) { + assert(expectations.keys.contains(command), "No response declared for command: \(command)") + guard let expectation = expectations[command] else { return (Process(), .err("No Expected Output")) } From ed3622cc4eb11e09f132b4bd4512a83b76470971 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 6 Oct 2022 23:00:21 +0200 Subject: [PATCH 038/181] =?UTF-8?q?=E2=9C=85=20Fix=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Parsers/HomebrewPackageTest.swift | 17 +++++++------ phpmon-tests/Parsers/PhpExtensionTest.swift | 4 +-- .../Versions/AppUpdaterCheckTest.swift | 4 +-- .../Versions/PhpVersionDetectionTest.swift | 4 +-- .../Versions/ValetVersionExtractorTest.swift | 4 +-- phpmon/Domain/Menu/MainMenu+Actions.swift | 25 ++++++++++--------- 6 files changed, 31 insertions(+), 27 deletions(-) diff --git a/phpmon-tests/Parsers/HomebrewPackageTest.swift b/phpmon-tests/Parsers/HomebrewPackageTest.swift index 5bd30a2..65b55c8 100644 --- a/phpmon-tests/Parsers/HomebrewPackageTest.swift +++ b/phpmon-tests/Parsers/HomebrewPackageTest.swift @@ -53,13 +53,14 @@ class HomebrewPackageTest: XCTestCase { /// and requires the Valet services to be installed: php, nginx and dnsmasq. /// If this test fails, there is an issue with your Homebrew installation /// or the JSON API of the Homebrew output may have changed. - func testCanParseServicesJsonFromCliOutput() throws { + func testCanParseServicesJsonFromCliOutput() async throws { + ActiveShell.useSystem() + let services = try! JSONDecoder().decode( [HomebrewService].self, - from: LegacyShell.pipe( - "sudo \(Paths.brew) services info --all --json", - requiresPath: true - ).data(using: .utf8)! + from: await Shell.pipe( + "sudo \(Paths.brew) services info --all --json" + ).out.data(using: .utf8)! ).filter({ service in return ["php", "nginx", "dnsmasq"].contains(service.name) }) @@ -74,10 +75,12 @@ class HomebrewPackageTest: XCTestCase { /// and requires the `php` formula to be installed. /// If this test fails, there is an issue with your Homebrew installation /// or the JSON API of the Homebrew output may have changed. - func testCanLoadExtensionJsonFromCliOutput() throws { + func testCanLoadExtensionJsonFromCliOutput() async throws { + ActiveShell.useSystem() + let package = try! JSONDecoder().decode( [HomebrewPackage].self, - from: LegacyShell.pipe("\(Paths.brew) info php --json", requiresPath: true).data(using: .utf8)! + from: await Shell.pipe("\(Paths.brew) info php --json").out.data(using: .utf8)! ).first! XCTAssertTrue(package.name == "php") diff --git a/phpmon-tests/Parsers/PhpExtensionTest.swift b/phpmon-tests/Parsers/PhpExtensionTest.swift index 0e38c5e..5fc8c49 100644 --- a/phpmon-tests/Parsers/PhpExtensionTest.swift +++ b/phpmon-tests/Parsers/PhpExtensionTest.swift @@ -49,7 +49,7 @@ class PhpExtensionTest: XCTestCase { XCTAssertEqual(extensions[1].enabled, false) } - func testToggleWorksAsExpected() throws { + func testToggleWorksAsExpected() async throws { let destination = Utility.copyToTemporaryFile(resourceName: "php", fileExtension: "ini")! let extensions = PhpExtension.from(filePath: destination.path) XCTAssertEqual(extensions.count, 6) @@ -58,7 +58,7 @@ class PhpExtensionTest: XCTestCase { let xdebug = extensions.first! XCTAssertTrue(xdebug.name == "xdebug") XCTAssertEqual(xdebug.enabled, true) - xdebug.toggle() + await xdebug.toggle() XCTAssertEqual(xdebug.enabled, false) // Check if the file contains the appropriate data diff --git a/phpmon-tests/Versions/AppUpdaterCheckTest.swift b/phpmon-tests/Versions/AppUpdaterCheckTest.swift index a108ac5..04faaae 100644 --- a/phpmon-tests/Versions/AppUpdaterCheckTest.swift +++ b/phpmon-tests/Versions/AppUpdaterCheckTest.swift @@ -10,8 +10,8 @@ import XCTest class AppUpdaterCheckTest: XCTestCase { - func testCanRetrieveVersionFromCask() { - let caskVersion = AppUpdateChecker.retrieveVersionFromCask() + func testCanRetrieveVersionFromCask() async { + let caskVersion = await AppUpdateChecker.retrieveVersionFromCask() let version = VersionExtractor.from(caskVersion) diff --git a/phpmon-tests/Versions/PhpVersionDetectionTest.swift b/phpmon-tests/Versions/PhpVersionDetectionTest.swift index ac92e33..e25b2b5 100644 --- a/phpmon-tests/Versions/PhpVersionDetectionTest.swift +++ b/phpmon-tests/Versions/PhpVersionDetectionTest.swift @@ -10,8 +10,8 @@ import XCTest class PhpVersionDetectionTest: XCTestCase { - func testCanDetectValidPhpVersions() throws { - let outcome = PhpEnv.shared.extractPhpVersions(from: [ + func testCanDetectValidPhpVersions() async throws { + let outcome = await PhpEnv.shared.extractPhpVersions(from: [ "", // empty lines should be omitted "php@8.0", "php@8.0", // should only be detected once diff --git a/phpmon-tests/Versions/ValetVersionExtractorTest.swift b/phpmon-tests/Versions/ValetVersionExtractorTest.swift index aab593c..89c6a63 100644 --- a/phpmon-tests/Versions/ValetVersionExtractorTest.swift +++ b/phpmon-tests/Versions/ValetVersionExtractorTest.swift @@ -10,8 +10,8 @@ import XCTest class ValetVersionExtractorTest: XCTestCase { - func testDetermineValetVersion() { - let version = valet("--version", sudo: false) + func testDetermineValetVersion() async { + let version = await valet("--version", sudo: false) XCTAssert(version.contains("Laravel Valet 2") || version.contains("Laravel Valet 3")) } diff --git a/phpmon/Domain/Menu/MainMenu+Actions.swift b/phpmon/Domain/Menu/MainMenu+Actions.swift index 0e66a57..e4e04f4 100644 --- a/phpmon/Domain/Menu/MainMenu+Actions.swift +++ b/phpmon/Domain/Menu/MainMenu+Actions.swift @@ -47,6 +47,19 @@ extension MainMenu { } } + @objc func restartNginx() { + Task { // Simple restart service + await Actions.restartNginx() + } + } + + @objc func restartDnsMasq() { + Task { // Simple restart service + await Actions.restartDnsMasq() + } + } + + @MainActor @objc func restartValetServices() { Task { // Restart services and show notification await Actions.restartDnsMasq() @@ -73,18 +86,6 @@ extension MainMenu { } } - @objc func restartNginx() { - Task { - await Actions.restartNginx() - } - } - - @objc func restartDnsMasq() { - Task { - await Actions.restartDnsMasq() - } - } - @objc func disableAllXdebugModes() { guard let file = PhpEnv.shared.getConfigFile(forKey: "xdebug.mode") else { Log.info("xdebug.mode could not be found in any .ini file, aborting.") From 45a82b2c9e1e1265e9798f168dd4d9549476a549 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 6 Oct 2022 23:29:13 +0200 Subject: [PATCH 039/181] =?UTF-8?q?=F0=9F=8F=97=20Checked=20and=20fixed=20?= =?UTF-8?q?various=20Task=20{=20}=20blocks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Core/Actions.swift | 2 +- phpmon/Common/Core/Paths.swift | 7 ++- phpmon/Domain/DomainList/AddProxyVC.swift | 2 +- phpmon/Domain/DomainList/AddSiteVC.swift | 4 +- phpmon/Domain/DomainList/DomainListVC.swift | 2 +- .../DomainListWindowController.swift | 4 +- phpmon/Domain/Menu/MainMenu+Actions.swift | 15 ++--- phpmon/Domain/Menu/MainMenu+Startup.swift | 44 ++++++------- phpmon/Domain/Menu/MainMenu+Switcher.swift | 5 +- phpmon/Domain/Menu/MainMenu.swift | 2 +- phpmon/Domain/Notice/BetterAlert.swift | 4 +- phpmon/Domain/Notice/BetterAlertVC.swift | 2 +- .../PHP/ActivePhpInstallation+Checks.swift | 2 +- phpmon/Domain/Preferences/Preferences.swift | 4 +- phpmon/Domain/Presets/Preset.swift | 62 +++++++++---------- .../SwiftUI/Warning/WarningListView.swift | 2 +- phpmon/Next/Testables.swift | 11 +++- 17 files changed, 88 insertions(+), 86 deletions(-) diff --git a/phpmon/Common/Core/Actions.swift b/phpmon/Common/Core/Actions.swift index 0960a79..d48f732 100644 --- a/phpmon/Common/Core/Actions.swift +++ b/phpmon/Common/Core/Actions.swift @@ -139,7 +139,7 @@ class Actions { */ public static func fixMyValet(completed: @escaping () -> Void) { InternalSwitcher().performSwitch(to: PhpEnv.brewPhpAlias, completion: { - Task { // restart all services and fire callback upon completion + Task { // Restart all services asynchronously and fire callback upon completion await brew("services restart dnsmasq", sudo: true) await brew("services restart php", sudo: true) await brew("services restart nginx", sudo: true) diff --git a/phpmon/Common/Core/Paths.swift b/phpmon/Common/Core/Paths.swift index bdf7610..627e07f 100644 --- a/phpmon/Common/Core/Paths.swift +++ b/phpmon/Common/Core/Paths.swift @@ -62,7 +62,12 @@ public class Paths { } public static var homePath: String { - return NSHomeDirectory() + // TODO: Depending on the filesystem abstraction, return the correct information + if Shell is SystemShell { + return NSHomeDirectory() + } + + return "/Users/\(Paths.whoami)" } public static var cellarPath: String { diff --git a/phpmon/Domain/DomainList/AddProxyVC.swift b/phpmon/Domain/DomainList/AddProxyVC.swift index d926e64..f64a1bb 100644 --- a/phpmon/Domain/DomainList/AddProxyVC.swift +++ b/phpmon/Domain/DomainList/AddProxyVC.swift @@ -71,7 +71,7 @@ class AddProxyVC: NSViewController, NSTextFieldDelegate { App.shared.domainListWindowController?.contentVC.setUIBusy() - Task { + Task { // Ensure we proxy the site asynchronously and reload UI on main thread again await Shell.quiet("\(Paths.valet) proxy \(domain) \(proxyName)\(secure)") await Actions.restartNginx() diff --git a/phpmon/Domain/DomainList/AddSiteVC.swift b/phpmon/Domain/DomainList/AddSiteVC.swift index 1af3f1d..de756d5 100644 --- a/phpmon/Domain/DomainList/AddSiteVC.swift +++ b/phpmon/Domain/DomainList/AddSiteVC.swift @@ -71,9 +71,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate { // Adding `valet links` is a workaround for Valet malforming the config.json file // TODO: I will have to investigate and report this behaviour if possible - Task { - await Shell.quiet("cd '\(path)' && \(Paths.valet) link '\(name)' && valet links") - } + Task { await Shell.quiet("cd '\(path)' && \(Paths.valet) link '\(name)' && valet links") } dismissView(outcome: .OK) diff --git a/phpmon/Domain/DomainList/DomainListVC.swift b/phpmon/Domain/DomainList/DomainListVC.swift index b482b27..df410a1 100644 --- a/phpmon/Domain/DomainList/DomainListVC.swift +++ b/phpmon/Domain/DomainList/DomainListVC.swift @@ -137,7 +137,7 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource - Parameter completion: Callback that is fired when the work is done. */ internal func waitAndExecute(_ execute: @escaping () async -> Void, completion: @escaping () -> Void = {}) { - Task { + Task { // Legacy `waitAndExecute` with UI setUIBusy() await execute() diff --git a/phpmon/Domain/DomainList/DomainListWindowController.swift b/phpmon/Domain/DomainList/DomainListWindowController.swift index 947bb54..e3717d4 100644 --- a/phpmon/Domain/DomainList/DomainListWindowController.swift +++ b/phpmon/Domain/DomainList/DomainListWindowController.swift @@ -51,9 +51,7 @@ class DomainListWindowController: PMWindowController, NSSearchFieldDelegate, NST // MARK: - Reload functionality @IBAction func pressedReload(_ sender: Any?) { - Task { - await contentVC.reloadDomains() - } + Task { await contentVC.reloadDomains() } } @IBAction func pressedAddLink(_ sender: Any?) { diff --git a/phpmon/Domain/Menu/MainMenu+Actions.swift b/phpmon/Domain/Menu/MainMenu+Actions.swift index e4e04f4..8cceafd 100644 --- a/phpmon/Domain/Menu/MainMenu+Actions.swift +++ b/phpmon/Domain/Menu/MainMenu+Actions.swift @@ -59,7 +59,6 @@ extension MainMenu { } } - @MainActor @objc func restartValetServices() { Task { // Restart services and show notification await Actions.restartDnsMasq() @@ -135,7 +134,7 @@ extension MainMenu { } @objc func toggleExtension(sender: ExtensionMenuItem) { - Task { + Task { // Toggle extension async await sender.phpExtension?.toggle() if Preferences.isEnabled(.autoServiceRestartAfterExtensionToggle) { @@ -145,7 +144,7 @@ extension MainMenu { } private func performRollback() { - Task { + Task { // Rollback preset async await PresetHelper.rollbackPreset?.apply() PresetHelper.rollbackPreset = nil MainMenu.shared.rebuild() @@ -172,7 +171,7 @@ extension MainMenu { } @objc func togglePreset(sender: PresetMenuItem) { - Task { + Task { // Apply preset async await sender.preset?.apply() } } @@ -193,7 +192,7 @@ extension MainMenu { @objc func openPhpInfo() { asyncWithBusyUI { - Task { + Task { // Create temporary file and open the URL let url = await Actions.createTempPhpInfoFile() NSWorkspace.shared.open(url) } @@ -257,10 +256,8 @@ extension MainMenu { /** This async-friendly version of the switcher can be invoked elsewhere in the app: ``` - Task { - await MainMenu.shared.switchToPhp("8.1") - // thing to do after the switch - } + await MainMenu.shared.switchToPhp("8.1") + // thing to do after the switch ``` Since this async function uses `withCheckedContinuation` any code after will run only after the switcher is done. diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index c20b650..e7566b6 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -70,7 +70,7 @@ extension MainMenu { App.shared.handlePhpConfigWatcher() // Detect built-in and custom applications - detectApplications() + await detectApplications() // Load the rollback preset PresetHelper.loadRollbackPresetFromFile() @@ -135,7 +135,9 @@ extension MainMenu { }) .show() - Task { await startup() } + Task { // An issue occurred, fire startup checks again after dismissal + await startup() + } } } @@ -157,30 +159,28 @@ extension MainMenu { /** Detect which applications are installed that can be used to open a domain's source directory. */ - private func detectApplications() { - Task { - Log.info("Detecting applications...") + private func detectApplications() async { + Log.info("Detecting applications...") - App.shared.detectedApplications = await Application.detectPresetApplications() + App.shared.detectedApplications = await Application.detectPresetApplications() - let customApps = Preferences.custom.scanApps?.map { appName in - return Application(appName, .user_supplied) - } ?? [] + let customApps = Preferences.custom.scanApps?.map { appName in + return Application(appName, .user_supplied) + } ?? [] - var detectedCustomApps: [Application] = [] + var detectedCustomApps: [Application] = [] - for app in customApps where await app.isInstalled() { - detectedCustomApps.append(app) - } - - App.shared.detectedApplications - .append(contentsOf: detectedCustomApps) - - let appNames = App.shared.detectedApplications.map { app in - return app.name - } - - Log.info("Detected applications: \(appNames)") + for app in customApps where await app.isInstalled() { + detectedCustomApps.append(app) } + + App.shared.detectedApplications + .append(contentsOf: detectedCustomApps) + + let appNames = App.shared.detectedApplications.map { app in + return app.name + } + + Log.info("Detected applications: \(appNames)") } } diff --git a/phpmon/Domain/Menu/MainMenu+Switcher.swift b/phpmon/Domain/Menu/MainMenu+Switcher.swift index e8427a0..bc14115 100644 --- a/phpmon/Domain/Menu/MainMenu+Switcher.swift +++ b/phpmon/Domain/Menu/MainMenu+Switcher.swift @@ -18,8 +18,7 @@ extension MainMenu { // Mark as no longer busy PhpEnv.shared.isBusy = false - // Reload the site list - Task { + Task { // Things to do after reloading domain list data await self.reloadDomainListData() // Perform UI updates on main thread @@ -55,7 +54,7 @@ extension MainMenu { } @MainActor private func checkForPlatformIssues() { - Task { + Task { // Asynchronously check for platform issues if await Valet.shared.hasPlatformIssues() { Log.info("Composer platform issue(s) detected.") self.suggestFixMyComposer() diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index e41fcfc..aaa87ca 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -194,7 +194,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate func menuWillOpen(_ menu: NSMenu) { // Make sure the shortcut key does not trigger this when the menu is open App.shared.shortcutHotkey?.isPaused = true - Task { + Task { // Reload Homebrew services information asynchronously await ServicesManager.loadHomebrewServices() } } diff --git a/phpmon/Domain/Notice/BetterAlert.swift b/phpmon/Domain/Notice/BetterAlert.swift index 8013949..4f46e28 100644 --- a/phpmon/Domain/Notice/BetterAlert.swift +++ b/phpmon/Domain/Notice/BetterAlert.swift @@ -32,7 +32,7 @@ class BetterAlert { public func withPrimary( text: String, action: @escaping (BetterAlertVC) -> Void = { vc in - vc.close(with: .alertFirstButtonReturn) + DispatchQueue.main.async { vc.close(with: .alertFirstButtonReturn) } } ) -> Self { self.noticeVC.buttonPrimary.title = text @@ -43,7 +43,7 @@ class BetterAlert { public func withSecondary( text: String, action: ((BetterAlertVC) -> Void)? = { vc in - vc.close(with: .alertSecondButtonReturn) + DispatchQueue.main.async { vc.close(with: .alertSecondButtonReturn) } } ) -> Self { self.noticeVC.buttonSecondary.title = text diff --git a/phpmon/Domain/Notice/BetterAlertVC.swift b/phpmon/Domain/Notice/BetterAlertVC.swift index aad5ff4..eaca848 100644 --- a/phpmon/Domain/Notice/BetterAlertVC.swift +++ b/phpmon/Domain/Notice/BetterAlertVC.swift @@ -70,7 +70,7 @@ class BetterAlertVC: NSViewController { } } - public func close(with code: NSApplication.ModalResponse) { + @MainActor public func close(with code: NSApplication.ModalResponse) { self.view.window?.close() NSApplication.shared.stopModal(withCode: code) } diff --git a/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift b/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift index c126f20..99338ba 100644 --- a/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift +++ b/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift @@ -19,7 +19,7 @@ extension ActivePhpInstallation { This method actively presents a modal if said checks fails, so don't call this method too many times. */ public func notifyAboutBrokenPhpFpm() { - Task { + Task { // Determine whether FPM status is configured correctly in the background let fpmStatusConfiguredCorrectly = await self.checkPhpFpmStatus() if fpmStatusConfiguredCorrectly { diff --git a/phpmon/Domain/Preferences/Preferences.swift b/phpmon/Domain/Preferences/Preferences.swift index 02a5e1e..a38e3f0 100644 --- a/phpmon/Domain/Preferences/Preferences.swift +++ b/phpmon/Domain/Preferences/Preferences.swift @@ -28,9 +28,7 @@ class Preferences { environmentVariables: [:] ) - Task { - await loadCustomPreferences() - } + Task { await loadCustomPreferences() } } // MARK: - First Time Run diff --git a/phpmon/Domain/Presets/Preset.swift b/phpmon/Domain/Presets/Preset.swift index bd7eeae..297190b 100644 --- a/phpmon/Domain/Presets/Preset.swift +++ b/phpmon/Domain/Presets/Preset.swift @@ -68,52 +68,52 @@ struct Preset: Codable, Equatable { Applies a given preset. */ public func apply() async { - Task { - // Was this a rollback? - let wasRollback = (self.name == "AutomaticRevertSnapshot") + // Was this a rollback? + let wasRollback = (self.name == "AutomaticRevertSnapshot") - // Save the preset that would revert this preset - await self.persistRevert() + // Save the preset that would revert this preset + await self.persistRevert() - // Apply the PHP version if is considered a valid version - if self.version != nil { - if await !switchToPhpVersionIfValid() { - PresetHelper.rollbackPreset = nil - await Actions.restartPhpFpm() - return - } + // Apply the PHP version if is considered a valid version + if self.version != nil { + if await !switchToPhpVersionIfValid() { + PresetHelper.rollbackPreset = nil + await Actions.restartPhpFpm() + return } + } - // Apply the configuration changes first - for conf in configuration { - applyConfigurationValue(key: conf.key, value: conf.value ?? "") + // Apply the configuration changes first + for conf in configuration { + applyConfigurationValue(key: conf.key, value: conf.value ?? "") + } + + // Apply the extension changes in-place afterward + for ext in extensions { + for foundExt in PhpEnv.phpInstall.extensions + where foundExt.name == ext.key && foundExt.enabled != ext.value { + Log.info("Toggling extension \(foundExt.name) in \(foundExt.file)") + await foundExt.toggle() + break } + } - // Apply the extension changes in-place afterward - for ext in extensions { - for foundExt in PhpEnv.phpInstall.extensions - where foundExt.name == ext.key && foundExt.enabled != ext.value { - Log.info("Toggling extension \(foundExt.name) in \(foundExt.file)") - await foundExt.toggle() - break - } - } + // Reload what rollback file exists + PresetHelper.loadRollbackPresetFromFile() - // Reload what rollback file exists - PresetHelper.loadRollbackPresetFromFile() - - // Restart PHP FPM process (also reloads menu, which will show the preset rollback) - await Actions.restartPhpFpm() + // Restart PHP FPM process (also reloads menu, which will show the preset rollback) + await Actions.restartPhpFpm() + DispatchQueue.main.async { // Show the correct notification if wasRollback { - await LocalNotification.send( + LocalNotification.send( title: "notification.preset_reverted_title".localized, subtitle: "notification.preset_reverted_desc".localized, preference: .notifyAboutPresets ) } else { - await LocalNotification.send( + LocalNotification.send( title: "notification.preset_applied_title".localized, subtitle: "notification.preset_applied_desc".localized(self.name), preference: .notifyAboutPresets diff --git a/phpmon/Domain/SwiftUI/Warning/WarningListView.swift b/phpmon/Domain/SwiftUI/Warning/WarningListView.swift index c250c63..4f0380f 100644 --- a/phpmon/Domain/SwiftUI/Warning/WarningListView.swift +++ b/phpmon/Domain/SwiftUI/Warning/WarningListView.swift @@ -38,7 +38,7 @@ struct WarningListView: View { HStack(alignment: .center, spacing: 15) { Button("warnings.refresh.button".localizedForSwiftUI) { - Task { + Task { // Reload warnings await WarningManager.shared.checkEnvironment() self.warnings = WarningManager.shared.warnings } diff --git a/phpmon/Next/Testables.swift b/phpmon/Next/Testables.swift index efa800e..bb76d34 100644 --- a/phpmon/Next/Testables.swift +++ b/phpmon/Next/Testables.swift @@ -45,8 +45,10 @@ class Testables { : .fake(.binary) ], shellOutput: [ + "sysctl -n sysctl.proc_translated" + : .instant("0"), "id -un" - : .instant("username"), + : .instant("nicoverbruggen"), "which node" : .instant("/opt/homebrew/bin/node"), "php -v" @@ -77,8 +79,12 @@ class Testables { nicoverbruggen/cask shivammathur/php """), + "chmod +x /Users/nicoverbruggen/.config/phpmon/bin/pm81" + : .instant(""), "mkdir -p ~/.config/phpmon" : .instant(""), + "mkdir -p ~/.config/phpmon/bin" + : .instant(""), "/opt/homebrew/bin/brew info php --json" : .instant(ShellStrings.brewJson), "brew info shivammathur/php/php --json" @@ -92,7 +98,8 @@ class Testables { "/usr/bin/open -Ra \"Sublime Merge\"" : .instant("Unable to find application named 'Sublime Merge'", .stdErr), "/usr/bin/open -Ra \"iTerm\"" - : .instant("Unable to find application named 'iTerm'", .stdErr) + : .instant("Unable to find application named 'iTerm'", .stdErr), + ] ) } From 6feb8118d93007a3dca4340aca813d4b853042fa Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 6 Oct 2022 23:34:31 +0200 Subject: [PATCH 040/181] =?UTF-8?q?=F0=9F=9A=9B=20Move=20files=20around?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 38 +++++++++++++------ .../{Next => Common/Shell}/ActiveShell.swift | 0 phpmon/{Next => Common/Shell}/Shellable.swift | 0 .../{Next => Common/Shell}/SystemShell.swift | 0 .../Testables/TestableConfigurations.swift} | 4 +- .../Testables}/TestableFilesystem.swift | 0 .../Testables}/TestableShell.swift | 0 7 files changed, 29 insertions(+), 13 deletions(-) rename phpmon/{Next => Common/Shell}/ActiveShell.swift (100%) rename phpmon/{Next => Common/Shell}/Shellable.swift (100%) rename phpmon/{Next => Common/Shell}/SystemShell.swift (100%) rename phpmon/{Next/Testables.swift => Common/Testables/TestableConfigurations.swift} (98%) rename phpmon/{Next => Common/Testables}/TestableFilesystem.swift (100%) rename phpmon/{Next => Common/Testables}/TestableShell.swift (100%) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index deb4580..275ecaf 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -59,7 +59,7 @@ C40C7F2927721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */; }; C40C7F3027722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; }; C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; }; - C40F505628ECA64E004AD45B /* Testables.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40F505428ECA64E004AD45B /* Testables.swift */; }; + C40F505628ECA64E004AD45B /* TestableConfigurations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40F505428ECA64E004AD45B /* TestableConfigurations.swift */; }; C40FE737282ABA4F00A302C2 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; C40FE738282ABA4F00A302C2 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; C40FE73B282ABB2E00A302C2 /* AppVersionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE739282ABB2E00A302C2 /* AppVersionTest.swift */; }; @@ -211,7 +211,7 @@ C4AD38B228ECD9D300FA8D83 /* TestableFilesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFilesystem.swift */; }; C4AD38B328ECD9D300FA8D83 /* TestableFilesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFilesystem.swift */; }; C4AD38B528ECE2DB00FA8D83 /* brew-formula.json in Resources */ = {isa = PBXBuildFile; fileRef = C43A8A1F25D9D1D700591B77 /* brew-formula.json */; }; - C4AD38B628ECE56D00FA8D83 /* Testables.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40F505428ECA64E004AD45B /* Testables.swift */; }; + C4AD38B628ECE56D00FA8D83 /* TestableConfigurations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40F505428ECA64E004AD45B /* TestableConfigurations.swift */; }; C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AF9F70275445FF00D44ED0 /* valet-config.json */; }; C4AF9F78275447F100D44ED0 /* ValetConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F76275447F100D44ED0 /* ValetConfigurationTest.swift */; }; C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F792754499000D44ED0 /* Valet.swift */; }; @@ -365,7 +365,7 @@ C40C7F1D2772136000DDDCDC /* PhpEnv.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpEnv.swift; sourceTree = ""; }; C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ActivePhpInstallation+Checks.swift"; sourceTree = ""; }; C40C7F2F27722E8D00DDDCDC /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; - C40F505428ECA64E004AD45B /* Testables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Testables.swift; sourceTree = ""; }; + C40F505428ECA64E004AD45B /* TestableConfigurations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableConfigurations.swift; sourceTree = ""; }; C40FE736282ABA4F00A302C2 /* AppVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersion.swift; sourceTree = ""; }; C40FE739282ABB2E00A302C2 /* AppVersionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionTest.swift; sourceTree = ""; }; C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackage.swift; sourceTree = ""; }; @@ -881,12 +881,6 @@ C46EBC3F28DB9550007ACC74 /* Next */ = { isa = PBXGroup; children = ( - 03E36FE628D9219000636F7F /* ActiveShell.swift */, - C46EBC4628DB9644007ACC74 /* SystemShell.swift */, - C46EBC4928DB966A007ACC74 /* TestableShell.swift */, - C46EBC4328DB95F0007ACC74 /* Shellable.swift */, - C40F505428ECA64E004AD45B /* Testables.swift */, - C4AD38B128ECD9D300FA8D83 /* TestableFilesystem.swift */, ); path = Next; sourceTree = ""; @@ -992,6 +986,8 @@ C4B5853A2770FE2500DA4FBE /* Common */ = { isa = PBXGroup; children = ( + C4F787A728EF812600790735 /* Testables */, + C4F787A628EF811000790735 /* Shell */, C40C7F2127721F7300DDDCDC /* Core */, 54B20EDF263AA22C00D3250E /* PHP */, C44CCD4327AFE93300CE40E5 /* Errors */, @@ -1196,6 +1192,26 @@ path = "phpmon-tests"; sourceTree = ""; }; + C4F787A628EF811000790735 /* Shell */ = { + isa = PBXGroup; + children = ( + 03E36FE628D9219000636F7F /* ActiveShell.swift */, + C46EBC4628DB9644007ACC74 /* SystemShell.swift */, + C46EBC4328DB95F0007ACC74 /* Shellable.swift */, + ); + path = Shell; + sourceTree = ""; + }; + C4F787A728EF812600790735 /* Testables */ = { + isa = PBXGroup; + children = ( + C40F505428ECA64E004AD45B /* TestableConfigurations.swift */, + C46EBC4928DB966A007ACC74 /* TestableShell.swift */, + C4AD38B128ECD9D300FA8D83 /* TestableFilesystem.swift */, + ); + path = Testables; + sourceTree = ""; + }; C4F8C0A222D4F100002EFE61 /* Extensions */ = { isa = PBXGroup; children = ( @@ -1446,7 +1462,7 @@ C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */, C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */, C46EBC4728DB9644007ACC74 /* SystemShell.swift in Sources */, - C4AD38B628ECE56D00FA8D83 /* Testables.swift in Sources */, + C4AD38B628ECE56D00FA8D83 /* TestableConfigurations.swift in Sources */, C4068CAA27B0890D00544CD5 /* MenuBarIcons.swift in Sources */, C44264C02850BD2A007400F1 /* VersionPopoverView.swift in Sources */, C4C8E81B276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */, @@ -1617,7 +1633,7 @@ C44C198E276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */, C485707828BF456300539B36 /* Warning.swift in Sources */, C415938027A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */, - C40F505628ECA64E004AD45B /* Testables.swift in Sources */, + C40F505628ECA64E004AD45B /* TestableConfigurations.swift in Sources */, C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */, C449B4F227EE7FC400C47E8A /* DomainListPhpCell.swift in Sources */, C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */, diff --git a/phpmon/Next/ActiveShell.swift b/phpmon/Common/Shell/ActiveShell.swift similarity index 100% rename from phpmon/Next/ActiveShell.swift rename to phpmon/Common/Shell/ActiveShell.swift diff --git a/phpmon/Next/Shellable.swift b/phpmon/Common/Shell/Shellable.swift similarity index 100% rename from phpmon/Next/Shellable.swift rename to phpmon/Common/Shell/Shellable.swift diff --git a/phpmon/Next/SystemShell.swift b/phpmon/Common/Shell/SystemShell.swift similarity index 100% rename from phpmon/Next/SystemShell.swift rename to phpmon/Common/Shell/SystemShell.swift diff --git a/phpmon/Next/Testables.swift b/phpmon/Common/Testables/TestableConfigurations.swift similarity index 98% rename from phpmon/Next/Testables.swift rename to phpmon/Common/Testables/TestableConfigurations.swift index bb76d34..44b7d97 100644 --- a/phpmon/Next/Testables.swift +++ b/phpmon/Common/Testables/TestableConfigurations.swift @@ -1,5 +1,5 @@ // -// Testables.swift +// TestableConfigurations.swift // PHP Monitor // // Created by Nico Verbruggen on 04/10/2022. @@ -15,7 +15,7 @@ struct TestableConfiguration { } // swiftlint:disable colon trailing_comma -class Testables { +class TestableConfigurations { static var broken: TestableConfiguration { return TestableConfiguration( architecture: "arm64", diff --git a/phpmon/Next/TestableFilesystem.swift b/phpmon/Common/Testables/TestableFilesystem.swift similarity index 100% rename from phpmon/Next/TestableFilesystem.swift rename to phpmon/Common/Testables/TestableFilesystem.swift diff --git a/phpmon/Next/TestableShell.swift b/phpmon/Common/Testables/TestableShell.swift similarity index 100% rename from phpmon/Next/TestableShell.swift rename to phpmon/Common/Testables/TestableShell.swift From 03cf4ef3e4e2b14a6f9e9220e01323d2e2eaa063 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 6 Oct 2022 23:35:14 +0200 Subject: [PATCH 041/181] =?UTF-8?q?=F0=9F=91=8C=20Added=20various=20TODO?= =?UTF-8?q?=20items?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Testables/TestableShell.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/phpmon/Common/Testables/TestableShell.swift b/phpmon/Common/Testables/TestableShell.swift index 1a97a10..aae56b3 100644 --- a/phpmon/Common/Testables/TestableShell.swift +++ b/phpmon/Common/Testables/TestableShell.swift @@ -33,6 +33,8 @@ public class TestableShell: Shellable { didReceiveOutput: @escaping (String, ShellStream) -> Void, withTimeout timeout: TimeInterval ) async throws -> (Process, ShellOutput) { + // TODO: Add delay to track down issues + // TODO: Remove assertion assert(expectations.keys.contains(command), "No response declared for command: \(command)") guard let expectation = expectations[command] else { From 6db5cdec25017f70de23562346971923b72f4215 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 7 Oct 2022 22:55:48 +0200 Subject: [PATCH 042/181] =?UTF-8?q?=F0=9F=90=9B=20Introduce=20SLOW=20SHELL?= =?UTF-8?q?=20and=20fix=20a=20few=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcschemes/PHP Monitor.xcscheme | 5 ++++ phpmon/Common/Helpers/Filesystem.swift | 23 ++++++++++++++++--- phpmon/Common/PHP/PHP Version/PhpHelper.swift | 8 +++++-- phpmon/Common/PHP/PhpExtension.swift | 4 ++++ phpmon/Common/Shell/SystemShell.swift | 8 +++++++ .../Testables/TestableConfigurations.swift | 1 - phpmon/Common/Testables/TestableShell.swift | 11 +++++++-- phpmon/Domain/Menu/MainMenu+Switcher.swift | 2 +- phpmon/Domain/Menu/MainMenu.swift | 4 +++- 9 files changed, 56 insertions(+), 10 deletions(-) diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme index 1a553ad..0ed1bb3 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme @@ -73,6 +73,11 @@ value = "" isEnabled = "NO"> + + Bool { + return FileManager.default.isExecutableFile( + atPath: path.replacingTildeWithHomeDirectory + ) + } + /** Checks if a file or directory exists at the provided path. */ public static func exists(_ path: String) -> Bool { return FileManager.default.fileExists( - atPath: path.replacingOccurrences(of: "~", with: Paths.homePath) + atPath: path.replacingTildeWithHomeDirectory ) } @@ -26,7 +35,7 @@ class Filesystem { public static func fileExists(_ path: String) -> Bool { var isDirectory: ObjCBool = true let exists = FileManager.default.fileExists( - atPath: path.replacingOccurrences(of: "~", with: Paths.homePath), + atPath: path.replacingTildeWithHomeDirectory, isDirectory: &isDirectory ) @@ -39,7 +48,7 @@ class Filesystem { public static func directoryExists(_ path: String) -> Bool { var isDirectory: ObjCBool = true let exists = FileManager.default.fileExists( - atPath: path.replacingOccurrences(of: "~", with: Paths.homePath), + atPath: path.replacingTildeWithHomeDirectory, isDirectory: &isDirectory ) @@ -59,3 +68,11 @@ class Filesystem { } } + +extension String { + + var replacingTildeWithHomeDirectory: String { + return self.replacingOccurrences(of: "~", with: Paths.homePath) + } + +} diff --git a/phpmon/Common/PHP/PHP Version/PhpHelper.swift b/phpmon/Common/PHP/PHP Version/PhpHelper.swift index b0a8064..50fe730 100644 --- a/phpmon/Common/PHP/PHP Version/PhpHelper.swift +++ b/phpmon/Common/PHP/PHP Version/PhpHelper.swift @@ -27,7 +27,9 @@ class PhpHelper { Task { // Create the appropriate folders and check if the files exist do { - await Shell.quiet("mkdir -p ~/.config/phpmon/bin") + if !Filesystem.directoryExists("~/.config/phpmon/bin") { + await Shell.quiet("mkdir -p ~/.config/phpmon/bin") + } if Filesystem.fileExists(destination) { let contents = try String(contentsOfFile: destination) @@ -62,7 +64,9 @@ class PhpHelper { ) // Make sure the file is executable - await Shell.quiet("chmod +x \(destination)") + if !Filesystem.isExecutableFile(destination) { + await Shell.quiet("chmod +x \(destination)") + } // Create a symlink if the folder is not in the PATH if !inPath { diff --git a/phpmon/Common/PHP/PhpExtension.swift b/phpmon/Common/PHP/PhpExtension.swift index b6e7d7e..df20d07 100644 --- a/phpmon/Common/PHP/PhpExtension.swift +++ b/phpmon/Common/PHP/PhpExtension.swift @@ -85,6 +85,10 @@ class PhpExtension { await sed(file: file, original: line, replacement: newLine) enabled.toggle() + + DispatchQueue.main.async { + MainMenu.shared.rebuild(async: false) + } } // MARK: - Static Methods diff --git a/phpmon/Common/Shell/SystemShell.swift b/phpmon/Common/Shell/SystemShell.swift index 17a214a..e5a4ed2 100644 --- a/phpmon/Common/Shell/SystemShell.swift +++ b/phpmon/Common/Shell/SystemShell.swift @@ -88,6 +88,14 @@ class SystemShell: Shellable { let outputPipe = Pipe() let errorPipe = Pipe() + // Seriously slow down how long it takes for the shell to return output + // (in order to debug or identify async issues) + if ProcessInfo.processInfo.environment["SLOW_SHELL_MODE"] != nil { + print("[SLOW SHELL] \(command)") + let delayInSeconds = 3 + try! await Task.sleep(nanoseconds: UInt64(delayInSeconds * 1_000_000_000)) + } + task.standardOutput = outputPipe task.standardError = errorPipe task.launch() diff --git a/phpmon/Common/Testables/TestableConfigurations.swift b/phpmon/Common/Testables/TestableConfigurations.swift index 44b7d97..6d490e7 100644 --- a/phpmon/Common/Testables/TestableConfigurations.swift +++ b/phpmon/Common/Testables/TestableConfigurations.swift @@ -99,7 +99,6 @@ class TestableConfigurations { : .instant("Unable to find application named 'Sublime Merge'", .stdErr), "/usr/bin/open -Ra \"iTerm\"" : .instant("Unable to find application named 'iTerm'", .stdErr), - ] ) } diff --git a/phpmon/Common/Testables/TestableShell.swift b/phpmon/Common/Testables/TestableShell.swift index aae56b3..05a4ee3 100644 --- a/phpmon/Common/Testables/TestableShell.swift +++ b/phpmon/Common/Testables/TestableShell.swift @@ -33,8 +33,15 @@ public class TestableShell: Shellable { didReceiveOutput: @escaping (String, ShellStream) -> Void, withTimeout timeout: TimeInterval ) async throws -> (Process, ShellOutput) { - // TODO: Add delay to track down issues - // TODO: Remove assertion + + // Seriously slow down the shell's return rate in order to debug or identify async issues + if ProcessInfo.processInfo.environment["SLOW_SHELL_MODE"] != nil { + print("[SLOW SHELL] \(command)") + let delayInSeconds = 3 + try! await Task.sleep(nanoseconds: UInt64(delayInSeconds * 1_000_000_000)) + } + + // This assertion will only fire during test builds assert(expectations.keys.contains(command), "No response declared for command: \(command)") guard let expectation = expectations[command] else { diff --git a/phpmon/Domain/Menu/MainMenu+Switcher.swift b/phpmon/Domain/Menu/MainMenu+Switcher.swift index bc14115..58a3df2 100644 --- a/phpmon/Domain/Menu/MainMenu+Switcher.swift +++ b/phpmon/Domain/Menu/MainMenu+Switcher.swift @@ -118,6 +118,6 @@ extension MainMenu { preference: .notifyAboutVersionChange ) - Task { await PhpEnv.phpInstall.notifyAboutBrokenPhpFpm() } + Task { PhpEnv.phpInstall.notifyAboutBrokenPhpFpm() } } } diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index aaa87ca..4744b99 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -106,7 +106,9 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate @MainActor @objc func reloadPhpMonitorMenuInForeground() async { refreshActiveInstallation() refreshIcon() - rebuild(async: false) + DispatchQueue.main.async { + self.rebuild(async: false) + } await ServicesManager.loadHomebrewServices() } From f5d2ec2b7e31b47c8d7eff4e5995a8430ca860a6 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 8 Oct 2022 00:27:29 +0200 Subject: [PATCH 043/181] =?UTF-8?q?=F0=9F=91=8C=20Add=20`delay`=20global?= =?UTF-8?q?=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Core/Helpers.swift | 8 ++++++++ phpmon/Common/Shell/SystemShell.swift | 3 +-- phpmon/Common/Testables/TestableShell.swift | 6 ++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/phpmon/Common/Core/Helpers.swift b/phpmon/Common/Core/Helpers.swift index ba20a29..56082a1 100644 --- a/phpmon/Common/Core/Helpers.swift +++ b/phpmon/Common/Core/Helpers.swift @@ -49,3 +49,11 @@ func grepContains(file: String, query: String) async -> Bool { .trimmingCharacters(in: .whitespacesAndNewlines) .contains("YES") } + +/** + Attempts to introduce sleep for a particular duration. Use with caution. + Only intended for testing purposes. + */ +func delay(seconds: Double) async { + try! await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000)) +} diff --git a/phpmon/Common/Shell/SystemShell.swift b/phpmon/Common/Shell/SystemShell.swift index e5a4ed2..652b78c 100644 --- a/phpmon/Common/Shell/SystemShell.swift +++ b/phpmon/Common/Shell/SystemShell.swift @@ -92,8 +92,7 @@ class SystemShell: Shellable { // (in order to debug or identify async issues) if ProcessInfo.processInfo.environment["SLOW_SHELL_MODE"] != nil { print("[SLOW SHELL] \(command)") - let delayInSeconds = 3 - try! await Task.sleep(nanoseconds: UInt64(delayInSeconds * 1_000_000_000)) + await delay(seconds: 3.0) } task.standardOutput = outputPipe diff --git a/phpmon/Common/Testables/TestableShell.swift b/phpmon/Common/Testables/TestableShell.swift index 05a4ee3..578c871 100644 --- a/phpmon/Common/Testables/TestableShell.swift +++ b/phpmon/Common/Testables/TestableShell.swift @@ -37,8 +37,7 @@ public class TestableShell: Shellable { // Seriously slow down the shell's return rate in order to debug or identify async issues if ProcessInfo.processInfo.environment["SLOW_SHELL_MODE"] != nil { print("[SLOW SHELL] \(command)") - let delayInSeconds = 3 - try! await Task.sleep(nanoseconds: UInt64(delayInSeconds * 1_000_000_000)) + await delay(seconds: 3.0) } // This assertion will only fire during test builds @@ -100,8 +99,7 @@ struct BatchFakeShellOutput { for item in items { if !ignoreDelay { - let delay = UInt64(item.delay * 1_000_000_000) - try! await Task.sleep(nanoseconds: delay) + await delay(seconds: item.delay) } if item.stream == .stdErr { From b0f27dcfa5869c94bcb6a3f4d373286f3f9ec5e4 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 8 Oct 2022 01:04:51 +0200 Subject: [PATCH 044/181] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Filesystem=20to=20?= =?UTF-8?q?FileSystem=20/=20ActiveFileSystem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 62 ++++++++++++------- phpmon/Common/Core/Helpers.swift | 2 +- phpmon/Common/Core/Paths.swift | 6 +- .../Common/Filesystem/ActiveFileSystem.swift | 26 ++++++++ .../Filesystem/FileSystemProtocol.swift | 36 +++++++++++ .../RealFileSystem.swift} | 31 ++++------ phpmon/Common/PHP/ActivePhpInstallation.swift | 2 +- phpmon/Common/PHP/PHP Version/PhpEnv.swift | 4 +- phpmon/Common/PHP/PHP Version/PhpHelper.swift | 10 +-- phpmon/Common/PHP/PhpInstallation.swift | 2 +- phpmon/Common/Shell/ActiveShell.swift | 6 +- .../{SystemShell.swift => RealShell.swift} | 6 +- .../{Shellable.swift => ShellProtocol.swift} | 46 +++++++------- .../Common/Testables/TestableFileSystem.swift | 49 +++++++++++++++ .../Common/Testables/TestableFilesystem.swift | 24 ------- phpmon/Common/Testables/TestableShell.swift | 2 +- phpmon/Domain/App/Startup.swift | 6 +- phpmon/Domain/DomainList/AddSiteVC.swift | 2 +- .../Integrations/Composer/PhpFrameworks.swift | 2 +- .../Valet/Proxies/ValetProxy.swift | 2 +- .../Integrations/Valet/Sites/ValetSite.swift | 8 +-- phpmon/Domain/Preferences/CustomPrefs.swift | 6 +- phpmon/Domain/Watcher/PhpConfigWatcher.swift | 2 +- 23 files changed, 219 insertions(+), 123 deletions(-) create mode 100644 phpmon/Common/Filesystem/ActiveFileSystem.swift create mode 100644 phpmon/Common/Filesystem/FileSystemProtocol.swift rename phpmon/Common/{Helpers/Filesystem.swift => Filesystem/RealFileSystem.swift} (79%) rename phpmon/Common/Shell/{SystemShell.swift => RealShell.swift} (97%) rename phpmon/Common/Shell/{Shellable.swift => ShellProtocol.swift} (97%) create mode 100644 phpmon/Common/Testables/TestableFileSystem.swift delete mode 100644 phpmon/Common/Testables/TestableFilesystem.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 275ecaf..3258cb4 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -74,8 +74,6 @@ C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */; }; C417DC74277614690015E6EE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; }; C417DC75277614690015E6EE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; }; - C4188989275FE8CB001EF227 /* Filesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4188988275FE8CB001EF227 /* Filesystem.swift */; }; - C418898A275FE8CB001EF227 /* Filesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4188988275FE8CB001EF227 /* Filesystem.swift */; }; C41C02A627E60D7A009F26CB /* SiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A527E60D7A009F26CB /* SiteScanner.swift */; }; C41C02A927E61A65009F26CB /* ValetSite+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */; }; C41C02AA27E61CA3009F26CB /* SiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A527E60D7A009F26CB /* SiteScanner.swift */; }; @@ -150,10 +148,10 @@ C46E206D28299B3800D909D6 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; C46E206E28299B3800D909D6 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; C46E20702829D27F00D909D6 /* AppUpdaterCheckTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206F2829D27F00D909D6 /* AppUpdaterCheckTest.swift */; }; - C46EBC4428DB95F0007ACC74 /* Shellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* Shellable.swift */; }; - C46EBC4528DB95F0007ACC74 /* Shellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* Shellable.swift */; }; - C46EBC4728DB9644007ACC74 /* SystemShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4628DB9644007ACC74 /* SystemShell.swift */; }; - C46EBC4828DB9644007ACC74 /* SystemShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4628DB9644007ACC74 /* SystemShell.swift */; }; + C46EBC4428DB95F0007ACC74 /* ShellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */; }; + C46EBC4528DB95F0007ACC74 /* ShellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */; }; + C46EBC4728DB9644007ACC74 /* RealShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4628DB9644007ACC74 /* RealShell.swift */; }; + C46EBC4828DB9644007ACC74 /* RealShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4628DB9644007ACC74 /* RealShell.swift */; }; C46EBC4A28DB966A007ACC74 /* TestableShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4928DB966A007ACC74 /* TestableShell.swift */; }; C46EBC4B28DB966A007ACC74 /* TestableShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4928DB966A007ACC74 /* TestableShell.swift */; }; C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; }; @@ -208,8 +206,8 @@ C4A81CA528C67101008DD9D1 /* PMTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A81CA328C67101008DD9D1 /* PMTableView.swift */; }; C4AC51FC27E27F47008528CA /* DomainListKindCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */; }; C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; }; - C4AD38B228ECD9D300FA8D83 /* TestableFilesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFilesystem.swift */; }; - C4AD38B328ECD9D300FA8D83 /* TestableFilesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFilesystem.swift */; }; + C4AD38B228ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */; }; + C4AD38B328ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */; }; C4AD38B528ECE2DB00FA8D83 /* brew-formula.json in Resources */ = {isa = PBXBuildFile; fileRef = C43A8A1F25D9D1D700591B77 /* brew-formula.json */; }; C4AD38B628ECE56D00FA8D83 /* TestableConfigurations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40F505428ECA64E004AD45B /* TestableConfigurations.swift */; }; C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AF9F70275445FF00D44ED0 /* valet-config.json */; }; @@ -247,6 +245,9 @@ C4C3643A28AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */; }; C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; }; C4C3ED4327834C5200AB15D8 /* CustomPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */; }; + C4C8900328F0E28800CE5E97 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; }; + C4C8900528F0E3D100CE5E97 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; }; + C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */; }; C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; }; C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; }; C4C8E81B276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */; }; @@ -376,7 +377,6 @@ C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+InterApp.swift"; sourceTree = ""; }; C4168F4427ADB4A3003B6C39 /* DEVELOPER.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = DEVELOPER.md; sourceTree = ""; }; C417DC73277614690015E6EE /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; - C4188988275FE8CB001EF227 /* Filesystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filesystem.swift; sourceTree = ""; }; C41C02A527E60D7A009F26CB /* SiteScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteScanner.swift; sourceTree = ""; }; C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ValetSite+Fake.swift"; sourceTree = ""; }; C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PHP Monitor.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -430,8 +430,8 @@ C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListCellProtocol.swift; sourceTree = ""; }; C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateChecker.swift; sourceTree = ""; }; C46E206F2829D27F00D909D6 /* AppUpdaterCheckTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppUpdaterCheckTest.swift; sourceTree = ""; }; - C46EBC4328DB95F0007ACC74 /* Shellable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shellable.swift; sourceTree = ""; }; - C46EBC4628DB9644007ACC74 /* SystemShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemShell.swift; sourceTree = ""; }; + C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellProtocol.swift; sourceTree = ""; }; + C46EBC4628DB9644007ACC74 /* RealShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealShell.swift; sourceTree = ""; }; C46EBC4928DB966A007ACC74 /* TestableShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableShell.swift; sourceTree = ""; }; C46FA23E246C358E00944F05 /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = ""; }; C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigurationFile.swift; sourceTree = ""; }; @@ -457,7 +457,7 @@ C4A81CA328C67101008DD9D1 /* PMTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PMTableView.swift; sourceTree = ""; }; C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListKindCell.swift; sourceTree = ""; }; C4ACA38E25C754C100060C66 /* PhpExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtension.swift; sourceTree = ""; }; - C4AD38B128ECD9D300FA8D83 /* TestableFilesystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableFilesystem.swift; sourceTree = ""; }; + C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableFileSystem.swift; sourceTree = ""; }; C4AF9F70275445FF00D44ED0 /* valet-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "valet-config.json"; sourceTree = ""; }; C4AF9F76275447F100D44ED0 /* ValetConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetConfigurationTest.swift; sourceTree = ""; }; C4AF9F792754499000D44ED0 /* Valet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Valet.swift; sourceTree = ""; }; @@ -479,6 +479,9 @@ C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Items.swift"; sourceTree = ""; }; C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Startup.swift"; sourceTree = ""; }; C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPrefs.swift; sourceTree = ""; }; + C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystemProtocol.swift; sourceTree = ""; }; + C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealFileSystem.swift; sourceTree = ""; }; + C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveFileSystem.swift; sourceTree = ""; }; C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "App+ConfigWatch.swift"; sourceTree = ""; }; C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpConfigWatcher.swift; sourceTree = ""; }; C4CCBA6B275C567B008C7055 /* PMWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PMWindowController.swift; sourceTree = ""; }; @@ -905,7 +908,6 @@ children = ( C476FF9722B0DD830098105B /* Alert.swift */, 54B48B5E275F66AE006D90C5 /* Application.swift */, - C4188988275FE8CB001EF227 /* Filesystem.swift */, C474B00524C0E98C00066A22 /* LocalNotification.swift */, C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */, C4CCBA6B275C567B008C7055 /* PMWindowController.swift */, @@ -987,6 +989,7 @@ isa = PBXGroup; children = ( C4F787A728EF812600790735 /* Testables */, + C4C8900128F0E27900CE5E97 /* Filesystem */, C4F787A628EF811000790735 /* Shell */, C40C7F2127721F7300DDDCDC /* Core */, 54B20EDF263AA22C00D3250E /* PHP */, @@ -1106,6 +1109,16 @@ path = Commands; sourceTree = ""; }; + C4C8900128F0E27900CE5E97 /* Filesystem */ = { + isa = PBXGroup; + children = ( + C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */, + C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */, + C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */, + ); + path = Filesystem; + sourceTree = ""; + }; C4C8E81D276F5686003AC782 /* Watcher */ = { isa = PBXGroup; children = ( @@ -1196,8 +1209,8 @@ isa = PBXGroup; children = ( 03E36FE628D9219000636F7F /* ActiveShell.swift */, - C46EBC4628DB9644007ACC74 /* SystemShell.swift */, - C46EBC4328DB95F0007ACC74 /* Shellable.swift */, + C46EBC4628DB9644007ACC74 /* RealShell.swift */, + C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */, ); path = Shell; sourceTree = ""; @@ -1207,7 +1220,7 @@ children = ( C40F505428ECA64E004AD45B /* TestableConfigurations.swift */, C46EBC4928DB966A007ACC74 /* TestableShell.swift */, - C4AD38B128ECD9D300FA8D83 /* TestableFilesystem.swift */, + C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */, ); path = Testables; sourceTree = ""; @@ -1380,6 +1393,7 @@ files = ( C47699EF28A2F2A30060FEB8 /* WarningManager.swift in Sources */, C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */, + C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */, C4D8016622B1584700C6DA1B /* Startup.swift in Sources */, C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */, C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */, @@ -1390,6 +1404,7 @@ C4C0E8EA27F88B80002D32A9 /* ValetProxy+Fake.swift in Sources */, C4EB53E728553117006F9937 /* ArrayExtension.swift in Sources */, 5420395926135DC100FB00FA /* PrefsVC.swift in Sources */, + C4C8900328F0E28800CE5E97 /* FileSystemProtocol.swift in Sources */, C43603A0275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */, 5489625828312FAD004F647A /* CreatedFromFile.swift in Sources */, C4068CA727B07A1300544CD5 /* SelectPreferenceView.swift in Sources */, @@ -1413,7 +1428,7 @@ C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */, C4E9D2C02878B336008FFDAD /* OnboardingView.swift in Sources */, C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */, - C4AD38B228ECD9D300FA8D83 /* TestableFilesystem.swift in Sources */, + C4AD38B228ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */, C4EB53E528551F9B006F9937 /* HeaderView.swift in Sources */, C40FE737282ABA4F00A302C2 /* AppVersion.swift in Sources */, C44A874828905BB000498BC4 /* ProgressVC.swift in Sources */, @@ -1441,7 +1456,7 @@ C415937F27A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */, C4811D2422D70A4700B5F6B3 /* App.swift in Sources */, C495F5AF28A42E080087F70A /* EnvironmentCheck.swift in Sources */, - C46EBC4428DB95F0007ACC74 /* Shellable.swift in Sources */, + C46EBC4428DB95F0007ACC74 /* ShellProtocol.swift in Sources */, C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */, C4F30B03278E16BA00755FCE /* HomebrewService.swift in Sources */, 54D9E0B427E4F51E003B9AD9 /* Key.swift in Sources */, @@ -1461,7 +1476,7 @@ 03E36FE728D9219000636F7F /* ActiveShell.swift in Sources */, C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */, C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */, - C46EBC4728DB9644007ACC74 /* SystemShell.swift in Sources */, + C46EBC4728DB9644007ACC74 /* RealShell.swift in Sources */, C4AD38B628ECE56D00FA8D83 /* TestableConfigurations.swift in Sources */, C4068CAA27B0890D00544CD5 /* MenuBarIcons.swift in Sources */, C44264C02850BD2A007400F1 /* VersionPopoverView.swift in Sources */, @@ -1477,6 +1492,7 @@ C4C1019B27C65C6F001FACC2 /* Process.swift in Sources */, C4EC1E73279DFCF40010F296 /* Events.swift in Sources */, C44067FB27E25FD70045BD4E /* DomainListTLSCell.swift in Sources */, + C4C8900528F0E3D100CE5E97 /* RealFileSystem.swift in Sources */, C4A81CA428C67101008DD9D1 /* PMTableView.swift in Sources */, C4927F0B27B2DFC200C55AFD /* Errors.swift in Sources */, C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */, @@ -1484,7 +1500,6 @@ C4FE011128084FC200D1DE6D /* SelectionVC.swift in Sources */, C4709CA228524B3400088BB8 /* StatsView.swift in Sources */, C44CCD4027AFE2FC00CE40E5 /* AlertableError.swift in Sources */, - C4188989275FE8CB001EF227 /* Filesystem.swift in Sources */, C4B6091D2853AB9700C95265 /* ServicesView.swift in Sources */, C40508B128ADAB44008FAC1F /* NSMenuItemExtension.swift in Sources */, C4B97B7B275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */, @@ -1532,7 +1547,7 @@ C413E43528DA3EB100AE33C7 /* FakeShellTest.swift in Sources */, C4205A7F27F4D21800191A39 /* ValetProxy.swift in Sources */, C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */, - C46EBC4528DB95F0007ACC74 /* Shellable.swift in Sources */, + C46EBC4528DB95F0007ACC74 /* ShellProtocol.swift in Sources */, C44B3A4728E5C70100718CB1 /* TimeIntervalExtension.swift in Sources */, C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */, 54FCFD2B276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */, @@ -1600,7 +1615,7 @@ C41E871B2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */, C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */, C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */, - C4AD38B328ECD9D300FA8D83 /* TestableFilesystem.swift in Sources */, + C4AD38B328ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */, C40C5C9D2846A40600E28255 /* Preset.swift in Sources */, C4F30B09278E1A0E00755FCE /* CustomPrefs.swift in Sources */, C40FE738282ABA4F00A302C2 /* AppVersion.swift in Sources */, @@ -1650,7 +1665,7 @@ C4F780B725D80B5D000DBC97 /* App.swift in Sources */, C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */, C485707628BF455100539B36 /* SectionHeaderView.swift in Sources */, - C46EBC4828DB9644007ACC74 /* SystemShell.swift in Sources */, + C46EBC4828DB9644007ACC74 /* RealShell.swift in Sources */, C4E4404727C56F4700D225E1 /* ValetSite.swift in Sources */, C44CCD4A27AFF3BC00CE40E5 /* MainMenu+Async.swift in Sources */, C449B4F327EE7FC600C47E8A /* DomainListTypeCell.swift in Sources */, @@ -1671,7 +1686,6 @@ C464ADB0275A7A6A003FCD53 /* DomainListVC.swift in Sources */, C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */, C46EBC4B28DB966A007ACC74 /* TestableShell.swift in Sources */, - C418898A275FE8CB001EF227 /* Filesystem.swift in Sources */, C40FE73B282ABB2E00A302C2 /* AppVersionTest.swift in Sources */, C4F780C625D80B75000DBC97 /* XibLoadable.swift in Sources */, C46E206E28299B3800D909D6 /* AppUpdateChecker.swift in Sources */, diff --git a/phpmon/Common/Core/Helpers.swift b/phpmon/Common/Core/Helpers.swift index 56082a1..a53c14f 100644 --- a/phpmon/Common/Core/Helpers.swift +++ b/phpmon/Common/Core/Helpers.swift @@ -32,7 +32,7 @@ func sed(file: String, original: String, replacement: String) async { // Check if gsed exists; it is able to follow symlinks, // which we want to do to toggle the extension - if Filesystem.fileExists("\(Paths.binPath)/gsed") { + if FileSystem.fileExists("\(Paths.binPath)/gsed") { await Shell.quiet("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)") } else { await Shell.quiet("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)") diff --git a/phpmon/Common/Core/Paths.swift b/phpmon/Common/Core/Paths.swift index 627e07f..93017f7 100644 --- a/phpmon/Common/Core/Paths.swift +++ b/phpmon/Common/Core/Paths.swift @@ -63,7 +63,7 @@ public class Paths { public static var homePath: String { // TODO: Depending on the filesystem abstraction, return the correct information - if Shell is SystemShell { + if Shell is RealShell { return NSHomeDirectory() } @@ -91,9 +91,9 @@ public class Paths { // (PHP Monitor will not use the user's own PATH) private func detectComposerBinary() { - if Filesystem.fileExists("/usr/local/bin/composer") { + if FileSystem.fileExists("/usr/local/bin/composer") { Paths.composer = "/usr/local/bin/composer" - } else if Filesystem.fileExists("/opt/homebrew/bin/composer") { + } else if FileSystem.fileExists("/opt/homebrew/bin/composer") { Paths.composer = "/opt/homebrew/bin/composer" } else { Paths.composer = nil diff --git a/phpmon/Common/Filesystem/ActiveFileSystem.swift b/phpmon/Common/Filesystem/ActiveFileSystem.swift new file mode 100644 index 0000000..7ffee2c --- /dev/null +++ b/phpmon/Common/Filesystem/ActiveFileSystem.swift @@ -0,0 +1,26 @@ +// +// FS.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 08/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +var FileSystem: FileSystemProtocol { + return ActiveFileSystem.shared +} + +class ActiveFileSystem { + static var shared: FileSystemProtocol = RealFileSystem() + + public static func useTestable(_ files: [String: FakeFile]) { + // TODO + // Self.shared = TestableShell(expectations: expectations) + } + + public static func useSystem() { + Self.shared = RealFileSystem() + } +} diff --git a/phpmon/Common/Filesystem/FileSystemProtocol.swift b/phpmon/Common/Filesystem/FileSystemProtocol.swift new file mode 100644 index 0000000..cbcf1ec --- /dev/null +++ b/phpmon/Common/Filesystem/FileSystemProtocol.swift @@ -0,0 +1,36 @@ +// +// FileSystemProtocol.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 08/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +protocol FileSystemProtocol { + /** + Checks if a given path is a file *and* executable. + */ + func isExecutableFile(_ path: String) -> Bool + + /** + Checks if a file or directory exists at the provided path. + */ + func exists(_ path: String) -> Bool + + /** + Checks if a file exists at the provided path. + */ + func fileExists(_ path: String) -> Bool + + /** + Checks if a directory exists at the provided path. + */ + func directoryExists(_ path: String) -> Bool + + /** + Checks if a given file is a symbolic link. + */ + func fileIsSymlink(_ path: String) -> Bool +} diff --git a/phpmon/Common/Helpers/Filesystem.swift b/phpmon/Common/Filesystem/RealFileSystem.swift similarity index 79% rename from phpmon/Common/Helpers/Filesystem.swift rename to phpmon/Common/Filesystem/RealFileSystem.swift index 7155e3b..601f92b 100644 --- a/phpmon/Common/Helpers/Filesystem.swift +++ b/phpmon/Common/Filesystem/RealFileSystem.swift @@ -1,20 +1,24 @@ // -// Filesystem.swift +// RealFileSystem.swift // PHP Monitor // -// Created by Nico Verbruggen on 07/12/2021. +// Created by Nico Verbruggen on 08/10/2022. // Copyright © 2022 Nico Verbruggen. All rights reserved. // -import Cocoa import Foundation -class Filesystem { +extension String { + var replacingTildeWithHomeDirectory: String { + return self.replacingOccurrences(of: "~", with: Paths.homePath) + } +} +class RealFileSystem: FileSystemProtocol { /** Checks if a given path is a file *and* executable. */ - public static func isExecutableFile(_ path: String) -> Bool { + func isExecutableFile(_ path: String) -> Bool { return FileManager.default.isExecutableFile( atPath: path.replacingTildeWithHomeDirectory ) @@ -23,7 +27,7 @@ class Filesystem { /** Checks if a file or directory exists at the provided path. */ - public static func exists(_ path: String) -> Bool { + func exists(_ path: String) -> Bool { return FileManager.default.fileExists( atPath: path.replacingTildeWithHomeDirectory ) @@ -32,7 +36,7 @@ class Filesystem { /** Checks if a file exists at the provided path. */ - public static func fileExists(_ path: String) -> Bool { + func fileExists(_ path: String) -> Bool { var isDirectory: ObjCBool = true let exists = FileManager.default.fileExists( atPath: path.replacingTildeWithHomeDirectory, @@ -45,7 +49,7 @@ class Filesystem { /** Checks if a directory exists at the provided path. */ - public static func directoryExists(_ path: String) -> Bool { + func directoryExists(_ path: String) -> Bool { var isDirectory: ObjCBool = true let exists = FileManager.default.fileExists( atPath: path.replacingTildeWithHomeDirectory, @@ -58,7 +62,7 @@ class Filesystem { /** Checks if a given file is a symbolic link. */ - public static func fileIsSymlink(_ path: String) -> Bool { + func fileIsSymlink(_ path: String) -> Bool { do { let attribs = try FileManager.default.attributesOfItem(atPath: path) return attribs[.type] as! FileAttributeType == FileAttributeType.typeSymbolicLink @@ -66,13 +70,4 @@ class Filesystem { return false } } - -} - -extension String { - - var replacingTildeWithHomeDirectory: String { - return self.replacingOccurrences(of: "~", with: Paths.homePath) - } - } diff --git a/phpmon/Common/PHP/ActivePhpInstallation.swift b/phpmon/Common/PHP/ActivePhpInstallation.swift index 00a0c4a..71dda95 100644 --- a/phpmon/Common/PHP/ActivePhpInstallation.swift +++ b/phpmon/Common/PHP/ActivePhpInstallation.swift @@ -134,7 +134,7 @@ class ActivePhpInstallation { } // Make sure to check if valet-fpm.conf exists. If it does, we should be fine :) - return Filesystem.fileExists("\(Paths.etcPath)/php/\(self.version.short)/php-fpm.d/valet-fpm.conf") + return FileSystem.fileExists("\(Paths.etcPath)/php/\(self.version.short)/php-fpm.d/valet-fpm.conf") } // MARK: - Structs diff --git a/phpmon/Common/PHP/PHP Version/PhpEnv.swift b/phpmon/Common/PHP/PHP Version/PhpEnv.swift index 7d78d5f..8ec9a68 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnv.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnv.swift @@ -96,7 +96,7 @@ class PhpEnv { let phpAlias = homebrewPackage.version // Avoid inserting a duplicate - if !versionsOnly.contains(phpAlias) && Filesystem.fileExists("\(Paths.optPath)/php/bin/php") { + if !versionsOnly.contains(phpAlias) && FileSystem.fileExists("\(Paths.optPath)/php/bin/php") { versionsOnly.append(phpAlias) } @@ -145,7 +145,7 @@ class PhpEnv { // is supported and where the binary exists (avoids broken installs) if !output.contains(version) && supported.contains(version) - && (checkBinaries ? Filesystem.fileExists("\(Paths.optPath)/php@\(version)/bin/php") : true) { + && (checkBinaries ? FileSystem.fileExists("\(Paths.optPath)/php@\(version)/bin/php") : true) { output.append(version) } } diff --git a/phpmon/Common/PHP/PHP Version/PhpHelper.swift b/phpmon/Common/PHP/PHP Version/PhpHelper.swift index 50fe730..3f6a0bf 100644 --- a/phpmon/Common/PHP/PHP Version/PhpHelper.swift +++ b/phpmon/Common/PHP/PHP Version/PhpHelper.swift @@ -27,11 +27,11 @@ class PhpHelper { Task { // Create the appropriate folders and check if the files exist do { - if !Filesystem.directoryExists("~/.config/phpmon/bin") { + if !FileSystem.directoryExists("~/.config/phpmon/bin") { await Shell.quiet("mkdir -p ~/.config/phpmon/bin") } - if Filesystem.fileExists(destination) { + if FileSystem.fileExists(destination) { let contents = try String(contentsOfFile: destination) if !contents.contains(keyPhrase) { Log.info("The file at '\(destination)' already exists and was not generated by PHP Monitor " @@ -64,7 +64,7 @@ class PhpHelper { ) // Make sure the file is executable - if !Filesystem.isExecutableFile(destination) { + if !FileSystem.isExecutableFile(destination) { await Shell.quiet("chmod +x \(destination)") } @@ -90,13 +90,13 @@ class PhpHelper { let source = "\(Paths.homePath)/.config/phpmon/bin/pm\(dotless)" let destination = "/usr/local/bin/pm\(dotless)" - if !Filesystem.fileExists(destination) { + if !FileSystem.fileExists(destination) { Log.info("Creating new symlink: \(destination)") await Shell.quiet("ln -s \(source) \(destination)") return } - if !Filesystem.fileIsSymlink(destination) { + if !FileSystem.fileIsSymlink(destination) { Log.info("Overwriting existing file with new symlink: \(destination)") await Shell.quiet("ln -fs \(source) \(destination)") return diff --git a/phpmon/Common/PHP/PhpInstallation.swift b/phpmon/Common/PHP/PhpInstallation.swift index 61e6995..8172760 100644 --- a/phpmon/Common/PHP/PhpInstallation.swift +++ b/phpmon/Common/PHP/PhpInstallation.swift @@ -21,7 +21,7 @@ class PhpInstallation { let phpConfigExecutablePath = "\(Paths.optPath)/php@\(version)/bin/php-config" self.versionNumber = PhpVersionNumber.make(from: version)! - if Filesystem.fileExists(phpConfigExecutablePath) { + if FileSystem.fileExists(phpConfigExecutablePath) { let longVersionString = Command.execute( path: phpConfigExecutablePath, arguments: ["--version"] diff --git a/phpmon/Common/Shell/ActiveShell.swift b/phpmon/Common/Shell/ActiveShell.swift index 4210338..99a50a0 100644 --- a/phpmon/Common/Shell/ActiveShell.swift +++ b/phpmon/Common/Shell/ActiveShell.swift @@ -8,18 +8,18 @@ import Foundation -var Shell: Shellable { +var Shell: ShellProtocol { return ActiveShell.shared } class ActiveShell { - static var shared: Shellable = SystemShell() + static var shared: ShellProtocol = RealShell() public static func useTestable(_ expectations: [String: BatchFakeShellOutput]) { Self.shared = TestableShell(expectations: expectations) } public static func useSystem() { - Self.shared = SystemShell() + Self.shared = RealShell() } } diff --git a/phpmon/Common/Shell/SystemShell.swift b/phpmon/Common/Shell/RealShell.swift similarity index 97% rename from phpmon/Common/Shell/SystemShell.swift rename to phpmon/Common/Shell/RealShell.swift index 652b78c..c8de1a9 100644 --- a/phpmon/Common/Shell/SystemShell.swift +++ b/phpmon/Common/Shell/RealShell.swift @@ -1,5 +1,5 @@ // -// SystemShell.swift +// RealShell.swift // PHP Monitor // // Created by Nico Verbruggen on 21/09/2022. @@ -8,7 +8,7 @@ import Foundation -class SystemShell: Shellable { +class RealShell: ShellProtocol { /** The launch path of the terminal in question that is used. On macOS, we use /bin/sh since it's pretty fast. @@ -19,7 +19,7 @@ class SystemShell: Shellable { For some commands, we need to know what's in the user's PATH. The entire PATH is retrieved here, so we can set the PATH in our own terminal as necessary. */ - private(set) var PATH: String = { return SystemShell.getPath() }() + private(set) var PATH: String = { return RealShell.getPath() }() /** Exports are additional environment variables set by the user via the custom configuration. diff --git a/phpmon/Common/Shell/Shellable.swift b/phpmon/Common/Shell/ShellProtocol.swift similarity index 97% rename from phpmon/Common/Shell/Shellable.swift rename to phpmon/Common/Shell/ShellProtocol.swift index e2eee2a..3b84304 100644 --- a/phpmon/Common/Shell/Shellable.swift +++ b/phpmon/Common/Shell/ShellProtocol.swift @@ -1,5 +1,5 @@ // -// Shellable.swift +// ShellProtocol.swift // PHP Monitor // // Created by Nico Verbruggen on 21/09/2022. @@ -8,28 +8,7 @@ import Foundation -enum ShellStream { - case stdOut, stdErr, stdIn -} - -struct ShellOutput { - var out: String - var err: String - - var hasError: Bool { - return err.lengthOfBytes(using: .utf8) > 0 - } - - static func out(_ out: String?, _ err: String? = nil) -> ShellOutput { - return ShellOutput(out: out ?? "", err: err ?? "") - } - - static func err(_ err: String?) -> ShellOutput { - return ShellOutput(out: "", err: err ?? "") - } -} - -protocol Shellable { +protocol ShellProtocol { /** The PATH for the current shell. */ @@ -69,6 +48,27 @@ protocol Shellable { ) async throws -> (Process, ShellOutput) } +enum ShellStream { + case stdOut, stdErr, stdIn +} + +struct ShellOutput { + var out: String + var err: String + + var hasError: Bool { + return err.lengthOfBytes(using: .utf8) > 0 + } + + static func out(_ out: String?, _ err: String? = nil) -> ShellOutput { + return ShellOutput(out: out ?? "", err: err ?? "") + } + + static func err(_ err: String?) -> ShellOutput { + return ShellOutput(out: "", err: err ?? "") + } +} + enum ShellError: Error { case timedOut } diff --git a/phpmon/Common/Testables/TestableFileSystem.swift b/phpmon/Common/Testables/TestableFileSystem.swift new file mode 100644 index 0000000..1e2f1b9 --- /dev/null +++ b/phpmon/Common/Testables/TestableFileSystem.swift @@ -0,0 +1,49 @@ +// +// TestableFileSystem.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 04/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class TestableFileSystem: FileSystemProtocol { + func isExecutableFile(_ path: String) -> Bool { + // TODO + return false + } + + func exists(_ path: String) -> Bool { + // TODO + return false + } + + func fileExists(_ path: String) -> Bool { + // TODO + return false + } + + func directoryExists(_ path: String) -> Bool { + // TODO + return false + } + + func fileIsSymlink(_ path: String) -> Bool { + // TODO + return false + } +} + +enum FakeFileType { + case binary, text, directory, symlink +} + +struct FakeFile { + var type: FakeFileType + var content: String? + + public static func fake(_ type: FakeFileType, _ content: String? = nil) -> FakeFile { + return FakeFile(type: type, content: content) + } +} diff --git a/phpmon/Common/Testables/TestableFilesystem.swift b/phpmon/Common/Testables/TestableFilesystem.swift deleted file mode 100644 index 0999dc1..0000000 --- a/phpmon/Common/Testables/TestableFilesystem.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// TestableFilesystem.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 04/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. -// - -import Foundation - -class TestableFilesystem {} - -enum FakeFileType { - case binary, text, directory, symlink -} - -struct FakeFile { - var type: FakeFileType - var content: String? - - public static func fake(_ type: FakeFileType, _ content: String? = nil) -> FakeFile { - return FakeFile(type: type, content: content) - } -} diff --git a/phpmon/Common/Testables/TestableShell.swift b/phpmon/Common/Testables/TestableShell.swift index 578c871..9835728 100644 --- a/phpmon/Common/Testables/TestableShell.swift +++ b/phpmon/Common/Testables/TestableShell.swift @@ -8,7 +8,7 @@ import Foundation -public class TestableShell: Shellable { +public class TestableShell: ShellProtocol { var PATH: String { return "/usr/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin" } diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index c6b2b03..1511d25 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -105,7 +105,7 @@ class Startup { // The PHP binary must exist. // ================================================================================= EnvironmentCheck( - command: { return !Filesystem.fileExists(Paths.php) }, + command: { return !FileSystem.fileExists(Paths.php) }, name: "`\(Paths.php)` exists", titleText: "startup.errors.php_binary.title".localized, subtitleText: "startup.errors.php_binary.subtitle".localized, @@ -130,7 +130,7 @@ class Startup { // ================================================================================= EnvironmentCheck( command: { - return !(Filesystem.fileExists(Paths.valet) || Filesystem.fileExists("~/.composer/vendor/bin/valet")) + return !(FileSystem.fileExists(Paths.valet) || FileSystem.fileExists("~/.composer/vendor/bin/valet")) }, name: "`valet` binary exists", titleText: "startup.errors.valet_executable.title".localized, @@ -176,7 +176,7 @@ class Startup { // ================================================================================= EnvironmentCheck( command: { - return !Filesystem.directoryExists("~/.config/valet") + return !FileSystem.directoryExists("~/.config/valet") }, name: "`.config/valet` not empty (Valet installed)", titleText: "startup.errors.valet_not_installed.title".localized, diff --git a/phpmon/Domain/DomainList/AddSiteVC.swift b/phpmon/Domain/DomainList/AddSiteVC.swift index de756d5..9ae5fde 100644 --- a/phpmon/Domain/DomainList/AddSiteVC.swift +++ b/phpmon/Domain/DomainList/AddSiteVC.swift @@ -55,7 +55,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate { let path = pathControl.url!.path let name = inputDomainName.stringValue - if !Filesystem.exists(path) { + if !FileSystem.exists(path) { Alert.confirm( onWindow: view.window!, messageText: "domain_list.alert.folder_missing.title".localized, diff --git a/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift b/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift index 95fc0d0..2de9176 100644 --- a/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift +++ b/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift @@ -71,7 +71,7 @@ struct PhpFrameworks { public static func detectFallbackDependency(_ basePath: String) -> String? { for entry in Self.FileMapping { let found = entry.value - .map { path in return Filesystem.exists(basePath + path) } + .map { path in return FileSystem.exists(basePath + path) } .contains(true) if found { diff --git a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift b/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift index d1fd5a5..83bb354 100644 --- a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift +++ b/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift @@ -18,7 +18,7 @@ class ValetProxy: DomainListable { self.domain = configuration.domain self.tld = configuration.tld self.target = configuration.proxy! - self.secured = Filesystem.fileExists("~/.config/valet/Certificates/\(self.domain).\(self.tld).key") + self.secured = FileSystem.fileExists("~/.config/valet/Certificates/\(self.domain).\(self.tld).key") } // MARK: - DomainListable Protocol diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift index eac09c1..5ae014c 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift @@ -119,7 +119,7 @@ class ValetSite: DomainListable { - Note: The file is not validated, only its presence is checked. */ public func determineSecured() { - secured = Filesystem.fileExists("~/.config/valet/Certificates/\(self.name).\(self.tld).key") + secured = FileSystem.fileExists("~/.config/valet/Certificates/\(self.name).\(self.tld).key") } /** @@ -188,7 +188,7 @@ class ValetSite: DomainListable { let path = "\(absolutePath)/composer.json" do { - if Filesystem.fileExists(path) { + if FileSystem.fileExists(path) { let decoded = try JSONDecoder().decode( ComposerJson.self, from: String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8).data(using: .utf8)! @@ -209,7 +209,7 @@ class ValetSite: DomainListable { let path = "\(absolutePath)/.valetphprc" do { - if Filesystem.fileExists(path) { + if FileSystem.fileExists(path) { let contents = try String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8) if let version = VersionExtractor.from(contents) { self.composerPhp = version @@ -224,7 +224,7 @@ class ValetSite: DomainListable { // MARK: - File Parsing public static func isolatedVersion(_ filePath: String) -> String? { - if Filesystem.fileExists(filePath) { + if FileSystem.fileExists(filePath) { return NginxConfigurationFile .from(filePath: filePath)? .isolatedVersion ?? nil diff --git a/phpmon/Domain/Preferences/CustomPrefs.swift b/phpmon/Domain/Preferences/CustomPrefs.swift index 66e323f..d327c26 100644 --- a/phpmon/Domain/Preferences/CustomPrefs.swift +++ b/phpmon/Domain/Preferences/CustomPrefs.swift @@ -51,7 +51,7 @@ extension Preferences { // Attempt to load the file if it exists let url = URL(fileURLWithPath: "\(Paths.homePath)/.config/phpmon/config.json") - if Filesystem.fileExists(url.path) { + if FileSystem.fileExists(url.path) { Log.info("A custom ~/.config/phpmon/config.json file was found. Attempting to parse...") loadCustomPreferencesFile(url) @@ -61,7 +61,7 @@ extension Preferences { } func moveOutdatedConfigurationFile() async { - if Filesystem.fileExists("~/.phpmon.conf.json") && !Filesystem.fileExists("~/.config/phpmon/config.json") { + if FileSystem.fileExists("~/.phpmon.conf.json") && !FileSystem.fileExists("~/.config/phpmon/config.json") { Log.info("An outdated configuration file was found. Moving it...") await Shell.quiet("cp ~/.phpmon.conf.json ~/.config/phpmon/config.json") Log.info("The configuration file was copied successfully!") @@ -87,7 +87,7 @@ extension Preferences { if customPreferences.hasEnvironmentVariables() { Log.info("Configuring the additional exports...") - if let shell = Shell as? SystemShell { + if let shell = Shell as? RealShell { shell.exports = customPreferences.getEnvironmentVariables() } } diff --git a/phpmon/Domain/Watcher/PhpConfigWatcher.swift b/phpmon/Domain/Watcher/PhpConfigWatcher.swift index cc6d231..37d006b 100644 --- a/phpmon/Domain/Watcher/PhpConfigWatcher.swift +++ b/phpmon/Domain/Watcher/PhpConfigWatcher.swift @@ -51,7 +51,7 @@ class PhpConfigWatcher { eventMask: DispatchSource.FileSystemEvent, behaviour: FSWatcherBehaviour = .reloadsMenu ) { - if !Filesystem.exists(url.path) { + if !FileSystem.exists(url.path) { Log.warn("No watcher was created for \(url.path) because the requested file does not exist.") return } From ec4c4df5fd3c4a71f97510dde8bede505a6dcb4b Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 10 Oct 2022 21:49:43 +0200 Subject: [PATCH 045/181] =?UTF-8?q?=E2=9C=A8=20Add=20preference=20to=20dis?= =?UTF-8?q?able=20TLD=20alert=20(#206)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/Integrations/Valet/Valet.swift | 10 ++++++++-- phpmon/Domain/Preferences/PreferenceName.swift | 4 ++++ phpmon/Domain/Preferences/Preferences.swift | 1 + phpmon/Domain/Preferences/PrefsVC.swift | 13 ++++++++++++- phpmon/Localizable.strings | 5 +++++ 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 9dcea5e..5f0365a 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -78,13 +78,19 @@ class Valet { Notify the user about a non-default TLD being set. */ public static func notifyAboutUnsupportedTLD() { - if Valet.shared.config.tld != "test" { + if Valet.shared.config.tld != "test" && Preferences.isEnabled(.warnAboutNonStandardTLD) { DispatchQueue.main.async { BetterAlert().withInformation( title: "alert.warnings.tld_issue.title".localized, subtitle: "alert.warnings.tld_issue.subtitle".localized, description: "alert.warnings.tld_issue.description".localized - ).withPrimary(text: "OK").show() + ) + .withPrimary(text: "OK") + .withTertiary(text: "alert.do_not_tell_again".localized, action: { alert in + Preferences.update(.warnAboutNonStandardTLD, value: false) + alert.close(with: .alertThirdButtonReturn) + }) + .show() } } } diff --git a/phpmon/Domain/Preferences/PreferenceName.swift b/phpmon/Domain/Preferences/PreferenceName.swift index 7b68921..221e8ce 100644 --- a/phpmon/Domain/Preferences/PreferenceName.swift +++ b/phpmon/Domain/Preferences/PreferenceName.swift @@ -26,6 +26,9 @@ enum PreferenceName: String { case iconTypeToDisplay = "icon_type_to_display" case fullPhpVersionDynamicIcon = "full_php_in_menu_bar" + // WARNINGS + case warnAboutNonStandardTLD = "warn_about_non_standard_tld" + // NOTIFICATIONS case notifyAboutVersionChange = "notify_about_version_change" case notifyAboutPhpFpmRestart = "notify_about_php_fpm_restart" @@ -60,6 +63,7 @@ enum PreferenceName: String { .showPhpDoctorSuggestions, // Notifications + .warnAboutNonStandardTLD, .notifyAboutVersionChange, .notifyAboutPhpFpmRestart, .notifyAboutServices, diff --git a/phpmon/Domain/Preferences/Preferences.swift b/phpmon/Domain/Preferences/Preferences.swift index a38e3f0..c410e37 100644 --- a/phpmon/Domain/Preferences/Preferences.swift +++ b/phpmon/Domain/Preferences/Preferences.swift @@ -58,6 +58,7 @@ class Preferences { PreferenceName.fullPhpVersionDynamicIcon.rawValue: false, /// Preferences: Notifications + PreferenceName.warnAboutNonStandardTLD.rawValue: true, PreferenceName.notifyAboutVersionChange.rawValue: true, PreferenceName.notifyAboutPhpFpmRestart.rawValue: true, PreferenceName.notifyAboutServices.rawValue: true, diff --git a/phpmon/Domain/Preferences/PrefsVC.swift b/phpmon/Domain/Preferences/PrefsVC.swift index 346f6ec..3e1542f 100644 --- a/phpmon/Domain/Preferences/PrefsVC.swift +++ b/phpmon/Domain/Preferences/PrefsVC.swift @@ -188,6 +188,16 @@ class GenericPreferenceVC: NSViewController { ) } + func getWarnAboutNonStandardTLD() -> NSView { + return CheckboxPreferenceView.make( + sectionText: "prefs.warnings".localized, + descriptionText: "prefs.warn_about_non_standard_tld_desc".localized, + checkboxText: "prefs.warn_about_non_standard_tld".localized, + preference: .warnAboutNonStandardTLD, + action: {} + ) + } + func getDisplayMenuSectionPV( _ localizationKey: String, _ preference: PreferenceName, @@ -249,7 +259,8 @@ class NotificationPreferencesVC: GenericPreferenceVC { vc.getNotifyAboutSecureTogglePV(), vc.getNotifyAboutGlobalComposerStatusPV(), vc.getNotifyAboutServicesPV(), - vc.getNotifyAboutPhpFpmChangePV() + vc.getNotifyAboutPhpFpmChangePV(), + vc.getWarnAboutNonStandardTLD() ] return vc diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 7b6e6d2..501356b 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -227,6 +227,7 @@ "prefs.integrations" = "Integrations:"; "prefs.updates" = "Updates:"; "prefs.notifications" = "Notifications:"; +"prefs.warnings" = "Warnings:"; "prefs.menu_contents" = "Features in Menu:"; "prefs.icon_options.php" = "Display PHP Icon"; @@ -279,6 +280,9 @@ "prefs.notify_about_composer_success_desc" = "Displays a notification when the global Composer configuration was successfully updated."; "prefs.notify_about_composer_success" = "Notify about global composer update"; +"prefs.warn_about_non_standard_tld_desc" = "If you use a non-standard TLD, you may not wish to get repeated notifications about this."; +"prefs.warn_about_non_standard_tld" = "Warn about non-standard TLD"; + "prefs.display_global_version_switcher_desc" = "If disabled, you will not be able to change the globally linked PHP version via the main menu."; "prefs.display_global_version_switcher" = "PHP Switcher"; @@ -548,6 +552,7 @@ If you are seeing this message but are confused why this folder has gone missing "alert.warnings.tld_issue.title" = "You are not using `.test` as the TLD for Valet."; "alert.warnings.tld_issue.subtitle" = "Using a non-default TLD may not work correctly and is not officially supported."; "alert.warnings.tld_issue.description" = "PHP Monitor will remain functional, but there might be issues: the app might not correctly show which domains have been secured. For optimal results, go to your Valet configuration file (config.json in the Valet directory) and change the TLD back to `test`."; +"alert.do_not_tell_again" = "Don't Tell Me Again"; // WARNINGS From 12a4efc7753f7736cf969c6c7743fd07e241e6bf Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 12 Oct 2022 22:19:36 +0200 Subject: [PATCH 046/181] =?UTF-8?q?=F0=9F=91=8C=20Improvements=20to=20Bett?= =?UTF-8?q?erAlert,=20apply()=20configs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MainActor fixes for BetterAlert - Added `apply` for TestableConfiguration --- .../xcschemes/PHP Monitor.xcscheme | 2 +- .../Common/Filesystem/ActiveFileSystem.swift | 3 +- .../Testables/TestableConfigurations.swift | 5 +++ .../Common/Testables/TestableFileSystem.swift | 6 ++++ phpmon/Domain/App/AppDelegate.swift | 7 ++-- phpmon/Domain/App/Startup.swift | 32 +++++++++---------- phpmon/Domain/Menu/MainMenu+Actions.swift | 2 +- phpmon/Domain/Notice/BetterAlert.swift | 14 ++++---- phpmon/Domain/Notice/BetterAlertVC.swift | 6 ++-- 9 files changed, 42 insertions(+), 35 deletions(-) diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme index 0ed1bb3..02cf00d 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme @@ -76,7 +76,7 @@ + isEnabled = "NO"> Bool { // TODO return false diff --git a/phpmon/Domain/App/AppDelegate.swift b/phpmon/Domain/App/AppDelegate.swift index c34fc74..a446586 100644 --- a/phpmon/Domain/App/AppDelegate.swift +++ b/phpmon/Domain/App/AppDelegate.swift @@ -57,13 +57,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele */ override init() { logger.verbosity = .info + #if DEBUG logger.verbosity = .performance - - // TODO: Enable to fake broken setup during testing - // ActiveShell.useTestable(Testables.working.shellOutput) - + TestableConfigurations.working.apply() #endif + if CommandLine.arguments.contains("--v") { logger.verbosity = .performance Log.info("Extra verbose mode has been activated.") diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index 1511d25..bc14683 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -29,7 +29,7 @@ class Startup { // If we get here, something's gone wrong and the check has failed... Log.info("[FAIL] \(check.name)") - showAlert(for: check) + await showAlert(for: check) return false } @@ -45,29 +45,27 @@ class Startup { - ones that require an app restart, which prompt the user to exit the app - ones that allow the app to continue, which allow the user to retry */ - private func showAlert(for check: EnvironmentCheck) { - DispatchQueue.main.async { - if check.requiresAppRestart { - BetterAlert() - .withInformation( - title: check.titleText, - subtitle: check.subtitleText, - description: check.descriptionText - ) - .withPrimary(text: check.buttonText, action: { _ in - exit(1) - }).show() - } - + @MainActor private func showAlert(for check: EnvironmentCheck) { + if check.requiresAppRestart { BetterAlert() .withInformation( title: check.titleText, subtitle: check.subtitleText, description: check.descriptionText ) - .withPrimary(text: "OK") - .show() + .withPrimary(text: check.buttonText, action: { _ in + exit(1) + }).show() } + + BetterAlert() + .withInformation( + title: check.titleText, + subtitle: check.subtitleText, + description: check.descriptionText + ) + .withPrimary(text: "OK") + .show() } /** diff --git a/phpmon/Domain/Menu/MainMenu+Actions.swift b/phpmon/Domain/Menu/MainMenu+Actions.swift index 8cceafd..0c62285 100644 --- a/phpmon/Domain/Menu/MainMenu+Actions.swift +++ b/phpmon/Domain/Menu/MainMenu+Actions.swift @@ -12,7 +12,7 @@ extension MainMenu { // MARK: - Actions - @objc func fixHomebrewPermissions() { + @MainActor @objc func fixHomebrewPermissions() { if !BetterAlert() .withInformation( title: "alert.fix_homebrew_permissions.title".localized, diff --git a/phpmon/Domain/Notice/BetterAlert.swift b/phpmon/Domain/Notice/BetterAlert.swift index 4f46e28..3013415 100644 --- a/phpmon/Domain/Notice/BetterAlert.swift +++ b/phpmon/Domain/Notice/BetterAlert.swift @@ -31,8 +31,8 @@ class BetterAlert { public func withPrimary( text: String, - action: @escaping (BetterAlertVC) -> Void = { vc in - DispatchQueue.main.async { vc.close(with: .alertFirstButtonReturn) } + action: @MainActor @escaping (BetterAlertVC) -> Void = { vc in + vc.close(with: .alertFirstButtonReturn) } ) -> Self { self.noticeVC.buttonPrimary.title = text @@ -42,8 +42,8 @@ class BetterAlert { public func withSecondary( text: String, - action: ((BetterAlertVC) -> Void)? = { vc in - DispatchQueue.main.async { vc.close(with: .alertSecondButtonReturn) } + action: (@MainActor (BetterAlertVC) -> Void)? = { vc in + vc.close(with: .alertSecondButtonReturn) } ) -> Self { self.noticeVC.buttonSecondary.title = text @@ -53,7 +53,7 @@ class BetterAlert { public func withTertiary( text: String = "", - action: ((BetterAlertVC) -> Void)? = nil + action: (@MainActor (BetterAlertVC) -> Void)? = nil ) -> Self { if text == "" { self.noticeVC.buttonTertiary.bezelStyle = .helpButton @@ -84,7 +84,7 @@ class BetterAlert { Shows the modal and returns a ModalResponse. If you wish to simply show the alert and disregard the outcome, use `show`. */ - public func runModal() -> NSApplication.ModalResponse { + @MainActor public func runModal() -> NSApplication.ModalResponse { if !Thread.isMainThread { fatalError("You should always present alerts on the main thread!") } @@ -96,7 +96,7 @@ class BetterAlert { } /** Shows the modal and returns true if the user pressed the primary button. */ - public func didSelectPrimary() -> Bool { + @MainActor public func didSelectPrimary() -> Bool { return self.runModal() == .alertFirstButtonReturn } diff --git a/phpmon/Domain/Notice/BetterAlertVC.swift b/phpmon/Domain/Notice/BetterAlertVC.swift index eaca848..c3d58e1 100644 --- a/phpmon/Domain/Notice/BetterAlertVC.swift +++ b/phpmon/Domain/Notice/BetterAlertVC.swift @@ -21,9 +21,9 @@ class BetterAlertVC: NSViewController { @IBOutlet weak var buttonSecondary: NSButton! @IBOutlet weak var buttonTertiary: NSButton! - var actionPrimary: (BetterAlertVC) -> Void = { _ in } - var actionSecondary: ((BetterAlertVC) -> Void)? - var actionTertiary: ((BetterAlertVC) -> Void)? + var actionPrimary: (@MainActor (BetterAlertVC) -> Void) = { _ in } + var actionSecondary: (@MainActor (BetterAlertVC) -> Void)? + var actionTertiary: (@MainActor (BetterAlertVC) -> Void)? @IBOutlet weak var imageView: NSImageView! From ad46f51d73650473d86a12d17edea38833387601 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 12 Oct 2022 22:38:03 +0200 Subject: [PATCH 047/181] =?UTF-8?q?=F0=9F=91=8C=20Use=20fake=20filesystem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 2 +- .../Testables/TestableConfigurations.swift | 12 +++++-- .../Common/Testables/TestableFileSystem.swift | 31 +++++++++++++------ phpmon/Domain/App/App.swift | 10 ++++++ 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 3258cb4..34ad757 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -989,8 +989,8 @@ isa = PBXGroup; children = ( C4F787A728EF812600790735 /* Testables */, - C4C8900128F0E27900CE5E97 /* Filesystem */, C4F787A628EF811000790735 /* Shell */, + C4C8900128F0E27900CE5E97 /* Filesystem */, C40C7F2127721F7300DDDCDC /* Core */, 54B20EDF263AA22C00D3250E /* PHP */, C44CCD4327AFE93300CE40E5 /* Errors */, diff --git a/phpmon/Common/Testables/TestableConfigurations.swift b/phpmon/Common/Testables/TestableConfigurations.swift index f5525d0..20457e2 100644 --- a/phpmon/Common/Testables/TestableConfigurations.swift +++ b/phpmon/Common/Testables/TestableConfigurations.swift @@ -38,7 +38,11 @@ class TestableConfigurations { return TestableConfiguration( architecture: "arm64", filesystem: [ - "/opt/homebrew/brew" + "/opt/homebrew/bin/brew" + : .fake(.binary), + "/opt/homebrew/bin/php" + : .fake(.binary), + "/opt/homebrew/bin/valet" : .fake(.binary), "/opt/homebrew/opt/php" : .fake(.symlink, "/opt/homebrew/Cellar/php/8.1.10_1"), @@ -47,13 +51,15 @@ class TestableConfigurations { "/opt/homebrew/Cellar/php/8.1.10_1/bin/php" : .fake(.binary), "/opt/homebrew/Cellar/php/8.1.10_1/bin/php-config" - : .fake(.binary) + : .fake(.binary), + "~/.config/valet" + : .fake(.directory) ], shellOutput: [ "sysctl -n sysctl.proc_translated" : .instant("0"), "id -un" - : .instant("nicoverbruggen"), + : .instant("user"), "which node" : .instant("/opt/homebrew/bin/node"), "php -v" diff --git a/phpmon/Common/Testables/TestableFileSystem.swift b/phpmon/Common/Testables/TestableFileSystem.swift index f814e59..f5b4964 100644 --- a/phpmon/Common/Testables/TestableFileSystem.swift +++ b/phpmon/Common/Testables/TestableFileSystem.swift @@ -16,28 +16,39 @@ class TestableFileSystem: FileSystemProtocol { var files: [String: FakeFile] func isExecutableFile(_ path: String) -> Bool { - // TODO - return false + guard let file = files[path] else { + return false + } + + return file.type == .binary } func exists(_ path: String) -> Bool { - // TODO - return false + return files.keys.contains(path) } func fileExists(_ path: String) -> Bool { - // TODO - return false + guard let file = files[path] else { + return false + } + + return [.binary, .symlink, .text].contains(file.type) } func directoryExists(_ path: String) -> Bool { - // TODO - return false + guard let file = files[path] else { + return false + } + + return [.directory].contains(file.type) } func fileIsSymlink(_ path: String) -> Bool { - // TODO - return false + guard let file = files[path] else { + return false + } + + return file.type == .symlink } } diff --git a/phpmon/Domain/App/App.swift b/phpmon/Domain/App/App.swift index 308b099..83dd659 100644 --- a/phpmon/Domain/App/App.swift +++ b/phpmon/Domain/App/App.swift @@ -31,7 +31,17 @@ class App { return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String } + /** + A fake architecture. + When set, the real machine's system architecture is not used, + but this fixed value is used instead. + */ + static var fakeArchitecture: String? + + /** The system architecture. Paths differ based on this value. */ static var architecture: String { + if fakeArchitecture != nil { return fakeArchitecture! } + var systeminfo = utsname() uname(&systeminfo) let machine = withUnsafeBytes(of: &systeminfo.machine) {bufPtr->String in From 8a6656d3e25bddd8af8fef095d83f1aaeed142f9 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 12 Oct 2022 22:40:48 +0200 Subject: [PATCH 048/181] =?UTF-8?q?=F0=9F=91=8C=20Improve=20logging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon-tests/Next/FakeShellTest.swift | 4 +--- phpmon/Common/PHP/PHP Version/PhpHelper.swift | 2 +- phpmon/Common/Shell/RealShell.swift | 2 +- phpmon/Common/Testables/TestableShell.swift | 2 +- phpmon/Domain/App/AppDelegate.swift | 2 ++ 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/phpmon-tests/Next/FakeShellTest.swift b/phpmon-tests/Next/FakeShellTest.swift index 993cf5d..3a96cb1 100644 --- a/phpmon-tests/Next/FakeShellTest.swift +++ b/phpmon-tests/Next/FakeShellTest.swift @@ -26,9 +26,7 @@ class FakeShellTest: XCTestCase { .delayed(2, "Goodbye world") ]) - let output = await greeting.output(didReceiveOutput: { output, _ in - print(output) - }) + let output = await greeting.output(didReceiveOutput: { output, _ in }) XCTAssertEqual("Hello world\nGoodbye world", output.out) } diff --git a/phpmon/Common/PHP/PHP Version/PhpHelper.swift b/phpmon/Common/PHP/PHP Version/PhpHelper.swift index 3f6a0bf..d58a02e 100644 --- a/phpmon/Common/PHP/PHP Version/PhpHelper.swift +++ b/phpmon/Common/PHP/PHP Version/PhpHelper.swift @@ -80,7 +80,7 @@ class PhpHelper { await self.createSymlink(dotless) } } catch { - print(error) + Log.err(error) Log.err("Could not write PHP Monitor helper for PHP \(version) to \(destination))") } } diff --git a/phpmon/Common/Shell/RealShell.swift b/phpmon/Common/Shell/RealShell.swift index c8de1a9..77ab643 100644 --- a/phpmon/Common/Shell/RealShell.swift +++ b/phpmon/Common/Shell/RealShell.swift @@ -91,7 +91,7 @@ class RealShell: ShellProtocol { // Seriously slow down how long it takes for the shell to return output // (in order to debug or identify async issues) if ProcessInfo.processInfo.environment["SLOW_SHELL_MODE"] != nil { - print("[SLOW SHELL] \(command)") + Log.info("[SLOW SHELL] \(command)") await delay(seconds: 3.0) } diff --git a/phpmon/Common/Testables/TestableShell.swift b/phpmon/Common/Testables/TestableShell.swift index 9835728..3cb9984 100644 --- a/phpmon/Common/Testables/TestableShell.swift +++ b/phpmon/Common/Testables/TestableShell.swift @@ -36,7 +36,7 @@ public class TestableShell: ShellProtocol { // Seriously slow down the shell's return rate in order to debug or identify async issues if ProcessInfo.processInfo.environment["SLOW_SHELL_MODE"] != nil { - print("[SLOW SHELL] \(command)") + Log.info("[SLOW SHELL] \(command)") await delay(seconds: 3.0) } diff --git a/phpmon/Domain/App/AppDelegate.swift b/phpmon/Domain/App/AppDelegate.swift index a446586..73fc12f 100644 --- a/phpmon/Domain/App/AppDelegate.swift +++ b/phpmon/Domain/App/AppDelegate.swift @@ -60,6 +60,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele #if DEBUG logger.verbosity = .performance + + // Use a "working" test configuration TestableConfigurations.working.apply() #endif From eaa74b71416ba63469467c9d4dd4e3bfa4707643 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 12 Oct 2022 22:49:29 +0200 Subject: [PATCH 049/181] =?UTF-8?q?=F0=9F=91=8C=20Annotate=20configuration?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Testables/TestableConfigurations.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/phpmon/Common/Testables/TestableConfigurations.swift b/phpmon/Common/Testables/TestableConfigurations.swift index 20457e2..31ef899 100644 --- a/phpmon/Common/Testables/TestableConfigurations.swift +++ b/phpmon/Common/Testables/TestableConfigurations.swift @@ -21,6 +21,8 @@ struct TestableConfiguration { // swiftlint:disable colon trailing_comma class TestableConfigurations { + + /** A broken system, that will not get past initialization due to missing binaries. */ static var broken: TestableConfiguration { return TestableConfiguration( architecture: "arm64", @@ -33,7 +35,7 @@ class TestableConfigurations { ) } - // TODO: All expected, correct Terminal responses + /** A functional, working system setup that is compatible with PHP Monitor. */ static var working: TestableConfiguration { return TestableConfiguration( architecture: "arm64", From daaece3cfa57d2b486c4f34acaa6787173953033 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 12 Oct 2022 23:25:06 +0200 Subject: [PATCH 050/181] =?UTF-8?q?=E2=9C=A8=20Added=20fake=20commands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 38 ++++++++++++++++--- phpmon/Common/Command/ActiveCommand.swift | 25 ++++++++++++ phpmon/Common/Command/CommandProtocol.swift | 22 +++++++++++ .../RealCommand.swift} | 11 +----- phpmon/Common/PHP/ActivePhpInstallation.swift | 4 +- phpmon/Common/PHP/PhpInstallation.swift | 3 +- phpmon/Common/Testables/TestableCommand.swift | 27 +++++++++++++ .../Testables/TestableConfigurations.swift | 18 ++++++++- phpmon/Domain/Watcher/App+ConfigWatch.swift | 5 +++ 9 files changed, 134 insertions(+), 19 deletions(-) create mode 100644 phpmon/Common/Command/ActiveCommand.swift create mode 100644 phpmon/Common/Command/CommandProtocol.swift rename phpmon/Common/{Core/Command.swift => Command/RealCommand.swift} (63%) create mode 100644 phpmon/Common/Testables/TestableCommand.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 34ad757..3bcae17 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -220,8 +220,8 @@ C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B56360276AB0A500F12CCB /* VersionExtractorTest.swift */; }; C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; }; C4B5853F2770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; }; - C4B585442770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; }; - C4B585452770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; }; + C4B585442770FE3900DA4FBE /* RealCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* RealCommand.swift */; }; + C4B585452770FE3900DA4FBE /* RealCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* RealCommand.swift */; }; C4B6091A2853AAD300C95265 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B609192853AAD300C95265 /* SectionHeaderView.swift */; }; C4B6091D2853AB9700C95265 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B6091C2853AB9700C95265 /* ServicesView.swift */; }; C4B97B75275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */; }; @@ -278,6 +278,12 @@ C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */; }; C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; C4E4404727C56F4700D225E1 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; + C4E49DE728F764050026AC4E /* ActiveCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE628F764050026AC4E /* ActiveCommand.swift */; }; + C4E49DE828F764050026AC4E /* ActiveCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE628F764050026AC4E /* ActiveCommand.swift */; }; + C4E49DEA28F7643D0026AC4E /* CommandProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */; }; + C4E49DEB28F7643D0026AC4E /* CommandProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */; }; + C4E49DED28F764A00026AC4E /* TestableCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DEC28F764A00026AC4E /* TestableCommand.swift */; }; + C4E49DEE28F764A00026AC4E /* TestableCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DEC28F764A00026AC4E /* TestableCommand.swift */; }; C4E9D2C02878B336008FFDAD /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */; }; C4EB53E528551F9B006F9937 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E428551F9B006F9937 /* HeaderView.swift */; }; C4EB53E728553117006F9937 /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E628553117006F9937 /* ArrayExtension.swift */; }; @@ -465,7 +471,7 @@ C4B5635D276AB09000F12CCB /* VersionExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionExtractor.swift; sourceTree = ""; }; C4B56360276AB0A500F12CCB /* VersionExtractorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionExtractorTest.swift; sourceTree = ""; }; C4B5853B2770FE3900DA4FBE /* Paths.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Paths.swift; sourceTree = ""; }; - C4B5853D2770FE3900DA4FBE /* Command.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = ""; }; + C4B5853D2770FE3900DA4FBE /* RealCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealCommand.swift; sourceTree = ""; }; C4B609192853AAD300C95265 /* SectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderView.swift; sourceTree = ""; }; C4B6091C2853AB9700C95265 /* ServicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesView.swift; sourceTree = ""; }; C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+MenuOutlets.swift"; sourceTree = ""; }; @@ -498,6 +504,9 @@ C4DEB7D327A5D60B00834718 /* Stats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stats.swift; sourceTree = ""; }; C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSWindowExtension.swift; sourceTree = ""; }; C4E4404527C56F4700D225E1 /* ValetSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetSite.swift; sourceTree = ""; }; + C4E49DE628F764050026AC4E /* ActiveCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveCommand.swift; sourceTree = ""; }; + C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandProtocol.swift; sourceTree = ""; }; + C4E49DEC28F764A00026AC4E /* TestableCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableCommand.swift; sourceTree = ""; }; C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = ""; }; C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = ""; }; C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; @@ -663,7 +672,6 @@ C415D3B62770F294005EF286 /* Actions.swift */, C4EE188322D3386B00E126E5 /* Constants.swift */, C4EC1E72279DFCF40010F296 /* Events.swift */, - C4B5853D2770FE3900DA4FBE /* Command.swift */, C4B5853B2770FE3900DA4FBE /* Paths.swift */, C4C1019A27C65C6F001FACC2 /* Process.swift */, C40C7F2F27722E8D00DDDCDC /* Logger.swift */, @@ -991,6 +999,7 @@ C4F787A728EF812600790735 /* Testables */, C4F787A628EF811000790735 /* Shell */, C4C8900128F0E27900CE5E97 /* Filesystem */, + C4E49DE528F763E20026AC4E /* Command */, C40C7F2127721F7300DDDCDC /* Core */, 54B20EDF263AA22C00D3250E /* PHP */, C44CCD4327AFE93300CE40E5 /* Errors */, @@ -1162,6 +1171,16 @@ path = Switcher; sourceTree = ""; }; + C4E49DE528F763E20026AC4E /* Command */ = { + isa = PBXGroup; + children = ( + C4E49DE628F764050026AC4E /* ActiveCommand.swift */, + C4B5853D2770FE3900DA4FBE /* RealCommand.swift */, + C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */, + ); + path = Command; + sourceTree = ""; + }; C4E9D2BE2878B32D008FFDAD /* Onboarding */ = { isa = PBXGroup; children = ( @@ -1221,6 +1240,7 @@ C40F505428ECA64E004AD45B /* TestableConfigurations.swift */, C46EBC4928DB966A007ACC74 /* TestableShell.swift */, C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */, + C4E49DEC28F764A00026AC4E /* TestableCommand.swift */, ); path = Testables; sourceTree = ""; @@ -1413,11 +1433,13 @@ C4E0F7ED27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */, C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */, C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */, + C4E49DE728F764050026AC4E /* ActiveCommand.swift in Sources */, 54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */, C450C8C628C919EC002A2B4B /* PreferenceName.swift in Sources */, C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */, C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */, C4D9F24B280B69E100DCD39A /* AddProxyVC.swift in Sources */, + C4E49DED28F764A00026AC4E /* TestableCommand.swift in Sources */, C4A6957628D23EE300A14CF8 /* EnvironmentManager.swift in Sources */, C41E871A2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */, C40C7F2827721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */, @@ -1433,7 +1455,7 @@ C40FE737282ABA4F00A302C2 /* AppVersion.swift in Sources */, C44A874828905BB000498BC4 /* ProgressVC.swift in Sources */, C4CCBA6C275C567B008C7055 /* PMWindowController.swift in Sources */, - C4B585442770FE3900DA4FBE /* Command.swift in Sources */, + C4B585442770FE3900DA4FBE /* RealCommand.swift in Sources */, C44067F527E2582B0045BD4E /* DomainListNameCell.swift in Sources */, C40C5C9C2846A40600E28255 /* Preset.swift in Sources */, C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */, @@ -1531,6 +1553,7 @@ C4EE188422D3386B00E126E5 /* Constants.swift in Sources */, C493084A279F331F009C240B /* AddSiteVC.swift in Sources */, C4DEB7D427A5D60B00834718 /* Stats.swift in Sources */, + C4E49DEA28F7643D0026AC4E /* CommandProtocol.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1591,6 +1614,7 @@ C46FA98C2822F08F00D78807 /* PhpConfigurationTest.swift in Sources */, C4BF90C127C57C220054E78C /* MainMenu+FixMyValet.swift in Sources */, C4C0E8EB27F88B80002D32A9 /* ValetProxy+Fake.swift in Sources */, + C4E49DEB28F7643D0026AC4E /* CommandProtocol.swift in Sources */, C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */, C485707428BF454E00539B36 /* ServicesView.swift in Sources */, C4F780AE25D80B37000DBC97 /* PhpExtensionTest.swift in Sources */, @@ -1620,6 +1644,7 @@ C4F30B09278E1A0E00755FCE /* CustomPrefs.swift in Sources */, C40FE738282ABA4F00A302C2 /* AppVersion.swift in Sources */, C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */, + C4E49DEE28F764A00026AC4E /* TestableCommand.swift in Sources */, C484437C2804BB560041A78A /* ValetProxyScanner.swift in Sources */, C4AF9F78275447F100D44ED0 /* ValetConfigurationTest.swift in Sources */, C4CE3BBC27B324250086CA49 /* ComposerWindow.swift in Sources */, @@ -1631,6 +1656,7 @@ 54D9E0B327E4F51E003B9AD9 /* HotKeysController.swift in Sources */, 03E36FE828D9219000636F7F /* ActiveShell.swift in Sources */, C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */, + C4E49DE828F764050026AC4E /* ActiveCommand.swift in Sources */, C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */, C46E20702829D27F00D909D6 /* AppUpdaterCheckTest.swift in Sources */, C485707D28BF45A200539B36 /* WarningView.swift in Sources */, @@ -1660,7 +1686,7 @@ C4C0E8E027F88AEB002D32A9 /* FakeSiteScanner.swift in Sources */, C4AF9F7D275454A900D44ED0 /* ValetVersionExtractorTest.swift in Sources */, C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */, - C4B585452770FE3900DA4FBE /* Command.swift in Sources */, + C4B585452770FE3900DA4FBE /* RealCommand.swift in Sources */, C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */, C4F780B725D80B5D000DBC97 /* App.swift in Sources */, C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */, diff --git a/phpmon/Common/Command/ActiveCommand.swift b/phpmon/Common/Command/ActiveCommand.swift new file mode 100644 index 0000000..190e4e7 --- /dev/null +++ b/phpmon/Common/Command/ActiveCommand.swift @@ -0,0 +1,25 @@ +// +// ActiveCommand.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 12/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +var Command: CommandProtocol { + return ActiveCommand.shared +} + +class ActiveCommand { + static var shared: CommandProtocol = RealCommand() + + public static func useTestable(_ output: [String: String]) { + Self.shared = TestableCommand(commands: output) + } + + public static func useSystem() { + Self.shared = RealCommand() + } +} diff --git a/phpmon/Common/Command/CommandProtocol.swift b/phpmon/Common/Command/CommandProtocol.swift new file mode 100644 index 0000000..49411f0 --- /dev/null +++ b/phpmon/Common/Command/CommandProtocol.swift @@ -0,0 +1,22 @@ +// +// CommandProtocol.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 12/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +protocol CommandProtocol { + + /** + Immediately executes a command. + + - Parameter path: The path of the command or program to invoke. + - Parameter arguments: A list of arguments that are passed on. + - Parameter trimNewlines: Removes empty new line output. + */ + func execute(path: String, arguments: [String], trimNewlines: Bool) -> String + +} diff --git a/phpmon/Common/Core/Command.swift b/phpmon/Common/Command/RealCommand.swift similarity index 63% rename from phpmon/Common/Core/Command.swift rename to phpmon/Common/Command/RealCommand.swift index d7c8e90..f9a0001 100644 --- a/phpmon/Common/Core/Command.swift +++ b/phpmon/Common/Command/RealCommand.swift @@ -7,16 +7,9 @@ import Cocoa -public class Command { +public class RealCommand: CommandProtocol { - /** - Immediately executes a command. - - - Parameter path: The path of the command or program to invoke. - - Parameter arguments: A list of arguments that are passed on. - - Parameter trimNewlines: Removes empty new line output. - */ - public static func execute(path: String, arguments: [String], trimNewlines: Bool = false) -> String { + public func execute(path: String, arguments: [String], trimNewlines: Bool = false) -> String { let task = Process() task.launchPath = path task.arguments = arguments diff --git a/phpmon/Common/PHP/ActivePhpInstallation.swift b/phpmon/Common/PHP/ActivePhpInstallation.swift index 71dda95..e06fea5 100644 --- a/phpmon/Common/PHP/ActivePhpInstallation.swift +++ b/phpmon/Common/PHP/ActivePhpInstallation.swift @@ -65,7 +65,7 @@ class ActivePhpInstallation { ) // Return a list of .ini files parsed after php.ini - let paths = Command.execute(path: Paths.php, arguments: ["-r", "echo php_ini_scanned_files();"]) + let paths = Command.execute(path: Paths.php, arguments: ["-r", "echo php_ini_scanned_files();"], trimNewlines: false) .replacingOccurrences(of: "\n", with: "") .split(separator: ",") .map { String($0) } @@ -105,7 +105,7 @@ class ActivePhpInstallation { - Parameter key: The key of the `ini` value that needs to be retrieved. For example, you can use `memory_limit`. */ private func getByteCount(key: String) -> String { - let value = Command.execute(path: Paths.php, arguments: ["-r", "echo ini_get('\(key)');"]) + let value = Command.execute(path: Paths.php, arguments: ["-r", "echo ini_get('\(key)');"], trimNewlines: false) // Check if the value is unlimited if value == "-1" { diff --git a/phpmon/Common/PHP/PhpInstallation.swift b/phpmon/Common/PHP/PhpInstallation.swift index 8172760..c8abf99 100644 --- a/phpmon/Common/PHP/PhpInstallation.swift +++ b/phpmon/Common/PHP/PhpInstallation.swift @@ -24,7 +24,8 @@ class PhpInstallation { if FileSystem.fileExists(phpConfigExecutablePath) { let longVersionString = Command.execute( path: phpConfigExecutablePath, - arguments: ["--version"] + arguments: ["--version"], + trimNewlines: false ).trimmingCharacters(in: .whitespacesAndNewlines) // The parser should always work, or the string has to be very unusual. diff --git a/phpmon/Common/Testables/TestableCommand.swift b/phpmon/Common/Testables/TestableCommand.swift new file mode 100644 index 0000000..212e28c --- /dev/null +++ b/phpmon/Common/Testables/TestableCommand.swift @@ -0,0 +1,27 @@ +// +// TestableCommand.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 12/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class TestableCommand: CommandProtocol { + init(commands: [String: String]) { + self.commands = commands + } + + var commands: [String: String] + + func execute(path: String, arguments: [String]) -> String { + self.execute(path: path, arguments: arguments, trimNewlines: false) + } + + public func execute(path: String, arguments: [String], trimNewlines: Bool) -> String { + let concatenatedCommand = "\(path) \(arguments.joined(separator: " "))" + assert(commands.keys.contains(concatenatedCommand), "The expected command (\(concatenatedCommand)) was not found") + return self.commands[concatenatedCommand]! + } +} diff --git a/phpmon/Common/Testables/TestableConfigurations.swift b/phpmon/Common/Testables/TestableConfigurations.swift index 31ef899..40444f6 100644 --- a/phpmon/Common/Testables/TestableConfigurations.swift +++ b/phpmon/Common/Testables/TestableConfigurations.swift @@ -12,10 +12,12 @@ struct TestableConfiguration { let architecture: String let filesystem: [String: FakeFile] let shellOutput: [String: BatchFakeShellOutput] + let commandOutput: [String: String] func apply() { ActiveShell.useTestable(shellOutput) ActiveFileSystem.useTestable(filesystem) + ActiveCommand.useTestable(commandOutput) } } @@ -31,7 +33,8 @@ class TestableConfigurations { "id -un" : .instant("username"), "php -v" : .instant(""), "ls /opt/homebrew/opt | grep php" : .instant(""), - ] + ], + commandOutput: [:] ) } @@ -112,6 +115,19 @@ class TestableConfigurations { : .instant("Unable to find application named 'Sublime Merge'", .stdErr), "/usr/bin/open -Ra \"iTerm\"" : .instant("Unable to find application named 'iTerm'", .stdErr), + ], + commandOutput: [ + "/opt/homebrew/bin/php-config --version": "8.1.10", + "/opt/homebrew/bin/php -r echo ini_get('memory_limit');": "512M", + "/opt/homebrew/bin/php -r echo ini_get('upload_max_filesize');": "512M", + "/opt/homebrew/bin/php -r echo ini_get('post_max_size');": "512M", + "/opt/homebrew/bin/php -r echo php_ini_scanned_files();" + : """ + /opt/homebrew/etc/php/8.1/conf.d/error_log.ini, + /opt/homebrew/etc/php/8.1/conf.d/ext-opcache.ini, + /opt/homebrew/etc/php/8.1/conf.d/php-memory-limits.ini, + /opt/homebrew/etc/php/8.1/conf.d/xdebug.ini + """ ] ) } diff --git a/phpmon/Domain/Watcher/App+ConfigWatch.swift b/phpmon/Domain/Watcher/App+ConfigWatch.swift index bde582f..22b3e32 100644 --- a/phpmon/Domain/Watcher/App+ConfigWatch.swift +++ b/phpmon/Domain/Watcher/App+ConfigWatch.swift @@ -28,6 +28,11 @@ extension App { } func handlePhpConfigWatcher(forceReload: Bool = false) { + if ActiveFileSystem.shared is TestableFileSystem { + Log.warn("FS watcher is disabled when using testable filesystem.") + return + } + let url = URL(fileURLWithPath: "\(Paths.etcPath)/php/\(PhpEnv.phpInstall.version.short)") // Check whether the watcher exists and schedule on the main thread From 728274aaca0474e0e990584037c5b38e4d74be0f Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 14 Oct 2022 16:54:11 +0200 Subject: [PATCH 051/181] =?UTF-8?q?=F0=9F=91=8C=20Fix=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon-tests/Next/FakeShellTest.swift | 2 +- phpmon/Common/PHP/ActivePhpInstallation.swift | 12 ++++++++---- phpmon/Common/Testables/TestableCommand.swift | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/phpmon-tests/Next/FakeShellTest.swift b/phpmon-tests/Next/FakeShellTest.swift index 3a96cb1..16cfc34 100644 --- a/phpmon-tests/Next/FakeShellTest.swift +++ b/phpmon-tests/Next/FakeShellTest.swift @@ -26,7 +26,7 @@ class FakeShellTest: XCTestCase { .delayed(2, "Goodbye world") ]) - let output = await greeting.output(didReceiveOutput: { output, _ in }) + let output = await greeting.output(didReceiveOutput: { _, _ in }) XCTAssertEqual("Hello world\nGoodbye world", output.out) } diff --git a/phpmon/Common/PHP/ActivePhpInstallation.swift b/phpmon/Common/PHP/ActivePhpInstallation.swift index e06fea5..c4636f5 100644 --- a/phpmon/Common/PHP/ActivePhpInstallation.swift +++ b/phpmon/Common/PHP/ActivePhpInstallation.swift @@ -65,10 +65,14 @@ class ActivePhpInstallation { ) // Return a list of .ini files parsed after php.ini - let paths = Command.execute(path: Paths.php, arguments: ["-r", "echo php_ini_scanned_files();"], trimNewlines: false) - .replacingOccurrences(of: "\n", with: "") - .split(separator: ",") - .map { String($0) } + let paths = Command.execute( + path: Paths.php, + arguments: ["-r", "echo php_ini_scanned_files();"], + trimNewlines: false + ) + .replacingOccurrences(of: "\n", with: "") + .split(separator: ",") + .map { String($0) } // See if any extensions are present in said .ini files paths.forEach { (iniFilePath) in diff --git a/phpmon/Common/Testables/TestableCommand.swift b/phpmon/Common/Testables/TestableCommand.swift index 212e28c..a8295fb 100644 --- a/phpmon/Common/Testables/TestableCommand.swift +++ b/phpmon/Common/Testables/TestableCommand.swift @@ -21,7 +21,7 @@ class TestableCommand: CommandProtocol { public func execute(path: String, arguments: [String], trimNewlines: Bool) -> String { let concatenatedCommand = "\(path) \(arguments.joined(separator: " "))" - assert(commands.keys.contains(concatenatedCommand), "The expected command (\(concatenatedCommand)) was not found") + assert(commands.keys.contains(concatenatedCommand), "Command `\(concatenatedCommand)` not found") return self.commands[concatenatedCommand]! } } From 008603b8c328cc1b31763527fa667da99191fc3b Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 14 Oct 2022 16:55:37 +0200 Subject: [PATCH 052/181] =?UTF-8?q?=F0=9F=91=8C=20Fix=20project=20structur?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 36 +++++++++++-------- .../Shell}/FakeShellTest.swift | 0 .../Shell}/SystemShellTest.swift | 0 3 files changed, 22 insertions(+), 14 deletions(-) rename phpmon-tests/{Next => Testables/Shell}/FakeShellTest.swift (100%) rename phpmon-tests/{Next => Testables/Shell}/SystemShellTest.swift (100%) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 3bcae17..9e05ac7 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -680,13 +680,13 @@ path = Core; sourceTree = ""; }; - C413E43328DA3E8F00AE33C7 /* Next */ = { + C413E43328DA3E8F00AE33C7 /* Shell */ = { isa = PBXGroup; children = ( C413E43428DA3EB100AE33C7 /* FakeShellTest.swift */, C4159AF628E4D40400545349 /* SystemShellTest.swift */, ); - path = Next; + path = Shell; sourceTree = ""; }; C41C1B2A22B0097F00E7CF16 = { @@ -717,8 +717,6 @@ C41C1B3522B0097F00E7CF16 /* phpmon */ = { isa = PBXGroup; children = ( - C4AD38B428ECE1E100FA8D83 /* Tests */, - C46EBC3F28DB9550007ACC74 /* Next */, C4B5853A2770FE2500DA4FBE /* Common */, C41E181722CB61EB0072CF09 /* Domain */, 54D9E0BE27E4F5C0003B9AD9 /* Vendor */, @@ -889,11 +887,28 @@ path = DomainList; sourceTree = ""; }; - C46EBC3F28DB9550007ACC74 /* Next */ = { + C471E6D928F9AFC20021E251 /* Testables */ = { + isa = PBXGroup; + children = ( + C471E6DB28F9AFD10021E251 /* Command */, + C471E6DA28F9AFCB0021E251 /* Filesystem */, + C413E43328DA3E8F00AE33C7 /* Shell */, + ); + path = Testables; + sourceTree = ""; + }; + C471E6DA28F9AFCB0021E251 /* Filesystem */ = { isa = PBXGroup; children = ( ); - path = Next; + path = Filesystem; + sourceTree = ""; + }; + C471E6DB28F9AFD10021E251 /* Command */ = { + isa = PBXGroup; + children = ( + ); + path = Command; sourceTree = ""; }; C47331A0247093AC009A0597 /* Menu */ = { @@ -934,13 +949,6 @@ path = "PHP Version"; sourceTree = ""; }; - C4AD38B428ECE1E100FA8D83 /* Tests */ = { - isa = PBXGroup; - children = ( - ); - path = Tests; - sourceTree = ""; - }; C4AF9F6A275445C900D44ED0 /* Valet */ = { isa = PBXGroup; children = ( @@ -1213,7 +1221,7 @@ C4F7807A25D7F84B000DBC97 /* phpmon-tests */ = { isa = PBXGroup; children = ( - C413E43328DA3E8F00AE33C7 /* Next */, + C471E6D928F9AFC20021E251 /* Testables */, C4F7807D25D7F84B000DBC97 /* Info.plist */, C43A8A1925D9CD1000591B77 /* Utility.swift */, C40C7F1C27720E1400DDDCDC /* Test Files */, diff --git a/phpmon-tests/Next/FakeShellTest.swift b/phpmon-tests/Testables/Shell/FakeShellTest.swift similarity index 100% rename from phpmon-tests/Next/FakeShellTest.swift rename to phpmon-tests/Testables/Shell/FakeShellTest.swift diff --git a/phpmon-tests/Next/SystemShellTest.swift b/phpmon-tests/Testables/Shell/SystemShellTest.swift similarity index 100% rename from phpmon-tests/Next/SystemShellTest.swift rename to phpmon-tests/Testables/Shell/SystemShellTest.swift From d91e16d6745b8b344a77cbf7c8bdd90dcecb4ab8 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 14 Oct 2022 17:10:58 +0200 Subject: [PATCH 053/181] =?UTF-8?q?=E2=9C=85=20Add=20test=20plan,=20fix=20?= =?UTF-8?q?unit=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/PHP Monitor.xctestplan | 52 +++++++++++++++++++ PHP Monitor.xcodeproj/project.pbxproj | 8 +++ .../xcschemes/PHP Monitor.xcscheme | 8 ++- .../xcschemes/Unit Tests.xcscheme | 52 +++++++++++++++++++ phpmon-tests/Commands/CommandTest.swift | 3 +- .../Testables/Shell/SystemShellTest.swift | 4 +- phpmon/Common/PHP/PhpExtension.swift | 9 +++- 7 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 PHP Monitor.xcodeproj/PHP Monitor.xctestplan create mode 100644 PHP Monitor.xcodeproj/xcshareddata/xcschemes/Unit Tests.xcscheme diff --git a/PHP Monitor.xcodeproj/PHP Monitor.xctestplan b/PHP Monitor.xcodeproj/PHP Monitor.xctestplan new file mode 100644 index 0000000..a315285 --- /dev/null +++ b/PHP Monitor.xcodeproj/PHP Monitor.xctestplan @@ -0,0 +1,52 @@ +{ + "configurations" : [ + { + "id" : "98F42C11-E6D2-4AD9-A5CA-40EFE44F384A", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : false, + "commandLineArgumentEntries" : [ + { + "argument" : "--v", + "enabled" : false + } + ], + "environmentVariableEntries" : [ + { + "enabled" : false, + "key" : "EXTREME_DOCTOR_MODE", + "value" : "" + }, + { + "enabled" : false, + "key" : "SLOW_SHELL_MODE", + "value" : "" + }, + { + "enabled" : false, + "key" : "PAINT_PHPMON_SWIFTUI_VIEWS", + "value" : "" + } + ], + "targetForVariableExpansion" : { + "containerPath" : "container:PHP Monitor.xcodeproj", + "identifier" : "C41C1B3222B0097F00E7CF16", + "name" : "PHP Monitor" + } + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:PHP Monitor.xcodeproj", + "identifier" : "C4F7807825D7F84B000DBC97", + "name" : "phpmon-tests" + } + } + ], + "version" : 1 +} diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 9e05ac7..cef6353 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -159,6 +159,9 @@ C46FA9892822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */; }; C46FA98C2822F08F00D78807 /* PhpConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA98A2822F08F00D78807 /* PhpConfigurationTest.swift */; }; C4709CA228524B3400088BB8 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4709CA128524B3400088BB8 /* StatsView.swift */; }; + C471E79328F9B21F0021E251 /* ActiveFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */; }; + C471E79428F9B23B0021E251 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; }; + C471E79528F9B2420021E251 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; }; C473319F2470923A009A0597 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; }; C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; }; @@ -443,6 +446,7 @@ C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigurationFile.swift; sourceTree = ""; }; C46FA98A2822F08F00D78807 /* PhpConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigurationTest.swift; sourceTree = ""; }; C4709CA128524B3400088BB8 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = ""; }; + C471E79228F9B1D30021E251 /* PHP Monitor.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = "PHP Monitor.xctestplan"; path = "PHP Monitor.xcodeproj/PHP Monitor.xctestplan"; sourceTree = ""; }; C473319E2470923A009A0597 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; C47331A1247093B7009A0597 /* StatusMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMenu.swift; sourceTree = ""; }; C474B00524C0E98C00066A22 /* LocalNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotification.swift; sourceTree = ""; }; @@ -697,6 +701,7 @@ C4168F4427ADB4A3003B6C39 /* DEVELOPER.md */, 54D9E0C027E4F5E9003B9AD9 /* LICENSE */, C4F5FBCC28218C93001065C5 /* .swiftlint.yml */, + C471E79228F9B1D30021E251 /* PHP Monitor.xctestplan */, C4E713572570151400007428 /* docs */, C41C1B3522B0097F00E7CF16 /* phpmon */, C4F7807A25D7F84B000DBC97 /* phpmon-tests */, @@ -1581,6 +1586,7 @@ C46EBC4528DB95F0007ACC74 /* ShellProtocol.swift in Sources */, C44B3A4728E5C70100718CB1 /* TimeIntervalExtension.swift in Sources */, C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */, + C471E79528F9B2420021E251 /* RealFileSystem.swift in Sources */, 54FCFD2B276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */, C415D3B82770F294005EF286 /* Actions.swift in Sources */, 54B48B60275F66AE006D90C5 /* Application.swift in Sources */, @@ -1661,6 +1667,7 @@ C4080FF727BD8C6400BF2C6B /* BetterAlert.swift in Sources */, C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */, 5489625928313231004F647A /* CreatedFromFile.swift in Sources */, + C471E79328F9B21F0021E251 /* ActiveFileSystem.swift in Sources */, 54D9E0B327E4F51E003B9AD9 /* HotKeysController.swift in Sources */, 03E36FE828D9219000636F7F /* ActiveShell.swift in Sources */, C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */, @@ -1686,6 +1693,7 @@ C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */, C449B4F227EE7FC400C47E8A /* DomainListPhpCell.swift in Sources */, C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */, + C471E79428F9B23B0021E251 /* FileSystemProtocol.swift in Sources */, C4F30B0B278E203C00755FCE /* MainMenu+Startup.swift in Sources */, C485707C28BF459500539B36 /* NoWarningsView.swift in Sources */, C4F5FBCD28218CB8001065C5 /* Xdebug.swift in Sources */, diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme index 02cf00d..71155db 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme @@ -1,7 +1,7 @@ + version = "1.7"> @@ -27,6 +27,12 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/Unit Tests.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/Unit Tests.xcscheme new file mode 100644 index 0000000..59a0b6d --- /dev/null +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/Unit Tests.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/phpmon-tests/Commands/CommandTest.swift b/phpmon-tests/Commands/CommandTest.swift index 74e4969..e56a206 100644 --- a/phpmon-tests/Commands/CommandTest.swift +++ b/phpmon-tests/Commands/CommandTest.swift @@ -13,7 +13,8 @@ class CommandTest: XCTestCase { func testDeterminePhpVersion() { let version = Command.execute( path: Paths.php, - arguments: ["-v"] + arguments: ["-v"], + trimNewlines: false ) XCTAssert(version.contains("(cli)")) diff --git a/phpmon-tests/Testables/Shell/SystemShellTest.swift b/phpmon-tests/Testables/Shell/SystemShellTest.swift index 207586b..7771285 100644 --- a/phpmon-tests/Testables/Shell/SystemShellTest.swift +++ b/phpmon-tests/Testables/Shell/SystemShellTest.swift @@ -16,7 +16,7 @@ class SystemShellTest: XCTestCase { } func test_system_shell_is_default() async { - XCTAssertTrue(Shell is SystemShell) + XCTAssertTrue(Shell is RealShell) let output = await Shell.pipe("php -v") @@ -24,7 +24,7 @@ class SystemShellTest: XCTestCase { } func test_system_shell_has_path() { - let systemShell = Shell as! SystemShell + let systemShell = Shell as! RealShell XCTAssertTrue(systemShell.PATH.contains(":/usr/local/bin")) XCTAssertTrue(systemShell.PATH.contains(":/usr/bin")) diff --git a/phpmon/Common/PHP/PhpExtension.swift b/phpmon/Common/PHP/PhpExtension.swift index df20d07..345964a 100644 --- a/phpmon/Common/PHP/PhpExtension.swift +++ b/phpmon/Common/PHP/PhpExtension.swift @@ -86,9 +86,14 @@ class PhpExtension { enabled.toggle() - DispatchQueue.main.async { - MainMenu.shared.rebuild(async: false) + if !isRunningTests { + // When running unit tests, the MainMenu will not be available + // TODO: Fix this dependency issue, set up a notification mechanism + DispatchQueue.main.async { + MainMenu.shared.rebuild(async: false) + } } + } // MARK: - Static Methods From eaf6ef658fe65899eb9e72f5954e24c36ba7547a Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 14 Oct 2022 18:03:14 +0200 Subject: [PATCH 054/181] =?UTF-8?q?=F0=9F=9A=9B=20Moved=20tests=20around,?= =?UTF-8?q?=20added=20Feature,=20UI=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 937 +++++++++++++++++- .../xcschemes/PHP Monitor DEV.xcscheme | 18 +- .../xcschemes/PHP Monitor.xcscheme | 26 +- .../xcschemes/Unit Tests.xcscheme | 4 +- phpmon-tests/Info.plist | 22 - .../Common/Extensions/StringExtension.swift | 6 +- phpmon/Domain/App/AppDelegate.swift | 9 +- phpmon/Domain/Menu/MainMenu.swift | 2 +- .../PHP Monitor.xctestplan | 17 +- tests/feature/EmptyTest.swift | 11 + tests/ui/UI_Tests.swift | 35 + .../unit}/Commands/CommandTest.swift | 0 .../unit}/Parsers/HomebrewPackageTest.swift | 0 .../Parsers/NginxConfigurationTest.swift | 0 .../unit}/Parsers/PhpConfigurationTest.swift | 0 .../unit}/Parsers/PhpExtensionTest.swift | 0 .../Parsers/ValetConfigurationTest.swift | 0 .../unit}/Test Files/brew/brew-formula.json | 0 .../unit}/Test Files/brew/brew-services.json | 0 .../unit}/Test Files/nginx/nginx-proxy.test | 0 .../nginx/nginx-secure-proxy-custom-tld.test | 0 .../Test Files/nginx/nginx-secure-proxy.test | 0 .../Test Files/nginx/nginx-site-isolated.test | 0 .../unit}/Test Files/nginx/nginx-site.test | 0 .../unit}/Test Files/php/php.ini | 0 .../Test Files/phpmon/phpmon-config.json | 0 .../unit}/Test Files/valet/valet-config.json | 0 .../unit}/Testables/Shell/FakeShellTest.swift | 0 .../Testables/Shell/SystemShellTest.swift | 0 {phpmon-tests => tests/unit}/Utility.swift | 0 .../unit}/Versions/AppUpdaterCheckTest.swift | 0 .../unit}/Versions/AppVersionTest.swift | 0 .../Versions/PhpVersionDetectionTest.swift | 0 .../unit}/Versions/PhpVersionNumberTest.swift | 0 .../Versions/ValetVersionExtractorTest.swift | 0 .../unit}/Versions/VersionExtractorTest.swift | 0 36 files changed, 1028 insertions(+), 59 deletions(-) delete mode 100644 phpmon-tests/Info.plist rename {PHP Monitor.xcodeproj => tests}/PHP Monitor.xctestplan (71%) create mode 100644 tests/feature/EmptyTest.swift create mode 100644 tests/ui/UI_Tests.swift rename {phpmon-tests => tests/unit}/Commands/CommandTest.swift (100%) rename {phpmon-tests => tests/unit}/Parsers/HomebrewPackageTest.swift (100%) rename {phpmon-tests => tests/unit}/Parsers/NginxConfigurationTest.swift (100%) rename {phpmon-tests => tests/unit}/Parsers/PhpConfigurationTest.swift (100%) rename {phpmon-tests => tests/unit}/Parsers/PhpExtensionTest.swift (100%) rename {phpmon-tests => tests/unit}/Parsers/ValetConfigurationTest.swift (100%) rename {phpmon-tests => tests/unit}/Test Files/brew/brew-formula.json (100%) rename {phpmon-tests => tests/unit}/Test Files/brew/brew-services.json (100%) rename {phpmon-tests => tests/unit}/Test Files/nginx/nginx-proxy.test (100%) rename {phpmon-tests => tests/unit}/Test Files/nginx/nginx-secure-proxy-custom-tld.test (100%) rename {phpmon-tests => tests/unit}/Test Files/nginx/nginx-secure-proxy.test (100%) rename {phpmon-tests => tests/unit}/Test Files/nginx/nginx-site-isolated.test (100%) rename {phpmon-tests => tests/unit}/Test Files/nginx/nginx-site.test (100%) rename {phpmon-tests => tests/unit}/Test Files/php/php.ini (100%) rename {phpmon-tests => tests/unit}/Test Files/phpmon/phpmon-config.json (100%) rename {phpmon-tests => tests/unit}/Test Files/valet/valet-config.json (100%) rename {phpmon-tests => tests/unit}/Testables/Shell/FakeShellTest.swift (100%) rename {phpmon-tests => tests/unit}/Testables/Shell/SystemShellTest.swift (100%) rename {phpmon-tests => tests/unit}/Utility.swift (100%) rename {phpmon-tests => tests/unit}/Versions/AppUpdaterCheckTest.swift (100%) rename {phpmon-tests => tests/unit}/Versions/AppVersionTest.swift (100%) rename {phpmon-tests => tests/unit}/Versions/PhpVersionDetectionTest.swift (100%) rename {phpmon-tests => tests/unit}/Versions/PhpVersionNumberTest.swift (100%) rename {phpmon-tests => tests/unit}/Versions/ValetVersionExtractorTest.swift (100%) rename {phpmon-tests => tests/unit}/Versions/VersionExtractorTest.swift (100%) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index cef6353..9c0cd7f 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -162,6 +162,294 @@ C471E79328F9B21F0021E251 /* ActiveFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */; }; C471E79428F9B23B0021E251 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; }; C471E79528F9B2420021E251 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; }; + C471E7B028F9B4940021E251 /* EmptyTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C471E7AF28F9B4940021E251 /* EmptyTest.swift */; }; + C471E7BF28F9B90F0021E251 /* UI_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C471E7BE28F9B90F0021E251 /* UI_Tests.swift */; }; + C471E7C928F9BA2F0021E251 /* TestableConfigurations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40F505428ECA64E004AD45B /* TestableConfigurations.swift */; }; + C471E7CA28F9BA480021E251 /* TestableFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */; }; + C471E7CB28F9BA5B0021E251 /* TestableCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DEC28F764A00026AC4E /* TestableCommand.swift */; }; + C471E7CC28F9BA5B0021E251 /* TestableShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4928DB966A007ACC74 /* TestableShell.swift */; }; + C471E7CD28F9BA600021E251 /* ShellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */; }; + C471E7CE28F9BA600021E251 /* RealShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4628DB9644007ACC74 /* RealShell.swift */; }; + C471E7CF28F9BA600021E251 /* ActiveShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* ActiveShell.swift */; }; + C471E7D028F9BA630021E251 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; }; + C471E7D128F9BA630021E251 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; }; + C471E7D228F9BA630021E251 /* ActiveFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */; }; + C471E7D328F9BA8F0021E251 /* ActiveShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* ActiveShell.swift */; }; + C471E7D428F9BA8F0021E251 /* ActiveFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */; }; + C471E7D528F9BA8F0021E251 /* TestableConfigurations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40F505428ECA64E004AD45B /* TestableConfigurations.swift */; }; + C471E7D628F9BA8F0021E251 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; }; + C471E7D728F9BA8F0021E251 /* TestableFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */; }; + C471E7D828F9BA8F0021E251 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; }; + C471E7D928F9BA8F0021E251 /* TestableShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4928DB966A007ACC74 /* TestableShell.swift */; }; + C471E7DA28F9BA8F0021E251 /* TestableCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DEC28F764A00026AC4E /* TestableCommand.swift */; }; + C471E7DB28F9BA8F0021E251 /* RealShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4628DB9644007ACC74 /* RealShell.swift */; }; + C471E7DC28F9BA8F0021E251 /* ShellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */; }; + C471E7DD28F9BAA30021E251 /* CommandProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */; }; + C471E7DE28F9BAA30021E251 /* CommandProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */; }; + C471E7DF28F9BAAB0021E251 /* RealCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* RealCommand.swift */; }; + C471E7E028F9BAAB0021E251 /* ActiveCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE628F764050026AC4E /* ActiveCommand.swift */; }; + C471E7E128F9BAAB0021E251 /* RealCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* RealCommand.swift */; }; + C471E7E228F9BAAB0021E251 /* ActiveCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE628F764050026AC4E /* ActiveCommand.swift */; }; + C471E7E328F9BAC20021E251 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; }; + C471E7E428F9BAC20021E251 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; }; + C471E7E528F9BAC20021E251 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; }; + C471E7E628F9BAC20021E251 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C1019A27C65C6F001FACC2 /* Process.swift */; }; + C471E7E728F9BAC20021E251 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; }; + C471E7E828F9BAC20021E251 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; }; + C471E7E928F9BAC20021E251 /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; }; + C471E7EA28F9BAC30021E251 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; }; + C471E7EB28F9BAC30021E251 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; }; + C471E7EC28F9BAC30021E251 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; }; + C471E7ED28F9BAC30021E251 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C1019A27C65C6F001FACC2 /* Process.swift */; }; + C471E7EE28F9BAC30021E251 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; }; + C471E7EF28F9BAC30021E251 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; }; + C471E7F028F9BAC30021E251 /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; }; + C471E7F128F9BAC70021E251 /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; }; + C471E7F228F9BAC70021E251 /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; }; + C471E7F328F9BAC70021E251 /* PhpHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D936C827E3EB6100BD69FE /* PhpHelper.swift */; }; + C471E7F428F9BAC80021E251 /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; }; + C471E7F528F9BAC80021E251 /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; }; + C471E7F628F9BAC80021E251 /* PhpHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D936C827E3EB6100BD69FE /* PhpHelper.swift */; }; + C471E7F728F9BACB0021E251 /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */; }; + C471E7F828F9BACB0021E251 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; }; + C471E7F928F9BACB0021E251 /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */; }; + C471E7FA28F9BACB0021E251 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; }; + C471E7FB28F9BACE0021E251 /* HomebrewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F30B02278E16BA00755FCE /* HomebrewService.swift */; }; + C471E7FC28F9BACE0021E251 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; }; + C471E7FD28F9BACE0021E251 /* HomebrewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F30B02278E16BA00755FCE /* HomebrewService.swift */; }; + C471E7FE28F9BACE0021E251 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; }; + C471E7FF28F9BAD10021E251 /* Xdebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42337A2281F19F000459A48 /* Xdebug.swift */; }; + C471E80028F9BAD10021E251 /* Xdebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42337A2281F19F000459A48 /* Xdebug.swift */; }; + C471E80128F9BAD40021E251 /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; }; + C471E80228F9BAD40021E251 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; }; + C471E80328F9BAD40021E251 /* PhpConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */; }; + C471E80428F9BAD40021E251 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; }; + C471E80528F9BAD40021E251 /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; }; + C471E80628F9BAD40021E251 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; }; + C471E80728F9BAD40021E251 /* PhpConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */; }; + C471E80828F9BAD40021E251 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; }; + C471E80928F9BADC0021E251 /* CreatedFromFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5489625728312FAD004F647A /* CreatedFromFile.swift */; }; + C471E80A28F9BADC0021E251 /* CreatedFromFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5489625728312FAD004F647A /* CreatedFromFile.swift */; }; + C471E80B28F9BAE80021E251 /* XibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9225CC804200CC7490 /* XibLoadable.swift */; }; + C471E80C28F9BAE80021E251 /* NSWindowExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */; }; + C471E80D28F9BAE80021E251 /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E628553117006F9937 /* ArrayExtension.swift */; }; + C471E80E28F9BAE80021E251 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; }; + C471E80F28F9BAE80021E251 /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42759662627662800093CAE /* NSMenuExtension.swift */; }; + C471E81028F9BAE80021E251 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; }; + C471E81128F9BAE80021E251 /* NSMenuItemExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */; }; + C471E81228F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */; }; + C471E81328F9BAE80021E251 /* XibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9225CC804200CC7490 /* XibLoadable.swift */; }; + C471E81428F9BAE80021E251 /* NSWindowExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */; }; + C471E81528F9BAE80021E251 /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E628553117006F9937 /* ArrayExtension.swift */; }; + C471E81628F9BAE80021E251 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; }; + C471E81728F9BAE80021E251 /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42759662627662800093CAE /* NSMenuExtension.swift */; }; + C471E81828F9BAE80021E251 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; }; + C471E81928F9BAE80021E251 /* NSMenuItemExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */; }; + C471E81A28F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */; }; + C471E81B28F9BB250021E251 /* BetterAlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */; }; + C471E81C28F9BB250021E251 /* BetterAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */; }; + C471E81D28F9BB260021E251 /* BetterAlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */; }; + C471E81E28F9BB260021E251 /* BetterAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */; }; + C471E81F28F9BB290021E251 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; }; + C471E82028F9BB290021E251 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; }; + C471E82128F9BB2E0021E251 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; }; + C471E82228F9BB2E0021E251 /* ComposerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */; }; + C471E82328F9BB2E0021E251 /* ComposerJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D89BC52783C99400A02B68 /* ComposerJson.swift */; }; + C471E82428F9BB2E0021E251 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; }; + C471E82528F9BB2E0021E251 /* ComposerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */; }; + C471E82628F9BB2E0021E251 /* ComposerJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D89BC52783C99400A02B68 /* ComposerJson.swift */; }; + C471E82728F9BB310021E251 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; }; + C471E82828F9BB310021E251 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; }; + C471E82928F9BB330021E251 /* Valet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F792754499000D44ED0 /* Valet.swift */; }; + C471E82A28F9BB330021E251 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; + C471E82B28F9BB340021E251 /* Valet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F792754499000D44ED0 /* Valet.swift */; }; + C471E82C28F9BB340021E251 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; + C471E82D28F9BB650021E251 /* AlertableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */; }; + C471E82E28F9BB650021E251 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; }; + C471E82F28F9BB650021E251 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; }; + C471E83028F9BB650021E251 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Application.swift */; }; + C471E83128F9BB650021E251 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; }; + C471E83228F9BB650021E251 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; }; + C471E83328F9BB650021E251 /* PMWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CCBA6B275C567B008C7055 /* PMWindowController.swift */; }; + C471E83428F9BB650021E251 /* VersionExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5635D276AB09000F12CCB /* VersionExtractor.swift */; }; + C471E83528F9BB650021E251 /* ValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4205A7D27F4D21800191A39 /* ValetProxy.swift */; }; + C471E83628F9BB650021E251 /* ValetProxy+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */; }; + C471E83728F9BB650021E251 /* ProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E627F88B41002D32A9 /* ProxyScanner.swift */; }; + C471E83828F9BB650021E251 /* ValetProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C484437A2804BB560041A78A /* ValetProxyScanner.swift */; }; + C471E83928F9BB650021E251 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; + C471E83A28F9BB650021E251 /* ValetSite+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */; }; + C471E83B28F9BB650021E251 /* SiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A527E60D7A009F26CB /* SiteScanner.swift */; }; + C471E83C28F9BB650021E251 /* ValetSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E127F88B13002D32A9 /* ValetSiteScanner.swift */; }; + C471E83D28F9BB650021E251 /* FakeSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8DE27F88AEB002D32A9 /* FakeSiteScanner.swift */; }; + C471E83F28F9BB650021E251 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; }; + C471E84028F9BB650021E251 /* AppDelegate+MenuOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */; }; + C471E84128F9BB650021E251 /* AppDelegate+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */; }; + C471E84228F9BB650021E251 /* AppDelegate+InterApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */; }; + C471E84328F9BB650021E251 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; }; + C471E84428F9BB650021E251 /* App+ActivationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */; }; + C471E84528F9BB650021E251 /* App+GlobalHotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */; }; + C471E84628F9BB650021E251 /* InterAppHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EED88827A48778006D7272 /* InterAppHandler.swift */; }; + C471E84728F9BB650021E251 /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; }; + C471E84828F9BB650021E251 /* EnvironmentCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */; }; + C471E84928F9BB650021E251 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; + C471E84A28F9BB650021E251 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; + C471E84B28F9BB650021E251 /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; }; + C471E84C28F9BB650021E251 /* EnvironmentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A6957528D23EE300A14CF8 /* EnvironmentManager.swift */; }; + C471E84D28F9BB650021E251 /* ActivePhpInstallation+Checks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */; }; + C471E84E28F9BB650021E251 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; }; + C471E84F28F9BB650021E251 /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; }; + C471E85028F9BB650021E251 /* MainMenu+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */; }; + C471E85128F9BB650021E251 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; }; + C471E85228F9BB650021E251 /* MainMenu+FixMyValet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */; }; + C471E85328F9BB650021E251 /* MainMenu+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F361602836BFD9003598CC /* MainMenu+Actions.swift */; }; + C471E85428F9BB650021E251 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; }; + C471E85528F9BB650021E251 /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */; }; + C471E85628F9BB650021E251 /* DomainListCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */; }; + C471E85728F9BB650021E251 /* DomainListTLSCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067FA27E25FD70045BD4E /* DomainListTLSCell.swift */; }; + C471E85828F9BB650021E251 /* DomainListNameCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F427E2582B0045BD4E /* DomainListNameCell.swift */; }; + C471E85928F9BB650021E251 /* DomainListPhpCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F627E258410045BD4E /* DomainListPhpCell.swift */; }; + C471E85A28F9BB650021E251 /* DomainListTypeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F827E2585E0045BD4E /* DomainListTypeCell.swift */; }; + C471E85B28F9BB650021E251 /* DomainListKindCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */; }; + C471E85C28F9BB650021E251 /* DomainListWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* DomainListWindowController.swift */; }; + C471E85D28F9BB650021E251 /* DomainListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAE275A7A69003FCD53 /* DomainListVC.swift */; }; + C471E85E28F9BB650021E251 /* DomainListVC+ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41E87192763D42300161EE0 /* DomainListVC+ContextMenu.swift */; }; + C471E85F28F9BB650021E251 /* DomainListVC+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CA5EC2774F8EE00A2C80E /* DomainListVC+Actions.swift */; }; + C471E86028F9BB650021E251 /* SelectionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FE011028084FC200D1DE6D /* SelectionVC.swift */; }; + C471E86128F9BB650021E251 /* AddSiteVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4930849279F331F009C240B /* AddSiteVC.swift */; }; + C471E86228F9BB650021E251 /* AddProxyVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9F24A280B69E100DCD39A /* AddProxyVC.swift */; }; + C471E86328F9BB650021E251 /* PMTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A81CA328C67101008DD9D1 /* PMTableView.swift */; }; + C471E86428F9BB650021E251 /* Warning.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699F028A2F3150060FEB8 /* Warning.swift */; }; + C471E86528F9BB650021E251 /* WarningManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699EE28A2F2A30060FEB8 /* WarningManager.swift */; }; + C471E86628F9BB650021E251 /* WarningsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDAC28A2DAC600CEAC97 /* WarningsWindowController.swift */; }; + C471E86728F9BB650021E251 /* OnboardingWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE82288F1F9700FC478F /* OnboardingWindowController.swift */; }; + C471E86828F9BB650021E251 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PreferencesWindowController.swift */; }; + C471E86928F9BB650021E251 /* PreferencesWindowController+Hotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE7F288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift */; }; + C471E86A28F9BB650021E251 /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; }; + C471E86B28F9BB650021E251 /* PreferenceName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C450C8C528C919EC002A2B4B /* PreferenceName.swift */; }; + C471E86C28F9BB650021E251 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; }; + C471E86D28F9BB650021E251 /* CustomPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */; }; + C471E86E28F9BB650021E251 /* MenuBarIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA927B0890D00544CD5 /* MenuBarIcons.swift */; }; + C471E86F28F9BB650021E251 /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DEB7D327A5D60B00834718 /* Stats.swift */; }; + C471E87028F9BB650021E251 /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; }; + C471E87228F9BB650021E251 /* CheckboxPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD29276C8AA4004CE748 /* CheckboxPreferenceView.swift */; }; + C471E87428F9BB650021E251 /* SelectPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA627B07A1300544CD5 /* SelectPreferenceView.swift */; }; + C471E87628F9BB650021E251 /* HotkeyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */; }; + C471E87728F9BB650021E251 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CDA892288F1A71007CE25F /* Keys.swift */; }; + C471E87828F9BB650021E251 /* TerminalProgressWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */; }; + C471E87928F9BB650021E251 /* ProgressVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44A874728905BB000498BC4 /* ProgressVC.swift */; }; + C471E87B28F9BB650021E251 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; }; + C471E87C28F9BB650021E251 /* PhpConfigWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */; }; + C471E87D28F9BB650021E251 /* Preset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C5C9B2846A40600E28255 /* Preset.swift */; }; + C471E87E28F9BB650021E251 /* PresetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C463E37F284930EE00422731 /* PresetHelper.swift */; }; + C471E87F28F9BB650021E251 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.swift */; }; + C471E88028F9BB650021E251 /* WarningListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDA928A2C49900CEAC97 /* WarningListView.swift */; }; + C471E88128F9BB650021E251 /* NoWarningsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C708C28AA7F7900E8D498 /* NoWarningsView.swift */; }; + C471E88228F9BB650021E251 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */; }; + C471E88328F9BB650021E251 /* VersionPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */; }; + C471E88428F9BB650021E251 /* NoDomainResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508AE28ADA23D008FAC1F /* NoDomainResultsView.swift */; }; + C471E88528F9BB650021E251 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B6091C2853AB9700C95265 /* ServicesView.swift */; }; + C471E88628F9BB650021E251 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4709CA128524B3400088BB8 /* StatsView.swift */; }; + C471E88728F9BB650021E251 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B609192853AAD300C95265 /* SectionHeaderView.swift */; }; + C471E88828F9BB650021E251 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E428551F9B006F9937 /* HeaderView.swift */; }; + C471E88928F9BB650021E251 /* SwiftUIHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */; }; + C471E88B28F9BB650021E251 /* HotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AE27E4F51E003B9AD9 /* HotKey.swift */; }; + C471E88C28F9BB650021E251 /* HotKeysController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AC27E4F51E003B9AD9 /* HotKeysController.swift */; }; + C471E88D28F9BB650021E251 /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AD27E4F51E003B9AD9 /* Key.swift */; }; + C471E88E28F9BB650021E251 /* KeyCombo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AF27E4F51E003B9AD9 /* KeyCombo.swift */; }; + C471E88F28F9BB650021E251 /* ModifierFlagsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0B027E4F51E003B9AD9 /* ModifierFlagsExtension.swift */; }; + C471E89028F9BB8F0021E251 /* AlertableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */; }; + C471E89128F9BB8F0021E251 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; }; + C471E89228F9BB8F0021E251 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; }; + C471E89328F9BB8F0021E251 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Application.swift */; }; + C471E89428F9BB8F0021E251 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; }; + C471E89528F9BB8F0021E251 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; }; + C471E89628F9BB8F0021E251 /* PMWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CCBA6B275C567B008C7055 /* PMWindowController.swift */; }; + C471E89728F9BB8F0021E251 /* VersionExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5635D276AB09000F12CCB /* VersionExtractor.swift */; }; + C471E89828F9BB8F0021E251 /* ValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4205A7D27F4D21800191A39 /* ValetProxy.swift */; }; + C471E89928F9BB8F0021E251 /* ValetProxy+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */; }; + C471E89A28F9BB8F0021E251 /* ProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E627F88B41002D32A9 /* ProxyScanner.swift */; }; + C471E89B28F9BB8F0021E251 /* ValetProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C484437A2804BB560041A78A /* ValetProxyScanner.swift */; }; + C471E89C28F9BB8F0021E251 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; + C471E89D28F9BB8F0021E251 /* ValetSite+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */; }; + C471E89E28F9BB8F0021E251 /* SiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A527E60D7A009F26CB /* SiteScanner.swift */; }; + C471E89F28F9BB8F0021E251 /* ValetSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E127F88B13002D32A9 /* ValetSiteScanner.swift */; }; + C471E8A028F9BB8F0021E251 /* FakeSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8DE27F88AEB002D32A9 /* FakeSiteScanner.swift */; }; + C471E8A228F9BB8F0021E251 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; }; + C471E8A328F9BB8F0021E251 /* AppDelegate+MenuOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */; }; + C471E8A428F9BB8F0021E251 /* AppDelegate+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */; }; + C471E8A528F9BB8F0021E251 /* AppDelegate+InterApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */; }; + C471E8A628F9BB8F0021E251 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; }; + C471E8A728F9BB8F0021E251 /* App+ActivationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */; }; + C471E8A828F9BB8F0021E251 /* App+GlobalHotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */; }; + C471E8A928F9BB8F0021E251 /* InterAppHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EED88827A48778006D7272 /* InterAppHandler.swift */; }; + C471E8AA28F9BB8F0021E251 /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; }; + C471E8AB28F9BB8F0021E251 /* EnvironmentCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */; }; + C471E8AC28F9BB8F0021E251 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; + C471E8AD28F9BB8F0021E251 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; + C471E8AE28F9BB8F0021E251 /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; }; + C471E8AF28F9BB8F0021E251 /* EnvironmentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A6957528D23EE300A14CF8 /* EnvironmentManager.swift */; }; + C471E8B028F9BB8F0021E251 /* ActivePhpInstallation+Checks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */; }; + C471E8B128F9BB8F0021E251 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; }; + C471E8B228F9BB8F0021E251 /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; }; + C471E8B328F9BB8F0021E251 /* MainMenu+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */; }; + C471E8B428F9BB8F0021E251 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; }; + C471E8B528F9BB8F0021E251 /* MainMenu+FixMyValet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */; }; + C471E8B628F9BB8F0021E251 /* MainMenu+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F361602836BFD9003598CC /* MainMenu+Actions.swift */; }; + C471E8B728F9BB8F0021E251 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; }; + C471E8B828F9BB8F0021E251 /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */; }; + C471E8B928F9BB8F0021E251 /* DomainListCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */; }; + C471E8BA28F9BB8F0021E251 /* DomainListTLSCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067FA27E25FD70045BD4E /* DomainListTLSCell.swift */; }; + C471E8BB28F9BB8F0021E251 /* DomainListNameCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F427E2582B0045BD4E /* DomainListNameCell.swift */; }; + C471E8BC28F9BB8F0021E251 /* DomainListPhpCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F627E258410045BD4E /* DomainListPhpCell.swift */; }; + C471E8BD28F9BB8F0021E251 /* DomainListTypeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F827E2585E0045BD4E /* DomainListTypeCell.swift */; }; + C471E8BE28F9BB8F0021E251 /* DomainListKindCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */; }; + C471E8BF28F9BB8F0021E251 /* DomainListWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* DomainListWindowController.swift */; }; + C471E8C028F9BB8F0021E251 /* DomainListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAE275A7A69003FCD53 /* DomainListVC.swift */; }; + C471E8C128F9BB8F0021E251 /* DomainListVC+ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41E87192763D42300161EE0 /* DomainListVC+ContextMenu.swift */; }; + C471E8C228F9BB8F0021E251 /* DomainListVC+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CA5EC2774F8EE00A2C80E /* DomainListVC+Actions.swift */; }; + C471E8C328F9BB8F0021E251 /* SelectionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FE011028084FC200D1DE6D /* SelectionVC.swift */; }; + C471E8C428F9BB8F0021E251 /* AddSiteVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4930849279F331F009C240B /* AddSiteVC.swift */; }; + C471E8C528F9BB8F0021E251 /* AddProxyVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9F24A280B69E100DCD39A /* AddProxyVC.swift */; }; + C471E8C628F9BB8F0021E251 /* PMTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A81CA328C67101008DD9D1 /* PMTableView.swift */; }; + C471E8C728F9BB8F0021E251 /* Warning.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699F028A2F3150060FEB8 /* Warning.swift */; }; + C471E8C828F9BB8F0021E251 /* WarningManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699EE28A2F2A30060FEB8 /* WarningManager.swift */; }; + C471E8C928F9BB8F0021E251 /* WarningsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDAC28A2DAC600CEAC97 /* WarningsWindowController.swift */; }; + C471E8CA28F9BB8F0021E251 /* OnboardingWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE82288F1F9700FC478F /* OnboardingWindowController.swift */; }; + C471E8CB28F9BB8F0021E251 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PreferencesWindowController.swift */; }; + C471E8CC28F9BB8F0021E251 /* PreferencesWindowController+Hotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE7F288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift */; }; + C471E8CD28F9BB8F0021E251 /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; }; + C471E8CE28F9BB8F0021E251 /* PreferenceName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C450C8C528C919EC002A2B4B /* PreferenceName.swift */; }; + C471E8CF28F9BB8F0021E251 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; }; + C471E8D028F9BB8F0021E251 /* CustomPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */; }; + C471E8D128F9BB8F0021E251 /* MenuBarIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA927B0890D00544CD5 /* MenuBarIcons.swift */; }; + C471E8D228F9BB8F0021E251 /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DEB7D327A5D60B00834718 /* Stats.swift */; }; + C471E8D328F9BB8F0021E251 /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; }; + C471E8D528F9BB8F0021E251 /* CheckboxPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD29276C8AA4004CE748 /* CheckboxPreferenceView.swift */; }; + C471E8D728F9BB8F0021E251 /* SelectPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA627B07A1300544CD5 /* SelectPreferenceView.swift */; }; + C471E8D928F9BB8F0021E251 /* HotkeyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */; }; + C471E8DA28F9BB8F0021E251 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CDA892288F1A71007CE25F /* Keys.swift */; }; + C471E8DB28F9BB8F0021E251 /* TerminalProgressWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */; }; + C471E8DC28F9BB8F0021E251 /* ProgressVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44A874728905BB000498BC4 /* ProgressVC.swift */; }; + C471E8DE28F9BB8F0021E251 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; }; + C471E8DF28F9BB8F0021E251 /* PhpConfigWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */; }; + C471E8E028F9BB8F0021E251 /* Preset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C5C9B2846A40600E28255 /* Preset.swift */; }; + C471E8E128F9BB8F0021E251 /* PresetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C463E37F284930EE00422731 /* PresetHelper.swift */; }; + C471E8E228F9BB8F0021E251 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.swift */; }; + C471E8E328F9BB8F0021E251 /* WarningListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDA928A2C49900CEAC97 /* WarningListView.swift */; }; + C471E8E428F9BB8F0021E251 /* NoWarningsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C708C28AA7F7900E8D498 /* NoWarningsView.swift */; }; + C471E8E528F9BB8F0021E251 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */; }; + C471E8E628F9BB8F0021E251 /* VersionPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */; }; + C471E8E728F9BB8F0021E251 /* NoDomainResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508AE28ADA23D008FAC1F /* NoDomainResultsView.swift */; }; + C471E8E828F9BB8F0021E251 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B6091C2853AB9700C95265 /* ServicesView.swift */; }; + C471E8E928F9BB8F0021E251 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4709CA128524B3400088BB8 /* StatsView.swift */; }; + C471E8EA28F9BB8F0021E251 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B609192853AAD300C95265 /* SectionHeaderView.swift */; }; + C471E8EB28F9BB8F0021E251 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E428551F9B006F9937 /* HeaderView.swift */; }; + C471E8EC28F9BB8F0021E251 /* SwiftUIHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */; }; + C471E8EE28F9BB8F0021E251 /* HotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AE27E4F51E003B9AD9 /* HotKey.swift */; }; + C471E8EF28F9BB8F0021E251 /* HotKeysController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AC27E4F51E003B9AD9 /* HotKeysController.swift */; }; + C471E8F028F9BB8F0021E251 /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AD27E4F51E003B9AD9 /* Key.swift */; }; + C471E8F128F9BB8F0021E251 /* KeyCombo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AF27E4F51E003B9AD9 /* KeyCombo.swift */; }; + C471E8F228F9BB8F0021E251 /* ModifierFlagsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0B027E4F51E003B9AD9 /* ModifierFlagsExtension.swift */; }; C473319F2470923A009A0597 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; }; C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; }; @@ -335,6 +623,20 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + C471E7B128F9B4940021E251 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C41C1B2B22B0097F00E7CF16 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C41C1B3222B0097F00E7CF16; + remoteInfo = "PHP Monitor"; + }; + C471E7C228F9B90F0021E251 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C41C1B2B22B0097F00E7CF16 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C41C1B3222B0097F00E7CF16; + remoteInfo = "PHP Monitor"; + }; C4F7807E25D7F84B000DBC97 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C41C1B2B22B0097F00E7CF16 /* Project object */; @@ -446,7 +748,11 @@ C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigurationFile.swift; sourceTree = ""; }; C46FA98A2822F08F00D78807 /* PhpConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigurationTest.swift; sourceTree = ""; }; C4709CA128524B3400088BB8 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = ""; }; - C471E79228F9B1D30021E251 /* PHP Monitor.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = "PHP Monitor.xctestplan"; path = "PHP Monitor.xcodeproj/PHP Monitor.xctestplan"; sourceTree = ""; }; + C471E79228F9B1D30021E251 /* PHP Monitor.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "PHP Monitor.xctestplan"; sourceTree = ""; }; + C471E7AD28F9B4940021E251 /* Feature Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Feature Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + C471E7AF28F9B4940021E251 /* EmptyTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTest.swift; sourceTree = ""; }; + C471E7BC28F9B90F0021E251 /* UI Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + C471E7BE28F9B90F0021E251 /* UI_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UI_Tests.swift; sourceTree = ""; }; C473319E2470923A009A0597 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; C47331A1247093B7009A0597 /* StatusMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMenu.swift; sourceTree = ""; }; C474B00524C0E98C00066A22 /* LocalNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotification.swift; sourceTree = ""; }; @@ -525,8 +831,7 @@ C4F30B06278E195800755FCE /* brew-services.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "brew-services.json"; sourceTree = ""; }; C4F361602836BFD9003598CC /* MainMenu+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Actions.swift"; sourceTree = ""; }; C4F5FBCC28218C93001065C5 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; - C4F7807925D7F84B000DBC97 /* phpmon-tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "phpmon-tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - C4F7807D25D7F84B000DBC97 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C4F7807925D7F84B000DBC97 /* Unit Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Unit Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; C4F7809B25D80344000DBC97 /* CommandTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandTest.swift; sourceTree = ""; }; C4F780A725D80AE8000DBC97 /* php.ini */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = php.ini; sourceTree = ""; }; C4F780AD25D80B37000DBC97 /* PhpExtensionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtensionTest.swift; sourceTree = ""; }; @@ -546,6 +851,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C471E7AA28F9B4940021E251 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C471E7B928F9B90F0021E251 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; C4F7807625D7F84B000DBC97 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -701,10 +1020,9 @@ C4168F4427ADB4A3003B6C39 /* DEVELOPER.md */, 54D9E0C027E4F5E9003B9AD9 /* LICENSE */, C4F5FBCC28218C93001065C5 /* .swiftlint.yml */, - C471E79228F9B1D30021E251 /* PHP Monitor.xctestplan */, C4E713572570151400007428 /* docs */, C41C1B3522B0097F00E7CF16 /* phpmon */, - C4F7807A25D7F84B000DBC97 /* phpmon-tests */, + C471E79628F9B4260021E251 /* tests */, C41C1B3422B0097F00E7CF16 /* Products */, C4D309E72770EF2F00958BCF /* Frameworks */, ); @@ -714,7 +1032,9 @@ isa = PBXGroup; children = ( C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */, - C4F7807925D7F84B000DBC97 /* phpmon-tests.xctest */, + C4F7807925D7F84B000DBC97 /* Unit Tests.xctest */, + C471E7AD28F9B4940021E251 /* Feature Tests.xctest */, + C471E7BC28F9B90F0021E251 /* UI Tests.xctest */, ); name = Products; sourceTree = ""; @@ -916,6 +1236,33 @@ path = Command; sourceTree = ""; }; + C471E79628F9B4260021E251 /* tests */ = { + isa = PBXGroup; + children = ( + C471E79228F9B1D30021E251 /* PHP Monitor.xctestplan */, + C4F7807A25D7F84B000DBC97 /* unit */, + C471E7AE28F9B4940021E251 /* feature */, + C471E7BD28F9B90F0021E251 /* ui */, + ); + path = tests; + sourceTree = ""; + }; + C471E7AE28F9B4940021E251 /* feature */ = { + isa = PBXGroup; + children = ( + C471E7AF28F9B4940021E251 /* EmptyTest.swift */, + ); + path = feature; + sourceTree = ""; + }; + C471E7BD28F9B90F0021E251 /* ui */ = { + isa = PBXGroup; + children = ( + C471E7BE28F9B90F0021E251 /* UI_Tests.swift */, + ); + path = ui; + sourceTree = ""; + }; C47331A0247093AC009A0597 /* Menu */ = { isa = PBXGroup; children = ( @@ -1223,18 +1570,17 @@ path = Homebrew; sourceTree = ""; }; - C4F7807A25D7F84B000DBC97 /* phpmon-tests */ = { + C4F7807A25D7F84B000DBC97 /* unit */ = { isa = PBXGroup; children = ( - C471E6D928F9AFC20021E251 /* Testables */, - C4F7807D25D7F84B000DBC97 /* Info.plist */, C43A8A1925D9CD1000591B77 /* Utility.swift */, + C471E6D928F9AFC20021E251 /* Testables */, C40C7F1C27720E1400DDDCDC /* Test Files */, C4C1019927C65A4D001FACC2 /* Commands */, C4C1019827C65A1A001FACC2 /* Versions */, C4C1019727C65A11001FACC2 /* Parsers */, ); - path = "phpmon-tests"; + path = unit; sourceTree = ""; }; C4F787A628EF811000790735 /* Shell */ = { @@ -1296,9 +1642,45 @@ productReference = C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */; productType = "com.apple.product-type.application"; }; - C4F7807825D7F84B000DBC97 /* phpmon-tests */ = { + C471E7AC28F9B4940021E251 /* Feature Tests */ = { isa = PBXNativeTarget; - buildConfigurationList = C4F7808025D7F84B000DBC97 /* Build configuration list for PBXNativeTarget "phpmon-tests" */; + buildConfigurationList = C471E7B328F9B4940021E251 /* Build configuration list for PBXNativeTarget "Feature Tests" */; + buildPhases = ( + C471E7A928F9B4940021E251 /* Sources */, + C471E7AA28F9B4940021E251 /* Frameworks */, + C471E7AB28F9B4940021E251 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + C471E7B228F9B4940021E251 /* PBXTargetDependency */, + ); + name = "Feature Tests"; + productName = "Feature Tests"; + productReference = C471E7AD28F9B4940021E251 /* Feature Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + C471E7BB28F9B90F0021E251 /* UI Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = C471E7C428F9B90F0021E251 /* Build configuration list for PBXNativeTarget "UI Tests" */; + buildPhases = ( + C471E7B828F9B90F0021E251 /* Sources */, + C471E7B928F9B90F0021E251 /* Frameworks */, + C471E7BA28F9B90F0021E251 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + C471E7C328F9B90F0021E251 /* PBXTargetDependency */, + ); + name = "UI Tests"; + productName = "UI Tests"; + productReference = C471E7BC28F9B90F0021E251 /* UI Tests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; + C4F7807825D7F84B000DBC97 /* Unit Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = C4F7808025D7F84B000DBC97 /* Build configuration list for PBXNativeTarget "Unit Tests" */; buildPhases = ( C4F7807525D7F84B000DBC97 /* Sources */, C4F7807625D7F84B000DBC97 /* Frameworks */, @@ -1309,11 +1691,11 @@ dependencies = ( C4F7807F25D7F84B000DBC97 /* PBXTargetDependency */, ); - name = "phpmon-tests"; + name = "Unit Tests"; packageProductDependencies = ( ); productName = "phpmon-tests"; - productReference = C4F7807925D7F84B000DBC97 /* phpmon-tests.xctest */; + productReference = C4F7807925D7F84B000DBC97 /* Unit Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ @@ -1322,13 +1704,20 @@ C41C1B2B22B0097F00E7CF16 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1320; + LastSwiftUpdateCheck = 1400; LastUpgradeCheck = 1400; ORGANIZATIONNAME = "Nico Verbruggen"; TargetAttributes = { C41C1B3222B0097F00E7CF16 = { CreatedOnToolsVersion = 10.2.1; }; + C471E7AC28F9B4940021E251 = { + CreatedOnToolsVersion = 14.0.1; + }; + C471E7BB28F9B90F0021E251 = { + CreatedOnToolsVersion = 14.0.1; + TestTargetID = C41C1B3222B0097F00E7CF16; + }; C4F7807825D7F84B000DBC97 = { CreatedOnToolsVersion = 12.4; }; @@ -1350,7 +1739,9 @@ projectRoot = ""; targets = ( C41C1B3222B0097F00E7CF16 /* PHP Monitor */, - C4F7807825D7F84B000DBC97 /* phpmon-tests */, + C4F7807825D7F84B000DBC97 /* Unit Tests */, + C471E7AC28F9B4940021E251 /* Feature Tests */, + C471E7BB28F9B90F0021E251 /* UI Tests */, ); }; /* End PBXProject section */ @@ -1374,6 +1765,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C471E7AB28F9B4940021E251 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C471E7BA28F9B90F0021E251 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; C4F7807725D7F84B000DBC97 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1570,6 +1975,308 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C471E7A928F9B4940021E251 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C471E82D28F9BB650021E251 /* AlertableError.swift in Sources */, + C471E82E28F9BB650021E251 /* Errors.swift in Sources */, + C471E82F28F9BB650021E251 /* Alert.swift in Sources */, + C471E83028F9BB650021E251 /* Application.swift in Sources */, + C471E83128F9BB650021E251 /* LocalNotification.swift in Sources */, + C471E83228F9BB650021E251 /* MenuBarImageGenerator.swift in Sources */, + C471E83328F9BB650021E251 /* PMWindowController.swift in Sources */, + C471E83428F9BB650021E251 /* VersionExtractor.swift in Sources */, + C471E83528F9BB650021E251 /* ValetProxy.swift in Sources */, + C471E83628F9BB650021E251 /* ValetProxy+Fake.swift in Sources */, + C471E83728F9BB650021E251 /* ProxyScanner.swift in Sources */, + C471E83828F9BB650021E251 /* ValetProxyScanner.swift in Sources */, + C471E83928F9BB650021E251 /* ValetSite.swift in Sources */, + C471E83A28F9BB650021E251 /* ValetSite+Fake.swift in Sources */, + C471E83B28F9BB650021E251 /* SiteScanner.swift in Sources */, + C471E83C28F9BB650021E251 /* ValetSiteScanner.swift in Sources */, + C471E83D28F9BB650021E251 /* FakeSiteScanner.swift in Sources */, + C471E83F28F9BB650021E251 /* AppDelegate.swift in Sources */, + C471E84028F9BB650021E251 /* AppDelegate+MenuOutlets.swift in Sources */, + C471E84128F9BB650021E251 /* AppDelegate+Notifications.swift in Sources */, + C471E84228F9BB650021E251 /* AppDelegate+InterApp.swift in Sources */, + C471E84328F9BB650021E251 /* App.swift in Sources */, + C471E84428F9BB650021E251 /* App+ActivationPolicy.swift in Sources */, + C471E84528F9BB650021E251 /* App+GlobalHotkey.swift in Sources */, + C471E84628F9BB650021E251 /* InterAppHandler.swift in Sources */, + C471E84728F9BB650021E251 /* Startup.swift in Sources */, + C471E84828F9BB650021E251 /* EnvironmentCheck.swift in Sources */, + C471E84928F9BB650021E251 /* AppUpdateChecker.swift in Sources */, + C471E84A28F9BB650021E251 /* AppVersion.swift in Sources */, + C471E84B28F9BB650021E251 /* ServicesManager.swift in Sources */, + C471E84C28F9BB650021E251 /* EnvironmentManager.swift in Sources */, + C471E84D28F9BB650021E251 /* ActivePhpInstallation+Checks.swift in Sources */, + C471E84E28F9BB650021E251 /* MainMenu.swift in Sources */, + C471E84F28F9BB650021E251 /* MainMenu+Startup.swift in Sources */, + C471E85028F9BB650021E251 /* MainMenu+Async.swift in Sources */, + C471E85128F9BB650021E251 /* MainMenu+Switcher.swift in Sources */, + C471E85228F9BB650021E251 /* MainMenu+FixMyValet.swift in Sources */, + C471E85328F9BB650021E251 /* MainMenu+Actions.swift in Sources */, + C471E85428F9BB650021E251 /* StatusMenu.swift in Sources */, + C471E85528F9BB650021E251 /* StatusMenu+Items.swift in Sources */, + C471E85628F9BB650021E251 /* DomainListCellProtocol.swift in Sources */, + C471E85728F9BB650021E251 /* DomainListTLSCell.swift in Sources */, + C471E85828F9BB650021E251 /* DomainListNameCell.swift in Sources */, + C471E85928F9BB650021E251 /* DomainListPhpCell.swift in Sources */, + C471E85A28F9BB650021E251 /* DomainListTypeCell.swift in Sources */, + C471E85B28F9BB650021E251 /* DomainListKindCell.swift in Sources */, + C471E85C28F9BB650021E251 /* DomainListWindowController.swift in Sources */, + C471E85D28F9BB650021E251 /* DomainListVC.swift in Sources */, + C471E85E28F9BB650021E251 /* DomainListVC+ContextMenu.swift in Sources */, + C471E85F28F9BB650021E251 /* DomainListVC+Actions.swift in Sources */, + C471E86028F9BB650021E251 /* SelectionVC.swift in Sources */, + C471E86128F9BB650021E251 /* AddSiteVC.swift in Sources */, + C471E86228F9BB650021E251 /* AddProxyVC.swift in Sources */, + C471E86328F9BB650021E251 /* PMTableView.swift in Sources */, + C471E86428F9BB650021E251 /* Warning.swift in Sources */, + C471E86528F9BB650021E251 /* WarningManager.swift in Sources */, + C471E86628F9BB650021E251 /* WarningsWindowController.swift in Sources */, + C471E86728F9BB650021E251 /* OnboardingWindowController.swift in Sources */, + C471E86828F9BB650021E251 /* PreferencesWindowController.swift in Sources */, + C471E86928F9BB650021E251 /* PreferencesWindowController+Hotkey.swift in Sources */, + C471E86A28F9BB650021E251 /* PrefsVC.swift in Sources */, + C471E86B28F9BB650021E251 /* PreferenceName.swift in Sources */, + C471E86C28F9BB650021E251 /* Preferences.swift in Sources */, + C471E86D28F9BB650021E251 /* CustomPrefs.swift in Sources */, + C471E86E28F9BB650021E251 /* MenuBarIcons.swift in Sources */, + C471E86F28F9BB650021E251 /* Stats.swift in Sources */, + C471E87028F9BB650021E251 /* GlobalKeybindPreference.swift in Sources */, + C471E87228F9BB650021E251 /* CheckboxPreferenceView.swift in Sources */, + C471E87428F9BB650021E251 /* SelectPreferenceView.swift in Sources */, + C471E87628F9BB650021E251 /* HotkeyPreferenceView.swift in Sources */, + C471E87728F9BB650021E251 /* Keys.swift in Sources */, + C471E87828F9BB650021E251 /* TerminalProgressWindowController.swift in Sources */, + C471E87928F9BB650021E251 /* ProgressVC.swift in Sources */, + C471E87B28F9BB650021E251 /* App+ConfigWatch.swift in Sources */, + C471E87C28F9BB650021E251 /* PhpConfigWatcher.swift in Sources */, + C471E87D28F9BB650021E251 /* Preset.swift in Sources */, + C471E87E28F9BB650021E251 /* PresetHelper.swift in Sources */, + C471E87F28F9BB650021E251 /* WarningView.swift in Sources */, + C471E88028F9BB650021E251 /* WarningListView.swift in Sources */, + C471E88128F9BB650021E251 /* NoWarningsView.swift in Sources */, + C471E88228F9BB650021E251 /* OnboardingView.swift in Sources */, + C471E88328F9BB650021E251 /* VersionPopoverView.swift in Sources */, + C471E88428F9BB650021E251 /* NoDomainResultsView.swift in Sources */, + C471E88528F9BB650021E251 /* ServicesView.swift in Sources */, + C471E88628F9BB650021E251 /* StatsView.swift in Sources */, + C471E88728F9BB650021E251 /* SectionHeaderView.swift in Sources */, + C471E88828F9BB650021E251 /* HeaderView.swift in Sources */, + C471E88928F9BB650021E251 /* SwiftUIHelper.swift in Sources */, + C471E88B28F9BB650021E251 /* HotKey.swift in Sources */, + C471E88C28F9BB650021E251 /* HotKeysController.swift in Sources */, + C471E88D28F9BB650021E251 /* Key.swift in Sources */, + C471E88E28F9BB650021E251 /* KeyCombo.swift in Sources */, + C471E88F28F9BB650021E251 /* ModifierFlagsExtension.swift in Sources */, + C471E7E928F9BAC20021E251 /* Paths.swift in Sources */, + C471E7FE28F9BACE0021E251 /* HomebrewPackage.swift in Sources */, + C471E7D828F9BA8F0021E251 /* FileSystemProtocol.swift in Sources */, + C471E7F328F9BAC70021E251 /* PhpHelper.swift in Sources */, + C471E7E728F9BAC20021E251 /* Constants.swift in Sources */, + C471E81628F9BAE80021E251 /* DateExtension.swift in Sources */, + C471E7D728F9BA8F0021E251 /* TestableFileSystem.swift in Sources */, + C471E81A28F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */, + C471E7E128F9BAAB0021E251 /* RealCommand.swift in Sources */, + C471E7E228F9BAAB0021E251 /* ActiveCommand.swift in Sources */, + C471E80A28F9BADC0021E251 /* CreatedFromFile.swift in Sources */, + C471E80528F9BAD40021E251 /* ActivePhpInstallation.swift in Sources */, + C471E80628F9BAD40021E251 /* PhpInstallation.swift in Sources */, + C471E81828F9BAE80021E251 /* StringExtension.swift in Sources */, + C471E7FA28F9BACB0021E251 /* InternalSwitcher.swift in Sources */, + C471E82628F9BB2E0021E251 /* ComposerJson.swift in Sources */, + C471E82428F9BB2E0021E251 /* PhpFrameworks.swift in Sources */, + C471E7E828F9BAC20021E251 /* Actions.swift in Sources */, + C471E82528F9BB2E0021E251 /* ComposerWindow.swift in Sources */, + C471E80828F9BAD40021E251 /* PhpExtension.swift in Sources */, + C471E7F928F9BACB0021E251 /* PhpSwitcher.swift in Sources */, + C471E82A28F9BB330021E251 /* DomainListable.swift in Sources */, + C471E82728F9BB310021E251 /* HomebrewDiagnostics.swift in Sources */, + C471E81C28F9BB250021E251 /* BetterAlert.swift in Sources */, + C471E7DB28F9BA8F0021E251 /* RealShell.swift in Sources */, + C471E7FF28F9BAD10021E251 /* Xdebug.swift in Sources */, + C471E7F228F9BAC70021E251 /* PhpEnv.swift in Sources */, + C471E7E628F9BAC20021E251 /* Process.swift in Sources */, + C471E81928F9BAE80021E251 /* NSMenuItemExtension.swift in Sources */, + C471E7D928F9BA8F0021E251 /* TestableShell.swift in Sources */, + C471E81428F9BAE80021E251 /* NSWindowExtension.swift in Sources */, + C471E7D328F9BA8F0021E251 /* ActiveShell.swift in Sources */, + C471E7DE28F9BAA30021E251 /* CommandProtocol.swift in Sources */, + C471E7B028F9B4940021E251 /* EmptyTest.swift in Sources */, + C471E81B28F9BB250021E251 /* BetterAlertVC.swift in Sources */, + C471E82928F9BB330021E251 /* Valet.swift in Sources */, + C471E80728F9BAD40021E251 /* PhpConfigurationFile.swift in Sources */, + C471E7D528F9BA8F0021E251 /* TestableConfigurations.swift in Sources */, + C471E7E328F9BAC20021E251 /* Logger.swift in Sources */, + C471E7FD28F9BACE0021E251 /* HomebrewService.swift in Sources */, + C471E7E428F9BAC20021E251 /* Helpers.swift in Sources */, + C471E82028F9BB290021E251 /* NginxConfigurationFile.swift in Sources */, + C471E7D428F9BA8F0021E251 /* ActiveFileSystem.swift in Sources */, + C471E81528F9BAE80021E251 /* ArrayExtension.swift in Sources */, + C471E7DA28F9BA8F0021E251 /* TestableCommand.swift in Sources */, + C471E7E528F9BAC20021E251 /* Events.swift in Sources */, + C471E7D628F9BA8F0021E251 /* RealFileSystem.swift in Sources */, + C471E81728F9BAE80021E251 /* NSMenuExtension.swift in Sources */, + C471E81328F9BAE80021E251 /* XibLoadable.swift in Sources */, + C471E7F128F9BAC70021E251 /* PhpVersionNumber.swift in Sources */, + C471E7DC28F9BA8F0021E251 /* ShellProtocol.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C471E7B828F9B90F0021E251 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C471E89028F9BB8F0021E251 /* AlertableError.swift in Sources */, + C471E89128F9BB8F0021E251 /* Errors.swift in Sources */, + C471E89228F9BB8F0021E251 /* Alert.swift in Sources */, + C471E89328F9BB8F0021E251 /* Application.swift in Sources */, + C471E89428F9BB8F0021E251 /* LocalNotification.swift in Sources */, + C471E89528F9BB8F0021E251 /* MenuBarImageGenerator.swift in Sources */, + C471E89628F9BB8F0021E251 /* PMWindowController.swift in Sources */, + C471E89728F9BB8F0021E251 /* VersionExtractor.swift in Sources */, + C471E89828F9BB8F0021E251 /* ValetProxy.swift in Sources */, + C471E89928F9BB8F0021E251 /* ValetProxy+Fake.swift in Sources */, + C471E89A28F9BB8F0021E251 /* ProxyScanner.swift in Sources */, + C471E89B28F9BB8F0021E251 /* ValetProxyScanner.swift in Sources */, + C471E89C28F9BB8F0021E251 /* ValetSite.swift in Sources */, + C471E89D28F9BB8F0021E251 /* ValetSite+Fake.swift in Sources */, + C471E89E28F9BB8F0021E251 /* SiteScanner.swift in Sources */, + C471E89F28F9BB8F0021E251 /* ValetSiteScanner.swift in Sources */, + C471E8A028F9BB8F0021E251 /* FakeSiteScanner.swift in Sources */, + C471E8A228F9BB8F0021E251 /* AppDelegate.swift in Sources */, + C471E8A328F9BB8F0021E251 /* AppDelegate+MenuOutlets.swift in Sources */, + C471E8A428F9BB8F0021E251 /* AppDelegate+Notifications.swift in Sources */, + C471E8A528F9BB8F0021E251 /* AppDelegate+InterApp.swift in Sources */, + C471E8A628F9BB8F0021E251 /* App.swift in Sources */, + C471E8A728F9BB8F0021E251 /* App+ActivationPolicy.swift in Sources */, + C471E8A828F9BB8F0021E251 /* App+GlobalHotkey.swift in Sources */, + C471E8A928F9BB8F0021E251 /* InterAppHandler.swift in Sources */, + C471E8AA28F9BB8F0021E251 /* Startup.swift in Sources */, + C471E8AB28F9BB8F0021E251 /* EnvironmentCheck.swift in Sources */, + C471E8AC28F9BB8F0021E251 /* AppUpdateChecker.swift in Sources */, + C471E8AD28F9BB8F0021E251 /* AppVersion.swift in Sources */, + C471E8AE28F9BB8F0021E251 /* ServicesManager.swift in Sources */, + C471E8AF28F9BB8F0021E251 /* EnvironmentManager.swift in Sources */, + C471E8B028F9BB8F0021E251 /* ActivePhpInstallation+Checks.swift in Sources */, + C471E8B128F9BB8F0021E251 /* MainMenu.swift in Sources */, + C471E8B228F9BB8F0021E251 /* MainMenu+Startup.swift in Sources */, + C471E8B328F9BB8F0021E251 /* MainMenu+Async.swift in Sources */, + C471E8B428F9BB8F0021E251 /* MainMenu+Switcher.swift in Sources */, + C471E8B528F9BB8F0021E251 /* MainMenu+FixMyValet.swift in Sources */, + C471E8B628F9BB8F0021E251 /* MainMenu+Actions.swift in Sources */, + C471E8B728F9BB8F0021E251 /* StatusMenu.swift in Sources */, + C471E8B828F9BB8F0021E251 /* StatusMenu+Items.swift in Sources */, + C471E8B928F9BB8F0021E251 /* DomainListCellProtocol.swift in Sources */, + C471E8BA28F9BB8F0021E251 /* DomainListTLSCell.swift in Sources */, + C471E8BB28F9BB8F0021E251 /* DomainListNameCell.swift in Sources */, + C471E8BC28F9BB8F0021E251 /* DomainListPhpCell.swift in Sources */, + C471E8BD28F9BB8F0021E251 /* DomainListTypeCell.swift in Sources */, + C471E8BE28F9BB8F0021E251 /* DomainListKindCell.swift in Sources */, + C471E8BF28F9BB8F0021E251 /* DomainListWindowController.swift in Sources */, + C471E8C028F9BB8F0021E251 /* DomainListVC.swift in Sources */, + C471E8C128F9BB8F0021E251 /* DomainListVC+ContextMenu.swift in Sources */, + C471E8C228F9BB8F0021E251 /* DomainListVC+Actions.swift in Sources */, + C471E8C328F9BB8F0021E251 /* SelectionVC.swift in Sources */, + C471E8C428F9BB8F0021E251 /* AddSiteVC.swift in Sources */, + C471E8C528F9BB8F0021E251 /* AddProxyVC.swift in Sources */, + C471E8C628F9BB8F0021E251 /* PMTableView.swift in Sources */, + C471E8C728F9BB8F0021E251 /* Warning.swift in Sources */, + C471E8C828F9BB8F0021E251 /* WarningManager.swift in Sources */, + C471E8C928F9BB8F0021E251 /* WarningsWindowController.swift in Sources */, + C471E8CA28F9BB8F0021E251 /* OnboardingWindowController.swift in Sources */, + C471E8CB28F9BB8F0021E251 /* PreferencesWindowController.swift in Sources */, + C471E8CC28F9BB8F0021E251 /* PreferencesWindowController+Hotkey.swift in Sources */, + C471E8CD28F9BB8F0021E251 /* PrefsVC.swift in Sources */, + C471E8CE28F9BB8F0021E251 /* PreferenceName.swift in Sources */, + C471E8CF28F9BB8F0021E251 /* Preferences.swift in Sources */, + C471E8D028F9BB8F0021E251 /* CustomPrefs.swift in Sources */, + C471E8D128F9BB8F0021E251 /* MenuBarIcons.swift in Sources */, + C471E8D228F9BB8F0021E251 /* Stats.swift in Sources */, + C471E8D328F9BB8F0021E251 /* GlobalKeybindPreference.swift in Sources */, + C471E8D528F9BB8F0021E251 /* CheckboxPreferenceView.swift in Sources */, + C471E8D728F9BB8F0021E251 /* SelectPreferenceView.swift in Sources */, + C471E8D928F9BB8F0021E251 /* HotkeyPreferenceView.swift in Sources */, + C471E8DA28F9BB8F0021E251 /* Keys.swift in Sources */, + C471E8DB28F9BB8F0021E251 /* TerminalProgressWindowController.swift in Sources */, + C471E8DC28F9BB8F0021E251 /* ProgressVC.swift in Sources */, + C471E8DE28F9BB8F0021E251 /* App+ConfigWatch.swift in Sources */, + C471E8DF28F9BB8F0021E251 /* PhpConfigWatcher.swift in Sources */, + C471E8E028F9BB8F0021E251 /* Preset.swift in Sources */, + C471E8E128F9BB8F0021E251 /* PresetHelper.swift in Sources */, + C471E8E228F9BB8F0021E251 /* WarningView.swift in Sources */, + C471E8E328F9BB8F0021E251 /* WarningListView.swift in Sources */, + C471E8E428F9BB8F0021E251 /* NoWarningsView.swift in Sources */, + C471E8E528F9BB8F0021E251 /* OnboardingView.swift in Sources */, + C471E8E628F9BB8F0021E251 /* VersionPopoverView.swift in Sources */, + C471E8E728F9BB8F0021E251 /* NoDomainResultsView.swift in Sources */, + C471E8E828F9BB8F0021E251 /* ServicesView.swift in Sources */, + C471E8E928F9BB8F0021E251 /* StatsView.swift in Sources */, + C471E8EA28F9BB8F0021E251 /* SectionHeaderView.swift in Sources */, + C471E8EB28F9BB8F0021E251 /* HeaderView.swift in Sources */, + C471E8EC28F9BB8F0021E251 /* SwiftUIHelper.swift in Sources */, + C471E8EE28F9BB8F0021E251 /* HotKey.swift in Sources */, + C471E8EF28F9BB8F0021E251 /* HotKeysController.swift in Sources */, + C471E8F028F9BB8F0021E251 /* Key.swift in Sources */, + C471E8F128F9BB8F0021E251 /* KeyCombo.swift in Sources */, + C471E8F228F9BB8F0021E251 /* ModifierFlagsExtension.swift in Sources */, + C471E7F028F9BAC30021E251 /* Paths.swift in Sources */, + C471E7FC28F9BACE0021E251 /* HomebrewPackage.swift in Sources */, + C471E7CF28F9BA600021E251 /* ActiveShell.swift in Sources */, + C471E7F628F9BAC80021E251 /* PhpHelper.swift in Sources */, + C471E7EE28F9BAC30021E251 /* Constants.swift in Sources */, + C471E80E28F9BAE80021E251 /* DateExtension.swift in Sources */, + C471E7D028F9BA630021E251 /* FileSystemProtocol.swift in Sources */, + C471E81228F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */, + C471E7DF28F9BAAB0021E251 /* RealCommand.swift in Sources */, + C471E7E028F9BAAB0021E251 /* ActiveCommand.swift in Sources */, + C471E80928F9BADC0021E251 /* CreatedFromFile.swift in Sources */, + C471E80128F9BAD40021E251 /* ActivePhpInstallation.swift in Sources */, + C471E80228F9BAD40021E251 /* PhpInstallation.swift in Sources */, + C471E81028F9BAE80021E251 /* StringExtension.swift in Sources */, + C471E7F828F9BACB0021E251 /* InternalSwitcher.swift in Sources */, + C471E82328F9BB2E0021E251 /* ComposerJson.swift in Sources */, + C471E82128F9BB2E0021E251 /* PhpFrameworks.swift in Sources */, + C471E7EF28F9BAC30021E251 /* Actions.swift in Sources */, + C471E82228F9BB2E0021E251 /* ComposerWindow.swift in Sources */, + C471E80428F9BAD40021E251 /* PhpExtension.swift in Sources */, + C471E7F728F9BACB0021E251 /* PhpSwitcher.swift in Sources */, + C471E82C28F9BB340021E251 /* DomainListable.swift in Sources */, + C471E82828F9BB310021E251 /* HomebrewDiagnostics.swift in Sources */, + C471E81E28F9BB260021E251 /* BetterAlert.swift in Sources */, + C471E7D228F9BA630021E251 /* ActiveFileSystem.swift in Sources */, + C471E80028F9BAD10021E251 /* Xdebug.swift in Sources */, + C471E7F528F9BAC80021E251 /* PhpEnv.swift in Sources */, + C471E7ED28F9BAC30021E251 /* Process.swift in Sources */, + C471E81128F9BAE80021E251 /* NSMenuItemExtension.swift in Sources */, + C471E7CC28F9BA5B0021E251 /* TestableShell.swift in Sources */, + C471E80C28F9BAE80021E251 /* NSWindowExtension.swift in Sources */, + C471E7CA28F9BA480021E251 /* TestableFileSystem.swift in Sources */, + C471E7DD28F9BAA30021E251 /* CommandProtocol.swift in Sources */, + C471E7D128F9BA630021E251 /* RealFileSystem.swift in Sources */, + C471E81D28F9BB260021E251 /* BetterAlertVC.swift in Sources */, + C471E82B28F9BB340021E251 /* Valet.swift in Sources */, + C471E80328F9BAD40021E251 /* PhpConfigurationFile.swift in Sources */, + C471E7C928F9BA2F0021E251 /* TestableConfigurations.swift in Sources */, + C471E7EA28F9BAC30021E251 /* Logger.swift in Sources */, + C471E7FB28F9BACE0021E251 /* HomebrewService.swift in Sources */, + C471E7EB28F9BAC30021E251 /* Helpers.swift in Sources */, + C471E81F28F9BB290021E251 /* NginxConfigurationFile.swift in Sources */, + C471E7BF28F9B90F0021E251 /* UI_Tests.swift in Sources */, + C471E80D28F9BAE80021E251 /* ArrayExtension.swift in Sources */, + C471E7CD28F9BA600021E251 /* ShellProtocol.swift in Sources */, + C471E7EC28F9BAC30021E251 /* Events.swift in Sources */, + C471E7CE28F9BA600021E251 /* RealShell.swift in Sources */, + C471E80F28F9BAE80021E251 /* NSMenuExtension.swift in Sources */, + C471E80B28F9BAE80021E251 /* XibLoadable.swift in Sources */, + C471E7F428F9BAC80021E251 /* PhpVersionNumber.swift in Sources */, + C471E7CB28F9BA5B0021E251 /* TestableCommand.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; C4F7807525D7F84B000DBC97 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1737,6 +2444,16 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + C471E7B228F9B4940021E251 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C41C1B3222B0097F00E7CF16 /* PHP Monitor */; + targetProxy = C471E7B128F9B4940021E251 /* PBXContainerItemProxy */; + }; + C471E7C328F9B90F0021E251 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C41C1B3222B0097F00E7CF16 /* PHP Monitor */; + targetProxy = C471E7C228F9B90F0021E251 /* PBXContainerItemProxy */; + }; C4F7807F25D7F84B000DBC97 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C41C1B3222B0097F00E7CF16 /* PHP Monitor */; @@ -1933,6 +2650,150 @@ }; name = Release; }; + C471E7B428F9B4940021E251 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8M54J5J787; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ""; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon.feature-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + C471E7B528F9B4940021E251 /* Debug.Dev */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8M54J5J787; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ""; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon.feature-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + }; + name = Debug.Dev; + }; + C471E7B628F9B4940021E251 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8M54J5J787; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ""; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon.feature-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + C471E7B728F9B4940021E251 /* Release.Dev */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8M54J5J787; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ""; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon.feature-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + }; + name = Release.Dev; + }; + C471E7C528F9B90F0021E251 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8M54J5J787; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 12.3; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon.ui-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = "PHP Monitor"; + }; + name = Debug; + }; + C471E7C628F9B90F0021E251 /* Debug.Dev */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8M54J5J787; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 12.3; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon.ui-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = "PHP Monitor"; + }; + name = Debug.Dev; + }; + C471E7C728F9B90F0021E251 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8M54J5J787; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 12.3; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon.ui-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = "PHP Monitor"; + }; + name = Release; + }; + C471E7C828F9B90F0021E251 /* Release.Dev */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8M54J5J787; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 12.3; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon.ui-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = "PHP Monitor"; + }; + name = Release.Dev; + }; C4975D0728CD190C00FFB4E8 /* Release.Dev */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2022,14 +2883,15 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 8M54J5J787; - INFOPLIST_FILE = "phpmon-tests/Info.plist"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-tests"; + PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon.unit-tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; }; @@ -2131,14 +2993,15 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 8M54J5J787; - INFOPLIST_FILE = "phpmon-tests/Info.plist"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-tests"; + PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon.unit-tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; }; @@ -2151,14 +3014,15 @@ COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 8M54J5J787; - INFOPLIST_FILE = "phpmon-tests/Info.plist"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-tests"; + PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon.unit-tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; }; @@ -2171,14 +3035,15 @@ COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 8M54J5J787; - INFOPLIST_FILE = "phpmon-tests/Info.plist"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-tests"; + PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon.unit-tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; }; @@ -2209,7 +3074,29 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - C4F7808025D7F84B000DBC97 /* Build configuration list for PBXNativeTarget "phpmon-tests" */ = { + C471E7B328F9B4940021E251 /* Build configuration list for PBXNativeTarget "Feature Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C471E7B428F9B4940021E251 /* Debug */, + C471E7B528F9B4940021E251 /* Debug.Dev */, + C471E7B628F9B4940021E251 /* Release */, + C471E7B728F9B4940021E251 /* Release.Dev */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C471E7C428F9B90F0021E251 /* Build configuration list for PBXNativeTarget "UI Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C471E7C528F9B90F0021E251 /* Debug */, + C471E7C628F9B90F0021E251 /* Debug.Dev */, + C471E7C728F9B90F0021E251 /* Release */, + C471E7C828F9B90F0021E251 /* Release.Dev */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C4F7808025D7F84B000DBC97 /* Build configuration list for PBXNativeTarget "Unit Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( C4F7808125D7F84B000DBC97 /* Debug */, diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index de38b69..f129fcd 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -29,12 +29,24 @@ shouldUseLaunchSchemeArgsEnv = "YES"> + skipped = "NO" + parallelizable = "YES"> + + + + diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme index 71155db..b7409be 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme @@ -39,8 +39,30 @@ + + + + + + + + diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/Unit Tests.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/Unit Tests.xcscheme index 59a0b6d..25a1543 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/Unit Tests.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/Unit Tests.xcscheme @@ -17,8 +17,8 @@ diff --git a/phpmon-tests/Info.plist b/phpmon-tests/Info.plist deleted file mode 100644 index 64d65ca..0000000 --- a/phpmon-tests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - - diff --git a/phpmon/Common/Extensions/StringExtension.swift b/phpmon/Common/Extensions/StringExtension.swift index a8746c1..a67b0d4 100644 --- a/phpmon/Common/Extensions/StringExtension.swift +++ b/phpmon/Common/Extensions/StringExtension.swift @@ -42,6 +42,11 @@ extension String { return count } + func matches(pattern: String) -> Bool { + let pred = NSPredicate(format: "self LIKE %@", pattern) + return !NSArray(object: self).filtered(using: pred).isEmpty + } + subscript(r: Range) -> String { let start = r.lowerBound let end = r.upperBound @@ -98,5 +103,4 @@ extension String { return "" } } - } diff --git a/phpmon/Domain/App/AppDelegate.swift b/phpmon/Domain/App/AppDelegate.swift index 73fc12f..71a7d12 100644 --- a/phpmon/Domain/App/AppDelegate.swift +++ b/phpmon/Domain/App/AppDelegate.swift @@ -61,8 +61,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele #if DEBUG logger.verbosity = .performance - // Use a "working" test configuration - TestableConfigurations.working.apply() + if let profile = CommandLine.arguments.first(where: { $0.matches(pattern: "--configuration:*") }) { + Self.initializeTestingProfile(profile.replacingOccurrences(of: "--configuration:", with: "")) + } #endif if CommandLine.arguments.contains("--v") { @@ -86,6 +87,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele self.phpEnvironment = PhpEnv.shared } + static func initializeTestingProfile(_ profile: String) { + Log.info("The profile `\(profile)` is being requested...") + } + // MARK: - Lifecycle /** diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index 4744b99..175aba7 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -21,7 +21,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate /** The status bar item with variable length. */ - @MainActor let statusItem = NSStatusBar.system.statusItem( + let statusItem = NSStatusBar.system.statusItem( withLength: NSStatusItem.variableLength ) diff --git a/PHP Monitor.xcodeproj/PHP Monitor.xctestplan b/tests/PHP Monitor.xctestplan similarity index 71% rename from PHP Monitor.xcodeproj/PHP Monitor.xctestplan rename to tests/PHP Monitor.xctestplan index a315285..cd0bc27 100644 --- a/PHP Monitor.xcodeproj/PHP Monitor.xctestplan +++ b/tests/PHP Monitor.xctestplan @@ -41,10 +41,25 @@ }, "testTargets" : [ { + "parallelizable" : true, "target" : { "containerPath" : "container:PHP Monitor.xcodeproj", "identifier" : "C4F7807825D7F84B000DBC97", - "name" : "phpmon-tests" + "name" : "Unit Tests" + } + }, + { + "target" : { + "containerPath" : "container:PHP Monitor.xcodeproj", + "identifier" : "C471E7AC28F9B4940021E251", + "name" : "Feature Tests" + } + }, + { + "target" : { + "containerPath" : "container:PHP Monitor.xcodeproj", + "identifier" : "C471E7BB28F9B90F0021E251", + "name" : "UI Tests" } } ], diff --git a/tests/feature/EmptyTest.swift b/tests/feature/EmptyTest.swift new file mode 100644 index 0000000..762a07e --- /dev/null +++ b/tests/feature/EmptyTest.swift @@ -0,0 +1,11 @@ +// +// Feature_Tests.swift +// Feature Tests +// +// Created by Nico Verbruggen on 14/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import XCTest + +final class Feature_Tests: XCTestCase {} diff --git a/tests/ui/UI_Tests.swift b/tests/ui/UI_Tests.swift new file mode 100644 index 0000000..e003de5 --- /dev/null +++ b/tests/ui/UI_Tests.swift @@ -0,0 +1,35 @@ +// +// UI_Tests.swift +// UI Tests +// +// Created by Nico Verbruggen on 14/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import XCTest + +final class UI_Tests: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + TestableConfigurations.broken.apply() + } + + override func tearDownWithError() throws { + } + + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launchArguments = ["--configuration:broken"] + app.launch() + } + + /* + func testLaunchPerformance() throws { + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + */ +} diff --git a/phpmon-tests/Commands/CommandTest.swift b/tests/unit/Commands/CommandTest.swift similarity index 100% rename from phpmon-tests/Commands/CommandTest.swift rename to tests/unit/Commands/CommandTest.swift diff --git a/phpmon-tests/Parsers/HomebrewPackageTest.swift b/tests/unit/Parsers/HomebrewPackageTest.swift similarity index 100% rename from phpmon-tests/Parsers/HomebrewPackageTest.swift rename to tests/unit/Parsers/HomebrewPackageTest.swift diff --git a/phpmon-tests/Parsers/NginxConfigurationTest.swift b/tests/unit/Parsers/NginxConfigurationTest.swift similarity index 100% rename from phpmon-tests/Parsers/NginxConfigurationTest.swift rename to tests/unit/Parsers/NginxConfigurationTest.swift diff --git a/phpmon-tests/Parsers/PhpConfigurationTest.swift b/tests/unit/Parsers/PhpConfigurationTest.swift similarity index 100% rename from phpmon-tests/Parsers/PhpConfigurationTest.swift rename to tests/unit/Parsers/PhpConfigurationTest.swift diff --git a/phpmon-tests/Parsers/PhpExtensionTest.swift b/tests/unit/Parsers/PhpExtensionTest.swift similarity index 100% rename from phpmon-tests/Parsers/PhpExtensionTest.swift rename to tests/unit/Parsers/PhpExtensionTest.swift diff --git a/phpmon-tests/Parsers/ValetConfigurationTest.swift b/tests/unit/Parsers/ValetConfigurationTest.swift similarity index 100% rename from phpmon-tests/Parsers/ValetConfigurationTest.swift rename to tests/unit/Parsers/ValetConfigurationTest.swift diff --git a/phpmon-tests/Test Files/brew/brew-formula.json b/tests/unit/Test Files/brew/brew-formula.json similarity index 100% rename from phpmon-tests/Test Files/brew/brew-formula.json rename to tests/unit/Test Files/brew/brew-formula.json diff --git a/phpmon-tests/Test Files/brew/brew-services.json b/tests/unit/Test Files/brew/brew-services.json similarity index 100% rename from phpmon-tests/Test Files/brew/brew-services.json rename to tests/unit/Test Files/brew/brew-services.json diff --git a/phpmon-tests/Test Files/nginx/nginx-proxy.test b/tests/unit/Test Files/nginx/nginx-proxy.test similarity index 100% rename from phpmon-tests/Test Files/nginx/nginx-proxy.test rename to tests/unit/Test Files/nginx/nginx-proxy.test diff --git a/phpmon-tests/Test Files/nginx/nginx-secure-proxy-custom-tld.test b/tests/unit/Test Files/nginx/nginx-secure-proxy-custom-tld.test similarity index 100% rename from phpmon-tests/Test Files/nginx/nginx-secure-proxy-custom-tld.test rename to tests/unit/Test Files/nginx/nginx-secure-proxy-custom-tld.test diff --git a/phpmon-tests/Test Files/nginx/nginx-secure-proxy.test b/tests/unit/Test Files/nginx/nginx-secure-proxy.test similarity index 100% rename from phpmon-tests/Test Files/nginx/nginx-secure-proxy.test rename to tests/unit/Test Files/nginx/nginx-secure-proxy.test diff --git a/phpmon-tests/Test Files/nginx/nginx-site-isolated.test b/tests/unit/Test Files/nginx/nginx-site-isolated.test similarity index 100% rename from phpmon-tests/Test Files/nginx/nginx-site-isolated.test rename to tests/unit/Test Files/nginx/nginx-site-isolated.test diff --git a/phpmon-tests/Test Files/nginx/nginx-site.test b/tests/unit/Test Files/nginx/nginx-site.test similarity index 100% rename from phpmon-tests/Test Files/nginx/nginx-site.test rename to tests/unit/Test Files/nginx/nginx-site.test diff --git a/phpmon-tests/Test Files/php/php.ini b/tests/unit/Test Files/php/php.ini similarity index 100% rename from phpmon-tests/Test Files/php/php.ini rename to tests/unit/Test Files/php/php.ini diff --git a/phpmon-tests/Test Files/phpmon/phpmon-config.json b/tests/unit/Test Files/phpmon/phpmon-config.json similarity index 100% rename from phpmon-tests/Test Files/phpmon/phpmon-config.json rename to tests/unit/Test Files/phpmon/phpmon-config.json diff --git a/phpmon-tests/Test Files/valet/valet-config.json b/tests/unit/Test Files/valet/valet-config.json similarity index 100% rename from phpmon-tests/Test Files/valet/valet-config.json rename to tests/unit/Test Files/valet/valet-config.json diff --git a/phpmon-tests/Testables/Shell/FakeShellTest.swift b/tests/unit/Testables/Shell/FakeShellTest.swift similarity index 100% rename from phpmon-tests/Testables/Shell/FakeShellTest.swift rename to tests/unit/Testables/Shell/FakeShellTest.swift diff --git a/phpmon-tests/Testables/Shell/SystemShellTest.swift b/tests/unit/Testables/Shell/SystemShellTest.swift similarity index 100% rename from phpmon-tests/Testables/Shell/SystemShellTest.swift rename to tests/unit/Testables/Shell/SystemShellTest.swift diff --git a/phpmon-tests/Utility.swift b/tests/unit/Utility.swift similarity index 100% rename from phpmon-tests/Utility.swift rename to tests/unit/Utility.swift diff --git a/phpmon-tests/Versions/AppUpdaterCheckTest.swift b/tests/unit/Versions/AppUpdaterCheckTest.swift similarity index 100% rename from phpmon-tests/Versions/AppUpdaterCheckTest.swift rename to tests/unit/Versions/AppUpdaterCheckTest.swift diff --git a/phpmon-tests/Versions/AppVersionTest.swift b/tests/unit/Versions/AppVersionTest.swift similarity index 100% rename from phpmon-tests/Versions/AppVersionTest.swift rename to tests/unit/Versions/AppVersionTest.swift diff --git a/phpmon-tests/Versions/PhpVersionDetectionTest.swift b/tests/unit/Versions/PhpVersionDetectionTest.swift similarity index 100% rename from phpmon-tests/Versions/PhpVersionDetectionTest.swift rename to tests/unit/Versions/PhpVersionDetectionTest.swift diff --git a/phpmon-tests/Versions/PhpVersionNumberTest.swift b/tests/unit/Versions/PhpVersionNumberTest.swift similarity index 100% rename from phpmon-tests/Versions/PhpVersionNumberTest.swift rename to tests/unit/Versions/PhpVersionNumberTest.swift diff --git a/phpmon-tests/Versions/ValetVersionExtractorTest.swift b/tests/unit/Versions/ValetVersionExtractorTest.swift similarity index 100% rename from phpmon-tests/Versions/ValetVersionExtractorTest.swift rename to tests/unit/Versions/ValetVersionExtractorTest.swift diff --git a/phpmon-tests/Versions/VersionExtractorTest.swift b/tests/unit/Versions/VersionExtractorTest.swift similarity index 100% rename from phpmon-tests/Versions/VersionExtractorTest.swift rename to tests/unit/Versions/VersionExtractorTest.swift From d401fe997d5bb9102e78ed2c66c32f9a49c65cd7 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 15 Oct 2022 15:14:49 +0200 Subject: [PATCH 055/181] =?UTF-8?q?=E2=9C=85=20Make=20UI=20test=20actually?= =?UTF-8?q?=20functional?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcschemes/PHP Monitor DEV.xcscheme | 4 ++++ phpmon/Domain/App/AppDelegate.swift | 11 +++++++++++ tests/ui/UI_Tests.swift | 17 ++++------------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index f129fcd..83de33d 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -78,6 +78,10 @@ argument = "--v" isEnabled = "NO"> + + Date: Sat, 15 Oct 2022 15:36:03 +0200 Subject: [PATCH 056/181] =?UTF-8?q?=E2=9C=85=20Real=20UI=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcschemes/PHP Monitor DEV.xcscheme | 4 ++++ .../Common/Testables/TestableConfigurations.swift | 1 + tests/ui/UI_Tests.swift | 15 +++++++++++++++ 3 files changed, 20 insertions(+) diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index 83de33d..1418749 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -80,6 +80,10 @@ + + diff --git a/phpmon/Common/Testables/TestableConfigurations.swift b/phpmon/Common/Testables/TestableConfigurations.swift index 40444f6..e36e447 100644 --- a/phpmon/Common/Testables/TestableConfigurations.swift +++ b/phpmon/Common/Testables/TestableConfigurations.swift @@ -33,6 +33,7 @@ class TestableConfigurations { "id -un" : .instant("username"), "php -v" : .instant(""), "ls /opt/homebrew/opt | grep php" : .instant(""), + "valet --version" : .instant("zsh: command not found: valet") ], commandOutput: [:] ) diff --git a/tests/ui/UI_Tests.swift b/tests/ui/UI_Tests.swift index 1224f2e..f929869 100644 --- a/tests/ui/UI_Tests.swift +++ b/tests/ui/UI_Tests.swift @@ -21,6 +21,21 @@ final class UI_Tests: XCTestCase { let app = XCUIApplication() app.launchArguments = ["--configuration:working"] app.launch() + // XCTAssert(app.dialogs["Notice"].waitForExistence(timeout: 5)) sleep(10) } + + func testApplicationCanLaunchWithTestConfigurationAndThrowsAlert() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launchArguments = ["--configuration:broken"] + app.launch() + XCTAssert(app.dialogs["Notice"].waitForExistence(timeout: 5)) + app.buttons["OK"].click() + XCTAssert(app.dialogs["Notice"].waitForExistence(timeout: 5)) + XCTAssert(app.buttons["Quit"].waitForExistence(timeout: 1)) + // If this UI test presses the "Quit" button, the test takes forever + // because Xcode will attempt to figure out if the app closed correctly. + app.terminate() + } } From 273070ef2795fe2d7806e77a4e9191dbfdd46b7e Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 15 Oct 2022 16:37:33 +0200 Subject: [PATCH 057/181] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Sped=20up=20and=20?= =?UTF-8?q?improved=20UI=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 12 ++++-- tests/ui/StartupTest.swift | 53 +++++++++++++++++++++++++++ tests/ui/UITestCase.swift | 30 +++++++++++++++ tests/ui/UI_Tests.swift | 41 --------------------- 4 files changed, 91 insertions(+), 45 deletions(-) create mode 100644 tests/ui/StartupTest.swift create mode 100644 tests/ui/UITestCase.swift delete mode 100644 tests/ui/UI_Tests.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 9c0cd7f..74e7fe6 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -74,6 +74,7 @@ C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */; }; C417DC74277614690015E6EE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; }; C417DC75277614690015E6EE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; }; + C4181F1128FAF9330042EA28 /* UITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4181F1028FAF9330042EA28 /* UITestCase.swift */; }; C41C02A627E60D7A009F26CB /* SiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A527E60D7A009F26CB /* SiteScanner.swift */; }; C41C02A927E61A65009F26CB /* ValetSite+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */; }; C41C02AA27E61CA3009F26CB /* SiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A527E60D7A009F26CB /* SiteScanner.swift */; }; @@ -163,7 +164,7 @@ C471E79428F9B23B0021E251 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; }; C471E79528F9B2420021E251 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; }; C471E7B028F9B4940021E251 /* EmptyTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C471E7AF28F9B4940021E251 /* EmptyTest.swift */; }; - C471E7BF28F9B90F0021E251 /* UI_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C471E7BE28F9B90F0021E251 /* UI_Tests.swift */; }; + C471E7BF28F9B90F0021E251 /* StartupTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C471E7BE28F9B90F0021E251 /* StartupTest.swift */; }; C471E7C928F9BA2F0021E251 /* TestableConfigurations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40F505428ECA64E004AD45B /* TestableConfigurations.swift */; }; C471E7CA28F9BA480021E251 /* TestableFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */; }; C471E7CB28F9BA5B0021E251 /* TestableCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DEC28F764A00026AC4E /* TestableCommand.swift */; }; @@ -688,6 +689,7 @@ C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+InterApp.swift"; sourceTree = ""; }; C4168F4427ADB4A3003B6C39 /* DEVELOPER.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = DEVELOPER.md; sourceTree = ""; }; C417DC73277614690015E6EE /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; + C4181F1028FAF9330042EA28 /* UITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestCase.swift; sourceTree = ""; }; C41C02A527E60D7A009F26CB /* SiteScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteScanner.swift; sourceTree = ""; }; C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ValetSite+Fake.swift"; sourceTree = ""; }; C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PHP Monitor.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -752,7 +754,7 @@ C471E7AD28F9B4940021E251 /* Feature Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Feature Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; C471E7AF28F9B4940021E251 /* EmptyTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTest.swift; sourceTree = ""; }; C471E7BC28F9B90F0021E251 /* UI Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - C471E7BE28F9B90F0021E251 /* UI_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UI_Tests.swift; sourceTree = ""; }; + C471E7BE28F9B90F0021E251 /* StartupTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartupTest.swift; sourceTree = ""; }; C473319E2470923A009A0597 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; C47331A1247093B7009A0597 /* StatusMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMenu.swift; sourceTree = ""; }; C474B00524C0E98C00066A22 /* LocalNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotification.swift; sourceTree = ""; }; @@ -1258,7 +1260,8 @@ C471E7BD28F9B90F0021E251 /* ui */ = { isa = PBXGroup; children = ( - C471E7BE28F9B90F0021E251 /* UI_Tests.swift */, + C471E7BE28F9B90F0021E251 /* StartupTest.swift */, + C4181F1028FAF9330042EA28 /* UITestCase.swift */, ); path = ui; sourceTree = ""; @@ -2264,8 +2267,9 @@ C471E7EA28F9BAC30021E251 /* Logger.swift in Sources */, C471E7FB28F9BACE0021E251 /* HomebrewService.swift in Sources */, C471E7EB28F9BAC30021E251 /* Helpers.swift in Sources */, + C4181F1128FAF9330042EA28 /* UITestCase.swift in Sources */, C471E81F28F9BB290021E251 /* NginxConfigurationFile.swift in Sources */, - C471E7BF28F9B90F0021E251 /* UI_Tests.swift in Sources */, + C471E7BF28F9B90F0021E251 /* StartupTest.swift in Sources */, C471E80D28F9BAE80021E251 /* ArrayExtension.swift in Sources */, C471E7CD28F9BA600021E251 /* ShellProtocol.swift in Sources */, C471E7EC28F9BAC30021E251 /* Events.swift in Sources */, diff --git a/tests/ui/StartupTest.swift b/tests/ui/StartupTest.swift new file mode 100644 index 0000000..22008b4 --- /dev/null +++ b/tests/ui/StartupTest.swift @@ -0,0 +1,53 @@ +// +// UI_Tests.swift +// UI Tests +// +// Created by Nico Verbruggen on 14/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import XCTest + +final class StartupTest: UITestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + override func tearDownWithError() throws {} + + func testApplicationCanLaunchWithTestConfigurationAndIdles() throws { + let app = XCUIApplication() + app.launchArguments = ["--configuration:working"] + app.launch() + } + + func testApplicationCanLaunchWithTestConfigurationAndThrowsAlert() throws { + let app = XCUIApplication() + app.launchArguments = ["--configuration:broken"] + app.launch() + + // Dialog 1: "PHP is not correctly installed" + assertAllExist([ + app.dialogs["Notice"], + app.staticTexts["PHP is not correctly installed"], + app.buttons["OK"], + ]) + click(app.buttons["OK"]) + + // Dialog 2: PHP Monitor failed to start + assertAllExist([ + app.dialogs["Notice"], + app.staticTexts["PHP Monitor cannot start due to a problem with your system configuration"], + app.buttons["Retry"], + app.buttons["Quit"] + ]) + click(app.buttons["Retry"]) + + // Dialog 1 again + assertExists(app.staticTexts["PHP is not correctly installed"]) + + // At this point, we can terminate the test + app.terminate() + } +} diff --git a/tests/ui/UITestCase.swift b/tests/ui/UITestCase.swift new file mode 100644 index 0000000..f8520dc --- /dev/null +++ b/tests/ui/UITestCase.swift @@ -0,0 +1,30 @@ +// +// UITestCase.swift +// UI Tests +// +// Created by Nico Verbruggen on 15/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import XCTest + +class UITestCase: XCTestCase { + + /** Checks if a single element exists. */ + public func assertExists(_ element: XCUIElement, _ timeout: TimeInterval = 0.05) { + XCTAssert(element.waitForExistence(timeout: timeout)) + } + + /** Checks if all elements exist. */ + public func assertAllExist(_ elements: [XCUIElement], _ timeout: TimeInterval = 0.05) { + for element in elements { + XCTAssert(element.waitForExistence(timeout: timeout)) + } + } + + /** Clicks on a given element. */ + public func click(_ element: XCUIElement) { + element.click() + } + +} diff --git a/tests/ui/UI_Tests.swift b/tests/ui/UI_Tests.swift deleted file mode 100644 index f929869..0000000 --- a/tests/ui/UI_Tests.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// UI_Tests.swift -// UI Tests -// -// Created by Nico Verbruggen on 14/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. -// - -import XCTest - -final class UI_Tests: XCTestCase { - - override func setUpWithError() throws { - continueAfterFailure = false - } - - override func tearDownWithError() throws {} - - func testApplicationCanLaunchWithTestConfigurationAndIdles() throws { - // UI tests must launch the application that they test. - let app = XCUIApplication() - app.launchArguments = ["--configuration:working"] - app.launch() - // XCTAssert(app.dialogs["Notice"].waitForExistence(timeout: 5)) - sleep(10) - } - - func testApplicationCanLaunchWithTestConfigurationAndThrowsAlert() throws { - // UI tests must launch the application that they test. - let app = XCUIApplication() - app.launchArguments = ["--configuration:broken"] - app.launch() - XCTAssert(app.dialogs["Notice"].waitForExistence(timeout: 5)) - app.buttons["OK"].click() - XCTAssert(app.dialogs["Notice"].waitForExistence(timeout: 5)) - XCTAssert(app.buttons["Quit"].waitForExistence(timeout: 1)) - // If this UI test presses the "Quit" button, the test takes forever - // because Xcode will attempt to figure out if the app closed correctly. - app.terminate() - } -} From 5e3e0c087b2e4df68f992698c4ddf9f8ff4fb4a3 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sun, 16 Oct 2022 14:35:19 +0200 Subject: [PATCH 058/181] =?UTF-8?q?=F0=9F=91=8C=20Read=20configuration=20f?= =?UTF-8?q?rom=20JSON=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows us to alter the configuration prior to launching the app, which allows for additional flexibility during testing. --- PHP Monitor.xcodeproj/project.pbxproj | 76 +++++++- .../xcschemes/PHP Monitor DEV.xcscheme | 6 +- phpmon/Common/Extensions/DataExtension.swift | 19 ++ .../Common/Extensions/StringExtension.swift | 5 + phpmon/Common/Shell/ShellProtocol.swift | 2 +- .../Testables/TestableConfiguration.swift | 40 ++++ .../Common/Testables/TestableFileSystem.swift | 4 +- phpmon/Common/Testables/TestableShell.swift | 4 +- phpmon/Domain/App/AppDelegate.swift | 18 +- .../Shared}/TestableConfigurations.swift | 126 ++++++------- tests/{unit => Shared}/Utility.swift | 0 tests/Shared/XCPMApplication.swift | 23 +++ tests/ui/StartupTest.swift | 14 +- .../Test Files/brew/brew-services-normal.json | 173 ++++++++++++++++++ .../Test Files/brew/brew-services-sudo.json | 173 ++++++++++++++++++ .../Testables/TestableConfigurationTest.swift | 21 +++ 16 files changed, 602 insertions(+), 102 deletions(-) create mode 100644 phpmon/Common/Extensions/DataExtension.swift create mode 100644 phpmon/Common/Testables/TestableConfiguration.swift rename {phpmon/Common/Testables => tests/Shared}/TestableConfigurations.swift (62%) rename tests/{unit => Shared}/Utility.swift (100%) create mode 100644 tests/Shared/XCPMApplication.swift create mode 100644 tests/unit/Test Files/brew/brew-services-normal.json create mode 100644 tests/unit/Test Files/brew/brew-services-sudo.json create mode 100644 tests/unit/Testables/TestableConfigurationTest.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 74e7fe6..c549a24 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -500,8 +500,6 @@ C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; }; C4AD38B228ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */; }; C4AD38B328ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */; }; - C4AD38B528ECE2DB00FA8D83 /* brew-formula.json in Resources */ = {isa = PBXBuildFile; fileRef = C43A8A1F25D9D1D700591B77 /* brew-formula.json */; }; - C4AD38B628ECE56D00FA8D83 /* TestableConfigurations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40F505428ECA64E004AD45B /* TestableConfigurations.swift */; }; C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AF9F70275445FF00D44ED0 /* valet-config.json */; }; C4AF9F78275447F100D44ED0 /* ValetConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F76275447F100D44ED0 /* ValetConfigurationTest.swift */; }; C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F792754499000D44ED0 /* Valet.swift */; }; @@ -568,6 +566,30 @@ C4DEB7D427A5D60B00834718 /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DEB7D327A5D60B00834718 /* Stats.swift */; }; C4E0F7ED27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */; }; C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */; }; + C4E2E84828FC1D93003B070C /* TestableConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E2E84628FC1D8C003B070C /* TestableConfigurationTest.swift */; }; + C4E2E84A28FC1E70003B070C /* DataExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E2E84928FC1E70003B070C /* DataExtension.swift */; }; + C4E2E84B28FC1E70003B070C /* DataExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E2E84928FC1E70003B070C /* DataExtension.swift */; }; + C4E2E84C28FC1E70003B070C /* DataExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E2E84928FC1E70003B070C /* DataExtension.swift */; }; + C4E2E84D28FC1E70003B070C /* DataExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E2E84928FC1E70003B070C /* DataExtension.swift */; }; + C4E2E84F28FC22E4003B070C /* brew-formula.json in Resources */ = {isa = PBXBuildFile; fileRef = C43A8A1F25D9D1D700591B77 /* brew-formula.json */; }; + C4E2E85028FC22E4003B070C /* brew-formula.json in Resources */ = {isa = PBXBuildFile; fileRef = C43A8A1F25D9D1D700591B77 /* brew-formula.json */; }; + C4E2E85428FC256B003B070C /* brew-services-sudo.json in Resources */ = {isa = PBXBuildFile; fileRef = C4E2E85128FC256B003B070C /* brew-services-sudo.json */; }; + C4E2E85528FC256B003B070C /* brew-services-sudo.json in Resources */ = {isa = PBXBuildFile; fileRef = C4E2E85128FC256B003B070C /* brew-services-sudo.json */; }; + C4E2E85628FC256B003B070C /* brew-services-sudo.json in Resources */ = {isa = PBXBuildFile; fileRef = C4E2E85128FC256B003B070C /* brew-services-sudo.json */; }; + C4E2E85828FC256B003B070C /* brew-services-normal.json in Resources */ = {isa = PBXBuildFile; fileRef = C4E2E85228FC256B003B070C /* brew-services-normal.json */; }; + C4E2E85928FC256B003B070C /* brew-services-normal.json in Resources */ = {isa = PBXBuildFile; fileRef = C4E2E85228FC256B003B070C /* brew-services-normal.json */; }; + C4E2E85A28FC256B003B070C /* brew-services-normal.json in Resources */ = {isa = PBXBuildFile; fileRef = C4E2E85228FC256B003B070C /* brew-services-normal.json */; }; + C4E2E85C28FC282B003B070C /* TestableConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E2E85B28FC282B003B070C /* TestableConfiguration.swift */; }; + C4E2E85D28FC282B003B070C /* TestableConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E2E85B28FC282B003B070C /* TestableConfiguration.swift */; }; + C4E2E85E28FC282B003B070C /* TestableConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E2E85B28FC282B003B070C /* TestableConfiguration.swift */; }; + C4E2E85F28FC282B003B070C /* TestableConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E2E85B28FC282B003B070C /* TestableConfiguration.swift */; }; + C4E2E86128FC28A6003B070C /* brew-services.json in Resources */ = {isa = PBXBuildFile; fileRef = C4F30B06278E195800755FCE /* brew-services.json */; }; + C4E2E86228FC28A6003B070C /* brew-services.json in Resources */ = {isa = PBXBuildFile; fileRef = C4F30B06278E195800755FCE /* brew-services.json */; }; + C4E2E86528FC2F1B003B070C /* XCPMApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E2E86328FC2F1B003B070C /* XCPMApplication.swift */; }; + C4E2E86628FC2F1B003B070C /* XCPMApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E2E86328FC2F1B003B070C /* XCPMApplication.swift */; }; + C4E2E86728FC2F1B003B070C /* XCPMApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E2E86328FC2F1B003B070C /* XCPMApplication.swift */; }; + C4E2E86928FC3002003B070C /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A1925D9CD1000591B77 /* Utility.swift */; }; + C4E2E86A28FC3002003B070C /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A1925D9CD1000591B77 /* Utility.swift */; }; C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; C4E4404727C56F4700D225E1 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; C4E49DE728F764050026AC4E /* ActiveCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE628F764050026AC4E /* ActiveCommand.swift */; }; @@ -815,6 +837,12 @@ C4D9F24A280B69E100DCD39A /* AddProxyVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddProxyVC.swift; sourceTree = ""; }; C4DEB7D327A5D60B00834718 /* Stats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stats.swift; sourceTree = ""; }; C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSWindowExtension.swift; sourceTree = ""; }; + C4E2E84628FC1D8C003B070C /* TestableConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableConfigurationTest.swift; sourceTree = ""; }; + C4E2E84928FC1E70003B070C /* DataExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataExtension.swift; sourceTree = ""; }; + C4E2E85128FC256B003B070C /* brew-services-sudo.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "brew-services-sudo.json"; sourceTree = ""; }; + C4E2E85228FC256B003B070C /* brew-services-normal.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "brew-services-normal.json"; sourceTree = ""; }; + C4E2E85B28FC282B003B070C /* TestableConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableConfiguration.swift; sourceTree = ""; }; + C4E2E86328FC2F1B003B070C /* XCPMApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCPMApplication.swift; sourceTree = ""; }; C4E4404527C56F4700D225E1 /* ValetSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetSite.swift; sourceTree = ""; }; C4E49DE628F764050026AC4E /* ActiveCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveCommand.swift; sourceTree = ""; }; C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandProtocol.swift; sourceTree = ""; }; @@ -1176,6 +1204,8 @@ C459B4BF27F6094100E9B4B4 /* brew */ = { isa = PBXGroup; children = ( + C4E2E85228FC256B003B070C /* brew-services-normal.json */, + C4E2E85128FC256B003B070C /* brew-services-sudo.json */, C43A8A1F25D9D1D700591B77 /* brew-formula.json */, C4F30B06278E195800755FCE /* brew-services.json */, ); @@ -1220,6 +1250,7 @@ C471E6DB28F9AFD10021E251 /* Command */, C471E6DA28F9AFCB0021E251 /* Filesystem */, C413E43328DA3E8F00AE33C7 /* Shell */, + C4E2E84628FC1D8C003B070C /* TestableConfigurationTest.swift */, ); path = Testables; sourceTree = ""; @@ -1241,6 +1272,7 @@ C471E79628F9B4260021E251 /* tests */ = { isa = PBXGroup; children = ( + C4E2E86828FC2FF2003B070C /* Shared */, C471E79228F9B1D30021E251 /* PHP Monitor.xctestplan */, C4F7807A25D7F84B000DBC97 /* unit */, C471E7AE28F9B4940021E251 /* feature */, @@ -1534,6 +1566,16 @@ path = Switcher; sourceTree = ""; }; + C4E2E86828FC2FF2003B070C /* Shared */ = { + isa = PBXGroup; + children = ( + C4E2E86328FC2F1B003B070C /* XCPMApplication.swift */, + C40F505428ECA64E004AD45B /* TestableConfigurations.swift */, + C43A8A1925D9CD1000591B77 /* Utility.swift */, + ); + path = Shared; + sourceTree = ""; + }; C4E49DE528F763E20026AC4E /* Command */ = { isa = PBXGroup; children = ( @@ -1576,7 +1618,6 @@ C4F7807A25D7F84B000DBC97 /* unit */ = { isa = PBXGroup; children = ( - C43A8A1925D9CD1000591B77 /* Utility.swift */, C471E6D928F9AFC20021E251 /* Testables */, C40C7F1C27720E1400DDDCDC /* Test Files */, C4C1019927C65A4D001FACC2 /* Commands */, @@ -1599,10 +1640,10 @@ C4F787A728EF812600790735 /* Testables */ = { isa = PBXGroup; children = ( - C40F505428ECA64E004AD45B /* TestableConfigurations.swift */, C46EBC4928DB966A007ACC74 /* TestableShell.swift */, C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */, C4E49DEC28F764A00026AC4E /* TestableCommand.swift */, + C4E2E85B28FC282B003B070C /* TestableConfiguration.swift */, ); path = Testables; sourceTree = ""; @@ -1618,6 +1659,7 @@ C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */, C4EB53E628553117006F9937 /* ArrayExtension.swift */, C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */, + C4E2E84928FC1E70003B070C /* DataExtension.swift */, ); path = Extensions; sourceTree = ""; @@ -1762,7 +1804,6 @@ 54FCFD26276C883F004CE748 /* SelectPreferenceView.xib in Resources */, C473319F2470923A009A0597 /* Localizable.strings in Resources */, C4068CA427B0780A00544CD5 /* CheckboxPreferenceView.xib in Resources */, - C4AD38B528ECE2DB00FA8D83 /* brew-formula.json in Resources */, 54FCFD2D276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */, C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */, ); @@ -1772,6 +1813,10 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + C4E2E85528FC256B003B070C /* brew-services-sudo.json in Resources */, + C4E2E85928FC256B003B070C /* brew-services-normal.json in Resources */, + C4E2E84F28FC22E4003B070C /* brew-formula.json in Resources */, + C4E2E86228FC28A6003B070C /* brew-services.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1779,6 +1824,10 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + C4E2E85628FC256B003B070C /* brew-services-sudo.json in Resources */, + C4E2E85A28FC256B003B070C /* brew-services-normal.json in Resources */, + C4E2E85028FC22E4003B070C /* brew-formula.json in Resources */, + C4E2E86128FC28A6003B070C /* brew-services.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1794,12 +1843,14 @@ C44F868E2835BD8D005C353A /* phpmon-config.json in Resources */, C43A8A2025D9D1D700591B77 /* brew-formula.json in Resources */, C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */, + C4E2E85828FC256B003B070C /* brew-services-normal.json in Resources */, C44C1992276E44CB0072762D /* ProgressWindow.storyboard in Resources */, C42F26762805FEE200938AC7 /* nginx-secure-proxy.test in Resources */, C4F30B08278E195800755FCE /* brew-services.json in Resources */, 54A18D40282A566E000A0D81 /* nginx-secure-proxy-custom-tld.test in Resources */, C42CFB1627DFDE7900862737 /* nginx-site.test in Resources */, C459B4BD27F6093700E9B4B4 /* nginx-proxy.test in Resources */, + C4E2E85428FC256B003B070C /* brew-services-sudo.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1866,6 +1917,7 @@ C40C7F2827721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */, C463E380284930EE00422731 /* PresetHelper.swift in Sources */, C41C02A927E61A65009F26CB /* ValetSite+Fake.swift in Sources */, + C4E2E85C28FC282B003B070C /* TestableConfiguration.swift in Sources */, C4C0E8DF27F88AEB002D32A9 /* FakeSiteScanner.swift in Sources */, C44B3A4628E5C70100718CB1 /* TimeIntervalExtension.swift in Sources */, C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */, @@ -1920,7 +1972,6 @@ C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */, C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */, C46EBC4728DB9644007ACC74 /* RealShell.swift in Sources */, - C4AD38B628ECE56D00FA8D83 /* TestableConfigurations.swift in Sources */, C4068CAA27B0890D00544CD5 /* MenuBarIcons.swift in Sources */, C44264C02850BD2A007400F1 /* VersionPopoverView.swift in Sources */, C4C8E81B276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */, @@ -1965,6 +2016,7 @@ C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */, C40508AF28ADA23D008FAC1F /* NoDomainResultsView.swift in Sources */, C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */, + C4E2E84A28FC1E70003B070C /* DataExtension.swift in Sources */, C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */, C42337A3281F19F000459A48 /* Xdebug.swift in Sources */, C4B97B75275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */, @@ -1998,12 +2050,14 @@ C471E83A28F9BB650021E251 /* ValetSite+Fake.swift in Sources */, C471E83B28F9BB650021E251 /* SiteScanner.swift in Sources */, C471E83C28F9BB650021E251 /* ValetSiteScanner.swift in Sources */, + C4E2E86928FC3002003B070C /* Utility.swift in Sources */, C471E83D28F9BB650021E251 /* FakeSiteScanner.swift in Sources */, C471E83F28F9BB650021E251 /* AppDelegate.swift in Sources */, C471E84028F9BB650021E251 /* AppDelegate+MenuOutlets.swift in Sources */, C471E84128F9BB650021E251 /* AppDelegate+Notifications.swift in Sources */, C471E84228F9BB650021E251 /* AppDelegate+InterApp.swift in Sources */, C471E84328F9BB650021E251 /* App.swift in Sources */, + C4E2E85E28FC282B003B070C /* TestableConfiguration.swift in Sources */, C471E84428F9BB650021E251 /* App+ActivationPolicy.swift in Sources */, C471E84528F9BB650021E251 /* App+GlobalHotkey.swift in Sources */, C471E84628F9BB650021E251 /* InterAppHandler.swift in Sources */, @@ -2031,6 +2085,7 @@ C471E85C28F9BB650021E251 /* DomainListWindowController.swift in Sources */, C471E85D28F9BB650021E251 /* DomainListVC.swift in Sources */, C471E85E28F9BB650021E251 /* DomainListVC+ContextMenu.swift in Sources */, + C4E2E86628FC2F1B003B070C /* XCPMApplication.swift in Sources */, C471E85F28F9BB650021E251 /* DomainListVC+Actions.swift in Sources */, C471E86028F9BB650021E251 /* SelectionVC.swift in Sources */, C471E86128F9BB650021E251 /* AddSiteVC.swift in Sources */, @@ -2046,6 +2101,7 @@ C471E86B28F9BB650021E251 /* PreferenceName.swift in Sources */, C471E86C28F9BB650021E251 /* Preferences.swift in Sources */, C471E86D28F9BB650021E251 /* CustomPrefs.swift in Sources */, + C4E2E84C28FC1E70003B070C /* DataExtension.swift in Sources */, C471E86E28F9BB650021E251 /* MenuBarIcons.swift in Sources */, C471E86F28F9BB650021E251 /* Stats.swift in Sources */, C471E87028F9BB650021E251 /* GlobalKeybindPreference.swift in Sources */, @@ -2141,6 +2197,7 @@ C471E89528F9BB8F0021E251 /* MenuBarImageGenerator.swift in Sources */, C471E89628F9BB8F0021E251 /* PMWindowController.swift in Sources */, C471E89728F9BB8F0021E251 /* VersionExtractor.swift in Sources */, + C4E2E86728FC2F1B003B070C /* XCPMApplication.swift in Sources */, C471E89828F9BB8F0021E251 /* ValetProxy.swift in Sources */, C471E89928F9BB8F0021E251 /* ValetProxy+Fake.swift in Sources */, C471E89A28F9BB8F0021E251 /* ProxyScanner.swift in Sources */, @@ -2179,6 +2236,7 @@ C471E8BC28F9BB8F0021E251 /* DomainListPhpCell.swift in Sources */, C471E8BD28F9BB8F0021E251 /* DomainListTypeCell.swift in Sources */, C471E8BE28F9BB8F0021E251 /* DomainListKindCell.swift in Sources */, + C4E2E86A28FC3002003B070C /* Utility.swift in Sources */, C471E8BF28F9BB8F0021E251 /* DomainListWindowController.swift in Sources */, C471E8C028F9BB8F0021E251 /* DomainListVC.swift in Sources */, C471E8C128F9BB8F0021E251 /* DomainListVC+ContextMenu.swift in Sources */, @@ -2196,6 +2254,7 @@ C471E8CD28F9BB8F0021E251 /* PrefsVC.swift in Sources */, C471E8CE28F9BB8F0021E251 /* PreferenceName.swift in Sources */, C471E8CF28F9BB8F0021E251 /* Preferences.swift in Sources */, + C4E2E84D28FC1E70003B070C /* DataExtension.swift in Sources */, C471E8D028F9BB8F0021E251 /* CustomPrefs.swift in Sources */, C471E8D128F9BB8F0021E251 /* MenuBarIcons.swift in Sources */, C471E8D228F9BB8F0021E251 /* Stats.swift in Sources */, @@ -2217,6 +2276,7 @@ C471E8E628F9BB8F0021E251 /* VersionPopoverView.swift in Sources */, C471E8E728F9BB8F0021E251 /* NoDomainResultsView.swift in Sources */, C471E8E828F9BB8F0021E251 /* ServicesView.swift in Sources */, + C4E2E85F28FC282B003B070C /* TestableConfiguration.swift in Sources */, C471E8E928F9BB8F0021E251 /* StatsView.swift in Sources */, C471E8EA28F9BB8F0021E251 /* SectionHeaderView.swift in Sources */, C471E8EB28F9BB8F0021E251 /* HeaderView.swift in Sources */, @@ -2351,10 +2411,12 @@ C450C8C728C919EC002A2B4B /* PreferenceName.swift in Sources */, C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */, C485707B28BF458900539B36 /* VersionPopoverView.swift in Sources */, + C4E2E85D28FC282B003B070C /* TestableConfiguration.swift in Sources */, C485706E28BF451C00539B36 /* OnboardingWindowController.swift in Sources */, C43603A1275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */, C4C3643A28AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */, C42759682627662800093CAE /* NSMenuExtension.swift in Sources */, + C4E2E84828FC1D93003B070C /* TestableConfigurationTest.swift in Sources */, C4D936CB27E3EE4A00BD69FE /* DomainListCellProtocol.swift in Sources */, C4B97B76275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */, C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */, @@ -2382,6 +2444,7 @@ 54D9E0B327E4F51E003B9AD9 /* HotKeysController.swift in Sources */, 03E36FE828D9219000636F7F /* ActiveShell.swift in Sources */, C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */, + C4E2E86528FC2F1B003B070C /* XCPMApplication.swift in Sources */, C4E49DE828F764050026AC4E /* ActiveCommand.swift in Sources */, C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */, C46E20702829D27F00D909D6 /* AppUpdaterCheckTest.swift in Sources */, @@ -2390,6 +2453,7 @@ C44CCD4127AFE2FC00CE40E5 /* AlertableError.swift in Sources */, C4CDA894288F1A71007CE25F /* Keys.swift in Sources */, C4D936CA27E3EB6100BD69FE /* PhpHelper.swift in Sources */, + C4E2E84B28FC1E70003B070C /* DataExtension.swift in Sources */, C449B4F127EE7FC200C47E8A /* DomainListNameCell.swift in Sources */, C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */, 54FCFD31276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */, diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index 1418749..2f5fe3c 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -79,12 +79,12 @@ isEnabled = "NO"> + argument = "--configuration:/tmp/pmc_broken.json" + isEnabled = "NO"> diff --git a/phpmon/Common/Extensions/DataExtension.swift b/phpmon/Common/Extensions/DataExtension.swift new file mode 100644 index 0000000..1608b93 --- /dev/null +++ b/phpmon/Common/Extensions/DataExtension.swift @@ -0,0 +1,19 @@ +// +// DataExtension.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 16/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +extension Data { + var prettyPrintedJSONString: NSString? { + guard let object = try? JSONSerialization.jsonObject(with: self, options: []), + let data = try? JSONSerialization.data(withJSONObject: object, options: [.prettyPrinted]), + let prettyPrintedString = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else { return nil } + + return prettyPrintedString + } +} diff --git a/phpmon/Common/Extensions/StringExtension.swift b/phpmon/Common/Extensions/StringExtension.swift index a67b0d4..4c24f3d 100644 --- a/phpmon/Common/Extensions/StringExtension.swift +++ b/phpmon/Common/Extensions/StringExtension.swift @@ -47,6 +47,11 @@ extension String { return !NSArray(object: self).filtered(using: pred).isEmpty } + static func random(_ length: Int) -> String { + let characters = "0123456789abcdefghijklmnopqrstuvwxyz" + return String((0..) -> String { let start = r.lowerBound let end = r.upperBound diff --git a/phpmon/Common/Shell/ShellProtocol.swift b/phpmon/Common/Shell/ShellProtocol.swift index 3b84304..a6e2fed 100644 --- a/phpmon/Common/Shell/ShellProtocol.swift +++ b/phpmon/Common/Shell/ShellProtocol.swift @@ -48,7 +48,7 @@ protocol ShellProtocol { ) async throws -> (Process, ShellOutput) } -enum ShellStream { +enum ShellStream: Codable { case stdOut, stdErr, stdIn } diff --git a/phpmon/Common/Testables/TestableConfiguration.swift b/phpmon/Common/Testables/TestableConfiguration.swift new file mode 100644 index 0000000..b27ac18 --- /dev/null +++ b/phpmon/Common/Testables/TestableConfiguration.swift @@ -0,0 +1,40 @@ +// +// TestableConfiguration.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 16/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +public struct TestableConfiguration: Codable { + var architecture: String + var filesystem: [String: FakeFile] + var shellOutput: [String: BatchFakeShellOutput] + var commandOutput: [String: String] + + func apply() { + ActiveShell.useTestable(shellOutput) + ActiveFileSystem.useTestable(filesystem) + ActiveCommand.useTestable(commandOutput) + } + + func toJson(pretty: Bool = false) -> String { + let data = try! JSONEncoder().encode(self) + + if pretty { + return data.prettyPrintedJSONString! as String + } + + return String(data: data, encoding: .utf8)! + } + + static func loadFrom(path: String) -> TestableConfiguration { + return try! JSONDecoder().decode( + TestableConfiguration.self, + from: try! String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8) + .data(using: .utf8)! + ) + } +} diff --git a/phpmon/Common/Testables/TestableFileSystem.swift b/phpmon/Common/Testables/TestableFileSystem.swift index f5b4964..ca6f41b 100644 --- a/phpmon/Common/Testables/TestableFileSystem.swift +++ b/phpmon/Common/Testables/TestableFileSystem.swift @@ -52,11 +52,11 @@ class TestableFileSystem: FileSystemProtocol { } } -enum FakeFileType { +enum FakeFileType: Codable { case binary, text, directory, symlink } -struct FakeFile { +struct FakeFile: Codable { var type: FakeFileType var content: String? diff --git a/phpmon/Common/Testables/TestableShell.swift b/phpmon/Common/Testables/TestableShell.swift index 3cb9984..9ab9c93 100644 --- a/phpmon/Common/Testables/TestableShell.swift +++ b/phpmon/Common/Testables/TestableShell.swift @@ -55,7 +55,7 @@ public class TestableShell: ShellProtocol { } } -struct FakeShellOutput { +struct FakeShellOutput: Codable { let delay: TimeInterval let output: String let stream: ShellStream @@ -69,7 +69,7 @@ struct FakeShellOutput { } } -struct BatchFakeShellOutput { +struct BatchFakeShellOutput: Codable { var items: [FakeShellOutput] static func with(_ items: [FakeShellOutput]) -> BatchFakeShellOutput { diff --git a/phpmon/Domain/App/AppDelegate.swift b/phpmon/Domain/App/AppDelegate.swift index 4e0ada5..0716271 100644 --- a/phpmon/Domain/App/AppDelegate.swift +++ b/phpmon/Domain/App/AppDelegate.swift @@ -87,19 +87,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele self.phpEnvironment = PhpEnv.shared } - static func initializeTestingProfile(_ profile: String) { - Log.info("The profile `\(profile)` is being requested...") - - switch profile { - case "broken": - Log.info("Applying broken PHP Monitor configuration profile!") - TestableConfigurations.broken.apply() - case "working": - Log.info("Applying working PHP Monitor configuration profile!") - TestableConfigurations.working.apply() - default: - assert(true, "No profile for this name is supported.") - } + static func initializeTestingProfile(_ path: String) { + Log.info("The configuration with path `\(path)` is being requested...") + TestableConfiguration + .loadFrom(path: path) + .apply() } // MARK: - Lifecycle diff --git a/phpmon/Common/Testables/TestableConfigurations.swift b/tests/Shared/TestableConfigurations.swift similarity index 62% rename from phpmon/Common/Testables/TestableConfigurations.swift rename to tests/Shared/TestableConfigurations.swift index e36e447..1c77e5a 100644 --- a/phpmon/Common/Testables/TestableConfigurations.swift +++ b/tests/Shared/TestableConfigurations.swift @@ -8,37 +8,7 @@ import Foundation -struct TestableConfiguration { - let architecture: String - let filesystem: [String: FakeFile] - let shellOutput: [String: BatchFakeShellOutput] - let commandOutput: [String: String] - - func apply() { - ActiveShell.useTestable(shellOutput) - ActiveFileSystem.useTestable(filesystem) - ActiveCommand.useTestable(commandOutput) - } -} - -// swiftlint:disable colon trailing_comma class TestableConfigurations { - - /** A broken system, that will not get past initialization due to missing binaries. */ - static var broken: TestableConfiguration { - return TestableConfiguration( - architecture: "arm64", - filesystem: [:], - shellOutput: [ - "id -un" : .instant("username"), - "php -v" : .instant(""), - "ls /opt/homebrew/opt | grep php" : .instant(""), - "valet --version" : .instant("zsh: command not found: valet") - ], - commandOutput: [:] - ) - } - /** A functional, working system setup that is compatible with PHP Monitor. */ static var working: TestableConfiguration { return TestableConfiguration( @@ -59,7 +29,9 @@ class TestableConfigurations { "/opt/homebrew/Cellar/php/8.1.10_1/bin/php-config" : .fake(.binary), "~/.config/valet" - : .fake(.directory) + : .fake(.directory), + "/opt/homebrew/etc/php/8.1/php-fpm.d/valet-fpm.conf" + : .fake(.text) ], shellOutput: [ "sysctl -n sysctl.proc_translated" @@ -69,13 +41,40 @@ class TestableConfigurations { "which node" : .instant("/opt/homebrew/bin/node"), "php -v" - : .instant(ShellStrings.phpVersion), + : .instant(""" + PHP 8.1.10 (cli) (built: Sep 3 2022 12:09:27) (NTS) + Copyright (c) The PHP Group + Zend Engine v4.1.10, Copyright (c) Zend Technologies + with Zend OPcache v8.1.10, Copyright (c), by Zend Technologies + """), "ls /opt/homebrew/opt | grep php" : .instant("php"), "ls /opt/homebrew/opt | grep php@" : .instant("php@8.1"), "sudo /opt/homebrew/bin/brew services info nginx --json" - : .delayed(0.2, ShellStrings.nginxJson), + : .delayed(0.2, """ + [ + { + "name": "nginx", + "service_name": "homebrew.mxcl.nginx", + "running": true, + "loaded": true, + "schedulable": false, + "pid": 133, + "exit_code": 0, + "user": "root", + "status": "started", + "file": "/Library/LaunchDaemons/homebrew.mxcl.nginx.plist", + "command": "/opt/homebrew/opt/nginx/bin/nginx -g daemon off;", + "working_dir": "/opt/homebrew", + "root_dir": null, + "log_path": null, + "error_log_path": null, + "interval": null, + "cron": null + } + ] + """), "cat /private/etc/sudoers.d/brew" : .instant(""" Cmnd_Alias BREW = /opt/homebrew/bin/brew * @@ -102,8 +101,6 @@ class TestableConfigurations { : .instant(""), "mkdir -p ~/.config/phpmon/bin" : .instant(""), - "/opt/homebrew/bin/brew info php --json" - : .instant(ShellStrings.brewJson), "brew info shivammathur/php/php --json" : .instant("Error: No available formula with the name \"shivammathur/php/php\"."), "/usr/bin/open -Ra \"PhpStorm\"" @@ -116,6 +113,14 @@ class TestableConfigurations { : .instant("Unable to find application named 'Sublime Merge'", .stdErr), "/usr/bin/open -Ra \"iTerm\"" : .instant("Unable to find application named 'iTerm'", .stdErr), + "/opt/homebrew/bin/brew info php --json" + : .instant(ShellStrings.shared.brewJson), + "sudo /opt/homebrew/bin/brew services info --all --json" + : .instant(ShellStrings.shared.brewServicesAsRoot), + "/opt/homebrew/bin/brew services info --all --json" + : .instant(ShellStrings.shared.brewServicesAsUser), + "curl -s --max-time 5 '\(Constants.Urls.StableBuildCaskFile.absoluteString)' | grep version" + : .instant("version '5.6.2_976'") ], commandOutput: [ "/opt/homebrew/bin/php-config --version": "8.1.10", @@ -134,43 +139,24 @@ class TestableConfigurations { } } -struct ShellStrings { +class ShellStrings { + static var shared = ShellStrings() - static let phpVersion = """ - PHP 8.1.10 (cli) (built: Sep 3 2022 12:09:27) (NTS) - Copyright (c) The PHP Group - Zend Engine v4.1.10, Copyright (c) Zend Technologies - with Zend OPcache v8.1.10, Copyright (c), by Zend Technologies - """ + var brewJson: String = "" + var brewServicesAsUser: String = "" + var brewServicesAsRoot: String = "" - static let nginxJson = """ - [ - { - "name": "nginx", - "service_name": "homebrew.mxcl.nginx", - "running": true, - "loaded": true, - "schedulable": false, - "pid": 133, - "exit_code": 0, - "user": "root", - "status": "started", - "file": "/Library/LaunchDaemons/homebrew.mxcl.nginx.plist", - "command": "/opt/homebrew/opt/nginx/bin/nginx -g daemon off;", - "working_dir": "/opt/homebrew", - "root_dir": null, - "log_path": null, - "error_log_path": null, - "interval": null, - "cron": null - } - ] - """ + init() { + self.brewJson = loadFile("brew-formula") + self.brewServicesAsUser = loadFile("brew-services-normal") + self.brewServicesAsRoot = loadFile("brew-services-sudo") + } - static let brewJson: String = { - return try! String(contentsOf: Bundle.main.url( - forResource: "brew-formula", - withExtension: "json" + private func loadFile(_ fileName: String, fileExtension: String = "json") -> String { + let bundle = Bundle(for: type(of: self)) + return try! String(contentsOf: bundle.url( + forResource: fileName, + withExtension: fileExtension )!, encoding: .utf8) - }() + } } diff --git a/tests/unit/Utility.swift b/tests/Shared/Utility.swift similarity index 100% rename from tests/unit/Utility.swift rename to tests/Shared/Utility.swift diff --git a/tests/Shared/XCPMApplication.swift b/tests/Shared/XCPMApplication.swift new file mode 100644 index 0000000..e96029a --- /dev/null +++ b/tests/Shared/XCPMApplication.swift @@ -0,0 +1,23 @@ +// +// XCPMApplication.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 16/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import XCTest + +class XCPMApplication: XCUIApplication { + public func withConfiguration(_ configuration: TestableConfiguration) { + let path = persistTestable(configuration) + self.launchArguments = ["--configuration:\(path)"] + } + + private func persistTestable(_ configuration: TestableConfiguration) -> String { + let tempDirectoryURL = NSURL.fileURL(withPath: NSTemporaryDirectory(), isDirectory: true) + let targetURL = tempDirectoryURL.appendingPathComponent("\(UUID().uuidString).json") + try! configuration.toJson().write(toFile: targetURL.path, atomically: true, encoding: .utf8) + return targetURL.path + } +} diff --git a/tests/ui/StartupTest.swift b/tests/ui/StartupTest.swift index 22008b4..0dc4c01 100644 --- a/tests/ui/StartupTest.swift +++ b/tests/ui/StartupTest.swift @@ -1,5 +1,5 @@ // -// UI_Tests.swift +// StartupTest.swift // UI Tests // // Created by Nico Verbruggen on 14/10/2022. @@ -17,14 +17,18 @@ final class StartupTest: UITestCase { override func tearDownWithError() throws {} func testApplicationCanLaunchWithTestConfigurationAndIdles() throws { - let app = XCUIApplication() - app.launchArguments = ["--configuration:working"] + let app = XCPMApplication() + app.withConfiguration(TestableConfigurations.working) app.launch() + sleep(5) } func testApplicationCanLaunchWithTestConfigurationAndThrowsAlert() throws { - let app = XCUIApplication() - app.launchArguments = ["--configuration:broken"] + var configuration = TestableConfigurations.working + configuration.filesystem["/opt/homebrew/bin/php"] = nil // PHP binary must be missing + + let app = XCPMApplication() + app.withConfiguration(configuration) app.launch() // Dialog 1: "PHP is not correctly installed" diff --git a/tests/unit/Test Files/brew/brew-services-normal.json b/tests/unit/Test Files/brew/brew-services-normal.json new file mode 100644 index 0000000..7913c7e --- /dev/null +++ b/tests/unit/Test Files/brew/brew-services-normal.json @@ -0,0 +1,173 @@ +[ + { + "name": "dnsmasq", + "service_name": "homebrew.mxcl.dnsmasq", + "running": false, + "loaded": false, + "schedulable": false, + "pid": null, + "exit_code": null, + "user": "root", + "status": "none", + "file": "/Users/nicoverbruggen/Library/LaunchAgents/homebrew.mxcl.dnsmasq.plist", + "command": "/opt/homebrew/opt/dnsmasq/sbin/dnsmasq --keep-in-foreground -C /opt/homebrew/etc/dnsmasq.conf -7 /opt/homebrew/etc/dnsmasq.d,*.conf", + "working_dir": null, + "root_dir": null, + "log_path": null, + "error_log_path": null, + "interval": null, + "cron": null + }, + { + "name": "httpd", + "service_name": "homebrew.mxcl.httpd", + "running": false, + "loaded": false, + "schedulable": false, + "pid": null, + "exit_code": null, + "user": null, + "status": "none", + "file": "/opt/homebrew/opt/httpd/homebrew.mxcl.httpd.plist", + "command": "/opt/homebrew/opt/httpd/bin/httpd -D FOREGROUND", + "working_dir": null, + "root_dir": null, + "log_path": null, + "error_log_path": null, + "interval": null, + "cron": null + }, + { + "name": "mailhog", + "service_name": "homebrew.mxcl.mailhog", + "running": true, + "loaded": true, + "schedulable": false, + "pid": 686, + "exit_code": 0, + "user": "nicoverbruggen", + "status": "started", + "file": "/Users/nicoverbruggen/Library/LaunchAgents/homebrew.mxcl.mailhog.plist", + "command": "/opt/homebrew/opt/mailhog/bin/MailHog", + "working_dir": null, + "root_dir": null, + "log_path": "/opt/homebrew/var/log/mailhog.log", + "error_log_path": "/opt/homebrew/var/log/mailhog.log", + "interval": null, + "cron": null + }, + { + "name": "nginx", + "service_name": "homebrew.mxcl.nginx", + "running": false, + "loaded": false, + "schedulable": false, + "pid": null, + "exit_code": null, + "user": "root", + "status": "none", + "file": "/Users/nicoverbruggen/Library/LaunchAgents/homebrew.mxcl.nginx.plist", + "command": "/opt/homebrew/opt/nginx/bin/nginx -g daemon off;", + "working_dir": "/opt/homebrew", + "root_dir": null, + "log_path": null, + "error_log_path": null, + "interval": null, + "cron": null + }, + { + "name": "php", + "service_name": "homebrew.mxcl.php", + "running": false, + "loaded": false, + "schedulable": false, + "pid": null, + "exit_code": null, + "user": "root", + "status": "none", + "file": "/Users/nicoverbruggen/Library/LaunchAgents/homebrew.mxcl.php.plist", + "command": "/opt/homebrew/opt/php/sbin/php-fpm --nodaemonize", + "working_dir": "/opt/homebrew/var", + "root_dir": null, + "log_path": null, + "error_log_path": "/opt/homebrew/var/log/php-fpm.log", + "interval": null, + "cron": null + }, + { + "name": "php@7.4", + "service_name": "homebrew.mxcl.php@7.4", + "running": false, + "loaded": false, + "schedulable": false, + "pid": null, + "exit_code": null, + "user": null, + "status": "none", + "file": "/opt/homebrew/opt/php@7.4/homebrew.mxcl.php@7.4.plist", + "command": "/opt/homebrew/opt/php@7.4/sbin/php-fpm --nodaemonize", + "working_dir": "/opt/homebrew/var", + "root_dir": null, + "log_path": null, + "error_log_path": "/opt/homebrew/var/log/php-fpm.log", + "interval": null, + "cron": null + }, + { + "name": "php@8.0", + "service_name": "homebrew.mxcl.php@8.0", + "running": false, + "loaded": false, + "schedulable": false, + "pid": null, + "exit_code": null, + "user": null, + "status": "none", + "file": "/opt/homebrew/opt/php@8.0/homebrew.mxcl.php@8.0.plist", + "command": "/opt/homebrew/opt/php@8.0/sbin/php-fpm --nodaemonize", + "working_dir": "/opt/homebrew/var", + "root_dir": null, + "log_path": null, + "error_log_path": "/opt/homebrew/var/log/php-fpm.log", + "interval": null, + "cron": null + }, + { + "name": "php@8.2", + "service_name": "homebrew.mxcl.php@8.2", + "running": false, + "loaded": false, + "schedulable": false, + "pid": null, + "exit_code": null, + "user": "root", + "status": "none", + "file": "/Users/nicoverbruggen/Library/LaunchAgents/homebrew.mxcl.php@8.2.plist", + "command": "/opt/homebrew/opt/php@8.2/sbin/php-fpm --nodaemonize", + "working_dir": "/opt/homebrew/var", + "root_dir": null, + "log_path": null, + "error_log_path": "/opt/homebrew/var/log/php-fpm.log", + "interval": null, + "cron": null + }, + { + "name": "unbound", + "service_name": "homebrew.mxcl.unbound", + "running": false, + "loaded": false, + "schedulable": false, + "pid": null, + "exit_code": null, + "user": null, + "status": "none", + "file": "/opt/homebrew/opt/unbound/homebrew.mxcl.unbound.plist", + "command": "/opt/homebrew/opt/unbound/sbin/unbound -d -c /opt/homebrew/etc/unbound/unbound.conf", + "working_dir": null, + "root_dir": null, + "log_path": null, + "error_log_path": null, + "interval": null, + "cron": null + } +] diff --git a/tests/unit/Test Files/brew/brew-services-sudo.json b/tests/unit/Test Files/brew/brew-services-sudo.json new file mode 100644 index 0000000..6554f3f --- /dev/null +++ b/tests/unit/Test Files/brew/brew-services-sudo.json @@ -0,0 +1,173 @@ +[ + { + "name": "dnsmasq", + "service_name": "homebrew.mxcl.dnsmasq", + "running": true, + "loaded": true, + "schedulable": false, + "pid": 122, + "exit_code": 0, + "user": "root", + "status": "started", + "file": "/Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist", + "command": "/opt/homebrew/opt/dnsmasq/sbin/dnsmasq --keep-in-foreground -C /opt/homebrew/etc/dnsmasq.conf -7 /opt/homebrew/etc/dnsmasq.d,*.conf", + "working_dir": null, + "root_dir": null, + "log_path": null, + "error_log_path": null, + "interval": null, + "cron": null + }, + { + "name": "httpd", + "service_name": "homebrew.mxcl.httpd", + "running": false, + "loaded": false, + "schedulable": false, + "pid": null, + "exit_code": null, + "user": null, + "status": "none", + "file": "/opt/homebrew/opt/httpd/homebrew.mxcl.httpd.plist", + "command": "/opt/homebrew/opt/httpd/bin/httpd -D FOREGROUND", + "working_dir": null, + "root_dir": null, + "log_path": null, + "error_log_path": null, + "interval": null, + "cron": null + }, + { + "name": "mailhog", + "service_name": "homebrew.mxcl.mailhog", + "running": false, + "loaded": false, + "schedulable": false, + "pid": null, + "exit_code": null, + "user": "root", + "status": "none", + "file": "/Library/LaunchDaemons/homebrew.mxcl.mailhog.plist", + "command": "/opt/homebrew/opt/mailhog/bin/MailHog", + "working_dir": null, + "root_dir": null, + "log_path": "/opt/homebrew/var/log/mailhog.log", + "error_log_path": "/opt/homebrew/var/log/mailhog.log", + "interval": null, + "cron": null + }, + { + "name": "nginx", + "service_name": "homebrew.mxcl.nginx", + "running": true, + "loaded": true, + "schedulable": false, + "pid": 133, + "exit_code": 0, + "user": "root", + "status": "started", + "file": "/Library/LaunchDaemons/homebrew.mxcl.nginx.plist", + "command": "/opt/homebrew/opt/nginx/bin/nginx -g daemon off;", + "working_dir": "/opt/homebrew", + "root_dir": null, + "log_path": null, + "error_log_path": null, + "interval": null, + "cron": null + }, + { + "name": "php", + "service_name": "homebrew.mxcl.php", + "running": true, + "loaded": true, + "schedulable": false, + "pid": 160, + "exit_code": 0, + "user": "root", + "status": "started", + "file": "/Library/LaunchDaemons/homebrew.mxcl.php.plist", + "command": "/opt/homebrew/opt/php/sbin/php-fpm --nodaemonize", + "working_dir": "/opt/homebrew/var", + "root_dir": null, + "log_path": null, + "error_log_path": "/opt/homebrew/var/log/php-fpm.log", + "interval": null, + "cron": null + }, + { + "name": "php@7.4", + "service_name": "homebrew.mxcl.php@7.4", + "running": false, + "loaded": false, + "schedulable": false, + "pid": null, + "exit_code": null, + "user": null, + "status": "none", + "file": "/opt/homebrew/opt/php@7.4/homebrew.mxcl.php@7.4.plist", + "command": "/opt/homebrew/opt/php@7.4/sbin/php-fpm --nodaemonize", + "working_dir": "/opt/homebrew/var", + "root_dir": null, + "log_path": null, + "error_log_path": "/opt/homebrew/var/log/php-fpm.log", + "interval": null, + "cron": null + }, + { + "name": "php@8.0", + "service_name": "homebrew.mxcl.php@8.0", + "running": false, + "loaded": false, + "schedulable": false, + "pid": null, + "exit_code": null, + "user": null, + "status": "none", + "file": "/opt/homebrew/opt/php@8.0/homebrew.mxcl.php@8.0.plist", + "command": "/opt/homebrew/opt/php@8.0/sbin/php-fpm --nodaemonize", + "working_dir": "/opt/homebrew/var", + "root_dir": null, + "log_path": null, + "error_log_path": "/opt/homebrew/var/log/php-fpm.log", + "interval": null, + "cron": null + }, + { + "name": "php@8.2", + "service_name": "homebrew.mxcl.php@8.2", + "running": true, + "loaded": true, + "schedulable": false, + "pid": 147, + "exit_code": 0, + "user": "root", + "status": "started", + "file": "/Library/LaunchDaemons/homebrew.mxcl.php@8.2.plist", + "command": "/opt/homebrew/opt/php@8.2/sbin/php-fpm --nodaemonize", + "working_dir": "/opt/homebrew/var", + "root_dir": null, + "log_path": null, + "error_log_path": "/opt/homebrew/var/log/php-fpm.log", + "interval": null, + "cron": null + }, + { + "name": "unbound", + "service_name": "homebrew.mxcl.unbound", + "running": false, + "loaded": false, + "schedulable": false, + "pid": null, + "exit_code": null, + "user": null, + "status": "none", + "file": "/opt/homebrew/opt/unbound/homebrew.mxcl.unbound.plist", + "command": "/opt/homebrew/opt/unbound/sbin/unbound -d -c /opt/homebrew/etc/unbound/unbound.conf", + "working_dir": null, + "root_dir": null, + "log_path": null, + "error_log_path": null, + "interval": null, + "cron": null + } +] diff --git a/tests/unit/Testables/TestableConfigurationTest.swift b/tests/unit/Testables/TestableConfigurationTest.swift new file mode 100644 index 0000000..b51cee7 --- /dev/null +++ b/tests/unit/Testables/TestableConfigurationTest.swift @@ -0,0 +1,21 @@ +// +// TestableConfigurationTest.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 16/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import XCTest + +class TestableConfigurationTest: XCTestCase { + func test_configuration_can_be_saved_as_json() async { + var configuration = TestableConfigurations.working + configuration.filesystem["/opt/homebrew/bin/php"] = nil + print(configuration.filesystem.keys) + let json = configuration.toJson() + try! json.write(toFile: "/tmp/pmc_working.json", atomically: true, encoding: .utf8) + try! json.write(toFile: "/tmp/pmc_broken.json", atomically: true, encoding: .utf8) + } +} + From 4c752b6a15d1f8d684f69b7dc4c3c4ee3b65f45b Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sun, 16 Oct 2022 15:13:13 +0200 Subject: [PATCH 059/181] =?UTF-8?q?=E2=9C=85=20Localization=20support=20fo?= =?UTF-8?q?r=20in=20test=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 6 +++ phpmon/Common/Extensions/DataExtension.swift | 4 +- .../Common/Extensions/StringExtension.swift | 17 ++++-- phpmon/Domain/App/App.swift | 17 +++--- phpmon/Domain/App/ServicesManager.swift | 8 +-- phpmon/Localizable.strings | 6 +++ tests/ui/StartupTest.swift | 54 +++++++++++++------ 7 files changed, 82 insertions(+), 30 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index c549a24..f6b744e 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -136,6 +136,9 @@ C44F868E2835BD8D005C353A /* phpmon-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C44F868D2835BD8D005C353A /* phpmon-config.json */; }; C450C8C628C919EC002A2B4B /* PreferenceName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C450C8C528C919EC002A2B4B /* PreferenceName.swift */; }; C450C8C728C919EC002A2B4B /* PreferenceName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C450C8C528C919EC002A2B4B /* PreferenceName.swift */; }; + C4570C3A28FC355300D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; + C4570C3B28FC355300D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; + C4570C3C28FC355400D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C459B4BD27F6093700E9B4B4 /* nginx-proxy.test in Resources */ = {isa = PBXBuildFile; fileRef = C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */; }; C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; }; C45E76152854A65300B4FE0C /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; }; @@ -1813,6 +1816,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + C4570C3B28FC355300D18420 /* Localizable.strings in Resources */, C4E2E85528FC256B003B070C /* brew-services-sudo.json in Resources */, C4E2E85928FC256B003B070C /* brew-services-normal.json in Resources */, C4E2E84F28FC22E4003B070C /* brew-formula.json in Resources */, @@ -1824,6 +1828,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + C4570C3A28FC355300D18420 /* Localizable.strings in Resources */, C4E2E85628FC256B003B070C /* brew-services-sudo.json in Resources */, C4E2E85A28FC256B003B070C /* brew-services-normal.json in Resources */, C4E2E85028FC22E4003B070C /* brew-formula.json in Resources */, @@ -1835,6 +1840,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + C4570C3C28FC355400D18420 /* Localizable.strings in Resources */, 54FCFD27276C883F004CE748 /* SelectPreferenceView.xib in Resources */, 54FCFD2E276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */, C42CFB1827DFDFDC00862737 /* nginx-site-isolated.test in Resources */, diff --git a/phpmon/Common/Extensions/DataExtension.swift b/phpmon/Common/Extensions/DataExtension.swift index 1608b93..ce241b7 100644 --- a/phpmon/Common/Extensions/DataExtension.swift +++ b/phpmon/Common/Extensions/DataExtension.swift @@ -12,7 +12,9 @@ extension Data { var prettyPrintedJSONString: NSString? { guard let object = try? JSONSerialization.jsonObject(with: self, options: []), let data = try? JSONSerialization.data(withJSONObject: object, options: [.prettyPrinted]), - let prettyPrintedString = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else { return nil } + let prettyPrintedString = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else { + return nil + } return prettyPrintedString } diff --git a/phpmon/Common/Extensions/StringExtension.swift b/phpmon/Common/Extensions/StringExtension.swift index 4c24f3d..5af4bf2 100644 --- a/phpmon/Common/Extensions/StringExtension.swift +++ b/phpmon/Common/Extensions/StringExtension.swift @@ -7,15 +7,26 @@ import Foundation import SwiftUI +struct Localization { + static var bundle: Bundle = { + var bundle = Bundle.main + if isRunningTests { + bundle = Bundle(identifier: "com.nicoverbruggen.phpmon.dev") + ?? Bundle(identifier: "com.nicoverbruggen.phpmon")! + } + return bundle + }() +} + extension String { var localized: String { if #available(macOS 13, *) { return NSLocalizedString( - self, tableName: nil, bundle: Bundle.main, value: "", comment: "" + self, tableName: nil, bundle: Localization.bundle, value: "", comment: "" ).replacingOccurrences(of: "Preferences", with: "Settings") } - return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "") + return NSLocalizedString(self, tableName: nil, bundle: Localization.bundle, value: "", comment: "") } var localizedForSwiftUI: LocalizedStringKey { @@ -49,7 +60,7 @@ extension String { static func random(_ length: Int) -> String { let characters = "0123456789abcdefghijklmnopqrstuvwxyz" - return String((0..) -> String { diff --git a/phpmon/Domain/App/App.swift b/phpmon/Domain/App/App.swift index 83dd659..0b7da2a 100644 --- a/phpmon/Domain/App/App.swift +++ b/phpmon/Domain/App/App.swift @@ -31,12 +31,10 @@ class App { return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String } - /** - A fake architecture. - When set, the real machine's system architecture is not used, - but this fixed value is used instead. - */ - static var fakeArchitecture: String? + /** Just the bundle name. */ + static var identifier: String { + Bundle.main.infoDictionary?["CFBundleIdentifier"] as! String + } /** The system architecture. Paths differ based on this value. */ static var architecture: String { @@ -55,6 +53,13 @@ class App { return machine } + /** + A fake architecture. + When set, the real machine's system architecture is not used, + but this fixed value is used instead. + */ + static var fakeArchitecture: String? + // MARK: Variables /** Technical information about the current environment. */ diff --git a/phpmon/Domain/App/ServicesManager.swift b/phpmon/Domain/App/ServicesManager.swift index 00d4fd0..ab5697c 100644 --- a/phpmon/Domain/App/ServicesManager.swift +++ b/phpmon/Domain/App/ServicesManager.swift @@ -51,9 +51,11 @@ class ServicesManager: ObservableObject { .decode([HomebrewService].self, from: rootJson) .filter({ return userServiceNames.contains($0.name) }) - ServicesManager.shared.userServices = Dictionary( - uniqueKeysWithValues: rootServices.map { ($0.name, $0) } - ) + DispatchQueue.main.async { + ServicesManager.shared.userServices = Dictionary( + uniqueKeysWithValues: rootServices.map { ($0.name, $0) } + ) + } } /** diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 501356b..f10113f 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -83,6 +83,12 @@ "mi_xdebug_actions" = "Actions"; "mi_xdebug_disable_all" = "Disable All Modes"; +// GENERIC + +"generic.ok" = "OK"; +"generic.retry" = "Retry"; +"generic.notice" = "Notice"; + // PRESET LOADING "preset_help_title" = "Working with Configuration Presets"; diff --git a/tests/ui/StartupTest.swift b/tests/ui/StartupTest.swift index 0dc4c01..bdeea0c 100644 --- a/tests/ui/StartupTest.swift +++ b/tests/ui/StartupTest.swift @@ -16,13 +16,6 @@ final class StartupTest: UITestCase { override func tearDownWithError() throws {} - func testApplicationCanLaunchWithTestConfigurationAndIdles() throws { - let app = XCPMApplication() - app.withConfiguration(TestableConfigurations.working) - app.launch() - sleep(5) - } - func testApplicationCanLaunchWithTestConfigurationAndThrowsAlert() throws { var configuration = TestableConfigurations.working configuration.filesystem["/opt/homebrew/bin/php"] = nil // PHP binary must be missing @@ -33,25 +26,52 @@ final class StartupTest: UITestCase { // Dialog 1: "PHP is not correctly installed" assertAllExist([ - app.dialogs["Notice"], - app.staticTexts["PHP is not correctly installed"], - app.buttons["OK"], + app.dialogs["generic.notice".localized], + app.staticTexts["startup.errors.php_binary.title".localized], + app.buttons["generic.ok".localized], ]) - click(app.buttons["OK"]) + click(app.buttons["generic.ok".localized]) // Dialog 2: PHP Monitor failed to start assertAllExist([ - app.dialogs["Notice"], - app.staticTexts["PHP Monitor cannot start due to a problem with your system configuration"], - app.buttons["Retry"], - app.buttons["Quit"] + app.dialogs["generic.notice".localized], + app.staticTexts["alert.cannot_start.title".localized], + app.buttons["alert.cannot_start.retry".localized], + app.buttons["alert.cannot_start.close".localized] ]) - click(app.buttons["Retry"]) + click(app.buttons["alert.cannot_start.retry".localized]) // Dialog 1 again - assertExists(app.staticTexts["PHP is not correctly installed"]) + assertExists(app.staticTexts["startup.errors.php_binary.title".localized]) // At this point, we can terminate the test app.terminate() } + + func testApplicationCanWarnAboutPhpFpmIssue() throws { + var configuration = TestableConfigurations.working + configuration.filesystem["/opt/homebrew/etc/php/8.1/php-fpm.d/valet-fpm.conf"] = nil + + let app = XCPMApplication() + app.withConfiguration(configuration) + app.launch() + + assertExists(app.staticTexts["alert.php_fpm_broken.title".localized], 3.0) + click(app.buttons["generic.ok".localized]) + } + + func testApplicationCanLaunchWithTestConfigurationAndCanClickMenuItem() throws { + let app = XCPMApplication() + app.withConfiguration(TestableConfigurations.working) + app.launch() + + // Note: If this fails here, make sure the menu bar item can be displayed + // If you use Bartender or something like this, this item may be hidden and tests will fail + let statusBarItem = app.statusItems.firstMatch + statusBarItem.click() + assertAllExist([ + app.menuItems["mi_about".localized], + app.menuItems["mi_quit".localized] + ]) + } } From 83657fee6f7393ff1e76c5ad4501eb9f6840dbd3 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sun, 16 Oct 2022 15:37:17 +0200 Subject: [PATCH 060/181] =?UTF-8?q?=E2=9C=85=20Tests=20are=20final?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/ui/StartupTest.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/ui/StartupTest.swift b/tests/ui/StartupTest.swift index bdeea0c..73795d9 100644 --- a/tests/ui/StartupTest.swift +++ b/tests/ui/StartupTest.swift @@ -16,7 +16,7 @@ final class StartupTest: UITestCase { override func tearDownWithError() throws {} - func testApplicationCanLaunchWithTestConfigurationAndThrowsAlert() throws { + final func testApplicationCanLaunchWithTestConfigurationAndThrowsAlert() throws { var configuration = TestableConfigurations.working configuration.filesystem["/opt/homebrew/bin/php"] = nil // PHP binary must be missing @@ -48,7 +48,7 @@ final class StartupTest: UITestCase { app.terminate() } - func testApplicationCanWarnAboutPhpFpmIssue() throws { + final func testApplicationCanWarnAboutPhpFpmIssue() throws { var configuration = TestableConfigurations.working configuration.filesystem["/opt/homebrew/etc/php/8.1/php-fpm.d/valet-fpm.conf"] = nil @@ -60,7 +60,7 @@ final class StartupTest: UITestCase { click(app.buttons["generic.ok".localized]) } - func testApplicationCanLaunchWithTestConfigurationAndCanClickMenuItem() throws { + final func testPhpMonitorLaunchesCorrectlyAndIdles() throws { let app = XCPMApplication() app.withConfiguration(TestableConfigurations.working) app.launch() @@ -73,5 +73,6 @@ final class StartupTest: UITestCase { app.menuItems["mi_about".localized], app.menuItems["mi_quit".localized] ]) + sleep(2) } } From ea5dd3bc464d806be4227b47230be663421b4210 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 18 Oct 2022 14:11:55 +0200 Subject: [PATCH 061/181] =?UTF-8?q?=F0=9F=91=8C=20Snake=20case=20for=20tes?= =?UTF-8?q?ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcschemes/PHP Monitor DEV.xcscheme | 2 +- tests/ui/StartupTest.swift | 6 +++--- tests/unit/Commands/CommandTest.swift | 2 +- tests/unit/Parsers/HomebrewPackageTest.swift | 10 ++++++---- .../unit/Parsers/NginxConfigurationTest.swift | 10 +++++----- tests/unit/Parsers/PhpConfigurationTest.swift | 8 ++++---- tests/unit/Parsers/PhpExtensionTest.swift | 8 ++++---- .../unit/Parsers/ValetConfigurationTest.swift | 2 +- .../Testables/TestableConfigurationTest.swift | 3 ++- tests/unit/Versions/AppUpdaterCheckTest.swift | 8 ++++---- tests/unit/Versions/AppVersionTest.swift | 12 ++++++------ .../Versions/PhpVersionDetectionTest.swift | 2 +- tests/unit/Versions/PhpVersionNumberTest.swift | 18 +++++++++--------- .../Versions/ValetVersionExtractorTest.swift | 2 +- tests/unit/Versions/VersionExtractorTest.swift | 4 ++-- 15 files changed, 50 insertions(+), 47 deletions(-) diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index 2f5fe3c..0f36fe6 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -80,7 +80,7 @@ + isEnabled = "YES"> String in @@ -39,7 +39,7 @@ class PhpExtensionTest: XCTestCase { XCTAssertFalse(extensionNames.contains("nice")) } - func testExtensionStatusIsCorrect() throws { + func test_extension_status_is_correct() throws { let extensions = PhpExtension.from(filePath: Self.phpIniFileUrl.path) // xdebug should be enabled @@ -49,7 +49,7 @@ class PhpExtensionTest: XCTestCase { XCTAssertEqual(extensions[1].enabled, false) } - func testToggleWorksAsExpected() async throws { + func test_toggle_works_as_expected() async throws { let destination = Utility.copyToTemporaryFile(resourceName: "php", fileExtension: "ini")! let extensions = PhpExtension.from(filePath: destination.path) XCTAssertEqual(extensions.count, 6) diff --git a/tests/unit/Parsers/ValetConfigurationTest.swift b/tests/unit/Parsers/ValetConfigurationTest.swift index fab1e15..e640177 100644 --- a/tests/unit/Parsers/ValetConfigurationTest.swift +++ b/tests/unit/Parsers/ValetConfigurationTest.swift @@ -17,7 +17,7 @@ class ValetConfigurationTest: XCTestCase { )! } - func testCanLoadConfigFile() throws { + func test_can_load_config_file() throws { let json = try? String( contentsOf: Self.jsonConfigFileUrl, encoding: .utf8 diff --git a/tests/unit/Testables/TestableConfigurationTest.swift b/tests/unit/Testables/TestableConfigurationTest.swift index b51cee7..e31e54f 100644 --- a/tests/unit/Testables/TestableConfigurationTest.swift +++ b/tests/unit/Testables/TestableConfigurationTest.swift @@ -12,8 +12,9 @@ class TestableConfigurationTest: XCTestCase { func test_configuration_can_be_saved_as_json() async { var configuration = TestableConfigurations.working configuration.filesystem["/opt/homebrew/bin/php"] = nil - print(configuration.filesystem.keys) + let json = configuration.toJson() + try! json.write(toFile: "/tmp/pmc_working.json", atomically: true, encoding: .utf8) try! json.write(toFile: "/tmp/pmc_broken.json", atomically: true, encoding: .utf8) } diff --git a/tests/unit/Versions/AppUpdaterCheckTest.swift b/tests/unit/Versions/AppUpdaterCheckTest.swift index 04faaae..11215fd 100644 --- a/tests/unit/Versions/AppUpdaterCheckTest.swift +++ b/tests/unit/Versions/AppUpdaterCheckTest.swift @@ -10,7 +10,7 @@ import XCTest class AppUpdaterCheckTest: XCTestCase { - func testCanRetrieveVersionFromCask() async { + func test_can_retrieve_version_from_cask() async { let caskVersion = await AppUpdateChecker.retrieveVersionFromCask() let version = VersionExtractor.from(caskVersion) @@ -18,21 +18,21 @@ class AppUpdaterCheckTest: XCTestCase { XCTAssertNotNil(version) } - func testTaggedReleaseOmitsZeroPatch() { + func test_tagged_release_omits_zero_patch() { let version = AppVersion.from("3.5.0_333")! XCTAssertEqual(version.tagged, "3.5") XCTAssertEqual(version.version, "3.5.0") } - func testTaggedReleaseDoesntOmitNonZeroPatch() { + func test_tagged_release_doesnt_omit_non_zero_patch() { let version = AppVersion.from("3.5.1_333")! XCTAssertEqual(version.tagged, "3.5.1") XCTAssertEqual(version.version, "3.5.1") } - func testTagTruncationDoesntAffectMajorVersions() { + func test_tag_truncation_does_not_affect_major_versions() { var version = AppVersion.from("5.0_333")! XCTAssertEqual(version.tagged, "5.0") diff --git a/tests/unit/Versions/AppVersionTest.swift b/tests/unit/Versions/AppVersionTest.swift index e96846e..040f819 100644 --- a/tests/unit/Versions/AppVersionTest.swift +++ b/tests/unit/Versions/AppVersionTest.swift @@ -10,11 +10,11 @@ import XCTest class AppVersionTest: XCTestCase { - func testCanRetrieveInternalAppVersion() { + func test_can_retrieve_internal_app_version() { XCTAssertNotNil(AppVersion.fromCurrentVersion()) } - func testCanParseNormalVersionString() { + func test_can_parse_normal_version_string() { let version = AppVersion.from("1.0.0") XCTAssertNotNil(version) @@ -23,7 +23,7 @@ class AppVersionTest: XCTestCase { XCTAssertEqual(nil, version?.suffix) } - func testCanParseCaskVersionString() { + func test_can_parse_cask_version_string() { let version = AppVersion.from("1.0.0_600") XCTAssertNotNil(version) @@ -32,7 +32,7 @@ class AppVersionTest: XCTestCase { XCTAssertEqual(nil, version?.suffix) } - func testCanParseDevVersionStringWithoutBuildNumber() { + func test_can_parse_dev_version_string_without_build_number() { let version = AppVersion.from("1.0.0-dev") XCTAssertNotNil(version) @@ -41,7 +41,7 @@ class AppVersionTest: XCTestCase { XCTAssertEqual("dev", version?.suffix) } - func testCanParseDevVersionStringWithBuildNumber() { + func test_can_parse_dev_version_string_with_build_number() { let version = AppVersion.from("1.0.0-dev,870") XCTAssertNotNil(version) @@ -50,7 +50,7 @@ class AppVersionTest: XCTestCase { XCTAssertEqual("dev", version?.suffix) } - func testCanParseUnderscoresAsBuildSeparatorToo() { + func test_can_parse_underscores_as_build_separator() { let version = AppVersion.from("1.0.0-dev_870") XCTAssertNotNil(version) diff --git a/tests/unit/Versions/PhpVersionDetectionTest.swift b/tests/unit/Versions/PhpVersionDetectionTest.swift index e25b2b5..64541b0 100644 --- a/tests/unit/Versions/PhpVersionDetectionTest.swift +++ b/tests/unit/Versions/PhpVersionDetectionTest.swift @@ -10,7 +10,7 @@ import XCTest class PhpVersionDetectionTest: XCTestCase { - func testCanDetectValidPhpVersions() async throws { + func test_can_detect_valid_php_versions() async throws { let outcome = await PhpEnv.shared.extractPhpVersions(from: [ "", // empty lines should be omitted "php@8.0", diff --git a/tests/unit/Versions/PhpVersionNumberTest.swift b/tests/unit/Versions/PhpVersionNumberTest.swift index f8b904a..17fbbae 100644 --- a/tests/unit/Versions/PhpVersionNumberTest.swift +++ b/tests/unit/Versions/PhpVersionNumberTest.swift @@ -11,7 +11,7 @@ import XCTest // swiftlint:disable type_body_length class PhpVersionNumberTest: XCTestCase { - func testCanDeconstructPhpVersion() throws { + func test_can_deconstruct_php_version() throws { XCTAssertEqual( try! PhpVersionNumber.parse("PHP 8.2.0-dev"), PhpVersionNumber(major: 8, minor: 2, patch: 0) @@ -38,13 +38,13 @@ class PhpVersionNumberTest: XCTestCase { ) } - func testPhpVersionNumberParse() throws { + func test_php_version_number_parse() throws { XCTAssertThrowsError(try PhpVersionNumber.parse("OOF")) { error in XCTAssertTrue(error is VersionParseError) } } - func testCanCheckFixedConstraints() throws { + func test_can_check_fixed_constraints() throws { XCTAssertEqual( PhpVersionNumberCollection .make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]) @@ -78,7 +78,7 @@ class PhpVersionNumberTest: XCTestCase { ) } - func testCanCheckCaretConstraints() throws { + func test_can_check_caret_constraints() throws { // 1. Imprecise checks XCTAssertEqual( PhpVersionNumberCollection @@ -138,7 +138,7 @@ class PhpVersionNumberTest: XCTestCase { ) } - func testCanCheckTildeConstraints() throws { + func test_can_check_tilde_constraints() throws { // 1. Imprecise checks XCTAssertEqual( PhpVersionNumberCollection @@ -207,7 +207,7 @@ class PhpVersionNumberTest: XCTestCase { ) } - func testCanCheckGreaterThanOrEqualConstraints() throws { + func test_can_check_greater_than_or_equal_constraints() throws { XCTAssertEqual( PhpVersionNumberCollection .make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]) @@ -243,7 +243,7 @@ class PhpVersionNumberTest: XCTestCase { ) } - func testCanCheckGreaterThanConstraints() throws { + func test_can_check_greater_than_constraints() throws { XCTAssertEqual( PhpVersionNumberCollection .make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]) @@ -289,7 +289,7 @@ class PhpVersionNumberTest: XCTestCase { ) } - func testCanCheckLessThanOrEqualConstraints() throws { + func test_can_check_less_than_or_equal_constraints() throws { XCTAssertEqual( PhpVersionNumberCollection .make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]) @@ -325,7 +325,7 @@ class PhpVersionNumberTest: XCTestCase { ) } - func testCanCheckLessThanConstraints() throws { + func test_can_check_less_than_constraints() throws { XCTAssertEqual( PhpVersionNumberCollection .make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"]) diff --git a/tests/unit/Versions/ValetVersionExtractorTest.swift b/tests/unit/Versions/ValetVersionExtractorTest.swift index 89c6a63..e1499f3 100644 --- a/tests/unit/Versions/ValetVersionExtractorTest.swift +++ b/tests/unit/Versions/ValetVersionExtractorTest.swift @@ -10,7 +10,7 @@ import XCTest class ValetVersionExtractorTest: XCTestCase { - func testDetermineValetVersion() async { + func test_can_determine_valet_version() async { let version = await valet("--version", sudo: false) XCTAssert(version.contains("Laravel Valet 2") || version.contains("Laravel Valet 3")) } diff --git a/tests/unit/Versions/VersionExtractorTest.swift b/tests/unit/Versions/VersionExtractorTest.swift index 5a32ba0..3501af1 100644 --- a/tests/unit/Versions/VersionExtractorTest.swift +++ b/tests/unit/Versions/VersionExtractorTest.swift @@ -10,12 +10,12 @@ import XCTest class VersionExtractorTest: XCTestCase { - func testExtractVersion() { + func test_extract_version() { XCTAssertEqual(VersionExtractor.from("Laravel Valet 2.17.1"), "2.17.1") XCTAssertEqual(VersionExtractor.from("Laravel Valet 2.0"), "2.0") } - func testVersionComparison() { + func test_version_comparison() { XCTAssertEqual("2.0".versionCompare("2.1"), .orderedAscending) XCTAssertEqual("2.1".versionCompare("2.0"), .orderedDescending) XCTAssertEqual("2.0".versionCompare("2.0"), .orderedSame) From 4b8b46a8225c7799fad5d7bd95f8ccfad69ce02f Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 18 Oct 2022 14:12:57 +0200 Subject: [PATCH 062/181] =?UTF-8?q?=E2=9C=85=20Fix=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/Testables/TestableConfigurationTest.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/unit/Testables/TestableConfigurationTest.swift b/tests/unit/Testables/TestableConfigurationTest.swift index e31e54f..9398783 100644 --- a/tests/unit/Testables/TestableConfigurationTest.swift +++ b/tests/unit/Testables/TestableConfigurationTest.swift @@ -11,12 +11,10 @@ import XCTest class TestableConfigurationTest: XCTestCase { func test_configuration_can_be_saved_as_json() async { var configuration = TestableConfigurations.working + try! configuration.toJson().write(toFile: "/tmp/pmc_working.json", atomically: true, encoding: .utf8) + configuration.filesystem["/opt/homebrew/bin/php"] = nil - - let json = configuration.toJson() - - try! json.write(toFile: "/tmp/pmc_working.json", atomically: true, encoding: .utf8) - try! json.write(toFile: "/tmp/pmc_broken.json", atomically: true, encoding: .utf8) + try! configuration.toJson().write(toFile: "/tmp/pmc_broken.json", atomically: true, encoding: .utf8) } } From 507d7d5b235501782c1d896ea21c025c977824e4 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 18 Oct 2022 16:43:17 +0200 Subject: [PATCH 063/181] =?UTF-8?q?=E2=9C=85=20Fix=20PHP=20version=20detec?= =?UTF-8?q?tion=20requirement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Shared/TestableConfigurations.swift | 2 ++ tests/ui/StartupTest.swift | 3 +++ 2 files changed, 5 insertions(+) diff --git a/tests/Shared/TestableConfigurations.swift b/tests/Shared/TestableConfigurations.swift index 1c77e5a..05f00e4 100644 --- a/tests/Shared/TestableConfigurations.swift +++ b/tests/Shared/TestableConfigurations.swift @@ -22,6 +22,8 @@ class TestableConfigurations { : .fake(.binary), "/opt/homebrew/opt/php" : .fake(.symlink, "/opt/homebrew/Cellar/php/8.1.10_1"), + "/opt/homebrew/opt/php@8.1/bin/php" + : .fake(.symlink, "/opt/homebrew/Cellar/php/8.1.10_1/bin/php"), "/opt/homebrew/Cellar/php/8.1.10_1" : .fake(.directory), "/opt/homebrew/Cellar/php/8.1.10_1/bin/php" diff --git a/tests/ui/StartupTest.swift b/tests/ui/StartupTest.swift index c4ef20d..5631360 100644 --- a/tests/ui/StartupTest.swift +++ b/tests/ui/StartupTest.swift @@ -70,6 +70,9 @@ final class StartupTest: UITestCase { let statusBarItem = app.statusItems.firstMatch statusBarItem.click() assertAllExist([ + // "Switch to PHP 8.1 (php)" should be visible since it is aliased to `php` + app.menuItems["\("mi_php_switch".localized) 8.1 (php)"], + // We should see the about and quit items app.menuItems["mi_about".localized], app.menuItems["mi_quit".localized] ]) From 1c0d9f6826009ce3a3fbe06797f1a511873ff241 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 18 Oct 2022 23:41:46 +0200 Subject: [PATCH 064/181] =?UTF-8?q?=F0=9F=91=8C=20Swift=206=20compatibilit?= =?UTF-8?q?y?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcschemes/PHP Monitor DEV.xcscheme | 4 ++-- phpmon/Common/Shell/RealShell.swift | 8 ++++++-- phpmon/Common/Shell/ShellProtocol.swift | 7 ++++++- .../Common/Testables/TestableConfiguration.swift | 5 +++-- phpmon/Domain/DomainList/AddSiteVC.swift | 6 +++++- phpmon/Domain/DomainList/DomainListVC.swift | 2 +- .../unit/Testables/TestableConfigurationTest.swift | 14 ++++++++++++-- 7 files changed, 35 insertions(+), 11 deletions(-) diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index 0f36fe6..0b2dcd1 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -79,11 +79,11 @@ isEnabled = "NO"> diff --git a/phpmon/Common/Shell/RealShell.swift b/phpmon/Common/Shell/RealShell.swift index 77ab643..7f10d21 100644 --- a/phpmon/Common/Shell/RealShell.swift +++ b/phpmon/Common/Shell/RealShell.swift @@ -8,6 +8,9 @@ import Foundation +extension Process: @unchecked Sendable {} +extension Timer: @unchecked Sendable {} + class RealShell: ShellProtocol { /** The launch path of the terminal in question that is used. @@ -123,7 +126,8 @@ class RealShell: ShellProtocol { withTimeout timeout: TimeInterval = 5.0 ) async throws -> (Process, ShellOutput) { let task = getShellProcess(for: command) - var output = ShellOutput(out: "", err: "") + // TODO: Make ShellOutput a struct again and add a class type for this use case only + let output = ShellOutput(out: "", err: "") task.listen { incoming in output.out += incoming; didReceiveOutput(incoming, .stdOut) @@ -134,7 +138,7 @@ class RealShell: ShellProtocol { return try await withCheckedThrowingContinuation({ continuation in var timer: Timer? - task.terminationHandler = { process in + task.terminationHandler = { [timer, output] process in process.haltListening() timer?.invalidate() diff --git a/phpmon/Common/Shell/ShellProtocol.swift b/phpmon/Common/Shell/ShellProtocol.swift index a6e2fed..eea59d4 100644 --- a/phpmon/Common/Shell/ShellProtocol.swift +++ b/phpmon/Common/Shell/ShellProtocol.swift @@ -52,7 +52,7 @@ enum ShellStream: Codable { case stdOut, stdErr, stdIn } -struct ShellOutput { +class ShellOutput { var out: String var err: String @@ -60,6 +60,11 @@ struct ShellOutput { return err.lengthOfBytes(using: .utf8) > 0 } + init(out: String, err: String) { + self.out = out + self.err = err + } + static func out(_ out: String?, _ err: String? = nil) -> ShellOutput { return ShellOutput(out: out ?? "", err: err ?? "") } diff --git a/phpmon/Common/Testables/TestableConfiguration.swift b/phpmon/Common/Testables/TestableConfiguration.swift index b27ac18..80ed1cd 100644 --- a/phpmon/Common/Testables/TestableConfiguration.swift +++ b/phpmon/Common/Testables/TestableConfiguration.swift @@ -31,10 +31,11 @@ public struct TestableConfiguration: Codable { } static func loadFrom(path: String) -> TestableConfiguration { + let url = URL(fileURLWithPath: path.replacingTildeWithHomeDirectory) + return try! JSONDecoder().decode( TestableConfiguration.self, - from: try! String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8) - .data(using: .utf8)! + from: try! String(contentsOf: url, encoding: .utf8).data(using: .utf8)! ) } } diff --git a/phpmon/Domain/DomainList/AddSiteVC.swift b/phpmon/Domain/DomainList/AddSiteVC.swift index 9ae5fde..a427613 100644 --- a/phpmon/Domain/DomainList/AddSiteVC.swift +++ b/phpmon/Domain/DomainList/AddSiteVC.swift @@ -51,7 +51,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate { // MARK: - Outlet Interactions - @IBAction func pressedCreateLink(_ sender: Any) async { + func createLink() async { let path = pathControl.url!.path let name = inputDomainName.stringValue @@ -89,6 +89,10 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate { ) } + @IBAction func pressedCreateLink(_ sender: Any) { + Task { await createLink() } + } + @IBAction func pressedCancel(_ sender: Any) { dismissView(outcome: .cancel) } diff --git a/phpmon/Domain/DomainList/DomainListVC.swift b/phpmon/Domain/DomainList/DomainListVC.swift index df410a1..24c47be 100644 --- a/phpmon/Domain/DomainList/DomainListVC.swift +++ b/phpmon/Domain/DomainList/DomainListVC.swift @@ -110,7 +110,7 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource @MainActor public func setUIBusy() { // If it takes more than 0.5s to set the UI to not busy, show a spinner timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: { _ in - self.progressIndicator.startAnimation(true) + DispatchQueue.main.async { self.progressIndicator.startAnimation(true) } }) tableView.alphaValue = 0.3 diff --git a/tests/unit/Testables/TestableConfigurationTest.swift b/tests/unit/Testables/TestableConfigurationTest.swift index 9398783..97ec216 100644 --- a/tests/unit/Testables/TestableConfigurationTest.swift +++ b/tests/unit/Testables/TestableConfigurationTest.swift @@ -11,10 +11,20 @@ import XCTest class TestableConfigurationTest: XCTestCase { func test_configuration_can_be_saved_as_json() async { var configuration = TestableConfigurations.working - try! configuration.toJson().write(toFile: "/tmp/pmc_working.json", atomically: true, encoding: .utf8) + + try! configuration.toJson().write( + toFile: NSHomeDirectory() + "/.phpmon_fconf_working.json", + atomically: true, + encoding: .utf8 + ) configuration.filesystem["/opt/homebrew/bin/php"] = nil - try! configuration.toJson().write(toFile: "/tmp/pmc_broken.json", atomically: true, encoding: .utf8) + + try! configuration.toJson().write( + toFile: NSHomeDirectory() + "/.phpmon_fconf_broken.json", + atomically: true, + encoding: .utf8 + ) } } From 658cec27c10e4d7a742101f0ab2f0f79f7657b3b Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 18 Oct 2022 23:44:18 +0200 Subject: [PATCH 065/181] =?UTF-8?q?=F0=9F=91=8C=20Cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Shell/RealShell.swift | 4 ++-- phpmon/Common/Shell/ShellProtocol.swift | 4 ++++ phpmon/Common/Testables/TestableShell.swift | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/phpmon/Common/Shell/RealShell.swift b/phpmon/Common/Shell/RealShell.swift index 7f10d21..32bb684 100644 --- a/phpmon/Common/Shell/RealShell.swift +++ b/phpmon/Common/Shell/RealShell.swift @@ -126,8 +126,8 @@ class RealShell: ShellProtocol { withTimeout timeout: TimeInterval = 5.0 ) async throws -> (Process, ShellOutput) { let task = getShellProcess(for: command) - // TODO: Make ShellOutput a struct again and add a class type for this use case only - let output = ShellOutput(out: "", err: "") + + let output = ShellOutput.empty() task.listen { incoming in output.out += incoming; didReceiveOutput(incoming, .stdOut) diff --git a/phpmon/Common/Shell/ShellProtocol.swift b/phpmon/Common/Shell/ShellProtocol.swift index eea59d4..7cb77c6 100644 --- a/phpmon/Common/Shell/ShellProtocol.swift +++ b/phpmon/Common/Shell/ShellProtocol.swift @@ -65,6 +65,10 @@ class ShellOutput { self.err = err } + static func empty() -> ShellOutput { + return ShellOutput(out: "", err: "") + } + static func out(_ out: String?, _ err: String? = nil) -> ShellOutput { return ShellOutput(out: out ?? "", err: err ?? "") } diff --git a/phpmon/Common/Testables/TestableShell.swift b/phpmon/Common/Testables/TestableShell.swift index 9ab9c93..f982b30 100644 --- a/phpmon/Common/Testables/TestableShell.swift +++ b/phpmon/Common/Testables/TestableShell.swift @@ -95,7 +95,7 @@ struct BatchFakeShellOutput: Codable { didReceiveOutput: @escaping (String, ShellStream) -> Void, ignoreDelay: Bool = false ) async -> ShellOutput { - var output = ShellOutput(out: "", err: "") + let output = ShellOutput.empty() for item in items { if !ignoreDelay { From 4bfa98fc20e834b3d9dd6c15486a58a4fd35b698 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 19 Oct 2022 13:44:53 +0200 Subject: [PATCH 066/181] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20`Dispat?= =?UTF-8?q?chQueue`=20to=20new=20`Task`=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcshareddata/xcschemes/PHP Monitor DEV.xcscheme | 2 +- phpmon/Common/Core/Actions.swift | 10 ++++------ phpmon/Common/PHP/PHP Version/PhpHelper.swift | 1 + phpmon/Common/PHP/PhpExtension.swift | 2 +- phpmon/Common/PHP/Switcher/InternalSwitcher.swift | 5 ++--- phpmon/Domain/App/AppUpdateChecker.swift | 6 +++--- phpmon/Domain/App/InterAppHandler.swift | 2 +- phpmon/Domain/App/ServicesManager.swift | 4 ++-- phpmon/Domain/App/Startup.swift | 2 +- phpmon/Domain/DomainList/AddProxyVC.swift | 3 ++- phpmon/Domain/DomainList/DomainListVC+Actions.swift | 2 +- phpmon/Domain/DomainList/DomainListVC.swift | 11 +++++++---- .../Domain/Integrations/Composer/ComposerWindow.swift | 7 ++++--- .../Integrations/Homebrew/HomebrewDiagnostics.swift | 2 +- phpmon/Domain/Integrations/Valet/Valet.swift | 4 ++-- phpmon/Domain/Menu/MainMenu+Actions.swift | 4 ++-- phpmon/Domain/Menu/MainMenu+Async.swift | 4 ++-- phpmon/Domain/Menu/MainMenu+FixMyValet.swift | 2 +- phpmon/Domain/Menu/MainMenu+Startup.swift | 8 ++++---- phpmon/Domain/Menu/MainMenu+Switcher.swift | 2 +- phpmon/Domain/Menu/MainMenu.swift | 10 +++++----- phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift | 2 +- phpmon/Domain/Preferences/Stats.swift | 2 +- phpmon/Domain/Presets/Preset.swift | 4 ++-- .../Progress/TerminalProgressWindowController.swift | 2 +- phpmon/Domain/Watcher/App+ConfigWatch.swift | 2 +- 26 files changed, 54 insertions(+), 51 deletions(-) diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index 0b2dcd1..ff88595 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -80,7 +80,7 @@ + isEnabled = "NO"> URL { // Write a file called `phpmon_phpinfo.php` to /tmp + // TODO: Use FileSystem abstraction try! " Date: Wed, 19 Oct 2022 13:51:22 +0200 Subject: [PATCH 067/181] =?UTF-8?q?=F0=9F=91=8C=20Cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Shell/RealShell.swift | 14 ++++++-------- phpmon/Domain/App/ServicesManager.swift | 7 ++----- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/phpmon/Common/Shell/RealShell.swift b/phpmon/Common/Shell/RealShell.swift index 32bb684..ac52ba9 100644 --- a/phpmon/Common/Shell/RealShell.swift +++ b/phpmon/Common/Shell/RealShell.swift @@ -136,12 +136,16 @@ class RealShell: ShellProtocol { } return try await withCheckedThrowingContinuation({ continuation in - var timer: Timer? + let timer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { _ in + task.terminationHandler = nil + task.terminate() + return continuation.resume(throwing: ShellError.timedOut) + } task.terminationHandler = { [timer, output] process in process.haltListening() - timer?.invalidate() + timer.invalidate() if !output.err.isEmpty { return continuation.resume(returning: (process, .err(output.err))) @@ -150,12 +154,6 @@ class RealShell: ShellProtocol { return continuation.resume(returning: (process, .out(output.out))) } - timer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { _ in - task.terminationHandler = nil - task.terminate() - return continuation.resume(throwing: ShellError.timedOut) - } - task.launch() task.waitUntilExit() }) diff --git a/phpmon/Domain/App/ServicesManager.swift b/phpmon/Domain/App/ServicesManager.swift index bfad38e..08e46ef 100644 --- a/phpmon/Domain/App/ServicesManager.swift +++ b/phpmon/Domain/App/ServicesManager.swift @@ -25,8 +25,7 @@ class ServicesManager: ObservableObject { let normalJson = await Shell .pipe("sudo \(Paths.brew) services info --all --json") - .out - .data(using: .utf8)! + .out.data(using: .utf8)! let normalServices = try! JSONDecoder() .decode([HomebrewService].self, from: normalJson) @@ -38,9 +37,7 @@ class ServicesManager: ObservableObject { ) } - guard let userServiceNames = Preferences.custom.services else { - return - } + guard let userServiceNames = Preferences.custom.services else { return } let rootJson = await Shell .pipe("\(Paths.brew) services info --all --json") From 696f9bf351089fb28e802e7453bd025386fb253c Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 19 Oct 2022 14:27:39 +0200 Subject: [PATCH 068/181] =?UTF-8?q?=F0=9F=91=8C=20Various=20async=20improv?= =?UTF-8?q?ements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Core/Actions.swift | 14 +++--- phpmon/Common/PHP/PhpExtension.swift | 2 +- phpmon/Domain/App/AppDelegate+InterApp.swift | 3 +- phpmon/Domain/App/InterAppHandler.swift | 28 +++++------ phpmon/Domain/Menu/MainMenu.swift | 53 ++++++++------------ phpmon/Domain/Warnings/WarningManager.swift | 2 +- phpmon/Domain/Watcher/App+ConfigWatch.swift | 2 +- 7 files changed, 43 insertions(+), 61 deletions(-) diff --git a/phpmon/Common/Core/Actions.swift b/phpmon/Common/Core/Actions.swift index 24234b7..3963ded 100644 --- a/phpmon/Common/Core/Actions.swift +++ b/phpmon/Common/Core/Actions.swift @@ -88,23 +88,23 @@ class Actions { NSWorkspace.shared.activateFileViewerSelecting(files as [URL]) } - public static func openGlobalComposerFolder() { - let file = URL(string: "~/.composer/composer.json".replacingTildeWithHomeDirectory)! - NSWorkspace.shared.activateFileViewerSelecting([file] as [URL]) - } - public static func openPhpConfigFolder(version: String) { let files = [NSURL(fileURLWithPath: "\(Paths.etcPath)/php/\(version)/php.ini")] NSWorkspace.shared.activateFileViewerSelecting(files as [URL]) } + public static func openGlobalComposerFolder() { + let file = URL(string: "file://~/.composer/composer.json".replacingTildeWithHomeDirectory)! + NSWorkspace.shared.activateFileViewerSelecting([file] as [URL]) + } + public static func openValetConfigFolder() { - let file = URL(string: "~/.config/valet".replacingTildeWithHomeDirectory)! + let file = URL(string: "file://~/.config/valet".replacingTildeWithHomeDirectory)! NSWorkspace.shared.activateFileViewerSelecting([file] as [URL]) } public static func openPhpMonitorConfigFile() { - let file = URL(string: "~/.config/phpmon".replacingTildeWithHomeDirectory)! + let file = URL(string: "file://~/.config/phpmon".replacingTildeWithHomeDirectory)! NSWorkspace.shared.activateFileViewerSelecting([file] as [URL]) } diff --git a/phpmon/Common/PHP/PhpExtension.swift b/phpmon/Common/PHP/PhpExtension.swift index 7c9d397..bd7b71e 100644 --- a/phpmon/Common/PHP/PhpExtension.swift +++ b/phpmon/Common/PHP/PhpExtension.swift @@ -90,7 +90,7 @@ class PhpExtension { // When running unit tests, the MainMenu will not be available // TODO: Fix this dependency issue, set up a notification mechanism Task { @MainActor in - MainMenu.shared.rebuild(async: false) + MainMenu.shared.rebuild() } } diff --git a/phpmon/Domain/App/AppDelegate+InterApp.swift b/phpmon/Domain/App/AppDelegate+InterApp.swift index 3c1f089..715502b 100644 --- a/phpmon/Domain/App/AppDelegate+InterApp.swift +++ b/phpmon/Domain/App/AppDelegate+InterApp.swift @@ -20,8 +20,7 @@ extension AppDelegate { Please note that PHP Monitor needs to be running in the background for this to work. */ - func application(_ application: NSApplication, open urls: [URL]) { - + @MainActor func application(_ application: NSApplication, open urls: [URL]) { if !Preferences.isEnabled(.allowProtocolForIntegrations) { Log.info("Acting on commands via phpmon:// has been disabled.") return diff --git a/phpmon/Domain/App/InterAppHandler.swift b/phpmon/Domain/App/InterAppHandler.swift index f5d638b..c8bb0a5 100644 --- a/phpmon/Domain/App/InterAppHandler.swift +++ b/phpmon/Domain/App/InterAppHandler.swift @@ -21,46 +21,42 @@ class InterApp { let action: (String) -> Void } - static func getCommands() -> [InterApp.Action] { return [ + @MainActor static func getCommands() -> [InterApp.Action] { return [ InterApp.Action(command: "list", action: { _ in DomainListVC.show() }), InterApp.Action(command: "services/stop", action: { _ in - Task { // Stopping services as standalone task - await MainMenu.shared.stopValetServices() - } + Task { MainMenu.shared.stopValetServices() } }), InterApp.Action(command: "services/restart/all", action: { _ in - Task { // Restarting services as standalone task - await MainMenu.shared.restartValetServices() - } + Task { MainMenu.shared.restartValetServices() } }), InterApp.Action(command: "services/restart/nginx", action: { _ in - MainMenu.shared.restartNginx() + Task { MainMenu.shared.restartNginx() } }), InterApp.Action(command: "services/restart/php", action: { _ in - MainMenu.shared.restartPhpFpm() + Task { MainMenu.shared.restartPhpFpm() } }), InterApp.Action(command: "services/restart/dnsmasq", action: { _ in - MainMenu.shared.restartDnsMasq() + Task { MainMenu.shared.restartDnsMasq() } }), InterApp.Action(command: "locate/config", action: { _ in - MainMenu.shared.openActiveConfigFolder() + Task { MainMenu.shared.openActiveConfigFolder() } }), InterApp.Action(command: "locate/composer", action: { _ in - MainMenu.shared.openGlobalComposerFolder() + Task { MainMenu.shared.openGlobalComposerFolder() } }), InterApp.Action(command: "locate/valet", action: { _ in - MainMenu.shared.openValetConfigFolder() + Task { MainMenu.shared.openValetConfigFolder() } }), InterApp.Action(command: "phpinfo", action: { _ in - MainMenu.shared.openPhpInfo() + Task { MainMenu.shared.openPhpInfo() } }), InterApp.Action(command: "switch/php/", action: { version in if PhpEnv.shared.availablePhpVersions.contains(version) { - MainMenu.shared.switchToPhpVersion(version) + Task { MainMenu.shared.switchToPhpVersion(version) } } else { - Task { @MainActor in + Task { BetterAlert().withInformation( title: "alert.php_switch_unavailable.title".localized, subtitle: "alert.php_switch_unavailable.subtitle".localized(version) diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index 4d95035..8ec944e 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -7,6 +7,7 @@ import Cocoa +@MainActor class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate { static let shared = MainMenu() @@ -31,38 +32,22 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate Rebuilds the menu (either asynchronously or synchronously). Defaults to rebuilding the menu asynchronously. */ - func rebuild(async: Bool = true) { - if !async { - self.rebuildMenu() - return - } - - // Update the menu item on the main thread + func rebuild() { Task { @MainActor [self] in - self.rebuildMenu() + let menu = StatusMenu() + menu.addMenuItems() + menu.items.forEach({ (item) in + item.target = self + }) + statusItem.menu = menu + statusItem.menu?.delegate = self } } - /** - Update the menu's contents, based on what's going on. - This will rebuild the entire menu, so this can take a few moments. - - Use `rebuild(async:)` to ensure the rebuilding happens in the background. - */ - private func rebuildMenu() { - let menu = StatusMenu() - menu.addMenuItems() - menu.items.forEach({ (item) in - item.target = self - }) - statusItem.menu = menu - statusItem.menu?.delegate = self - } - /** Sets the status bar image based on a version string. */ - @MainActor func setStatusBarImage(version: String) { + func setStatusBarImage(version: String) { setStatusBar( image: (Preferences.preferences[.iconTypeToDisplay] as! String != MenuBarIcon.noIcon.rawValue) ? MenuBarImageGenerator.textToImageWithIcon(text: version) @@ -74,7 +59,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate Sets the status bar image, based on the provided NSImage. The image will be used as a template image. */ - @MainActor func setStatusBar(image: NSImage) { + func setStatusBar(image: NSImage) { if let button = statusItem.button { image.isTemplate = true button.image = image @@ -89,7 +74,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate PhpEnv.shared.currentInstall = ActivePhpInstallation() updatePhpVersionInStatusBar() } else { - Log.perf("Skipping version refresh due to busy status") + Log.perf("Skipping version refresh due to busy status!") } } @@ -103,13 +88,15 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate Reloads the menu in the foreground. This mimics the exact behaviours of `asyncExecution` as set in the method below. */ - @MainActor @objc func reloadPhpMonitorMenuInForeground() async { - refreshActiveInstallation() - refreshIcon() - Task { @MainActor in - self.rebuild(async: false) + @objc func reloadPhpMonitorMenuInForeground() { + Log.perf("The menu will be reloaded...") + Task { [self] in + self.refreshActiveInstallation() + self.refreshIcon() + self.rebuild() + await ServicesManager.loadHomebrewServices() + Log.perf("The menu has been reloaded!") } - await ServicesManager.loadHomebrewServices() } /** diff --git a/phpmon/Domain/Warnings/WarningManager.swift b/phpmon/Domain/Warnings/WarningManager.swift index b0b8b1a..516e0e7 100644 --- a/phpmon/Domain/Warnings/WarningManager.swift +++ b/phpmon/Domain/Warnings/WarningManager.swift @@ -70,7 +70,7 @@ class WarningManager { await loopOverEvaluations() } - MainMenu.shared.rebuild() + await MainMenu.shared.rebuild() } private func loopOverEvaluations() async { diff --git a/phpmon/Domain/Watcher/App+ConfigWatch.swift b/phpmon/Domain/Watcher/App+ConfigWatch.swift index 5640df9..8423d68 100644 --- a/phpmon/Domain/Watcher/App+ConfigWatch.swift +++ b/phpmon/Domain/Watcher/App+ConfigWatch.swift @@ -21,7 +21,7 @@ extension App { let distance = self.watcher.lastUpdate?.distance(to: Date().timeIntervalSince1970) if distance == nil || distance != nil && distance! > 0.75 { Log.perf("Refreshing menu...") - MainMenu.shared.reloadPhpMonitorMenuInBackground() + Task { @MainActor in MainMenu.shared.reloadPhpMonitorMenuInBackground() } self.watcher.lastUpdate = Date().timeIntervalSince1970 } } From 8310cf2729a6c1bcb22b29ba1ff29b634dccfb83 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 19 Oct 2022 14:57:28 +0200 Subject: [PATCH 069/181] =?UTF-8?q?=F0=9F=91=8C=20Touching=20up=20correctn?= =?UTF-8?q?ess=20of=20process=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Shell/RealShell.swift | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/phpmon/Common/Shell/RealShell.swift b/phpmon/Common/Shell/RealShell.swift index ac52ba9..d8b5a2b 100644 --- a/phpmon/Common/Shell/RealShell.swift +++ b/phpmon/Common/Shell/RealShell.swift @@ -125,11 +125,11 @@ class RealShell: ShellProtocol { didReceiveOutput: @escaping (String, ShellStream) -> Void, withTimeout timeout: TimeInterval = 5.0 ) async throws -> (Process, ShellOutput) { - let task = getShellProcess(for: command) + let process = getShellProcess(for: command) let output = ShellOutput.empty() - task.listen { incoming in + process.listen { incoming in output.out += incoming; didReceiveOutput(incoming, .stdOut) } didReceiveStandardErrorData: { incoming in output.err += incoming; didReceiveOutput(incoming, .stdErr) @@ -137,16 +137,19 @@ class RealShell: ShellProtocol { return try await withCheckedThrowingContinuation({ continuation in let timer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { _ in - task.terminationHandler = nil - task.terminate() - return continuation.resume(throwing: ShellError.timedOut) + // Only terminate if the process is still running + if process.isRunning { + process.terminationHandler = nil + process.terminate() + return continuation.resume(throwing: ShellError.timedOut) + } } - task.terminationHandler = { [timer, output] process in - process.haltListening() - + process.terminationHandler = { [timer, output] process in timer.invalidate() + process.haltListening() + if !output.err.isEmpty { return continuation.resume(returning: (process, .err(output.err))) } @@ -154,8 +157,8 @@ class RealShell: ShellProtocol { return continuation.resume(returning: (process, .out(output.out))) } - task.launch() - task.waitUntilExit() + process.launch() + process.waitUntilExit() }) } } From ac2184ba97bfcc40c910a56cd778494f1aa5afa4 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 20 Oct 2022 20:44:44 +0200 Subject: [PATCH 070/181] =?UTF-8?q?=F0=9F=91=8C=20Add=20`brew=20tap=20home?= =?UTF-8?q?brew/services`=20instruction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This now recommends the appropriate solution for #208. --- phpmon/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index f10113f..fa14f71 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -514,7 +514,7 @@ If you are seeing this message but are confused why this folder has gone missing /// Cannot retrieve services "startup.errors.services_json_error.title" = "Cannot determine services status"; "startup.errors.services_json_error.subtitle" = "PHP Monitor usually queries `brew` using the following command to test if the services can be retrieved: `sudo brew services info nginx --json`.\n\nPHP Monitor could not interpret this response."; -"startup.errors.services_json_error.desc" = "This can happen if your Homebrew installation is out of date, in which case Homebrew won't return JSON yet. You can usually fix this by running `brew update`. You can also try running `sudo brew services info nginx --json` in your terminal of choice."; +"startup.errors.services_json_error.desc" = "This can happen if your Homebrew installation is out of date, in which case Homebrew won't return JSON yet. You can usually fix this by running `brew update` or `brew tap homebrew/services`. You can also try running `sudo brew services info nginx --json` in your terminal of choice."; /// Issue with `which` alias "startup.errors.which_alias_issue.title" = "A configuration issue was detected"; From 04ed29bc9f91c8241ddd3006d9cc4177b9f54081 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 21 Oct 2022 18:19:31 +0200 Subject: [PATCH 071/181] =?UTF-8?q?=F0=9F=91=8C=20Fix=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/Menu/MainMenu+Startup.swift | 2 +- phpmon/Domain/Menu/MainMenu+Switcher.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 27f16cf..9a87858 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -145,7 +145,7 @@ extension MainMenu { Schedule a request to fetch the PHP version every 60 seconds. */ private func startSharedTimer() { - Task { @MainActor [self] in + DispatchQueue.main.async { [self] in App.shared.timer = Timer.scheduledTimer( timeInterval: 60, target: self, diff --git a/phpmon/Domain/Menu/MainMenu+Switcher.swift b/phpmon/Domain/Menu/MainMenu+Switcher.swift index 1f134cc..b8464b7 100644 --- a/phpmon/Domain/Menu/MainMenu+Switcher.swift +++ b/phpmon/Domain/Menu/MainMenu+Switcher.swift @@ -12,9 +12,9 @@ extension MainMenu { // MARK: - PhpSwitcherDelegate - func switcherDidStartSwitching(to version: String) {} + nonisolated func switcherDidStartSwitching(to version: String) {} - func switcherDidCompleteSwitch(to version: String) { + nonisolated func switcherDidCompleteSwitch(to version: String) { // Mark as no longer busy PhpEnv.shared.isBusy = false From 1ece5c34bfad9c64fb551546a68b19263645ef99 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 21 Oct 2022 19:27:31 +0200 Subject: [PATCH 072/181] =?UTF-8?q?=E2=9C=85=20Correctly=20parse=20pre-rel?= =?UTF-8?q?ease=20PHP=20versions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/PHP/ActivePhpInstallation.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phpmon/Common/PHP/ActivePhpInstallation.swift b/phpmon/Common/PHP/ActivePhpInstallation.swift index c4636f5..84021ce 100644 --- a/phpmon/Common/PHP/ActivePhpInstallation.swift +++ b/phpmon/Common/PHP/ActivePhpInstallation.swift @@ -90,7 +90,8 @@ class ActivePhpInstallation { let output = Command.execute(path: Paths.phpConfig, arguments: ["--version"], trimNewlines: true) self.hasErrorState = (output == "" || output.contains("Warning") || output.contains("Error")) - self.version = PhpVersionNumber.make(from: output) + + self.version = try! PhpVersionNumber.parse(output) } /** From e18db4eadd4deae18a870ed2bad419868b4f0eec Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 21 Oct 2022 19:31:05 +0200 Subject: [PATCH 073/181] =?UTF-8?q?=F0=9F=91=8C=20Mark=20`determineVersion?= =?UTF-8?q?`=20as=20`throws`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/PHP/ActivePhpInstallation.swift | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/phpmon/Common/PHP/ActivePhpInstallation.swift b/phpmon/Common/PHP/ActivePhpInstallation.swift index 84021ce..eacc365 100644 --- a/phpmon/Common/PHP/ActivePhpInstallation.swift +++ b/phpmon/Common/PHP/ActivePhpInstallation.swift @@ -39,7 +39,12 @@ class ActivePhpInstallation { init() { // Show information about the current version - determineVersion() + do { + try determineVersion() + } catch { + // TODO: Throw up an alert if the PHP version cannot be parsed + fatalError("Could not determine or parse PHP version") + } // Initialize the list of ini files that are loaded iniFiles = [] @@ -86,12 +91,12 @@ class ActivePhpInstallation { When the app tries to retrieve the version, the installation is considered broken if the output is nothing, _or_ if the output contains the word "Warning" or "Error". In normal situations this should not be the case. */ - private func determineVersion() { + private func determineVersion() throws { let output = Command.execute(path: Paths.phpConfig, arguments: ["--version"], trimNewlines: true) self.hasErrorState = (output == "" || output.contains("Warning") || output.contains("Error")) - self.version = try! PhpVersionNumber.parse(output) + self.version = try? PhpVersionNumber.parse(output) } /** From 771f8dc75720ce7fb9dabb6b6276f1354a37bae9 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 21 Oct 2022 19:45:42 +0200 Subject: [PATCH 074/181] =?UTF-8?q?=F0=9F=8F=97=20WIP=20for=20fake=20site?= =?UTF-8?q?=20enhancements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 30 +++++++++++++- .../Valet/{ => Domains}/DomainListable.swift | 0 .../Valet/Domains/ValetInteractable.swift | 41 +++++++++++++++++++ .../Valet/Domains/ValetInteractor.swift | 29 +++++++++++++ .../Sites/SiteScanner/FakeSiteScanner.swift | 12 +++--- .../Valet/Sites/ValetSite+Fake.swift | 4 +- .../SwiftUI/Domains/VersionPopoverView.swift | 10 ++--- 7 files changed, 111 insertions(+), 15 deletions(-) rename phpmon/Domain/Integrations/Valet/{ => Domains}/DomainListable.swift (100%) create mode 100644 phpmon/Domain/Integrations/Valet/Domains/ValetInteractable.swift create mode 100644 phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index f6b744e..5593f96 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -35,6 +35,14 @@ 54FCFD2E276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD2C276C8D67004CE748 /* HotkeyPreferenceView.xib */; }; 54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */; }; 54FCFD31276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */; }; + C40175B229030F4600763A68 /* ValetInteractable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B129030F4600763A68 /* ValetInteractable.swift */; }; + C40175B329030F4600763A68 /* ValetInteractable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B129030F4600763A68 /* ValetInteractable.swift */; }; + C40175B429030F4600763A68 /* ValetInteractable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B129030F4600763A68 /* ValetInteractable.swift */; }; + C40175B529030F4600763A68 /* ValetInteractable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B129030F4600763A68 /* ValetInteractable.swift */; }; + C40175B82903108900763A68 /* ValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B72903108900763A68 /* ValetInteractor.swift */; }; + C40175B92903108900763A68 /* ValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B72903108900763A68 /* ValetInteractor.swift */; }; + C40175BA2903108900763A68 /* ValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B72903108900763A68 /* ValetInteractor.swift */; }; + C40175BB2903108900763A68 /* ValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B72903108900763A68 /* ValetInteractor.swift */; }; C40508AF28ADA23D008FAC1F /* NoDomainResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508AE28ADA23D008FAC1F /* NoDomainResultsView.swift */; }; C40508B128ADAB44008FAC1F /* NSMenuItemExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */; }; C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */; }; @@ -690,6 +698,8 @@ 54FCFD29276C8AA4004CE748 /* CheckboxPreferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxPreferenceView.swift; sourceTree = ""; }; 54FCFD2C276C8D67004CE748 /* HotkeyPreferenceView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HotkeyPreferenceView.xib; sourceTree = ""; }; 54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HotkeyPreferenceView.swift; sourceTree = ""; }; + C40175B129030F4600763A68 /* ValetInteractable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetInteractable.swift; sourceTree = ""; }; + C40175B72903108900763A68 /* ValetInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetInteractor.swift; sourceTree = ""; }; C40508AE28ADA23D008FAC1F /* NoDomainResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoDomainResultsView.swift; sourceTree = ""; }; C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSMenuItemExtension.swift; sourceTree = ""; }; C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = InternetAccessPolicy.strings; sourceTree = ""; }; @@ -983,6 +993,16 @@ path = Views; sourceTree = ""; }; + C40175B629030F7A00763A68 /* Domains */ = { + isa = PBXGroup; + children = ( + C42F26722805B4B400938AC7 /* DomainListable.swift */, + C40175B129030F4600763A68 /* ValetInteractable.swift */, + C40175B72903108900763A68 /* ValetInteractor.swift */, + ); + path = Domains; + sourceTree = ""; + }; C405A4CD24B9B9070062FAFA /* IAP */ = { isa = PBXGroup; children = ( @@ -1343,7 +1363,7 @@ isa = PBXGroup; children = ( C4AF9F792754499000D44ED0 /* Valet.swift */, - C42F26722805B4B400938AC7 /* DomainListable.swift */, + C40175B629030F7A00763A68 /* Domains */, C4C0E8D927F887BD002D32A9 /* Proxies */, C4C0E8D827F887A5002D32A9 /* Sites */, ); @@ -1894,6 +1914,7 @@ C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */, C4D8016622B1584700C6DA1B /* Startup.swift in Sources */, C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */, + C40175B229030F4600763A68 /* ValetInteractable.swift in Sources */, C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */, C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */, C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */, @@ -1945,6 +1966,7 @@ C4C3643928AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */, C4AC51FC27E27F47008528CA /* DomainListKindCell.swift in Sources */, C4CDA893288F1A71007CE25F /* Keys.swift in Sources */, + C40175B82903108900763A68 /* ValetInteractor.swift in Sources */, C4F361612836BFD9003598CC /* MainMenu+Actions.swift in Sources */, C46EBC4A28DB966A007ACC74 /* TestableShell.swift in Sources */, C44C198D276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */, @@ -2091,6 +2113,7 @@ C471E85C28F9BB650021E251 /* DomainListWindowController.swift in Sources */, C471E85D28F9BB650021E251 /* DomainListVC.swift in Sources */, C471E85E28F9BB650021E251 /* DomainListVC+ContextMenu.swift in Sources */, + C40175B429030F4600763A68 /* ValetInteractable.swift in Sources */, C4E2E86628FC2F1B003B070C /* XCPMApplication.swift in Sources */, C471E85F28F9BB650021E251 /* DomainListVC+Actions.swift in Sources */, C471E86028F9BB650021E251 /* SelectionVC.swift in Sources */, @@ -2098,6 +2121,7 @@ C471E86228F9BB650021E251 /* AddProxyVC.swift in Sources */, C471E86328F9BB650021E251 /* PMTableView.swift in Sources */, C471E86428F9BB650021E251 /* Warning.swift in Sources */, + C40175BA2903108900763A68 /* ValetInteractor.swift in Sources */, C471E86528F9BB650021E251 /* WarningManager.swift in Sources */, C471E86628F9BB650021E251 /* WarningsWindowController.swift in Sources */, C471E86728F9BB650021E251 /* OnboardingWindowController.swift in Sources */, @@ -2195,6 +2219,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C40175B529030F4600763A68 /* ValetInteractable.swift in Sources */, C471E89028F9BB8F0021E251 /* AlertableError.swift in Sources */, C471E89128F9BB8F0021E251 /* Errors.swift in Sources */, C471E89228F9BB8F0021E251 /* Alert.swift in Sources */, @@ -2302,6 +2327,7 @@ C471E81228F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */, C471E7DF28F9BAAB0021E251 /* RealCommand.swift in Sources */, C471E7E028F9BAAB0021E251 /* ActiveCommand.swift in Sources */, + C40175BB2903108900763A68 /* ValetInteractor.swift in Sources */, C471E80928F9BADC0021E251 /* CreatedFromFile.swift in Sources */, C471E80128F9BAD40021E251 /* ActivePhpInstallation.swift in Sources */, C471E80228F9BAD40021E251 /* PhpInstallation.swift in Sources */, @@ -2429,6 +2455,7 @@ C485706D28BF450900539B36 /* NSMenuItemExtension.swift in Sources */, C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */, C495F5B028A42E080087F70A /* EnvironmentCheck.swift in Sources */, + C40175B329030F4600763A68 /* ValetInteractable.swift in Sources */, C41E871B2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */, C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */, C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */, @@ -2440,6 +2467,7 @@ C4E49DEE28F764A00026AC4E /* TestableCommand.swift in Sources */, C484437C2804BB560041A78A /* ValetProxyScanner.swift in Sources */, C4AF9F78275447F100D44ED0 /* ValetConfigurationTest.swift in Sources */, + C40175B92903108900763A68 /* ValetInteractor.swift in Sources */, C4CE3BBC27B324250086CA49 /* ComposerWindow.swift in Sources */, C40B24F427A310830018C7D2 /* StatusMenu.swift in Sources */, C417DC75277614690015E6EE /* Helpers.swift in Sources */, diff --git a/phpmon/Domain/Integrations/Valet/DomainListable.swift b/phpmon/Domain/Integrations/Valet/Domains/DomainListable.swift similarity index 100% rename from phpmon/Domain/Integrations/Valet/DomainListable.swift rename to phpmon/Domain/Integrations/Valet/Domains/DomainListable.swift diff --git a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractable.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractable.swift new file mode 100644 index 0000000..21ecfab --- /dev/null +++ b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractable.swift @@ -0,0 +1,41 @@ +// +// ValetInteractable.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 21/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +protocol DomainInteractable { + + func secure() async throws + + func unsecure() async throws + + func isolate(version: PhpVersionNumber) async throws + + func unlink() async throws + +} + +extension ValetSite: DomainInteractable { + + func secure() async throws { + try await ValetInteractor.secure(site: self) + } + + func unsecure() async throws { + try await ValetInteractor.unsecure(site: self) + } + + func isolate(version: PhpVersionNumber) async throws { + try await ValetInteractor.isolate(site: self, version: version) + } + + func unlink() async throws { + try await ValetInteractor.unlink(site: self) + } + +} diff --git a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift new file mode 100644 index 0000000..2fe0a33 --- /dev/null +++ b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift @@ -0,0 +1,29 @@ +// +// ValetInteractor.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 21/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class ValetInteractor { + + public static func secure(site: ValetSite) async throws { + // TODO + } + + public static func unsecure(site: ValetSite) async throws { + // TODO + } + + public static func isolate(site: ValetSite, version: PhpVersionNumber) async throws { + // TODO + } + + public static func unlink(site: ValetSite) async throws { + // TODO + } + +} diff --git a/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/FakeSiteScanner.swift b/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/FakeSiteScanner.swift index 4291f58..a7c2cc7 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/FakeSiteScanner.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/FakeSiteScanner.swift @@ -8,22 +8,22 @@ class FakeSiteScanner: SiteScanner { let fakes = [ - ValetSite(fakeWithName: "laravel", tld: "test", secure: true, + FakeValetSite(fakeWithName: "laravel", tld: "test", secure: true, path: "~/Code/laravel/framework", linked: true), - ValetSite(fakeWithName: "tailwind", tld: "test", secure: true, + FakeValetSite(fakeWithName: "tailwind", tld: "test", secure: true, path: "~/Code/tailwind/site", linked: true, constraint: "8.0"), - ValetSite(fakeWithName: "forge", tld: "test", secure: true, + FakeValetSite(fakeWithName: "forge", tld: "test", secure: true, path: "~/Code/laravel/forge", linked: true), - ValetSite(fakeWithName: "concord", tld: "test", secure: false, + FakeValetSite(fakeWithName: "concord", tld: "test", secure: false, path: "~/Code/concord", linked: true, driver: "Laravel (^8.0)", constraint: "^7.4", isolated: "7.4"), - ValetSite(fakeWithName: "drupal", tld: "test", secure: false, + FakeValetSite(fakeWithName: "drupal", tld: "test", secure: false, path: "~/Sites/drupal", linked: false, driver: "Drupal", constraint: "^7.4", isolated: "7.4"), - ValetSite(fakeWithName: "wordpress", tld: "test", secure: false, + FakeValetSite(fakeWithName: "wordpress", tld: "test", secure: false, path: "~/Sites/wordpress", linked: false, driver: "WordPress", constraint: "^7.4", isolated: "7.4") ] diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite+Fake.swift b/phpmon/Domain/Integrations/Valet/Sites/ValetSite+Fake.swift index 98df3b3..5e511cf 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/ValetSite+Fake.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/ValetSite+Fake.swift @@ -8,8 +8,7 @@ import Foundation -extension ValetSite { - +class FakeValetSite: ValetSite { convenience init( fakeWithName name: String, tld: String, @@ -44,5 +43,4 @@ extension ValetSite { .isEmpty }.contains(true) } - } diff --git a/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift b/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift index a16579e..9ce4f11 100644 --- a/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift +++ b/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift @@ -121,7 +121,7 @@ struct DisclaimerView: View { struct VersionPopoverView_Previews: PreviewProvider { static var previews: some View { VersionPopoverView( - site: ValetSite( + site: FakeValetSite( fakeWithName: "amazingwebsite", tld: "test", secure: true, @@ -135,7 +135,7 @@ struct VersionPopoverView_Previews: PreviewProvider { .previewDisplayName("Unknown Requirement") VersionPopoverView( - site: ValetSite( + site: FakeValetSite( fakeWithName: "amazingwebsite", tld: "test", secure: true, @@ -148,7 +148,7 @@ struct VersionPopoverView_Previews: PreviewProvider { ) .previewDisplayName("Requirement Matches") VersionPopoverView( - site: ValetSite( + site: FakeValetSite( fakeWithName: "anothersite", tld: "test", secure: true, @@ -162,7 +162,7 @@ struct VersionPopoverView_Previews: PreviewProvider { ) .previewDisplayName("Isolated") VersionPopoverView( - site: ValetSite( + site: FakeValetSite( fakeWithName: "anothersite", tld: "test", secure: true, @@ -176,7 +176,7 @@ struct VersionPopoverView_Previews: PreviewProvider { ) .previewDisplayName("Isolated Mismatch") VersionPopoverView( - site: ValetSite( + site: FakeValetSite( fakeWithName: "anothersite", tld: "test", secure: true, From 786b59aa925bff1280b8af580c1a1c2447f675e2 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 22 Oct 2022 17:39:43 +0200 Subject: [PATCH 075/181] =?UTF-8?q?=F0=9F=90=9B=20Handle=20empty=20output?= =?UTF-8?q?=20for=20`brew=20info`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift index 0c72a0f..21ce04a 100644 --- a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift +++ b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift @@ -86,7 +86,7 @@ class HomebrewDiagnostics { private static func hasAliasConflict() async -> Bool { let tapAlias = await Shell.pipe("brew info shivammathur/php/php --json").out - if tapAlias.contains("brew tap shivammathur/php") || tapAlias.contains("Error") { + if tapAlias.contains("brew tap shivammathur/php") || tapAlias.contains("Error") || tapAlias.isEmpty { Log.info("The user does not appear to have tapped: shivammathur/php") return false } else { From 24659d4385f995ea1054da81d522447db1c1f1c7 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 25 Oct 2022 23:30:49 +0200 Subject: [PATCH 076/181] =?UTF-8?q?=F0=9F=91=8C=20Bump=20recommended=20Val?= =?UTF-8?q?et=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Core/Constants.swift | 10 +++--- .../Valet/Domains/DomainListable.swift | 32 +++++++++++++++++++ .../Valet/Domains/ValetInteractor.swift | 2 -- .../Integrations/Valet/Sites/ValetSite.swift | 30 ----------------- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/phpmon/Common/Core/Constants.swift b/phpmon/Common/Core/Constants.swift index 21e8a92..3f2df26 100644 --- a/phpmon/Common/Core/Constants.swift +++ b/phpmon/Common/Core/Constants.swift @@ -19,11 +19,10 @@ struct Constants { The minimum version of Valet that is recommended. If the installed version is older, a notification will be shown every time the app launches (with a recommendation to upgrade). - - The minimum requirement is currently synced to PHP 8.1 compatibility. - See also: https://github.com/laravel/valet/releases/tag/v2.16.2 + + See also: https://github.com/laravel/valet/releases/tag/v3.1.10 */ - static let MinimumRecommendedValetVersion = "2.16.2" + static let MinimumRecommendedValetVersion = "3.1.10" /** * The PHP versions supported by this application. @@ -42,13 +41,14 @@ struct Constants { "7.4", "8.0", "8.1", + "8.2", // ==================== // EXPERIMENTAL SUPPORT // ==================== // Every release that supports the next release will always support the next // dev release. In this case, that means that the version below is detected. - "8.2" + "8.3" ] struct Urls { diff --git a/phpmon/Domain/Integrations/Valet/Domains/DomainListable.swift b/phpmon/Domain/Integrations/Valet/Domains/DomainListable.swift index 8a54576..81b336d 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/DomainListable.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/DomainListable.swift @@ -25,3 +25,35 @@ protocol DomainListable { func getListableUrl() -> URL? } + +extension ValetSite { + + func getListableName() -> String { + return self.name + } + + func getListableSecured() -> Bool { + return self.secured + } + + func getListableAbsolutePath() -> String { + return self.absolutePath + } + + func getListablePhpVersion() -> String { + return self.servingPhpVersion + } + + func getListableKind() -> String { + return (self.aliasPath == nil) ? "linked" : "parked" + } + + func getListableType() -> String { + return self.driver ?? "ZZZ" + } + + func getListableUrl() -> URL? { + return URL(string: "\(self.secured ? "https://" : "http://")\(self.name).\(Valet.shared.config.tld)") + } + +} diff --git a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift index 2fe0a33..5352aed 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift @@ -9,7 +9,6 @@ import Foundation class ValetInteractor { - public static func secure(site: ValetSite) async throws { // TODO } @@ -25,5 +24,4 @@ class ValetInteractor { public static func unlink(site: ValetSite) async throws { // TODO } - } diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift index 5ae014c..33212f0 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift @@ -232,34 +232,4 @@ class ValetSite: DomainListable { return nil } - - // MARK: - DomainListable Protocol - - func getListableName() -> String { - return self.name - } - - func getListableSecured() -> Bool { - return self.secured - } - - func getListableAbsolutePath() -> String { - return self.absolutePath - } - - func getListablePhpVersion() -> String { - return self.servingPhpVersion - } - - func getListableKind() -> String { - return (self.aliasPath == nil) ? "linked" : "parked" - } - - func getListableType() -> String { - return self.driver ?? "ZZZ" - } - - func getListableUrl() -> URL? { - return URL(string: "\(self.secured ? "https://" : "http://")\(self.name).\(Valet.shared.config.tld)") - } } From e8c85f93f9d376ddb341b5f7982594465d49a067 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 1 Nov 2022 12:10:47 +0100 Subject: [PATCH 077/181] =?UTF-8?q?=F0=9F=91=8C=20Change=20where=20scanner?= =?UTF-8?q?s=20are=20initialized?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 20 +++++++++++++++ .../xcschemes/PHP Monitor DEV.xcscheme | 2 +- .../Testables/TestableConfiguration.swift | 9 +++++++ phpmon/Domain/App/AppDelegate.swift | 5 +--- .../ProxyScanner/EmptyProxyScanner.swift | 15 +++++++++++ .../Valet/Sites/ValetSite+Fake.swift | 3 +++ phpmon/Domain/Integrations/Valet/Valet.swift | 25 +++++++------------ .../Integrations/Valet/ValetScanners.swift | 21 ++++++++++++++++ 8 files changed, 79 insertions(+), 21 deletions(-) create mode 100644 phpmon/Domain/Integrations/Valet/Proxies/ProxyScanner/EmptyProxyScanner.swift create mode 100644 phpmon/Domain/Integrations/Valet/ValetScanners.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 5593f96..0373001 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -561,6 +561,14 @@ C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */; }; C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; }; C4CE3BBC27B324250086CA49 /* ComposerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */; }; + C4D36601291132B7006BD146 /* ValetScanners.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36600291132B7006BD146 /* ValetScanners.swift */; }; + C4D36602291132B7006BD146 /* ValetScanners.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36600291132B7006BD146 /* ValetScanners.swift */; }; + C4D36603291132B7006BD146 /* ValetScanners.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36600291132B7006BD146 /* ValetScanners.swift */; }; + C4D36604291132B7006BD146 /* ValetScanners.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36600291132B7006BD146 /* ValetScanners.swift */; }; + C4D366062911331E006BD146 /* EmptyProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D366052911331E006BD146 /* EmptyProxyScanner.swift */; }; + C4D366072911331E006BD146 /* EmptyProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D366052911331E006BD146 /* EmptyProxyScanner.swift */; }; + C4D366082911331E006BD146 /* EmptyProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D366052911331E006BD146 /* EmptyProxyScanner.swift */; }; + C4D366092911331E006BD146 /* EmptyProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D366052911331E006BD146 /* EmptyProxyScanner.swift */; }; C4D5CFCA27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; }; C4D5CFCB27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; }; C4D8016622B1584700C6DA1B /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; }; @@ -841,6 +849,8 @@ C4CDA892288F1A71007CE25F /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = ""; }; C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Switcher.swift"; sourceTree = ""; }; C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerWindow.swift; sourceTree = ""; }; + C4D36600291132B7006BD146 /* ValetScanners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetScanners.swift; sourceTree = ""; }; + C4D366052911331E006BD146 /* EmptyProxyScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyProxyScanner.swift; sourceTree = ""; }; C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationFile.swift; sourceTree = ""; }; C4D8016522B1584700C6DA1B /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = ""; }; C4D89BC52783C99400A02B68 /* ComposerJson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerJson.swift; sourceTree = ""; }; @@ -1363,6 +1373,7 @@ isa = PBXGroup; children = ( C4AF9F792754499000D44ED0 /* Valet.swift */, + C4D36600291132B7006BD146 /* ValetScanners.swift */, C40175B629030F7A00763A68 /* Domains */, C4C0E8D927F887BD002D32A9 /* Proxies */, C4C0E8D827F887A5002D32A9 /* Sites */, @@ -1499,6 +1510,7 @@ children = ( C4C0E8E627F88B41002D32A9 /* ProxyScanner.swift */, C484437A2804BB560041A78A /* ValetProxyScanner.swift */, + C4D366052911331E006BD146 /* EmptyProxyScanner.swift */, ); path = ProxyScanner; sourceTree = ""; @@ -1918,6 +1930,7 @@ C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */, C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */, C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */, + C4D366062911331E006BD146 /* EmptyProxyScanner.swift in Sources */, C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */, C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */, C4C0E8EA27F88B80002D32A9 /* ValetProxy+Fake.swift in Sources */, @@ -2025,6 +2038,7 @@ C4B6091D2853AB9700C95265 /* ServicesView.swift in Sources */, C40508B128ADAB44008FAC1F /* NSMenuItemExtension.swift in Sources */, C4B97B7B275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */, + C4D36601291132B7006BD146 /* ValetScanners.swift in Sources */, C4EED88927A48778006D7272 /* InterAppHandler.swift in Sources */, C40C7F1E2772136000DDDCDC /* PhpEnv.swift in Sources */, C476FF9822B0DD830098105B /* Alert.swift in Sources */, @@ -2082,6 +2096,7 @@ C471E83D28F9BB650021E251 /* FakeSiteScanner.swift in Sources */, C471E83F28F9BB650021E251 /* AppDelegate.swift in Sources */, C471E84028F9BB650021E251 /* AppDelegate+MenuOutlets.swift in Sources */, + C4D36603291132B7006BD146 /* ValetScanners.swift in Sources */, C471E84128F9BB650021E251 /* AppDelegate+Notifications.swift in Sources */, C471E84228F9BB650021E251 /* AppDelegate+InterApp.swift in Sources */, C471E84328F9BB650021E251 /* App.swift in Sources */, @@ -2106,6 +2121,7 @@ C471E85528F9BB650021E251 /* StatusMenu+Items.swift in Sources */, C471E85628F9BB650021E251 /* DomainListCellProtocol.swift in Sources */, C471E85728F9BB650021E251 /* DomainListTLSCell.swift in Sources */, + C4D366082911331E006BD146 /* EmptyProxyScanner.swift in Sources */, C471E85828F9BB650021E251 /* DomainListNameCell.swift in Sources */, C471E85928F9BB650021E251 /* DomainListPhpCell.swift in Sources */, C471E85A28F9BB650021E251 /* DomainListTypeCell.swift in Sources */, @@ -2228,6 +2244,7 @@ C471E89528F9BB8F0021E251 /* MenuBarImageGenerator.swift in Sources */, C471E89628F9BB8F0021E251 /* PMWindowController.swift in Sources */, C471E89728F9BB8F0021E251 /* VersionExtractor.swift in Sources */, + C4D366092911331E006BD146 /* EmptyProxyScanner.swift in Sources */, C4E2E86728FC2F1B003B070C /* XCPMApplication.swift in Sources */, C471E89828F9BB8F0021E251 /* ValetProxy.swift in Sources */, C471E89928F9BB8F0021E251 /* ValetProxy+Fake.swift in Sources */, @@ -2310,6 +2327,7 @@ C4E2E85F28FC282B003B070C /* TestableConfiguration.swift in Sources */, C471E8E928F9BB8F0021E251 /* StatsView.swift in Sources */, C471E8EA28F9BB8F0021E251 /* SectionHeaderView.swift in Sources */, + C4D36604291132B7006BD146 /* ValetScanners.swift in Sources */, C471E8EB28F9BB8F0021E251 /* HeaderView.swift in Sources */, C471E8EC28F9BB8F0021E251 /* SwiftUIHelper.swift in Sources */, C471E8EE28F9BB8F0021E251 /* HotKey.swift in Sources */, @@ -2413,6 +2431,7 @@ C485707A28BF457800539B36 /* WarningListView.swift in Sources */, C4C0E8E827F88B41002D32A9 /* ProxyScanner.swift in Sources */, C449B4F027EE7FB800C47E8A /* DomainListTLSCell.swift in Sources */, + C4D366072911331E006BD146 /* EmptyProxyScanner.swift in Sources */, C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */, C43A8A2425D9D20D00591B77 /* HomebrewPackageTest.swift in Sources */, C485707928BF456C00539B36 /* ArrayExtension.swift in Sources */, @@ -2532,6 +2551,7 @@ C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */, C4A81CA528C67101008DD9D1 /* PMTableView.swift in Sources */, C45E76152854A65300B4FE0C /* ServicesManager.swift in Sources */, + C4D36602291132B7006BD146 /* ValetScanners.swift in Sources */, C464ADAD275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */, C40C7F1F2772136000DDDCDC /* PhpEnv.swift in Sources */, C464ADB0275A7A6A003FCD53 /* DomainListVC.swift in Sources */, diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index ff88595..0b2dcd1 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -80,7 +80,7 @@ + isEnabled = "YES"> String { diff --git a/phpmon/Domain/App/AppDelegate.swift b/phpmon/Domain/App/AppDelegate.swift index 0716271..4a2d268 100644 --- a/phpmon/Domain/App/AppDelegate.swift +++ b/phpmon/Domain/App/AppDelegate.swift @@ -60,7 +60,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele #if DEBUG logger.verbosity = .performance - if let profile = CommandLine.arguments.first(where: { $0.matches(pattern: "--configuration:*") }) { Self.initializeTestingProfile(profile.replacingOccurrences(of: "--configuration:", with: "")) } @@ -89,9 +88,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele static func initializeTestingProfile(_ path: String) { Log.info("The configuration with path `\(path)` is being requested...") - TestableConfiguration - .loadFrom(path: path) - .apply() + TestableConfiguration.loadFrom(path: path).apply() } // MARK: - Lifecycle diff --git a/phpmon/Domain/Integrations/Valet/Proxies/ProxyScanner/EmptyProxyScanner.swift b/phpmon/Domain/Integrations/Valet/Proxies/ProxyScanner/EmptyProxyScanner.swift new file mode 100644 index 0000000..abfad2e --- /dev/null +++ b/phpmon/Domain/Integrations/Valet/Proxies/ProxyScanner/EmptyProxyScanner.swift @@ -0,0 +1,15 @@ +// +// EmptyProxyScanner.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 01/11/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class EmptyProxyScanner: ProxyScanner { + func resolveProxies(directoryPath: String) -> [ValetProxy] { + return [] + } +} diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite+Fake.swift b/phpmon/Domain/Integrations/Valet/Sites/ValetSite+Fake.swift index 5e511cf..5fc8074 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/ValetSite+Fake.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/ValetSite+Fake.swift @@ -35,6 +35,8 @@ class FakeValetSite: ValetSite { self.isolatedPhpVersion = PhpInstallation(isolated) } + // TODO: Resolve this at a later time + /* self.composerPhpCompatibleWithLinked = self.composerPhp.split(separator: "|") .map { string in let origin = self.isolatedPhpVersion?.versionNumber.short ?? PhpEnv.phpInstall.version.long @@ -42,5 +44,6 @@ class FakeValetSite: ValetSite { .matching(constraint: string.trimmingCharacters(in: .whitespacesAndNewlines)) .isEmpty }.contains(true) + */ } } diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index e0fb485..5b11982 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -41,23 +41,16 @@ class Valet { self.version = nil self.sites = [] self.proxies = [] + self.checkForMarketingMode() } - /** - If marketing mode is enabled, show a list of sites that are used for promotional screenshots. - This can be done by swapping out the real Valet scanner with one that always returns a fixed - list of fake sites. You should not interact with these sites! - */ - static var siteScanner: SiteScanner { + /// If marketing mode is enabled, you can tinker around with the site list + /// without actually modifying items on your local system. + public func checkForMarketingMode() { if ProcessInfo.processInfo.environment["PHPMON_MARKETING_MODE"] != nil { - return FakeSiteScanner() + Log.info("Using a fake list of sites for Marketing Mode!") + ValetScanners.useFake() } - - return ValetSiteScanner() - } - - static var proxyScanner: ProxyScanner { - return ValetProxyScanner() } /** @@ -198,7 +191,7 @@ class Valet { Returns a count of how many sites are linked and parked. */ private func countPaths() -> Int { - return Self.siteScanner + return ValetScanners.siteScanner .resolveSiteCount(paths: config.paths) } @@ -208,13 +201,13 @@ class Valet { private func resolvePaths() { isBusy = true - sites = Self.siteScanner + sites = ValetScanners.siteScanner .resolveSitesFrom(paths: config.paths) .sorted { $0.absolutePath < $1.absolutePath } - proxies = Self.proxyScanner + proxies = ValetScanners.proxyScanner .resolveProxies( directoryPath: FileManager.default .homeDirectoryForCurrentUser diff --git a/phpmon/Domain/Integrations/Valet/ValetScanners.swift b/phpmon/Domain/Integrations/Valet/ValetScanners.swift new file mode 100644 index 0000000..58b5e93 --- /dev/null +++ b/phpmon/Domain/Integrations/Valet/ValetScanners.swift @@ -0,0 +1,21 @@ +// +// Valet+Scanners.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 01/11/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class ValetScanners { + + static var siteScanner: SiteScanner = ValetSiteScanner() + static var proxyScanner: ProxyScanner = ValetProxyScanner() + + public static func useFake() { + ValetScanners.siteScanner = FakeSiteScanner() + ValetScanners.proxyScanner = EmptyProxyScanner() + } + +} From 8417d637fe48675dc15e8bd1c15fb7ba3a39d314 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 1 Nov 2022 13:47:16 +0100 Subject: [PATCH 078/181] =?UTF-8?q?=F0=9F=91=8C=20FileSystem=20changes,=20?= =?UTF-8?q?rework=20and=20testing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 22 ++++- .../xcschemes/PHP Monitor DEV.xcscheme | 2 +- .../Filesystem/FileSystemProtocol.swift | 40 +++++---- phpmon/Common/Filesystem/RealFileSystem.swift | 66 +++++++++++---- phpmon/Common/Helpers/System.swift | 18 ++++ phpmon/Common/PHP/PHP Version/PhpHelper.swift | 18 ++-- .../PHP/Switcher/InternalSwitcher.swift | 14 ++-- .../Common/Testables/TestableFileSystem.swift | 84 ++++++++++++++++++- phpmon/Domain/DomainList/AddSiteVC.swift | 2 +- .../Integrations/Composer/PhpFrameworks.swift | 2 +- phpmon/Domain/Integrations/Valet/Valet.swift | 5 +- phpmon/Domain/Watcher/PhpConfigWatcher.swift | 2 +- tests/Shared/TestableConfigurations.swift | 17 +++- tests/Shared/Utility.swift | 2 +- tests/unit/Commands/CommandTest.swift | 2 +- tests/unit/Parsers/HomebrewPackageTest.swift | 2 +- .../unit/Parsers/NginxConfigurationTest.swift | 2 +- tests/unit/Parsers/PhpExtensionTest.swift | 2 +- .../unit/Parsers/ValetConfigurationTest.swift | 2 +- .../Filesystem/TestableFileSystemTest.swift | 61 ++++++++++++++ .../Testables/Shell/SystemShellTest.swift | 2 +- ...hellTest.swift => TestableShellTest.swift} | 6 +- tests/unit/Versions/AppUpdaterCheckTest.swift | 2 +- .../Versions/PhpVersionDetectionTest.swift | 2 +- .../Versions/ValetVersionExtractorTest.swift | 2 +- .../unit/Versions/VersionExtractorTest.swift | 2 +- 26 files changed, 299 insertions(+), 82 deletions(-) create mode 100644 phpmon/Common/Helpers/System.swift create mode 100644 tests/unit/Testables/Filesystem/TestableFileSystemTest.swift rename tests/unit/Testables/Shell/{FakeShellTest.swift => TestableShellTest.swift} (92%) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 0373001..7571def 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -72,7 +72,7 @@ C40FE738282ABA4F00A302C2 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; C40FE73B282ABB2E00A302C2 /* AppVersionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE739282ABB2E00A302C2 /* AppVersionTest.swift */; }; C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; }; - C413E43528DA3EB100AE33C7 /* FakeShellTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C413E43428DA3EB100AE33C7 /* FakeShellTest.swift */; }; + C413E43528DA3EB100AE33C7 /* TestableShellTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C413E43428DA3EB100AE33C7 /* TestableShellTest.swift */; }; C415937F27A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; }; C415938027A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; }; C4159AF728E4D40400545349 /* SystemShellTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4159AF628E4D40400545349 /* SystemShellTest.swift */; }; @@ -569,6 +569,11 @@ C4D366072911331E006BD146 /* EmptyProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D366052911331E006BD146 /* EmptyProxyScanner.swift */; }; C4D366082911331E006BD146 /* EmptyProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D366052911331E006BD146 /* EmptyProxyScanner.swift */; }; C4D366092911331E006BD146 /* EmptyProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D366052911331E006BD146 /* EmptyProxyScanner.swift */; }; + C4D3660B29113F20006BD146 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3660A29113F20006BD146 /* System.swift */; }; + C4D3660C29113F20006BD146 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3660A29113F20006BD146 /* System.swift */; }; + C4D3660D29113F20006BD146 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3660A29113F20006BD146 /* System.swift */; }; + C4D3660E29113F20006BD146 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3660A29113F20006BD146 /* System.swift */; }; + C4D36611291140BE006BD146 /* TestableFileSystemTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3660F291140BE006BD146 /* TestableFileSystemTest.swift */; }; C4D5CFCA27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; }; C4D5CFCB27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; }; C4D8016622B1584700C6DA1B /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; }; @@ -725,7 +730,7 @@ C40FE736282ABA4F00A302C2 /* AppVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersion.swift; sourceTree = ""; }; C40FE739282ABB2E00A302C2 /* AppVersionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionTest.swift; sourceTree = ""; }; C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackage.swift; sourceTree = ""; }; - C413E43428DA3EB100AE33C7 /* FakeShellTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeShellTest.swift; sourceTree = ""; }; + C413E43428DA3EB100AE33C7 /* TestableShellTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableShellTest.swift; sourceTree = ""; }; C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpFrameworks.swift; sourceTree = ""; }; C4159AF628E4D40400545349 /* SystemShellTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemShellTest.swift; sourceTree = ""; }; C415D3B62770F294005EF286 /* Actions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = ""; }; @@ -851,6 +856,8 @@ C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerWindow.swift; sourceTree = ""; }; C4D36600291132B7006BD146 /* ValetScanners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetScanners.swift; sourceTree = ""; }; C4D366052911331E006BD146 /* EmptyProxyScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyProxyScanner.swift; sourceTree = ""; }; + C4D3660A29113F20006BD146 /* System.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = System.swift; sourceTree = ""; }; + C4D3660F291140BE006BD146 /* TestableFileSystemTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableFileSystemTest.swift; sourceTree = ""; }; C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationFile.swift; sourceTree = ""; }; C4D8016522B1584700C6DA1B /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = ""; }; C4D89BC52783C99400A02B68 /* ComposerJson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerJson.swift; sourceTree = ""; }; @@ -1069,7 +1076,7 @@ C413E43328DA3E8F00AE33C7 /* Shell */ = { isa = PBXGroup; children = ( - C413E43428DA3EB100AE33C7 /* FakeShellTest.swift */, + C413E43428DA3EB100AE33C7 /* TestableShellTest.swift */, C4159AF628E4D40400545349 /* SystemShellTest.swift */, ); path = Shell; @@ -1291,6 +1298,7 @@ C471E6DA28F9AFCB0021E251 /* Filesystem */ = { isa = PBXGroup; children = ( + C4D3660F291140BE006BD146 /* TestableFileSystemTest.swift */, ); path = Filesystem; sourceTree = ""; @@ -1355,6 +1363,7 @@ C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */, C4CCBA6B275C567B008C7055 /* PMWindowController.swift */, C4B5635D276AB09000F12CCB /* VersionExtractor.swift */, + C4D3660A29113F20006BD146 /* System.swift */, ); path = Helpers; sourceTree = ""; @@ -2038,6 +2047,7 @@ C4B6091D2853AB9700C95265 /* ServicesView.swift in Sources */, C40508B128ADAB44008FAC1F /* NSMenuItemExtension.swift in Sources */, C4B97B7B275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */, + C4D3660B29113F20006BD146 /* System.swift in Sources */, C4D36601291132B7006BD146 /* ValetScanners.swift in Sources */, C4EED88927A48778006D7272 /* InterAppHandler.swift in Sources */, C40C7F1E2772136000DDDCDC /* PhpEnv.swift in Sources */, @@ -2146,6 +2156,7 @@ C471E86A28F9BB650021E251 /* PrefsVC.swift in Sources */, C471E86B28F9BB650021E251 /* PreferenceName.swift in Sources */, C471E86C28F9BB650021E251 /* Preferences.swift in Sources */, + C4D3660D29113F20006BD146 /* System.swift in Sources */, C471E86D28F9BB650021E251 /* CustomPrefs.swift in Sources */, C4E2E84C28FC1E70003B070C /* DataExtension.swift in Sources */, C471E86E28F9BB650021E251 /* MenuBarIcons.swift in Sources */, @@ -2355,6 +2366,7 @@ C471E82128F9BB2E0021E251 /* PhpFrameworks.swift in Sources */, C471E7EF28F9BAC30021E251 /* Actions.swift in Sources */, C471E82228F9BB2E0021E251 /* ComposerWindow.swift in Sources */, + C4D3660E29113F20006BD146 /* System.swift in Sources */, C471E80428F9BAD40021E251 /* PhpExtension.swift in Sources */, C471E7F728F9BACB0021E251 /* PhpSwitcher.swift in Sources */, C471E82C28F9BB340021E251 /* DomainListable.swift in Sources */, @@ -2401,7 +2413,7 @@ C41CA5EE2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */, C4FACE81288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift in Sources */, 54D9E0B727E4F51E003B9AD9 /* HotKey.swift in Sources */, - C413E43528DA3EB100AE33C7 /* FakeShellTest.swift in Sources */, + C413E43528DA3EB100AE33C7 /* TestableShellTest.swift in Sources */, C4205A7F27F4D21800191A39 /* ValetProxy.swift in Sources */, C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */, C46EBC4528DB95F0007ACC74 /* ShellProtocol.swift in Sources */, @@ -2505,7 +2517,9 @@ C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */, C44CCD4127AFE2FC00CE40E5 /* AlertableError.swift in Sources */, C4CDA894288F1A71007CE25F /* Keys.swift in Sources */, + C4D3660C29113F20006BD146 /* System.swift in Sources */, C4D936CA27E3EB6100BD69FE /* PhpHelper.swift in Sources */, + C4D36611291140BE006BD146 /* TestableFileSystemTest.swift in Sources */, C4E2E84B28FC1E70003B070C /* DataExtension.swift in Sources */, C449B4F127EE7FC200C47E8A /* DomainListNameCell.swift in Sources */, C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */, diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index 0b2dcd1..ff88595 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -80,7 +80,7 @@ + isEnabled = "NO"> String + + // MARK: - Move & Delete Files + + func move(from path: String, to newPath: String) throws + + func remove(_ path: String) throws + + // MARK: — Attributes + + func makeExecutable(_ path: String) throws + + // MARK: - Checks + func isExecutableFile(_ path: String) -> Bool - /** - Checks if a file or directory exists at the provided path. - */ - func exists(_ path: String) -> Bool + func isWriteableFile(_ path: String) -> Bool + + func anyExists(_ path: String) -> Bool - /** - Checks if a file exists at the provided path. - */ func fileExists(_ path: String) -> Bool - /** - Checks if a directory exists at the provided path. - */ func directoryExists(_ path: String) -> Bool - /** - Checks if a given file is a symbolic link. - */ func fileIsSymlink(_ path: String) -> Bool } diff --git a/phpmon/Common/Filesystem/RealFileSystem.swift b/phpmon/Common/Filesystem/RealFileSystem.swift index 601f92b..6d6262f 100644 --- a/phpmon/Common/Filesystem/RealFileSystem.swift +++ b/phpmon/Common/Filesystem/RealFileSystem.swift @@ -15,27 +15,67 @@ extension String { } class RealFileSystem: FileSystemProtocol { - /** - Checks if a given path is a file *and* executable. - */ + + // MARK: - Basics + + func createDirectory(_ path: String, withIntermediateDirectories: Bool) { + try! FileManager.default.createDirectory( + atPath: path.replacingTildeWithHomeDirectory, + withIntermediateDirectories: withIntermediateDirectories + ) + } + + func writeAtomicallyToFile(_ path: String, content: String) throws { + try content.write( + to: URL(fileURLWithPath: path.replacingTildeWithHomeDirectory), + atomically: true, + encoding: String.Encoding.utf8 + ) + } + + func readStringFromFile(_ path: String) throws -> String { + return try String( + contentsOf: URL(fileURLWithPath: path.replacingTildeWithHomeDirectory), + encoding: .utf8 + ) + } + + // MARK: - Move & Delete Files + + func move(from path: String, to newPath: String) throws { + // TODO + } + + func remove(_ path: String) throws { + // TODO + } + + // MARK: — FS Attributes + + func makeExecutable(_ path: String) throws { + system("chmod +x \(path.replacingTildeWithHomeDirectory)") + } + + // MARK: - Checks + func isExecutableFile(_ path: String) -> Bool { return FileManager.default.isExecutableFile( atPath: path.replacingTildeWithHomeDirectory ) } - /** - Checks if a file or directory exists at the provided path. - */ - func exists(_ path: String) -> Bool { + func isWriteableFile(_ path: String) -> Bool { + return FileManager.default.isWritableFile( + atPath: path.replacingTildeWithHomeDirectory + ) + } + + func anyExists(_ path: String) -> Bool { return FileManager.default.fileExists( atPath: path.replacingTildeWithHomeDirectory ) } - /** - Checks if a file exists at the provided path. - */ func fileExists(_ path: String) -> Bool { var isDirectory: ObjCBool = true let exists = FileManager.default.fileExists( @@ -46,9 +86,6 @@ class RealFileSystem: FileSystemProtocol { return exists && !isDirectory.boolValue } - /** - Checks if a directory exists at the provided path. - */ func directoryExists(_ path: String) -> Bool { var isDirectory: ObjCBool = true let exists = FileManager.default.fileExists( @@ -59,9 +96,6 @@ class RealFileSystem: FileSystemProtocol { return exists && isDirectory.boolValue } - /** - Checks if a given file is a symbolic link. - */ func fileIsSymlink(_ path: String) -> Bool { do { let attribs = try FileManager.default.attributesOfItem(atPath: path) diff --git a/phpmon/Common/Helpers/System.swift b/phpmon/Common/Helpers/System.swift new file mode 100644 index 0000000..9b8d000 --- /dev/null +++ b/phpmon/Common/Helpers/System.swift @@ -0,0 +1,18 @@ +// +// System.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 01/11/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +public func system(_ command: String) { + let argsArray = command.split(separator: " ").map { String($0) } + guard argsArray.isEmpty else { return } + let command = strdup(argsArray.first!) + let args = argsArray.map { strdup($0) } + [nil] + posix_spawn(nil, command, nil, nil, args, nil) + return +} diff --git a/phpmon/Common/PHP/PHP Version/PhpHelper.swift b/phpmon/Common/PHP/PHP Version/PhpHelper.swift index 28a200b..4e1ce7b 100644 --- a/phpmon/Common/PHP/PHP Version/PhpHelper.swift +++ b/phpmon/Common/PHP/PHP Version/PhpHelper.swift @@ -23,12 +23,15 @@ class PhpHelper { let inPath = Shell.PATH.contains("\(Paths.homePath)/.config/phpmon/bin") // Check if we can create symlinks (`/usr/local/bin` must be writable) - let canWriteSymlinks = FileManager.default.isWritableFile(atPath: "/usr/local/bin/") + let canWriteSymlinks = FileSystem.isWriteableFile("/usr/local/bin/") Task { // Create the appropriate folders and check if the files exist do { if !FileSystem.directoryExists("~/.config/phpmon/bin") { - await Shell.quiet("mkdir -p ~/.config/phpmon/bin") + try FileSystem.createDirectory( + "~/.config/phpmon/bin", + withIntermediateDirectories: true + ) } if FileSystem.fileExists(destination) { @@ -56,17 +59,10 @@ class PhpHelper { export PATH=\(path):$PATH """ - // Write to the destination - // TODO: Use FileSystem abstraction - try script.write( - to: URL(fileURLWithPath: destination), - atomically: true, - encoding: String.Encoding.utf8 - ) + try FileSystem.writeAtomicallyToFile(destination, content: script) - // Make sure the file is executable if !FileSystem.isExecutableFile(destination) { - await Shell.quiet("chmod +x \(destination)") + try FileSystem.makeExecutable(destination) } // Create a symlink if the folder is not in the PATH diff --git a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift index 372c0cb..f6d9374 100644 --- a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift +++ b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift @@ -74,22 +74,22 @@ class InternalSwitcher: PhpSwitcher { func requiresDisablingOfDefaultPhpFpmPool(_ version: String) -> Bool { let pool = "\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf" - return FileManager.default.fileExists(atPath: pool) + return FileSystem.fileExists(pool) } func disableDefaultPhpFpmPool(_ version: String) async { let pool = "\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf" - if FileManager.default.fileExists(atPath: pool) { + if FileSystem.fileExists(pool) { Log.info("A default `www.conf` file was found in the php-fpm.d directory for PHP \(version).") - let existing = URL(string: "file://\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf")! - let new = URL(string: "file://\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf.disabled-by-phpmon")! + let existing = "\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf" + let new = "\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf.disabled-by-phpmon" do { - if FileManager.default.fileExists(atPath: new.path) { + if FileSystem.fileExists(new) { Log.info("A moved `www.conf.disabled-by-phpmon` file was found for PHP \(version), " + "cleaning up so the newer `www.conf` can be moved again.") - try FileManager.default.removeItem(at: new) + try FileSystem.remove(new) } - try FileManager.default.moveItem(at: existing, to: new) + try FileSystem.move(from: existing, to: new) Log.info("Success: A default `www.conf` file was disabled for PHP \(version).") } catch { Log.err(error) diff --git a/phpmon/Common/Testables/TestableFileSystem.swift b/phpmon/Common/Testables/TestableFileSystem.swift index ca6f41b..b3f425d 100644 --- a/phpmon/Common/Testables/TestableFileSystem.swift +++ b/phpmon/Common/Testables/TestableFileSystem.swift @@ -15,6 +15,54 @@ class TestableFileSystem: FileSystemProtocol { var files: [String: FakeFile] + // MARK: - Basics + + func createDirectory(_ path: String, withIntermediateDirectories: Bool) throws { + if files[path] != nil { + throw TestableFileSystemError.alreadyExists + } + + self.files[path] = .fake(.directory) + } + + func writeAtomicallyToFile(_ path: String, content: String) throws { + if files[path] != nil { + throw TestableFileSystemError.alreadyExists + } + + self.files[path] = .fake(.text, content) + } + + func readStringFromFile(_ path: String) throws -> String { + guard let file = files[path] else { + throw TestableFileSystemError.fileMissing + } + + return file.content ?? "" + } + + // MARK: - Move & Delete Files + + func move(from path: String, to newPath: String) throws { + // TODO + } + + func remove(_ path: String) throws { + // TODO + } + + // MARK: — Attributes + + func makeExecutable(_ path: String) throws { + guard let file = files[path] else { + throw TestableFileSystemError.fileMissing + } + + file.type = .binary + } + + // MARK: - Checks + func isExecutableFile(_ path: String) -> Bool { guard let file = files[path] else { return false @@ -23,7 +71,15 @@ class TestableFileSystem: FileSystemProtocol { return file.type == .binary } - func exists(_ path: String) -> Bool { + func isWriteableFile(_ path: String) -> Bool { + guard let file = files[path] else { + return false + } + + return !file.readOnly + } + + func anyExists(_ path: String) -> Bool { return files.keys.contains(path) } @@ -56,11 +112,31 @@ enum FakeFileType: Codable { case binary, text, directory, symlink } -struct FakeFile: Codable { +class FakeFile: Codable { var type: FakeFileType var content: String? + var readOnly: Bool = false - public static func fake(_ type: FakeFileType, _ content: String? = nil) -> FakeFile { - return FakeFile(type: type, content: content) + init(type: FakeFileType, content: String?, readOnly: Bool = false) { + self.type = type + self.content = content + self.readOnly = readOnly + } + + public static func fake( + _ type: FakeFileType, + _ content: String? = nil, + readOnly: Bool = false + ) -> FakeFile { + return FakeFile( + type: type, + content: content, + readOnly: readOnly + ) } } + +enum TestableFileSystemError: Error { + case fileMissing + case alreadyExists +} diff --git a/phpmon/Domain/DomainList/AddSiteVC.swift b/phpmon/Domain/DomainList/AddSiteVC.swift index a427613..17fd285 100644 --- a/phpmon/Domain/DomainList/AddSiteVC.swift +++ b/phpmon/Domain/DomainList/AddSiteVC.swift @@ -55,7 +55,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate { let path = pathControl.url!.path let name = inputDomainName.stringValue - if !FileSystem.exists(path) { + if !FileSystem.anyExists(path) { Alert.confirm( onWindow: view.window!, messageText: "domain_list.alert.folder_missing.title".localized, diff --git a/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift b/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift index 2de9176..dd66bdc 100644 --- a/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift +++ b/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift @@ -71,7 +71,7 @@ struct PhpFrameworks { public static func detectFallbackDependency(_ basePath: String) -> String? { for entry in Self.FileMapping { let found = entry.value - .map { path in return FileSystem.exists(basePath + path) } + .map { path in return FileSystem.anyExists(basePath + path) } .contains(true) if found { diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 5b11982..2bdc6fe 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -98,13 +98,10 @@ class Valet { If the JSON is invalid when the app launches, an alert will be presented, however. */ public func loadConfiguration() { - let file = FileManager.default.homeDirectoryForCurrentUser - .appendingPathComponent(".config/valet/config.json") - do { config = try JSONDecoder().decode( Valet.Configuration.self, - from: try String(contentsOf: file, encoding: .utf8).data(using: .utf8)! + from: FileSystem.readStringFromFile("~/.config/valet/config.json").data(using: .utf8)! ) } catch { Log.err(error) diff --git a/phpmon/Domain/Watcher/PhpConfigWatcher.swift b/phpmon/Domain/Watcher/PhpConfigWatcher.swift index 37d006b..fc00cc3 100644 --- a/phpmon/Domain/Watcher/PhpConfigWatcher.swift +++ b/phpmon/Domain/Watcher/PhpConfigWatcher.swift @@ -51,7 +51,7 @@ class PhpConfigWatcher { eventMask: DispatchSource.FileSystemEvent, behaviour: FSWatcherBehaviour = .reloadsMenu ) { - if !FileSystem.exists(url.path) { + if !FileSystem.anyExists(url.path) { Log.warn("No watcher was created for \(url.path) because the requested file does not exist.") return } diff --git a/tests/Shared/TestableConfigurations.swift b/tests/Shared/TestableConfigurations.swift index 05f00e4..28374d1 100644 --- a/tests/Shared/TestableConfigurations.swift +++ b/tests/Shared/TestableConfigurations.swift @@ -14,6 +14,8 @@ class TestableConfigurations { return TestableConfiguration( architecture: "arm64", filesystem: [ + "/usr/local/bin/" + : .fake(.directory, readOnly: true), "/opt/homebrew/bin/brew" : .fake(.binary), "/opt/homebrew/bin/php" @@ -30,10 +32,21 @@ class TestableConfigurations { : .fake(.binary), "/opt/homebrew/Cellar/php/8.1.10_1/bin/php-config" : .fake(.binary), - "~/.config/valet" + "/Users/user/.config/valet" : .fake(.directory), + "/Users/user/.config/valet/config.json" + : .fake(.text, """ + { + "tld": "test", + "paths": [ + "/Users/user/.config/valet/Sites", + "/Users/user/Sites" + ], + "loopback": "127.0.0.1" + } + """), "/opt/homebrew/etc/php/8.1/php-fpm.d/valet-fpm.conf" - : .fake(.text) + : .fake(.text), ], shellOutput: [ "sysctl -n sysctl.proc_translated" diff --git a/tests/Shared/Utility.swift b/tests/Shared/Utility.swift index abf2b78..3caa4ec 100644 --- a/tests/Shared/Utility.swift +++ b/tests/Shared/Utility.swift @@ -1,6 +1,6 @@ // // Utility.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 14/02/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Commands/CommandTest.swift b/tests/unit/Commands/CommandTest.swift index d5ea2b6..1241bf2 100644 --- a/tests/unit/Commands/CommandTest.swift +++ b/tests/unit/Commands/CommandTest.swift @@ -1,6 +1,6 @@ // // CommandTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 13/02/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Parsers/HomebrewPackageTest.swift b/tests/unit/Parsers/HomebrewPackageTest.swift index f6a30f7..81c9718 100644 --- a/tests/unit/Parsers/HomebrewPackageTest.swift +++ b/tests/unit/Parsers/HomebrewPackageTest.swift @@ -1,6 +1,6 @@ // // BrewJsonParserTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 14/02/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Parsers/NginxConfigurationTest.swift b/tests/unit/Parsers/NginxConfigurationTest.swift index 07fa2a3..b09175c 100644 --- a/tests/unit/Parsers/NginxConfigurationTest.swift +++ b/tests/unit/Parsers/NginxConfigurationTest.swift @@ -1,6 +1,6 @@ // // NginxConfigurationTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 29/11/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Parsers/PhpExtensionTest.swift b/tests/unit/Parsers/PhpExtensionTest.swift index 0bbebc2..2647559 100644 --- a/tests/unit/Parsers/PhpExtensionTest.swift +++ b/tests/unit/Parsers/PhpExtensionTest.swift @@ -1,6 +1,6 @@ // // ExtensionParserTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 13/02/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Parsers/ValetConfigurationTest.swift b/tests/unit/Parsers/ValetConfigurationTest.swift index e640177..5b57fe9 100644 --- a/tests/unit/Parsers/ValetConfigurationTest.swift +++ b/tests/unit/Parsers/ValetConfigurationTest.swift @@ -1,6 +1,6 @@ // // ValetConfigParserTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 29/11/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift b/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift new file mode 100644 index 0000000..1329933 --- /dev/null +++ b/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift @@ -0,0 +1,61 @@ +// +// TestableFileSystemTest.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 01/11/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import XCTest + +class TestableFileSystemTest: XCTestCase { + + override class func setUp() { + ActiveFileSystem.useTestable([ + "/home/user/bin": .fake(.directory), + "/home/user/bin/foo": .fake(.binary), + "/home/user/documents": .fake(.directory), + "/home/user/docs": .fake(.symlink, "/home/user/documents"), + "/home/user/documents/nice.txt": .fake(.text, "69"), + "/home/user/documents/script.sh": .fake(.text, "echo 'cool';") + ]) + } + + func test_testable_fs_is_in_use() { + XCTAssertTrue(FileSystem is TestableFileSystem) + } + + func test_binary_directory_exists() { + XCTAssertTrue(FileSystem.directoryExists("/home/user/bin")) + } + + func test_binary_directory_is_writable() { + XCTAssertTrue(FileSystem.isWriteableFile("/home/user/bin")) + } + + func test_binary_exists() { + XCTAssertTrue(FileSystem.isExecutableFile("/home/user/bin/foo")) + } + + func test_can_write_text_to_executable() throws { + try! FileSystem.writeAtomicallyToFile("/home/user/bin/bar", content: "bar bar bar!") + + XCTAssertFalse(FileSystem.isExecutableFile("/home/user/bin/bar")) + + try! FileSystem.makeExecutable("/home/user/bin/bar") + + XCTAssertTrue(FileSystem.isExecutableFile("/home/user/bin/bar")) + } + + func test_can_create_directory() throws { + try! FileSystem.createDirectory( + "/home/nico/phpmon/config", + withIntermediateDirectories: true + ) + + XCTAssertTrue(FileSystem.anyExists("/home/nico/phpmon/config")) + XCTAssertTrue(FileSystem.directoryExists("/home/nico/phpmon/config")) + } + + // TODO: Implement and test the remove() and move() methods and reorganize method order +} diff --git a/tests/unit/Testables/Shell/SystemShellTest.swift b/tests/unit/Testables/Shell/SystemShellTest.swift index 7771285..467fc9a 100644 --- a/tests/unit/Testables/Shell/SystemShellTest.swift +++ b/tests/unit/Testables/Shell/SystemShellTest.swift @@ -1,6 +1,6 @@ // // SystemShellTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 28/09/2022. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Testables/Shell/FakeShellTest.swift b/tests/unit/Testables/Shell/TestableShellTest.swift similarity index 92% rename from tests/unit/Testables/Shell/FakeShellTest.swift rename to tests/unit/Testables/Shell/TestableShellTest.swift index 16cfc34..d2a819a 100644 --- a/tests/unit/Testables/Shell/FakeShellTest.swift +++ b/tests/unit/Testables/Shell/TestableShellTest.swift @@ -1,6 +1,6 @@ // -// ShellTest.swift -// phpmon-tests +// TestableShellTest.swift +// PHP Monitor // // Created by Nico Verbruggen on 20/09/2022. // Copyright © 2022 Nico Verbruggen. All rights reserved. @@ -8,7 +8,7 @@ import XCTest -class FakeShellTest: XCTestCase { +class TestableShellTest: XCTestCase { func test_fake_shell_output_can_be_declared() async { let greeting = BatchFakeShellOutput(items: [ .instant("Hello world\n"), diff --git a/tests/unit/Versions/AppUpdaterCheckTest.swift b/tests/unit/Versions/AppUpdaterCheckTest.swift index 11215fd..c70d5a2 100644 --- a/tests/unit/Versions/AppUpdaterCheckTest.swift +++ b/tests/unit/Versions/AppUpdaterCheckTest.swift @@ -1,6 +1,6 @@ // // AppUpdaterCheckTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 10/05/2022. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Versions/PhpVersionDetectionTest.swift b/tests/unit/Versions/PhpVersionDetectionTest.swift index 64541b0..d9f636e 100644 --- a/tests/unit/Versions/PhpVersionDetectionTest.swift +++ b/tests/unit/Versions/PhpVersionDetectionTest.swift @@ -1,6 +1,6 @@ // // PhpVersionDetectionTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 01/04/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Versions/ValetVersionExtractorTest.swift b/tests/unit/Versions/ValetVersionExtractorTest.swift index e1499f3..9e7673e 100644 --- a/tests/unit/Versions/ValetVersionExtractorTest.swift +++ b/tests/unit/Versions/ValetVersionExtractorTest.swift @@ -1,6 +1,6 @@ // // ValetTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 29/11/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Versions/VersionExtractorTest.swift b/tests/unit/Versions/VersionExtractorTest.swift index 3501af1..6b44657 100644 --- a/tests/unit/Versions/VersionExtractorTest.swift +++ b/tests/unit/Versions/VersionExtractorTest.swift @@ -1,6 +1,6 @@ // // VersionExtractorTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 16/12/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved. From fa2d2105c5de91e6eff4f629671c60bf478417af Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 1 Nov 2022 14:11:34 +0100 Subject: [PATCH 079/181] =?UTF-8?q?=F0=9F=91=8C=20Removed=20remaining=20`F?= =?UTF-8?q?ileManager.default`=20usage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Filesystem/FileSystemProtocol.swift | 10 ++++- phpmon/Common/Filesystem/RealFileSystem.swift | 21 +++++++++- phpmon/Common/PHP/PHP Version/PhpHelper.swift | 2 +- .../Common/Testables/TestableFileSystem.swift | 38 ++++++++++++++++++- phpmon/Domain/App/Startup.swift | 4 +- .../Sites/SiteScanner/ValetSiteScanner.swift | 29 ++++---------- .../Integrations/Valet/Sites/ValetSite.swift | 2 +- phpmon/Domain/Integrations/Valet/Valet.swift | 7 +--- phpmon/Domain/Warnings/WarningManager.swift | 2 +- phpmon/Domain/Watcher/PhpConfigWatcher.swift | 4 ++ 10 files changed, 82 insertions(+), 37 deletions(-) diff --git a/phpmon/Common/Filesystem/FileSystemProtocol.swift b/phpmon/Common/Filesystem/FileSystemProtocol.swift index e1e0fb5..f0892c7 100644 --- a/phpmon/Common/Filesystem/FileSystemProtocol.swift +++ b/phpmon/Common/Filesystem/FileSystemProtocol.swift @@ -16,7 +16,11 @@ protocol FileSystemProtocol { func writeAtomicallyToFile(_ path: String, content: String) throws - func readStringFromFile(_ path: String) throws -> String + func getStringFromFile(_ path: String) throws -> String + + func getContentsOfDirectory(_ path: String) throws -> [String] + + func getDestinationOfSymlink(_ path: String) throws -> String // MARK: - Move & Delete Files @@ -40,5 +44,7 @@ protocol FileSystemProtocol { func directoryExists(_ path: String) -> Bool - func fileIsSymlink(_ path: String) -> Bool + func isSymlink(_ path: String) -> Bool + + func isDirectory(_ path: String) -> Bool } diff --git a/phpmon/Common/Filesystem/RealFileSystem.swift b/phpmon/Common/Filesystem/RealFileSystem.swift index 6d6262f..f54c861 100644 --- a/phpmon/Common/Filesystem/RealFileSystem.swift +++ b/phpmon/Common/Filesystem/RealFileSystem.swift @@ -33,13 +33,21 @@ class RealFileSystem: FileSystemProtocol { ) } - func readStringFromFile(_ path: String) throws -> String { + func getStringFromFile(_ path: String) throws -> String { return try String( contentsOf: URL(fileURLWithPath: path.replacingTildeWithHomeDirectory), encoding: .utf8 ) } + func getContentsOfDirectory(_ path: String) throws -> [String] { + // TODO + } + + func getDestinationOfSymlink(_ path: String) throws -> String { + // TODO + } + // MARK: - Move & Delete Files func move(from path: String, to newPath: String) throws { @@ -96,7 +104,7 @@ class RealFileSystem: FileSystemProtocol { return exists && isDirectory.boolValue } - func fileIsSymlink(_ path: String) -> Bool { + func isSymlink(_ path: String) -> Bool { do { let attribs = try FileManager.default.attributesOfItem(atPath: path) return attribs[.type] as! FileAttributeType == FileAttributeType.typeSymbolicLink @@ -104,4 +112,13 @@ class RealFileSystem: FileSystemProtocol { return false } } + + func isDirectory(_ path: String) -> Bool { + do { + let attribs = try FileManager.default.attributesOfItem(atPath: path) + return attribs[.type] as! FileAttributeType == FileAttributeType.typeDirectory + } catch { + return false + } + } } diff --git a/phpmon/Common/PHP/PHP Version/PhpHelper.swift b/phpmon/Common/PHP/PHP Version/PhpHelper.swift index 4e1ce7b..1ed99e1 100644 --- a/phpmon/Common/PHP/PHP Version/PhpHelper.swift +++ b/phpmon/Common/PHP/PHP Version/PhpHelper.swift @@ -93,7 +93,7 @@ class PhpHelper { return } - if !FileSystem.fileIsSymlink(destination) { + if !FileSystem.isSymlink(destination) { Log.info("Overwriting existing file with new symlink: \(destination)") await Shell.quiet("ln -fs \(source) \(destination)") return diff --git a/phpmon/Common/Testables/TestableFileSystem.swift b/phpmon/Common/Testables/TestableFileSystem.swift index b3f425d..4f2b1b9 100644 --- a/phpmon/Common/Testables/TestableFileSystem.swift +++ b/phpmon/Common/Testables/TestableFileSystem.swift @@ -33,7 +33,7 @@ class TestableFileSystem: FileSystemProtocol { self.files[path] = .fake(.text, content) } - func readStringFromFile(_ path: String) throws -> String { + func getStringFromFile(_ path: String) throws -> String { guard let file = files[path] else { throw TestableFileSystemError.fileMissing } @@ -41,6 +41,30 @@ class TestableFileSystem: FileSystemProtocol { return file.content ?? "" } + func getContentsOfDirectory(_ path: String) throws -> [String] { + // TODO + } + + func getDestinationOfSymlink(_ path: String) throws -> String { + guard let file = files[path] else { + throw TestableFileSystemError.fileMissing + } + + if file.type != .symlink { + throw TestableFileSystemError.notSymlink + } + + guard let pathToSymlink = file.content else { + throw TestableFileSystemError.invalidSymlink + } + + if !files.keys.contains(pathToSymlink) { + throw TestableFileSystemError.invalidSymlink + } + + return pathToSymlink + } + // MARK: - Move & Delete Files func move(from path: String, to newPath: String) throws { @@ -99,13 +123,21 @@ class TestableFileSystem: FileSystemProtocol { return [.directory].contains(file.type) } - func fileIsSymlink(_ path: String) -> Bool { + func isSymlink(_ path: String) -> Bool { guard let file = files[path] else { return false } return file.type == .symlink } + + func isDirectory(_ path: String) -> Bool { + guard let file = files[path] else { + return false + } + + return file.type == .directory + } } enum FakeFileType: Codable { @@ -139,4 +171,6 @@ class FakeFile: Codable { enum TestableFileSystemError: Error { case fileMissing case alreadyExists + case notSymlink + case invalidSymlink } diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index 428ad26..be840db 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -86,7 +86,7 @@ class Startup { // The Homebrew binary must exist. // ================================================================================= EnvironmentCheck( - command: { return !FileManager.default.fileExists(atPath: Paths.brew) }, + command: { return !FileSystem.fileExists(Paths.brew) }, name: "`\(Paths.brew)` exists", titleText: "alert.homebrew_missing.title".localized, subtitleText: "alert.homebrew_missing.subtitle".localized, @@ -205,7 +205,7 @@ class Startup { command: { let nodePath = await Shell.pipe("which node").out return App.architecture == "x86_64" - && FileManager.default.fileExists(atPath: "/usr/local/bin/which") + && FileSystem.fileExists("/usr/local/bin/which") && nodePath.contains("env: node: No such file or directory") }, name: "`env: node` issue does not apply", diff --git a/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/ValetSiteScanner.swift b/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/ValetSiteScanner.swift index 82f27e3..801f89d 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/ValetSiteScanner.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/ValetSiteScanner.swift @@ -12,8 +12,8 @@ class ValetSiteScanner: SiteScanner { func resolveSiteCount(paths: [String]) -> Int { return paths.map { path in - let entries = try! FileManager.default - .contentsOfDirectory(atPath: path) + let entries = try! FileSystem + .getContentsOfDirectory(path) return entries .map { self.isSite($0, forPath: path) } @@ -27,8 +27,8 @@ class ValetSiteScanner: SiteScanner { var sites: [ValetSite] = [] paths.forEach { path in - let entries = try! FileManager.default - .contentsOfDirectory(atPath: path) + let entries = try! FileSystem + .getContentsOfDirectory(path) return entries.forEach { if let site = self.resolveSite(path: "\(path)/\($0)") { @@ -48,24 +48,19 @@ class ValetSiteScanner: SiteScanner { // Get the TLD from the global Valet object let tld = Valet.shared.config.tld - // See if the file is a symlink, if so, resolve it - guard let attrs = try? FileManager.default.attributesOfItem(atPath: path) else { + if !FileSystem.anyExists(path) { Log.warn("Could not parse the site: \(path), skipping!") - return nil } - // We can also determine whether the thing at the path is a directory, too - let type = attrs[FileAttributeKey.type] as! FileAttributeType - // We should also check that we can interpret the path correctly if URL(fileURLWithPath: path).lastPathComponent == "" { Log.warn("Could not parse the site: \(path), skipping!") return nil } - if type == FileAttributeType.typeSymbolicLink { + if FileSystem.isSymlink(path) { return ValetSite(aliasPath: path, tld: tld) - } else if type == FileAttributeType.typeDirectory { + } else if FileSystem.isDirectory(path) { return ValetSite(absolutePath: path, tld: tld) } @@ -79,14 +74,6 @@ class ValetSiteScanner: SiteScanner { private func isSite(_ entry: String, forPath path: String) -> Bool { let siteDir = path + "/" + entry - let attrs = try! FileManager.default.attributesOfItem(atPath: siteDir) - - let type = attrs[FileAttributeKey.type] as! FileAttributeType - - if type == FileAttributeType.typeSymbolicLink || type == FileAttributeType.typeDirectory { - return true - } - - return false + return (FileSystem.isDirectory(siteDir) || FileSystem.isSymlink(siteDir)) } } diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift index 33212f0..7502996 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift @@ -94,7 +94,7 @@ class ValetSite: DomainListable { convenience init(aliasPath: String, tld: String) { let name = URL(fileURLWithPath: aliasPath).lastPathComponent - let absolutePath = try! FileManager.default.destinationOfSymbolicLink(atPath: aliasPath) + let absolutePath = try! FileSystem.getDestinationOfSymlink(aliasPath) self.init(name: name, tld: tld, absolutePath: absolutePath, aliasPath: aliasPath) } diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 2bdc6fe..9a44bbe 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -101,7 +101,7 @@ class Valet { do { config = try JSONDecoder().decode( Valet.Configuration.self, - from: FileSystem.readStringFromFile("~/.config/valet/config.json").data(using: .utf8)! + from: FileSystem.getStringFromFile("~/.config/valet/config.json").data(using: .utf8)! ) } catch { Log.err(error) @@ -206,10 +206,7 @@ class Valet { proxies = ValetScanners.proxyScanner .resolveProxies( - directoryPath: FileManager.default - .homeDirectoryForCurrentUser - .appendingPathComponent(".config/valet/Nginx") - .path + directoryPath: "~/.config/valet/Nginx".replacingTildeWithHomeDirectory ) if let defaultPath = Valet.shared.config.defaultSite, diff --git a/phpmon/Domain/Warnings/WarningManager.swift b/phpmon/Domain/Warnings/WarningManager.swift index 516e0e7..ff2c3d5 100644 --- a/phpmon/Domain/Warnings/WarningManager.swift +++ b/phpmon/Domain/Warnings/WarningManager.swift @@ -33,7 +33,7 @@ class WarningManager { Warning( command: { return !Shell.PATH.contains("\(Paths.homePath)/.config/phpmon/bin") && - !FileManager.default.isWritableFile(atPath: "/usr/local/bin/") + !FileSystem.isWriteableFile("/usr/local/bin/") }, name: "Helpers cannot be symlinked and not in PATH", title: "warnings.helper_permissions.title", diff --git a/phpmon/Domain/Watcher/PhpConfigWatcher.swift b/phpmon/Domain/Watcher/PhpConfigWatcher.swift index fc00cc3..62ce21a 100644 --- a/phpmon/Domain/Watcher/PhpConfigWatcher.swift +++ b/phpmon/Domain/Watcher/PhpConfigWatcher.swift @@ -21,6 +21,10 @@ class PhpConfigWatcher { var watchers: [FSWatcher] = [] init(for url: URL) { + if FileSystem is TestableFileSystem { + fatalError("PhpConfigWatcher is not compatible with testable FS! You are not allowed to instantiate these while using a testable FS.") + } + self.url = url // Add a watcher for php.ini From 5caca85d7a72d7bd7c2401fec9f82df4c932b9a1 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 1 Nov 2022 16:47:45 +0100 Subject: [PATCH 080/181] =?UTF-8?q?=F0=9F=91=8C=20Fake=20FS:=20~=20and=20i?= =?UTF-8?q?ntermediate=20directories?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 20 +++++ .../xcschemes/PHP Monitor DEV.xcscheme | 2 +- phpmon/Common/Core/Paths.swift | 10 ++- .../Extensions/DictionaryExtension.swift | 17 ++++ .../Common/Filesystem/ActiveFileSystem.swift | 1 + .../Filesystem/FileSystemProtocol.swift | 2 +- phpmon/Common/Filesystem/RealFileSystem.swift | 8 +- phpmon/Common/Helpers/WIP.swift | 17 ++++ .../Testables/TestableConfiguration.swift | 1 - .../Common/Testables/TestableFileSystem.swift | 87 ++++++++++++++++++- .../Sites/SiteScanner/ValetSiteScanner.swift | 4 +- tests/Shared/TestableConfigurations.swift | 6 +- .../Filesystem/TestableFileSystemTest.swift | 42 +++++++-- 13 files changed, 192 insertions(+), 25 deletions(-) create mode 100644 phpmon/Common/Extensions/DictionaryExtension.swift create mode 100644 phpmon/Common/Helpers/WIP.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 7571def..961c03f 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -574,6 +574,14 @@ C4D3660D29113F20006BD146 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3660A29113F20006BD146 /* System.swift */; }; C4D3660E29113F20006BD146 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3660A29113F20006BD146 /* System.swift */; }; C4D36611291140BE006BD146 /* TestableFileSystemTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3660F291140BE006BD146 /* TestableFileSystemTest.swift */; }; + C4D36615291160A1006BD146 /* WIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36614291160A1006BD146 /* WIP.swift */; }; + C4D36616291160A1006BD146 /* WIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36614291160A1006BD146 /* WIP.swift */; }; + C4D36617291160A1006BD146 /* WIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36614291160A1006BD146 /* WIP.swift */; }; + C4D36618291160A1006BD146 /* WIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36614291160A1006BD146 /* WIP.swift */; }; + C4D3661A291173EA006BD146 /* DictionaryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36619291173EA006BD146 /* DictionaryExtension.swift */; }; + C4D3661B291173EA006BD146 /* DictionaryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36619291173EA006BD146 /* DictionaryExtension.swift */; }; + C4D3661C291173EA006BD146 /* DictionaryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36619291173EA006BD146 /* DictionaryExtension.swift */; }; + C4D3661D291173EA006BD146 /* DictionaryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36619291173EA006BD146 /* DictionaryExtension.swift */; }; C4D5CFCA27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; }; C4D5CFCB27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; }; C4D8016622B1584700C6DA1B /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; }; @@ -858,6 +866,8 @@ C4D366052911331E006BD146 /* EmptyProxyScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyProxyScanner.swift; sourceTree = ""; }; C4D3660A29113F20006BD146 /* System.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = System.swift; sourceTree = ""; }; C4D3660F291140BE006BD146 /* TestableFileSystemTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableFileSystemTest.swift; sourceTree = ""; }; + C4D36614291160A1006BD146 /* WIP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WIP.swift; sourceTree = ""; }; + C4D36619291173EA006BD146 /* DictionaryExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictionaryExtension.swift; sourceTree = ""; }; C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationFile.swift; sourceTree = ""; }; C4D8016522B1584700C6DA1B /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = ""; }; C4D89BC52783C99400A02B68 /* ComposerJson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerJson.swift; sourceTree = ""; }; @@ -1364,6 +1374,7 @@ C4CCBA6B275C567B008C7055 /* PMWindowController.swift */, C4B5635D276AB09000F12CCB /* VersionExtractor.swift */, C4D3660A29113F20006BD146 /* System.swift */, + C4D36614291160A1006BD146 /* WIP.swift */, ); path = Helpers; sourceTree = ""; @@ -1704,6 +1715,7 @@ C4EB53E628553117006F9937 /* ArrayExtension.swift */, C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */, C4E2E84928FC1E70003B070C /* DataExtension.swift */, + C4D36619291173EA006BD146 /* DictionaryExtension.swift */, ); path = Extensions; sourceTree = ""; @@ -1932,6 +1944,7 @@ files = ( C47699EF28A2F2A30060FEB8 /* WarningManager.swift in Sources */, C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */, + C4D3661A291173EA006BD146 /* DictionaryExtension.swift in Sources */, C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */, C4D8016622B1584700C6DA1B /* Startup.swift in Sources */, C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */, @@ -2054,6 +2067,7 @@ C476FF9822B0DD830098105B /* Alert.swift in Sources */, C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */, C4D5CFCA27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */, + C4D36615291160A1006BD146 /* WIP.swift in Sources */, C485707028BF452300539B36 /* WarningsWindowController.swift in Sources */, C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */, C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */, @@ -2130,6 +2144,7 @@ C471E85428F9BB650021E251 /* StatusMenu.swift in Sources */, C471E85528F9BB650021E251 /* StatusMenu+Items.swift in Sources */, C471E85628F9BB650021E251 /* DomainListCellProtocol.swift in Sources */, + C4D36617291160A1006BD146 /* WIP.swift in Sources */, C471E85728F9BB650021E251 /* DomainListTLSCell.swift in Sources */, C4D366082911331E006BD146 /* EmptyProxyScanner.swift in Sources */, C471E85828F9BB650021E251 /* DomainListNameCell.swift in Sources */, @@ -2237,6 +2252,7 @@ C471E7D628F9BA8F0021E251 /* RealFileSystem.swift in Sources */, C471E81728F9BAE80021E251 /* NSMenuExtension.swift in Sources */, C471E81328F9BAE80021E251 /* XibLoadable.swift in Sources */, + C4D3661C291173EA006BD146 /* DictionaryExtension.swift in Sources */, C471E7F128F9BAC70021E251 /* PhpVersionNumber.swift in Sources */, C471E7DC28F9BA8F0021E251 /* ShellProtocol.swift in Sources */, ); @@ -2300,6 +2316,7 @@ C471E8C028F9BB8F0021E251 /* DomainListVC.swift in Sources */, C471E8C128F9BB8F0021E251 /* DomainListVC+ContextMenu.swift in Sources */, C471E8C228F9BB8F0021E251 /* DomainListVC+Actions.swift in Sources */, + C4D36618291160A1006BD146 /* WIP.swift in Sources */, C471E8C328F9BB8F0021E251 /* SelectionVC.swift in Sources */, C471E8C428F9BB8F0021E251 /* AddSiteVC.swift in Sources */, C471E8C528F9BB8F0021E251 /* AddProxyVC.swift in Sources */, @@ -2392,6 +2409,7 @@ C4181F1128FAF9330042EA28 /* UITestCase.swift in Sources */, C471E81F28F9BB290021E251 /* NginxConfigurationFile.swift in Sources */, C471E7BF28F9B90F0021E251 /* StartupTest.swift in Sources */, + C4D3661D291173EA006BD146 /* DictionaryExtension.swift in Sources */, C471E80D28F9BAE80021E251 /* ArrayExtension.swift in Sources */, C471E7CD28F9BA600021E251 /* ShellProtocol.swift in Sources */, C471E7EC28F9BAC30021E251 /* Events.swift in Sources */, @@ -2424,6 +2442,7 @@ C415D3B82770F294005EF286 /* Actions.swift in Sources */, 54B48B60275F66AE006D90C5 /* Application.swift in Sources */, C4FE011228084FC200D1DE6D /* SelectionVC.swift in Sources */, + C4D3661B291173EA006BD146 /* DictionaryExtension.swift in Sources */, C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */, C493084B279F331F009C240B /* AddSiteVC.swift in Sources */, C44A874928905BB000498BC4 /* ProgressVC.swift in Sources */, @@ -2507,6 +2526,7 @@ 5489625928313231004F647A /* CreatedFromFile.swift in Sources */, C471E79328F9B21F0021E251 /* ActiveFileSystem.swift in Sources */, 54D9E0B327E4F51E003B9AD9 /* HotKeysController.swift in Sources */, + C4D36616291160A1006BD146 /* WIP.swift in Sources */, 03E36FE828D9219000636F7F /* ActiveShell.swift in Sources */, C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */, C4E2E86528FC2F1B003B070C /* XCPMApplication.swift in Sources */, diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index ff88595..0b2dcd1 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -80,7 +80,7 @@ + isEnabled = "YES"> String - func getContentsOfDirectory(_ path: String) throws -> [String] + func getShallowContentsOfDirectory(_ path: String) throws -> [String] func getDestinationOfSymlink(_ path: String) throws -> String diff --git a/phpmon/Common/Filesystem/RealFileSystem.swift b/phpmon/Common/Filesystem/RealFileSystem.swift index f54c861..e3ed7cf 100644 --- a/phpmon/Common/Filesystem/RealFileSystem.swift +++ b/phpmon/Common/Filesystem/RealFileSystem.swift @@ -40,12 +40,14 @@ class RealFileSystem: FileSystemProtocol { ) } - func getContentsOfDirectory(_ path: String) throws -> [String] { - // TODO + func getShallowContentsOfDirectory(_ path: String) throws -> [String] { + todo() + return [] } func getDestinationOfSymlink(_ path: String) throws -> String { - // TODO + todo() + return "" } // MARK: - Move & Delete Files diff --git a/phpmon/Common/Helpers/WIP.swift b/phpmon/Common/Helpers/WIP.swift new file mode 100644 index 0000000..dbe89f4 --- /dev/null +++ b/phpmon/Common/Helpers/WIP.swift @@ -0,0 +1,17 @@ +// +// WIP.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 01/11/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +func todo(_ context: String = "") { + if !context.isEmpty { + fatalError("To be implemented: \(context)") + } + + fatalError("To be implemented") +} diff --git a/phpmon/Common/Testables/TestableConfiguration.swift b/phpmon/Common/Testables/TestableConfiguration.swift index 60a1f21..ea4280d 100644 --- a/phpmon/Common/Testables/TestableConfiguration.swift +++ b/phpmon/Common/Testables/TestableConfiguration.swift @@ -26,7 +26,6 @@ public struct TestableConfiguration: Codable { ActiveCommand.useTestable(commandOutput) Log.info("Applying fake scanner...") ValetScanners.useFake() - Log.separator() } func toJson(pretty: Bool = false) -> String { diff --git a/phpmon/Common/Testables/TestableFileSystem.swift b/phpmon/Common/Testables/TestableFileSystem.swift index 4f2b1b9..ccddc7a 100644 --- a/phpmon/Common/Testables/TestableFileSystem.swift +++ b/phpmon/Common/Testables/TestableFileSystem.swift @@ -11,21 +11,40 @@ import Foundation class TestableFileSystem: FileSystemProtocol { init(files: [String: FakeFile]) { self.files = files + + for key in files.keys where key.contains("~") { + self.files.renameKey( + fromKey: key, + toKey: key.replacingOccurrences(of: "~", with: self.homeDirectory) + ) + } + + for file in self.files { + self.createIntermediateDirectories(file.key) + } } var files: [String: FakeFile] + private(set) var homeDirectory = "/Users/fake" + // MARK: - Basics func createDirectory(_ path: String, withIntermediateDirectories: Bool) throws { + let path = path.replacingTildeWithHomeDirectory + if files[path] != nil { throw TestableFileSystemError.alreadyExists } + self.createIntermediateDirectories(path) + self.files[path] = .fake(.directory) } func writeAtomicallyToFile(_ path: String, content: String) throws { + let path = path.replacingTildeWithHomeDirectory + if files[path] != nil { throw TestableFileSystemError.alreadyExists } @@ -34,6 +53,8 @@ class TestableFileSystem: FileSystemProtocol { } func getStringFromFile(_ path: String) throws -> String { + let path = path.replacingTildeWithHomeDirectory + guard let file = files[path] else { throw TestableFileSystemError.fileMissing } @@ -41,11 +62,23 @@ class TestableFileSystem: FileSystemProtocol { return file.content ?? "" } - func getContentsOfDirectory(_ path: String) throws -> [String] { - // TODO + func getShallowContentsOfDirectory(_ path: String) throws -> [String] { + let path = path.replacingTildeWithHomeDirectory + + var seek = path + if !seek.hasSuffix("/") { + seek = "\(seek)/" + } + + return self.files.keys + .filter { $0.hasPrefix(seek) } + .map { $0.replacingOccurrences(of: seek, with: "") } + .filter { !$0.contains("/") } } func getDestinationOfSymlink(_ path: String) throws -> String { + let path = path.replacingTildeWithHomeDirectory + guard let file = files[path] else { throw TestableFileSystemError.fileMissing } @@ -68,16 +101,22 @@ class TestableFileSystem: FileSystemProtocol { // MARK: - Move & Delete Files func move(from path: String, to newPath: String) throws { + let path = path.replacingTildeWithHomeDirectory + let newPath = newPath.replacingTildeWithHomeDirectory + // TODO } func remove(_ path: String) throws { + let path = path.replacingTildeWithHomeDirectory // TODO } // MARK: — Attributes func makeExecutable(_ path: String) throws { + let path = path.replacingTildeWithHomeDirectory + guard let file = files[path] else { throw TestableFileSystemError.fileMissing } @@ -88,7 +127,9 @@ class TestableFileSystem: FileSystemProtocol { // MARK: - Checks func isExecutableFile(_ path: String) -> Bool { - guard let file = files[path] else { + let path = path.replacingTildeWithHomeDirectory + + guard let file = files[path.replacingTildeWithHomeDirectory] else { return false } @@ -96,7 +137,9 @@ class TestableFileSystem: FileSystemProtocol { } func isWriteableFile(_ path: String) -> Bool { - guard let file = files[path] else { + let path = path.replacingTildeWithHomeDirectory + + guard let file = files[path.replacingTildeWithHomeDirectory] else { return false } @@ -104,10 +147,14 @@ class TestableFileSystem: FileSystemProtocol { } func anyExists(_ path: String) -> Bool { + let path = path.replacingTildeWithHomeDirectory + return files.keys.contains(path) } func fileExists(_ path: String) -> Bool { + let path = path.replacingTildeWithHomeDirectory + guard let file = files[path] else { return false } @@ -116,6 +163,8 @@ class TestableFileSystem: FileSystemProtocol { } func directoryExists(_ path: String) -> Bool { + let path = path.replacingTildeWithHomeDirectory + guard let file = files[path] else { return false } @@ -124,6 +173,8 @@ class TestableFileSystem: FileSystemProtocol { } func isSymlink(_ path: String) -> Bool { + let path = path.replacingTildeWithHomeDirectory + guard let file = files[path] else { return false } @@ -132,12 +183,40 @@ class TestableFileSystem: FileSystemProtocol { } func isDirectory(_ path: String) -> Bool { + let path = path.replacingTildeWithHomeDirectory + guard let file = files[path] else { return false } return file.type == .directory } + + public func printContents() { + for key in self.files.keys.sorted() { + print("\(key) -> \(self.files[key]!.type)") + } + } + + private func createIntermediateDirectories(_ path: String) { + let path = path.replacingTildeWithHomeDirectory + + let items = path.components(separatedBy: "/") + + var preceding = "" + + for item in items { + let key = preceding == "/" + ? "/\(item)" + : "\(preceding)/\(item)" + + if !self.files.keys.contains(key) { + self.files[key] = .fake(.directory) + } + + preceding = key + } + } } enum FakeFileType: Codable { diff --git a/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/ValetSiteScanner.swift b/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/ValetSiteScanner.swift index 801f89d..6fe84d3 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/ValetSiteScanner.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/ValetSiteScanner.swift @@ -13,7 +13,7 @@ class ValetSiteScanner: SiteScanner { return paths.map { path in let entries = try! FileSystem - .getContentsOfDirectory(path) + .getShallowContentsOfDirectory(path) return entries .map { self.isSite($0, forPath: path) } @@ -28,7 +28,7 @@ class ValetSiteScanner: SiteScanner { paths.forEach { path in let entries = try! FileSystem - .getContentsOfDirectory(path) + .getShallowContentsOfDirectory(path) return entries.forEach { if let site = self.resolveSite(path: "\(path)/\($0)") { diff --git a/tests/Shared/TestableConfigurations.swift b/tests/Shared/TestableConfigurations.swift index 28374d1..4186132 100644 --- a/tests/Shared/TestableConfigurations.swift +++ b/tests/Shared/TestableConfigurations.swift @@ -26,15 +26,11 @@ class TestableConfigurations { : .fake(.symlink, "/opt/homebrew/Cellar/php/8.1.10_1"), "/opt/homebrew/opt/php@8.1/bin/php" : .fake(.symlink, "/opt/homebrew/Cellar/php/8.1.10_1/bin/php"), - "/opt/homebrew/Cellar/php/8.1.10_1" - : .fake(.directory), "/opt/homebrew/Cellar/php/8.1.10_1/bin/php" : .fake(.binary), "/opt/homebrew/Cellar/php/8.1.10_1/bin/php-config" : .fake(.binary), - "/Users/user/.config/valet" - : .fake(.directory), - "/Users/user/.config/valet/config.json" + "~/.config/valet/config.json" : .fake(.text, """ { "tld": "test", diff --git a/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift b/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift index 1329933..4e2b8ec 100644 --- a/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift +++ b/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift @@ -12,12 +12,12 @@ class TestableFileSystemTest: XCTestCase { override class func setUp() { ActiveFileSystem.useTestable([ - "/home/user/bin": .fake(.directory), "/home/user/bin/foo": .fake(.binary), - "/home/user/documents": .fake(.directory), "/home/user/docs": .fake(.symlink, "/home/user/documents"), + "/home/user/documents/script.sh": .fake(.text, "echo 'cool';"), "/home/user/documents/nice.txt": .fake(.text, "69"), - "/home/user/documents/script.sh": .fake(.text, "echo 'cool';") + "/home/user/documents/filters/filter1.txt": .fake(.text, "F1"), + "/home/user/documents/filters/filter2.txt": .fake(.text, "F2") ]) } @@ -25,7 +25,11 @@ class TestableFileSystemTest: XCTestCase { XCTAssertTrue(FileSystem is TestableFileSystem) } - func test_binary_directory_exists() { + func test_intermediate_directories_are_automatically_created() { + XCTAssertTrue(FileSystem.directoryExists("/")) + XCTAssertTrue(FileSystem.directoryExists("/home")) + XCTAssertTrue(FileSystem.directoryExists("/home/user")) + XCTAssertTrue(FileSystem.directoryExists("/home/user/documents")) XCTAssertTrue(FileSystem.directoryExists("/home/user/bin")) } @@ -53,9 +57,37 @@ class TestableFileSystemTest: XCTestCase { withIntermediateDirectories: true ) - XCTAssertTrue(FileSystem.anyExists("/home/nico/phpmon/config")) + XCTAssertTrue(FileSystem + .anyExists("/home/nico/phpmon/config")) XCTAssertTrue(FileSystem.directoryExists("/home/nico/phpmon/config")) } + func test_can_create_nested_directories() throws { + try FileSystem.createDirectory( + "/home/user/thing/epic/nested/directories", + withIntermediateDirectories: true + ) + + XCTAssertTrue(FileSystem.directoryExists("/")) + XCTAssertTrue(FileSystem.directoryExists("/home")) + XCTAssertTrue(FileSystem.directoryExists("/home/user")) + XCTAssertTrue(FileSystem.directoryExists("/home/user/thing")) + XCTAssertTrue(FileSystem.directoryExists("/home/user/thing/epic/nested")) + XCTAssertTrue(FileSystem.directoryExists("/home/user/thing/epic/nested/directories")) + } + + func test_can_list_directory_contents() throws { + let contents = try! FileSystem.getShallowContentsOfDirectory("/home/user/documents") + + XCTAssertEqual( + contents.sorted(), + [ + "script.sh", + "nice.txt", + "filters" + ].sorted() + ) + } + // TODO: Implement and test the remove() and move() methods and reorganize method order } From ce44166b487f6721766d39a26fd7d70fc4f0464f Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 1 Nov 2022 17:02:26 +0100 Subject: [PATCH 081/181] =?UTF-8?q?=E2=9C=85=20Added=20more=20tests,=20add?= =?UTF-8?q?ed=20to=20fake=20&=20real=20FS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Filesystem/RealFileSystem.swift | 10 ++--- .../Common/Testables/TestableFileSystem.swift | 43 ++++++++++++++++--- .../Filesystem/TestableFileSystemTest.swift | 24 ++++++++++- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/phpmon/Common/Filesystem/RealFileSystem.swift b/phpmon/Common/Filesystem/RealFileSystem.swift index e3ed7cf..7e8ef30 100644 --- a/phpmon/Common/Filesystem/RealFileSystem.swift +++ b/phpmon/Common/Filesystem/RealFileSystem.swift @@ -41,23 +41,21 @@ class RealFileSystem: FileSystemProtocol { } func getShallowContentsOfDirectory(_ path: String) throws -> [String] { - todo() - return [] + return try FileManager.default.contentsOfDirectory(atPath: path) } func getDestinationOfSymlink(_ path: String) throws -> String { - todo() - return "" + return try FileManager.default.destinationOfSymbolicLink(atPath: path) } // MARK: - Move & Delete Files func move(from path: String, to newPath: String) throws { - // TODO + try FileManager.default.moveItem(atPath: path, toPath: newPath) } func remove(_ path: String) throws { - // TODO + try FileManager.default.removeItem(atPath: path) } // MARK: — FS Attributes diff --git a/phpmon/Common/Testables/TestableFileSystem.swift b/phpmon/Common/Testables/TestableFileSystem.swift index ccddc7a..9492260 100644 --- a/phpmon/Common/Testables/TestableFileSystem.swift +++ b/phpmon/Common/Testables/TestableFileSystem.swift @@ -9,23 +9,41 @@ import Foundation class TestableFileSystem: FileSystemProtocol { + + /** + Initialize a fake filesystem with a bunch of files. + You do not need to specify directories (unless symlinks), those will be created automatically. + */ init(files: [String: FakeFile]) { self.files = files - for key in files.keys where key.contains("~") { + // Ensure that each of the ~ characters are replaced with the home directory path + for key in self.files.keys where key.contains("~") { self.files.renameKey( fromKey: key, toKey: key.replacingOccurrences(of: "~", with: self.homeDirectory) ) } + // Ensure that intermediate directories are created for file in self.files { self.createIntermediateDirectories(file.key) } } - var files: [String: FakeFile] + /** + Internal file handling of the fake filesystem. + You can easily dump what's in here by using: + ``` + let fs = FileSystem as! TestableFileSystem + fs.printContents() + ``` + */ + private(set) var files: [String: FakeFile] + /** + The home directory for the fake filesystem. + */ private(set) var homeDirectory = "/Users/fake" // MARK: - Basics @@ -104,12 +122,27 @@ class TestableFileSystem: FileSystemProtocol { let path = path.replacingTildeWithHomeDirectory let newPath = newPath.replacingTildeWithHomeDirectory - // TODO + self.files.keys.forEach { key in + if key.hasPrefix(path) { + self.files.renameKey( + fromKey: key, + toKey: key.replacingOccurrences(of: path, with: newPath) + ) + } + } + + self.files.renameKey(fromKey: path, toKey: newPath) } func remove(_ path: String) throws { - let path = path.replacingTildeWithHomeDirectory - // TODO + // Remove recursively + self.files.keys.forEach { key in + if key.hasPrefix(path) { + self.files.removeValue(forKey: key) + } + } + + self.files.removeValue(forKey: path) } // MARK: — Attributes diff --git a/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift b/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift index 4e2b8ec..e465e7d 100644 --- a/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift +++ b/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift @@ -89,5 +89,27 @@ class TestableFileSystemTest: XCTestCase { ) } - // TODO: Implement and test the remove() and move() methods and reorganize method order + func test_can_delete_directory_recursively() { + XCTAssertTrue(FileSystem.directoryExists("/home/user/documents")) + XCTAssertTrue(FileSystem.directoryExists("/home/user/documents/filters")) + XCTAssertTrue(FileSystem.fileExists("/home/user/documents/filters/filter1.txt")) + + try! FileSystem.remove("/home/user/documents") + + XCTAssertFalse(FileSystem.directoryExists("/home/user/documents")) + XCTAssertFalse(FileSystem.directoryExists("/home/user/documents/filters")) + XCTAssertFalse(FileSystem.fileExists("/home/user/documents/filters/filter1.txt")) + } + + func test_can_move_directory() { + XCTAssertTrue(FileSystem.directoryExists("/home/user/documents")) + XCTAssertTrue(FileSystem.directoryExists("/home/user/documents/filters")) + XCTAssertTrue(FileSystem.fileExists("/home/user/documents/filters/filter1.txt")) + + try! FileSystem.move(from: "/home/user/documents", to: "/home/user/new") + + XCTAssertTrue(FileSystem.directoryExists("/home/user/new")) + XCTAssertTrue(FileSystem.directoryExists("/home/user/new/filters")) + XCTAssertTrue(FileSystem.fileExists("/home/user/new/filters/filter1.txt")) + } } From 4f6bae87d4f57fcc8b7a20f1ce2e3d0e231016aa Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 1 Nov 2022 17:08:01 +0100 Subject: [PATCH 082/181] =?UTF-8?q?=E2=9C=85=20Add=20Composer=20to=20testa?= =?UTF-8?q?ble=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Shared/TestableConfigurations.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Shared/TestableConfigurations.swift b/tests/Shared/TestableConfigurations.swift index 4186132..bd97a6b 100644 --- a/tests/Shared/TestableConfigurations.swift +++ b/tests/Shared/TestableConfigurations.swift @@ -16,6 +16,8 @@ class TestableConfigurations { filesystem: [ "/usr/local/bin/" : .fake(.directory, readOnly: true), + "/usr/local/bin/composer" + : .fake(.binary), "/opt/homebrew/bin/brew" : .fake(.binary), "/opt/homebrew/bin/php" From ff5fdd82b180319c0ab9571a530146cd1c04b2b4 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 1 Nov 2022 17:11:55 +0100 Subject: [PATCH 083/181] =?UTF-8?q?=F0=9F=91=8C=20Add=20default=20system?= =?UTF-8?q?=20"www.conf"=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Shared/TestableConfigurations.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Shared/TestableConfigurations.swift b/tests/Shared/TestableConfigurations.swift index bd97a6b..6578865 100644 --- a/tests/Shared/TestableConfigurations.swift +++ b/tests/Shared/TestableConfigurations.swift @@ -32,6 +32,8 @@ class TestableConfigurations { : .fake(.binary), "/opt/homebrew/Cellar/php/8.1.10_1/bin/php-config" : .fake(.binary), + "/opt/homebrew/etc/php/8.1/php-fpm.d/www.conf" + : .fake(.text), "~/.config/valet/config.json" : .fake(.text, """ { From f2d5b94831f879cf3423c19e6f92d60373c5b9cd Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 2 Nov 2022 19:44:36 +0100 Subject: [PATCH 084/181] =?UTF-8?q?=E2=9C=85=20Fix=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 12 +++++++---- .../Filesystem/RealFileSystemTest.swift | 21 +++++++++++++++++++ .../Filesystem/TestableFileSystemTest.swift | 2 +- ...temShellTest.swift => RealShellTest.swift} | 6 +++--- 4 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 tests/unit/Testables/Filesystem/RealFileSystemTest.swift rename tests/unit/Testables/Shell/{SystemShellTest.swift => RealShellTest.swift} (95%) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 961c03f..517f643 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -75,7 +75,7 @@ C413E43528DA3EB100AE33C7 /* TestableShellTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C413E43428DA3EB100AE33C7 /* TestableShellTest.swift */; }; C415937F27A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; }; C415938027A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; }; - C4159AF728E4D40400545349 /* SystemShellTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4159AF628E4D40400545349 /* SystemShellTest.swift */; }; + C4159AF728E4D40400545349 /* RealShellTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4159AF628E4D40400545349 /* RealShellTest.swift */; }; C415D3B72770F294005EF286 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; }; C415D3B82770F294005EF286 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; }; C415D3E82770F692005EF286 /* AppDelegate+InterApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */; }; @@ -131,6 +131,7 @@ C449B4F427EE7FC800C47E8A /* DomainListKindCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */; }; C44A874828905BB000498BC4 /* ProgressVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44A874728905BB000498BC4 /* ProgressVC.swift */; }; C44A874928905BB000498BC4 /* ProgressVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44A874728905BB000498BC4 /* ProgressVC.swift */; }; + C44AD3F72912EF7100997FF4 /* RealFileSystemTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44AD3F62912EF7100997FF4 /* RealFileSystemTest.swift */; }; C44B3A4628E5C70100718CB1 /* TimeIntervalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */; }; C44B3A4728E5C70100718CB1 /* TimeIntervalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */; }; C44C198D276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */; }; @@ -740,7 +741,7 @@ C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackage.swift; sourceTree = ""; }; C413E43428DA3EB100AE33C7 /* TestableShellTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableShellTest.swift; sourceTree = ""; }; C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpFrameworks.swift; sourceTree = ""; }; - C4159AF628E4D40400545349 /* SystemShellTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemShellTest.swift; sourceTree = ""; }; + C4159AF628E4D40400545349 /* RealShellTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealShellTest.swift; sourceTree = ""; }; C415D3B62770F294005EF286 /* Actions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = ""; }; C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+InterApp.swift"; sourceTree = ""; }; C4168F4427ADB4A3003B6C39 /* DEVELOPER.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = DEVELOPER.md; sourceTree = ""; }; @@ -784,6 +785,7 @@ C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIHelper.swift; sourceTree = ""; }; C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionPopoverView.swift; sourceTree = ""; }; C44A874728905BB000498BC4 /* ProgressVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressVC.swift; sourceTree = ""; }; + C44AD3F62912EF7100997FF4 /* RealFileSystemTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealFileSystemTest.swift; sourceTree = ""; }; C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeIntervalExtension.swift; sourceTree = ""; }; C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalProgressWindowController.swift; sourceTree = ""; }; C44C1990276E44CB0072762D /* ProgressWindow.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ProgressWindow.storyboard; sourceTree = ""; }; @@ -1087,7 +1089,7 @@ isa = PBXGroup; children = ( C413E43428DA3EB100AE33C7 /* TestableShellTest.swift */, - C4159AF628E4D40400545349 /* SystemShellTest.swift */, + C4159AF628E4D40400545349 /* RealShellTest.swift */, ); path = Shell; sourceTree = ""; @@ -1309,6 +1311,7 @@ isa = PBXGroup; children = ( C4D3660F291140BE006BD146 /* TestableFileSystemTest.swift */, + C44AD3F62912EF7100997FF4 /* RealFileSystemTest.swift */, ); path = Filesystem; sourceTree = ""; @@ -2489,7 +2492,7 @@ C4FC21B128391F8E00D368BB /* MainMenu+Actions.swift in Sources */, 54D9E0B927E4F51E003B9AD9 /* KeyCombo.swift in Sources */, C4EED88A27A48778006D7272 /* InterAppHandler.swift in Sources */, - C4159AF728E4D40400545349 /* SystemShellTest.swift in Sources */, + C4159AF728E4D40400545349 /* RealShellTest.swift in Sources */, C450C8C728C919EC002A2B4B /* PreferenceName.swift in Sources */, C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */, C485707B28BF458900539B36 /* VersionPopoverView.swift in Sources */, @@ -2560,6 +2563,7 @@ C485707C28BF459500539B36 /* NoWarningsView.swift in Sources */, C4F5FBCD28218CB8001065C5 /* Xdebug.swift in Sources */, C40B24F227A310770018C7D2 /* Events.swift in Sources */, + C44AD3F72912EF7100997FF4 /* RealFileSystemTest.swift in Sources */, C4F30B0A278E1A1A00755FCE /* ComposerJson.swift in Sources */, C4C0E8E027F88AEB002D32A9 /* FakeSiteScanner.swift in Sources */, C4AF9F7D275454A900D44ED0 /* ValetVersionExtractorTest.swift in Sources */, diff --git a/tests/unit/Testables/Filesystem/RealFileSystemTest.swift b/tests/unit/Testables/Filesystem/RealFileSystemTest.swift new file mode 100644 index 0000000..349b1c0 --- /dev/null +++ b/tests/unit/Testables/Filesystem/RealFileSystemTest.swift @@ -0,0 +1,21 @@ +// +// RealFileSystemTest.swift +// Unit Tests +// +// Created by Nico Verbruggen on 02/11/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import XCTest + +class RealFileSystemTest: XCTestCase { + + override class func setUp() { + ActiveFileSystem.useSystem() + } + + func test_testable_fs_is_in_use() { + XCTAssertTrue(FileSystem is RealFileSystem) + } + +} diff --git a/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift b/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift index e465e7d..698e9ba 100644 --- a/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift +++ b/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift @@ -10,7 +10,7 @@ import XCTest class TestableFileSystemTest: XCTestCase { - override class func setUp() { + override func setUp() async throws { ActiveFileSystem.useTestable([ "/home/user/bin/foo": .fake(.binary), "/home/user/docs": .fake(.symlink, "/home/user/documents"), diff --git a/tests/unit/Testables/Shell/SystemShellTest.swift b/tests/unit/Testables/Shell/RealShellTest.swift similarity index 95% rename from tests/unit/Testables/Shell/SystemShellTest.swift rename to tests/unit/Testables/Shell/RealShellTest.swift index 467fc9a..8c57917 100644 --- a/tests/unit/Testables/Shell/SystemShellTest.swift +++ b/tests/unit/Testables/Shell/RealShellTest.swift @@ -1,5 +1,5 @@ // -// SystemShellTest.swift +// RealShellTest.swift // PHP Monitor // // Created by Nico Verbruggen on 28/09/2022. @@ -8,9 +8,9 @@ import XCTest -class SystemShellTest: XCTestCase { +class RealShellTest: XCTestCase { - override class func setUp() { + override func setUp() async throws { // Reset to the default shell ActiveShell.useSystem() } From 4de7179d1ca6388930c1b05a2b2d9758100bc754 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 2 Nov 2022 20:08:15 +0100 Subject: [PATCH 085/181] =?UTF-8?q?=F0=9F=91=8C=20Include=20`brew=20(un)li?= =?UTF-8?q?nk`=20commands=20for=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/Watcher/PhpConfigWatcher.swift | 5 ++++- tests/Shared/TestableConfigurations.swift | 20 +++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/phpmon/Domain/Watcher/PhpConfigWatcher.swift b/phpmon/Domain/Watcher/PhpConfigWatcher.swift index 62ce21a..9758a78 100644 --- a/phpmon/Domain/Watcher/PhpConfigWatcher.swift +++ b/phpmon/Domain/Watcher/PhpConfigWatcher.swift @@ -22,7 +22,10 @@ class PhpConfigWatcher { init(for url: URL) { if FileSystem is TestableFileSystem { - fatalError("PhpConfigWatcher is not compatible with testable FS! You are not allowed to instantiate these while using a testable FS.") + fatalError(""" + PhpConfigWatcher is not compatible with testable FS! " + You are not allowed to instantiate these while using a testable FS. + """) } self.url = url diff --git a/tests/Shared/TestableConfigurations.swift b/tests/Shared/TestableConfigurations.swift index 6578865..c7aa016 100644 --- a/tests/Shared/TestableConfigurations.swift +++ b/tests/Shared/TestableConfigurations.swift @@ -135,7 +135,25 @@ class TestableConfigurations { "/opt/homebrew/bin/brew services info --all --json" : .instant(ShellStrings.shared.brewServicesAsUser), "curl -s --max-time 5 '\(Constants.Urls.StableBuildCaskFile.absoluteString)' | grep version" - : .instant("version '5.6.2_976'") + : .instant("version '5.6.2_976'"), + "/opt/homebrew/bin/brew unlink php" + : .delayed(0.2, "OK"), + "/opt/homebrew/bin/brew link php --overwrite --force" + : .delayed(0.2, "OK"), + "sudo /opt/homebrew/bin/brew services stop php" + : .delayed(0.2, "OK"), + "sudo /opt/homebrew/bin/brew services start php" + : .delayed(0.2, "OK"), + "sudo /opt/homebrew/bin/brew services stop nginx" + : .delayed(0.2, "OK"), + "sudo /opt/homebrew/bin/brew services start nginx" + : .delayed(0.2, "OK"), + "sudo /opt/homebrew/bin/brew services stop dnsmasq" + : .delayed(0.2, "OK"), + "sudo /opt/homebrew/bin/brew services start dnsmasq" + : .delayed(0.2, "OK"), + "ln -sF ~/.config/valet/valet81.sock ~/.config/valet/valet.sock" + : .instant("OK"), ], commandOutput: [ "/opt/homebrew/bin/php-config --version": "8.1.10", From 76d96b35074ed2f1e6f9b1272078d6fb597ee27a Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 2 Nov 2022 21:02:18 +0100 Subject: [PATCH 086/181] =?UTF-8?q?=E2=9C=85=20Fix=20issue=20with=20UI=20t?= =?UTF-8?q?ests=20and=20.localizable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 8 +++---- .../Common/Extensions/StringExtension.swift | 22 ++++++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 517f643..2531ea8 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2890,7 +2890,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 8M54J5J787; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 12.3; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon.ui-tests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2908,7 +2908,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 8M54J5J787; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 12.3; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon.ui-tests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2926,7 +2926,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 8M54J5J787; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 12.3; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon.ui-tests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2944,7 +2944,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 8M54J5J787; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 12.3; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon.ui-tests"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/phpmon/Common/Extensions/StringExtension.swift b/phpmon/Common/Extensions/StringExtension.swift index 5af4bf2..38ac652 100644 --- a/phpmon/Common/Extensions/StringExtension.swift +++ b/phpmon/Common/Extensions/StringExtension.swift @@ -9,12 +9,24 @@ import SwiftUI struct Localization { static var bundle: Bundle = { - var bundle = Bundle.main - if isRunningTests { - bundle = Bundle(identifier: "com.nicoverbruggen.phpmon.dev") - ?? Bundle(identifier: "com.nicoverbruggen.phpmon")! + if !isRunningTests { + return Bundle.main } - return bundle + + let foundBundle = Bundle(identifier: "com.nicoverbruggen.phpmon.dev") + ?? Bundle(identifier: "com.nicoverbruggen.phpmon") + ?? Bundle(identifier: "com.nicoverbruggen.phpmon.ui-tests") + + if foundBundle == nil { + let bundles = Bundle.allBundles + .map { $0.bundleIdentifier } + .filter { $0 != nil } + .map { $0! } + + fatalError("The following bundles were found: \(bundles)") + } + + return foundBundle! }() } From a21d928a6ced8bf1dfc2510df62c4aec0dd019c1 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 2 Nov 2022 21:22:49 +0100 Subject: [PATCH 087/181] =?UTF-8?q?=F0=9F=93=9D=20Update=20SECURITY.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SECURITY.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 1c149f3..e9a4382 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,9 +6,7 @@ Generally speaking, only the latest version of **PHP Monitor** is supported, exc | Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Recommended Valet Version | | ------- | ------------- | ------------------ | ----- | ----- | ----- | ---- -| 5.x | ✅ Universal binary | ✅ Yes | Big Sur (11.0)
Monterey (12.0)
Ventura (13.0)* | macOS 11+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)
PHP 7.0—PHP 8.2 (w/ Valet 3.x) | 3.0 recommended
2.16.2 minimum | - -_(*) macOS Ventura (13.0) is not officially supported until it officially releases._ +| 6.x | ✅ Universal binary | ✅ Yes | Big Sur (11.0)
Monterey (12.0)
Ventura (13.0) | macOS 11+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)
PHP 7.0—PHP 8.2 (w/ Valet 3.x) | 3.0 recommended
2.16.2 minimum | ## Legacy versions @@ -16,6 +14,7 @@ These versions of PHP Monitor are no longer supported, but if you’re using an | Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version | | ------- | ------------- | ------------------ | ----- | ----- | ----- | ---- +| 5.6 | ✅ Universal binary | ❌ | Big Sur (11.0)
Monterey (12.0)
Ventura (13.0)* | macOS 11+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)
PHP 7.0—PHP 8.2 (w/ Valet 3.x) | 3.0 recommended
2.16.2 minimum | | 4.1 | ✅ Universal binary | ❌ | Big Sur (11.0)
Monterey (12.0) | macOS 11+ | PHP 5.6—PHP 8.2 | 2.16.2 | | 4.0 | ✅ Universal binary | ❌ | Big Sur (11.0)
Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 | 2.13 | | 3.5 | ✅ Universal binary | ❌ | Big Sur (11.0)
Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 | 2.13 | From bc739e19822bfed32e8f330487c122d61f838c31 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 7 Nov 2022 20:42:52 +0100 Subject: [PATCH 088/181] =?UTF-8?q?=F0=9F=91=8C=20Add=20feature=20test=20f?= =?UTF-8?q?or=20InternalSwitcher?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 12 ++-- .../xcschemes/PHP Monitor DEV.xcscheme | 13 ++++- tests/feature/EmptyTest.swift | 11 ---- tests/feature/FeatureTestCase.swift | 49 +++++++++++++++++ tests/feature/InternalSwitcherTest.swift | 55 +++++++++++++++++++ 5 files changed, 124 insertions(+), 16 deletions(-) delete mode 100644 tests/feature/EmptyTest.swift create mode 100644 tests/feature/FeatureTestCase.swift create mode 100644 tests/feature/InternalSwitcherTest.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 2531ea8..5ad4364 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -149,6 +149,8 @@ C4570C3B28FC355300D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C4570C3C28FC355400D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C459B4BD27F6093700E9B4B4 /* nginx-proxy.test in Resources */ = {isa = PBXBuildFile; fileRef = C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */; }; + C45E2A7529199248005C7CFD /* InternalSwitcherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C471E7AF28F9B4940021E251 /* InternalSwitcherTest.swift */; }; + C45E2A77291992DA005C7CFD /* FeatureTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E2A76291992DA005C7CFD /* FeatureTestCase.swift */; }; C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; }; C45E76152854A65300B4FE0C /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; }; C463E380284930EE00422731 /* PresetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C463E37F284930EE00422731 /* PresetHelper.swift */; }; @@ -175,7 +177,6 @@ C471E79328F9B21F0021E251 /* ActiveFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */; }; C471E79428F9B23B0021E251 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; }; C471E79528F9B2420021E251 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; }; - C471E7B028F9B4940021E251 /* EmptyTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C471E7AF28F9B4940021E251 /* EmptyTest.swift */; }; C471E7BF28F9B90F0021E251 /* StartupTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C471E7BE28F9B90F0021E251 /* StartupTest.swift */; }; C471E7C928F9BA2F0021E251 /* TestableConfigurations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40F505428ECA64E004AD45B /* TestableConfigurations.swift */; }; C471E7CA28F9BA480021E251 /* TestableFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */; }; @@ -794,6 +795,7 @@ C44F868D2835BD8D005C353A /* phpmon-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "phpmon-config.json"; sourceTree = ""; }; C450C8C528C919EC002A2B4B /* PreferenceName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceName.swift; sourceTree = ""; }; C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-proxy.test"; sourceTree = ""; }; + C45E2A76291992DA005C7CFD /* FeatureTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureTestCase.swift; sourceTree = ""; }; C45E76132854A65300B4FE0C /* ServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesManager.swift; sourceTree = ""; }; C463E37F284930EE00422731 /* PresetHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetHelper.swift; sourceTree = ""; }; C464ADAB275A7A3F003FCD53 /* DomainListWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListWindowController.swift; sourceTree = ""; }; @@ -810,7 +812,7 @@ C4709CA128524B3400088BB8 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = ""; }; C471E79228F9B1D30021E251 /* PHP Monitor.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "PHP Monitor.xctestplan"; sourceTree = ""; }; C471E7AD28F9B4940021E251 /* Feature Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Feature Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - C471E7AF28F9B4940021E251 /* EmptyTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTest.swift; sourceTree = ""; }; + C471E7AF28F9B4940021E251 /* InternalSwitcherTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalSwitcherTest.swift; sourceTree = ""; }; C471E7BC28F9B90F0021E251 /* UI Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; C471E7BE28F9B90F0021E251 /* StartupTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartupTest.swift; sourceTree = ""; }; C473319E2470923A009A0597 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; @@ -1338,7 +1340,8 @@ C471E7AE28F9B4940021E251 /* feature */ = { isa = PBXGroup; children = ( - C471E7AF28F9B4940021E251 /* EmptyTest.swift */, + C471E7AF28F9B4940021E251 /* InternalSwitcherTest.swift */, + C45E2A76291992DA005C7CFD /* FeatureTestCase.swift */, ); path = feature; sourceTree = ""; @@ -2128,6 +2131,7 @@ C471E84228F9BB650021E251 /* AppDelegate+InterApp.swift in Sources */, C471E84328F9BB650021E251 /* App.swift in Sources */, C4E2E85E28FC282B003B070C /* TestableConfiguration.swift in Sources */, + C45E2A7529199248005C7CFD /* InternalSwitcherTest.swift in Sources */, C471E84428F9BB650021E251 /* App+ActivationPolicy.swift in Sources */, C471E84528F9BB650021E251 /* App+GlobalHotkey.swift in Sources */, C471E84628F9BB650021E251 /* InterAppHandler.swift in Sources */, @@ -2239,7 +2243,6 @@ C471E81428F9BAE80021E251 /* NSWindowExtension.swift in Sources */, C471E7D328F9BA8F0021E251 /* ActiveShell.swift in Sources */, C471E7DE28F9BAA30021E251 /* CommandProtocol.swift in Sources */, - C471E7B028F9B4940021E251 /* EmptyTest.swift in Sources */, C471E81B28F9BB250021E251 /* BetterAlertVC.swift in Sources */, C471E82928F9BB330021E251 /* Valet.swift in Sources */, C471E80728F9BAD40021E251 /* PhpConfigurationFile.swift in Sources */, @@ -2247,6 +2250,7 @@ C471E7E328F9BAC20021E251 /* Logger.swift in Sources */, C471E7FD28F9BACE0021E251 /* HomebrewService.swift in Sources */, C471E7E428F9BAC20021E251 /* Helpers.swift in Sources */, + C45E2A77291992DA005C7CFD /* FeatureTestCase.swift in Sources */, C471E82028F9BB290021E251 /* NginxConfigurationFile.swift in Sources */, C471E7D428F9BA8F0021E251 /* ActiveFileSystem.swift in Sources */, C471E81528F9BAE80021E251 /* ArrayExtension.swift in Sources */, diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index 0b2dcd1..b0a4d86 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -26,7 +26,8 @@ buildConfiguration = "Debug.Dev" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + + + + Date: Tue, 8 Nov 2022 20:14:10 +0100 Subject: [PATCH 089/181] =?UTF-8?q?=E2=9C=85=20Add=20tests=20for=20RealFil?= =?UTF-8?q?eSystem=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Filesystem/RealFileSystem.swift | 4 +- phpmon/Common/Helpers/System.swift | 24 +++++-- .../Filesystem/RealFileSystemTest.swift | 70 ++++++++++++++++++- 3 files changed, 88 insertions(+), 10 deletions(-) diff --git a/phpmon/Common/Filesystem/RealFileSystem.swift b/phpmon/Common/Filesystem/RealFileSystem.swift index 7e8ef30..221ecd9 100644 --- a/phpmon/Common/Filesystem/RealFileSystem.swift +++ b/phpmon/Common/Filesystem/RealFileSystem.swift @@ -61,7 +61,7 @@ class RealFileSystem: FileSystemProtocol { // MARK: — FS Attributes func makeExecutable(_ path: String) throws { - system("chmod +x \(path.replacingTildeWithHomeDirectory)") + _ = system("chmod +x \(path.replacingTildeWithHomeDirectory)") } // MARK: - Checks @@ -69,6 +69,8 @@ class RealFileSystem: FileSystemProtocol { func isExecutableFile(_ path: String) -> Bool { return FileManager.default.isExecutableFile( atPath: path.replacingTildeWithHomeDirectory + ) && FileManager.default.isReadableFile( + atPath: path.replacingTildeWithHomeDirectory ) } diff --git a/phpmon/Common/Helpers/System.swift b/phpmon/Common/Helpers/System.swift index 9b8d000..92b8ba2 100644 --- a/phpmon/Common/Helpers/System.swift +++ b/phpmon/Common/Helpers/System.swift @@ -8,11 +8,21 @@ import Foundation -public func system(_ command: String) { - let argsArray = command.split(separator: " ").map { String($0) } - guard argsArray.isEmpty else { return } - let command = strdup(argsArray.first!) - let args = argsArray.map { strdup($0) } + [nil] - posix_spawn(nil, command, nil, nil, args, nil) - return +/** + Run a simple blocking Shell command on the user's own system. + Avoid using this method in favor of the fakeable Shell class unless needed for express system operations. + */ +public func system(_ command: String) -> String { + let task = Process() + task.launchPath = "/bin/sh" + task.arguments = ["-c", command] + + let pipe = Pipe() + task.standardOutput = pipe + task.launch() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String + + return output } diff --git a/tests/unit/Testables/Filesystem/RealFileSystemTest.swift b/tests/unit/Testables/Filesystem/RealFileSystemTest.swift index 349b1c0..ce52758 100644 --- a/tests/unit/Testables/Filesystem/RealFileSystemTest.swift +++ b/tests/unit/Testables/Filesystem/RealFileSystemTest.swift @@ -9,13 +9,79 @@ import XCTest class RealFileSystemTest: XCTestCase { - - override class func setUp() { + override func setUp() { ActiveFileSystem.useSystem() } + private func createUniqueTemporaryDirectory() -> String { + let tempDirectoryURL = NSURL.fileURL(withPath: NSTemporaryDirectory(), isDirectory: true) + let fullTempDirectoryPath = tempDirectoryURL.appendingPathComponent("phpmon-fs-tests").path + try? FileManager.default.removeItem(atPath: fullTempDirectoryPath) + try! FileManager.default.createDirectory(atPath: fullTempDirectoryPath, withIntermediateDirectories: false) + return fullTempDirectoryPath + } + func test_testable_fs_is_in_use() { XCTAssertTrue(FileSystem is RealFileSystem) } + func test_temporary_path_exists() { + let temporaryDirectory = self.createUniqueTemporaryDirectory() + + // True + XCTAssertTrue(FileSystem.directoryExists(temporaryDirectory)) + XCTAssertTrue(FileSystem.anyExists(temporaryDirectory)) + + // False + XCTAssertFalse(FileSystem.fileExists(temporaryDirectory)) + } + + private func createTestBinaryFile(_ temporaryDirectory: String) -> String { + let executablePath = "\(temporaryDirectory)/exec.sh" + + try! FileSystem.writeAtomicallyToFile(executablePath, content: """ + !#/bin/bash + echo 'Hello world'; + """) + + return executablePath + } + + func test_make_binary_executable() { + let temporaryDirectory = self.createUniqueTemporaryDirectory() + let executable = self.createTestBinaryFile(temporaryDirectory) + + XCTAssertTrue(FileSystem.isWriteableFile(executable)) + XCTAssertFalse(FileSystem.isExecutableFile(executable)) + + try! FileSystem.makeExecutable(executable) + + XCTAssertTrue(FileSystem.isExecutableFile(executable)) + } + + func test_moving_file() { + let temporaryDirectory = self.createUniqueTemporaryDirectory() + let executable = self.createTestBinaryFile(temporaryDirectory) + + XCTAssertTrue(FileSystem.fileExists(executable)) + + let newExecutable = executable.replacingOccurrences(of: "/exec.sh", with: "/file.txt") + + try! FileSystem.move(from: executable, to: newExecutable) + + XCTAssertTrue(FileSystem.fileExists(newExecutable)) + XCTAssertFalse(FileSystem.fileExists(executable)) + } + + func test_deleting_file() { + let temporaryDirectory = self.createUniqueTemporaryDirectory() + let executable = self.createTestBinaryFile(temporaryDirectory) + + XCTAssertTrue(FileSystem.fileExists(executable)) + + try! FileSystem.remove(executable) + + XCTAssertFalse(FileSystem.fileExists(executable)) + } + } From a801174f0af1a41d5111629a55e15cebdb58f8b0 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 9 Nov 2022 20:05:15 +0100 Subject: [PATCH 090/181] =?UTF-8?q?=E2=9C=85=20Fully=20cover=20RealFileSys?= =?UTF-8?q?tem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Filesystem/RealFileSystemTest.swift | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/unit/Testables/Filesystem/RealFileSystemTest.swift b/tests/unit/Testables/Filesystem/RealFileSystemTest.swift index ce52758..8e9b9a0 100644 --- a/tests/unit/Testables/Filesystem/RealFileSystemTest.swift +++ b/tests/unit/Testables/Filesystem/RealFileSystemTest.swift @@ -36,6 +36,30 @@ class RealFileSystemTest: XCTestCase { XCTAssertFalse(FileSystem.fileExists(temporaryDirectory)) } + func test_directory_can_be_created_symlinked_and_read() { + let temporaryDirectory = self.createUniqueTemporaryDirectory() + + let folderPath = "\(temporaryDirectory)/brew/etc/lib/c" + + try! FileSystem.createDirectory(folderPath, withIntermediateDirectories: true) + + XCTAssertTrue(FileSystem.directoryExists("\(temporaryDirectory)/brew")) + XCTAssertTrue(FileSystem.directoryExists("\(temporaryDirectory)/brew/etc")) + XCTAssertTrue(FileSystem.directoryExists("\(temporaryDirectory)/brew/etc/lib")) + XCTAssertTrue(FileSystem.directoryExists("\(temporaryDirectory)/brew/etc/lib/c")) + + _ = system("ln -s \(temporaryDirectory)/brew/etc/lib/c \(temporaryDirectory)/c") + XCTAssertTrue(FileSystem.directoryExists("\(temporaryDirectory)/c")) + XCTAssertTrue(FileSystem.isSymlink("\(temporaryDirectory)/c")) + XCTAssertEqual( + try! FileSystem.getDestinationOfSymlink("\(temporaryDirectory)/c"), + "\(temporaryDirectory)/brew/etc/lib/c" + ) + + let contents = try! FileSystem.getShallowContentsOfDirectory("\(temporaryDirectory)/brew/etc/lib/c") + XCTAssertEqual([], contents) + } + private func createTestBinaryFile(_ temporaryDirectory: String) -> String { let executablePath = "\(temporaryDirectory)/exec.sh" @@ -47,6 +71,21 @@ class RealFileSystemTest: XCTestCase { return executablePath } + + + func test_can_read_file_as_text() { + let temporaryDirectory = self.createUniqueTemporaryDirectory() + let executable = self.createTestBinaryFile(temporaryDirectory) + + XCTAssertEqual( + try! FileSystem.getStringFromFile(executable), + """ + !#/bin/bash + echo 'Hello world'; + """ + ) + } + func test_make_binary_executable() { let temporaryDirectory = self.createUniqueTemporaryDirectory() let executable = self.createTestBinaryFile(temporaryDirectory) @@ -57,6 +96,15 @@ class RealFileSystemTest: XCTestCase { try! FileSystem.makeExecutable(executable) XCTAssertTrue(FileSystem.isExecutableFile(executable)) + XCTAssertFalse(FileSystem.isDirectory(executable)) + XCTAssertFalse(FileSystem.isSymlink(executable)) + } + + func test_non_existent_file_is_not_symlink_or_directory() { + let path = "/path/that/does/not/exist" + + XCTAssertFalse(FileSystem.isDirectory(path)) + XCTAssertFalse(FileSystem.isSymlink(path)) } func test_moving_file() { From c6aa06842ca8c6177ffb49a55251781e90fbc95f Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 18 Nov 2022 18:37:20 +0100 Subject: [PATCH 091/181] =?UTF-8?q?=F0=9F=94=A5=20Remove=20reference=20to?= =?UTF-8?q?=20LatestStablePhpVersion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Core/Constants.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/phpmon/Common/Core/Constants.swift b/phpmon/Common/Core/Constants.swift index 3f2df26..64d13fa 100644 --- a/phpmon/Common/Core/Constants.swift +++ b/phpmon/Common/Core/Constants.swift @@ -9,12 +9,6 @@ import Cocoa struct Constants { - /** - * The latest PHP version that is considered to be stable at the time of release. - * This version number is currently not used (only as a default fallback). - */ - static let LatestStablePhpVersion = "8.1" - /** The minimum version of Valet that is recommended. If the installed version is older, a notification will be shown From c3e55df9fb43cd73e977979db3977d424937daa9 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 21 Nov 2022 23:55:37 +0100 Subject: [PATCH 092/181] =?UTF-8?q?=E2=9C=A8=20WIP:=20Add=20support=20for?= =?UTF-8?q?=20`nginx-full`,=20formulae=20tweaks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 10 ++ phpmon/Common/Core/Actions.swift | 31 +++--- phpmon/Common/Core/Homebrew.swift | 37 +++++++ phpmon/Domain/App/App.swift | 3 - phpmon/Domain/App/ServicesManager.swift | 97 +++++++++++++------ phpmon/Domain/App/Startup.swift | 2 +- .../Homebrew/HomebrewDiagnostics.swift | 11 +++ phpmon/Domain/Menu/MainMenu+Startup.swift | 9 +- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 16 ++- 9 files changed, 153 insertions(+), 63 deletions(-) create mode 100644 phpmon/Common/Core/Homebrew.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 5ad4364..6b26180 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -555,6 +555,10 @@ C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; }; C4C8E81B276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */; }; C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */; }; + C4CB6E65292C362C002E9027 /* Homebrew.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CB6E64292C362C002E9027 /* Homebrew.swift */; }; + C4CB6E66292C362C002E9027 /* Homebrew.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CB6E64292C362C002E9027 /* Homebrew.swift */; }; + C4CB6E67292C362C002E9027 /* Homebrew.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CB6E64292C362C002E9027 /* Homebrew.swift */; }; + C4CB6E68292C362C002E9027 /* Homebrew.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CB6E64292C362C002E9027 /* Homebrew.swift */; }; C4CCBA6C275C567B008C7055 /* PMWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CCBA6B275C567B008C7055 /* PMWindowController.swift */; }; C4CCBA6D275C567B008C7055 /* PMWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CCBA6B275C567B008C7055 /* PMWindowController.swift */; }; C4CDA893288F1A71007CE25F /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CDA892288F1A71007CE25F /* Keys.swift */; }; @@ -862,6 +866,7 @@ C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveFileSystem.swift; sourceTree = ""; }; C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "App+ConfigWatch.swift"; sourceTree = ""; }; C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpConfigWatcher.swift; sourceTree = ""; }; + C4CB6E64292C362C002E9027 /* Homebrew.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Homebrew.swift; sourceTree = ""; }; C4CCBA6B275C567B008C7055 /* PMWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PMWindowController.swift; sourceTree = ""; }; C4CDA892288F1A71007CE25F /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = ""; }; C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Switcher.swift"; sourceTree = ""; }; @@ -1083,6 +1088,7 @@ C4C1019A27C65C6F001FACC2 /* Process.swift */, C40C7F2F27722E8D00DDDCDC /* Logger.swift */, C417DC73277614690015E6EE /* Helpers.swift */, + C4CB6E64292C362C002E9027 /* Homebrew.swift */, ); path = Core; sourceTree = ""; @@ -2094,6 +2100,7 @@ C4B97B75275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */, C41C02A627E60D7A009F26CB /* SiteScanner.swift in Sources */, C464ADAC275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */, + C4CB6E65292C362C002E9027 /* Homebrew.swift in Sources */, C464ADB2275A87CA003FCD53 /* DomainListCellProtocol.swift in Sources */, C4EE188422D3386B00E126E5 /* Constants.swift in Sources */, C493084A279F331F009C240B /* AddSiteVC.swift in Sources */, @@ -2250,6 +2257,7 @@ C471E7E328F9BAC20021E251 /* Logger.swift in Sources */, C471E7FD28F9BACE0021E251 /* HomebrewService.swift in Sources */, C471E7E428F9BAC20021E251 /* Helpers.swift in Sources */, + C4CB6E67292C362C002E9027 /* Homebrew.swift in Sources */, C45E2A77291992DA005C7CFD /* FeatureTestCase.swift in Sources */, C471E82028F9BB290021E251 /* NginxConfigurationFile.swift in Sources */, C471E7D428F9BA8F0021E251 /* ActiveFileSystem.swift in Sources */, @@ -2413,6 +2421,7 @@ C471E7EA28F9BAC30021E251 /* Logger.swift in Sources */, C471E7FB28F9BACE0021E251 /* HomebrewService.swift in Sources */, C471E7EB28F9BAC30021E251 /* Helpers.swift in Sources */, + C4CB6E68292C362C002E9027 /* Homebrew.swift in Sources */, C4181F1128FAF9330042EA28 /* UITestCase.swift in Sources */, C471E81F28F9BB290021E251 /* NginxConfigurationFile.swift in Sources */, C471E7BF28F9B90F0021E251 /* StartupTest.swift in Sources */, @@ -2502,6 +2511,7 @@ C485707B28BF458900539B36 /* VersionPopoverView.swift in Sources */, C4E2E85D28FC282B003B070C /* TestableConfiguration.swift in Sources */, C485706E28BF451C00539B36 /* OnboardingWindowController.swift in Sources */, + C4CB6E66292C362C002E9027 /* Homebrew.swift in Sources */, C43603A1275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */, C4C3643A28AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */, C42759682627662800093CAE /* NSMenuExtension.swift in Sources */, diff --git a/phpmon/Common/Core/Actions.swift b/phpmon/Common/Core/Actions.swift index 3963ded..ed62f17 100644 --- a/phpmon/Common/Core/Actions.swift +++ b/phpmon/Common/Core/Actions.swift @@ -13,31 +13,32 @@ class Actions { // MARK: - Services public static func restartPhpFpm() async { - await brew("services restart \(PhpEnv.phpInstall.formula)", sudo: true) + await brew("services restart \(Homebrew.Formulae.php.name)", sudo: Homebrew.Formulae.php.elevated) } public static func restartNginx() async { - await brew("services restart nginx", sudo: true) + await brew("services restart \(Homebrew.Formulae.nginx.name)", sudo: Homebrew.Formulae.nginx.elevated) } public static func restartDnsMasq() async { - await brew("services restart dnsmasq", sudo: true) + await brew("services restart \(Homebrew.Formulae.dnsmasq.name)", sudo: Homebrew.Formulae.dnsmasq.elevated) } public static func stopValetServices() async { - await brew("services stop \(PhpEnv.phpInstall.formula)", sudo: true) - await brew("services stop nginx", sudo: true) - await brew("services stop dnsmasq", sudo: true) + await brew("services stop \(Homebrew.Formulae.php)", sudo: Homebrew.Formulae.php.elevated) + await brew("services stop \(Homebrew.Formulae.nginx)", sudo: Homebrew.Formulae.nginx.elevated) + await brew("services stop \(Homebrew.Formulae.dnsmasq)", sudo: Homebrew.Formulae.dnsmasq.elevated) } public static func fixHomebrewPermissions() throws { var servicesCommands = [ - "\(Paths.brew) services stop nginx", - "\(Paths.brew) services stop dnsmasq" + "\(Paths.brew) services stop \(Homebrew.Formulae.nginx)", + "\(Paths.brew) services stop \(Homebrew.Formulae.dnsmasq)" ] + var cellarCommands = [ - "chown -R \(Paths.whoami):admin \(Paths.cellarPath)/nginx", - "chown -R \(Paths.whoami):admin \(Paths.cellarPath)/dnsmasq" + "chown -R \(Paths.whoami):admin \(Paths.cellarPath)/\(Homebrew.Formulae.nginx)", + "chown -R \(Paths.whoami):admin \(Paths.cellarPath)/\(Homebrew.Formulae.dnsmasq)" ] PhpEnv.shared.availablePhpVersions.forEach { version in @@ -68,7 +69,7 @@ class Actions { public static func stopService(name: String) async { await brew( "services stop \(name)", - sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name } + sudo: ServicesManager.shared.services[name]?.formula.elevated ?? false ) await ServicesManager.loadHomebrewServices() } @@ -76,7 +77,7 @@ class Actions { public static func startService(name: String) async { await brew( "services start \(name)", - sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name } + sudo: ServicesManager.shared.services[name]?.formula.elevated ?? false ) await ServicesManager.loadHomebrewServices() } @@ -138,9 +139,9 @@ class Actions { public static func fixMyValet(completed: @escaping () -> Void) { InternalSwitcher().performSwitch(to: PhpEnv.brewPhpAlias, completion: { Task { // Restart all services asynchronously and fire callback upon completion - await brew("services restart dnsmasq", sudo: true) - await brew("services restart php", sudo: true) - await brew("services restart nginx", sudo: true) + await brew("services restart \(Homebrew.Formulae.dnsmasq)", sudo: Homebrew.Formulae.dnsmasq.elevated) + await brew("services restart \(Homebrew.Formulae.php)", sudo: Homebrew.Formulae.php.elevated) + await brew("services restart \(Homebrew.Formulae.nginx)", sudo: Homebrew.Formulae.nginx.elevated) completed() } }) diff --git a/phpmon/Common/Core/Homebrew.swift b/phpmon/Common/Core/Homebrew.swift new file mode 100644 index 0000000..02f5e0f --- /dev/null +++ b/phpmon/Common/Core/Homebrew.swift @@ -0,0 +1,37 @@ +// +// Homebrew.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 21/11/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class Homebrew { + struct Formulae { + static var php: HomebrewFormula { + return HomebrewFormula(PhpEnv.phpInstall.formula, elevated: true) + } + + static var nginx: HomebrewFormula { + return HomebrewDiagnostics.usesNginxFullFormula + ? HomebrewFormula("nginx-full", elevated: true) + : HomebrewFormula("nginx", elevated: true) + } + + static var dnsmasq: HomebrewFormula { + return HomebrewFormula("dnsmasq", elevated: true) + } + } +} + +class HomebrewFormula { + let name: String + let elevated: Bool + + init(_ name: String, elevated: Bool = true) { + self.name = name + self.elevated = elevated + } +} diff --git a/phpmon/Domain/App/App.swift b/phpmon/Domain/App/App.swift index 0b7da2a..491bd75 100644 --- a/phpmon/Domain/App/App.swift +++ b/phpmon/Domain/App/App.swift @@ -83,9 +83,6 @@ class App { /** List of detected (installed) applications that PHP Monitor can work with. */ var detectedApplications: [Application] = [] - /** The services manager, responsible for figuring out what services are active/inactive. */ - var services = ServicesManager.shared - /** The warning manager, responsible for keeping track of warnings. */ var warnings = WarningManager.shared diff --git a/phpmon/Domain/App/ServicesManager.swift b/phpmon/Domain/App/ServicesManager.swift index 08e46ef..02ec464 100644 --- a/phpmon/Domain/App/ServicesManager.swift +++ b/phpmon/Domain/App/ServicesManager.swift @@ -13,45 +13,78 @@ class ServicesManager: ObservableObject { static var shared = ServicesManager() - @Published var rootServices: [String: HomebrewService] = [:] - @Published var userServices: [String: HomebrewService] = [:] + private var formulae: [HomebrewFormula] - public static func loadHomebrewServices() async { - let rootServiceNames = [ - PhpEnv.phpInstall.formula, - "nginx", - "dnsmasq" + @Published var services: [String: ServiceWrapper] = [:] + + init() { + formulae = [ + Homebrew.Formulae.php, + Homebrew.Formulae.nginx, + Homebrew.Formulae.dnsmasq ] - let normalJson = await Shell - .pipe("sudo \(Paths.brew) services info --all --json") - .out.data(using: .utf8)! + let additionalFormulae = (Preferences.custom.services ?? []).map({ item in + return HomebrewFormula(item, elevated: false) + }) - let normalServices = try! JSONDecoder() - .decode([HomebrewService].self, from: normalJson) - .filter({ return rootServiceNames.contains($0.name) }) + formulae.append(contentsOf: additionalFormulae) - Task { @MainActor in - ServicesManager.shared.rootServices = Dictionary( - uniqueKeysWithValues: normalServices.map { ($0.name, $0) } - ) + services = Dictionary(uniqueKeysWithValues: formulae.map { ($0.name, ServiceWrapper(formula: $0)) }) + } + + public static func loadHomebrewServices() async { + Task { + let rootServiceNames = Self.shared.formulae + .filter { $0.elevated } + .map { $0.name } + + let rootJson = await Shell + .pipe("sudo \(Paths.brew) services info --all --json") + .out.data(using: .utf8)! + + let rootServices = try! JSONDecoder() + .decode([HomebrewService].self, from: rootJson) + .filter({ return rootServiceNames.contains($0.name) }) + + Task { @MainActor in + for service in rootServices { + Self.shared.services[service.name]!.service = service + } + } } - guard let userServiceNames = Preferences.custom.services else { return } + Task { + let userServiceNames = Self.shared.formulae + .filter { !$0.elevated } + .map { $0.name } - let rootJson = await Shell - .pipe("\(Paths.brew) services info --all --json") - .out - .data(using: .utf8)! + let normalJson = await Shell + .pipe("\(Paths.brew) services info --all --json") + .out.data(using: .utf8)! - let rootServices = try! JSONDecoder() - .decode([HomebrewService].self, from: rootJson) - .filter({ return userServiceNames.contains($0.name) }) + let userServices = try! JSONDecoder() + .decode([HomebrewService].self, from: normalJson) + .filter({ return userServiceNames.contains($0.name) }) - Task { @MainActor in - ServicesManager.shared.userServices = Dictionary( - uniqueKeysWithValues: rootServices.map { ($0.name, $0) } - ) + Task { @MainActor in + for service in userServices { + Self.shared.services[service.name]!.service = service + } + } + } + } + + /** + Service wrapper, that contains the Homebrew JSON output (if determined) and the formula. + This helps the app determine whether a service should run as an administrator or not. + */ + public class ServiceWrapper { + public let formula: HomebrewFormula + public var service: HomebrewService? + + init(formula: HomebrewFormula) { + self.formula = formula } } @@ -60,11 +93,11 @@ class ServicesManager: ObservableObject { */ func withDummyServices(_ services: [String: Bool]) -> Self { for (service, enabled) in services { - let item = HomebrewService.dummy(named: service, enabled: enabled) - self.rootServices[service] = item + let item = ServiceWrapper(formula: HomebrewFormula(service)) + item.service = HomebrewService.dummy(named: service, enabled: enabled) + self.services[service] = item } return self } - } diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index be840db..5d53b78 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -162,7 +162,7 @@ class Startup { EnvironmentCheck( command: { await HomebrewDiagnostics.loadInstalledTaps() - return await HomebrewDiagnostics.cannotLoadService("nginx") + return await HomebrewDiagnostics.cannotLoadService("dnsmasq") }, name: "`sudo \(Paths.brew) services info` JSON loaded", titleText: "startup.errors.services_json_error.title".localized, diff --git a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift index 21ce04a..4309ff8 100644 --- a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift +++ b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift @@ -34,6 +34,17 @@ class HomebrewDiagnostics { return installedTaps.contains("nicoverbruggen/cask") }() + /** + Determines whether to use the regular `nginx` or `nginx-full` formula. + */ + public static var usesNginxFullFormula: Bool = { + guard let destination = try? FileManager.default + .destinationOfSymbolicLink(atPath: "\(Paths.binPath)/nginx") else { return false } + + // Verify that the `nginx` binary is symlinked to a directory that includes `nginx-full`. + return destination.contains("/nginx-full/") + }() + /** 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`). diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 9a87858..2e1e30f 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -36,8 +36,13 @@ extension MainMenu { // Determine install method Log.info(HomebrewDiagnostics.customCaskInstalled - ? "The app has probably been installed via Homebrew Cask." - : "The app has probably been installed directly." + ? "[BREW] The app has probably been installed via Homebrew Cask." + : "[BREW] The app has probably been installed directly." + ) + + Log.info(HomebrewDiagnostics.usesNginxFullFormula + ? "[BREW] The app will be using the `nginx-full` formula." + : "[BREW] The app will be using the `nginx` formula." ) // Attempt to find out more info about Valet diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 1b09703..abfe51e 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -17,9 +17,9 @@ struct ServicesView: View { static func asMenuItem(perRow: Int = 3) -> NSMenuItem { let item = NSMenuItem() var services = [ - PhpEnv.phpInstall.formula, - "nginx", - "dnsmasq" + Homebrew.Formulae.php.name, + Homebrew.Formulae.nginx.name, + Homebrew.Formulae.dnsmasq.name ] if Preferences.custom.hasServices() { @@ -76,16 +76,12 @@ struct CheckmarkView: View { @EnvironmentObject var manager: ServicesManager public func hasAnyServices() -> Bool { - return !manager.rootServices.isEmpty + return !manager.services.isEmpty } public func active() -> Bool? { - if manager.rootServices.keys.contains(serviceName) { - return manager.rootServices[serviceName]!.running - } - - if manager.userServices.keys.contains(serviceName) { - return manager.userServices[serviceName]!.running + if manager.services.keys.contains(serviceName) { + return manager.services[serviceName]!.service?.running ?? nil } return nil From 3f8739dc30d7ba60668923322489e22a257df3e8 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 22 Nov 2022 00:14:46 +0100 Subject: [PATCH 093/181] =?UTF-8?q?=F0=9F=91=8C=20Fix=20timing=20issue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/App/ServicesManager.swift | 8 +++++--- phpmon/Domain/Menu/MainMenu+Startup.swift | 3 +++ phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 13 +++++-------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/phpmon/Domain/App/ServicesManager.swift b/phpmon/Domain/App/ServicesManager.swift index 02ec464..ecc907f 100644 --- a/phpmon/Domain/App/ServicesManager.swift +++ b/phpmon/Domain/App/ServicesManager.swift @@ -18,6 +18,8 @@ class ServicesManager: ObservableObject { @Published var services: [String: ServiceWrapper] = [:] init() { + Log.info("Initializing ServicesManager") + formulae = [ Homebrew.Formulae.php, Homebrew.Formulae.nginx, @@ -79,8 +81,8 @@ class ServicesManager: ObservableObject { Service wrapper, that contains the Homebrew JSON output (if determined) and the formula. This helps the app determine whether a service should run as an administrator or not. */ - public class ServiceWrapper { - public let formula: HomebrewFormula + public struct ServiceWrapper { + public var formula: HomebrewFormula public var service: HomebrewService? init(formula: HomebrewFormula) { @@ -93,7 +95,7 @@ class ServicesManager: ObservableObject { */ func withDummyServices(_ services: [String: Bool]) -> Self { for (service, enabled) in services { - let item = ServiceWrapper(formula: HomebrewFormula(service)) + var item = ServiceWrapper(formula: HomebrewFormula(service)) item.service = HomebrewService.dummy(named: service, enabled: enabled) self.services[service] = item } diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 2e1e30f..fe24bd3 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -34,6 +34,9 @@ extension MainMenu { // Determine what the `php` formula is aliased to await PhpEnv.shared.determinePhpAlias() + // Initialize preferences + _ = Preferences.shared + // Determine install method Log.info(HomebrewDiagnostics.customCaskInstalled ? "[BREW] The app has probably been installed via Homebrew Cask." diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index abfe51e..652f42f 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -16,15 +16,10 @@ struct ServicesView: View { static func asMenuItem(perRow: Int = 3) -> NSMenuItem { let item = NSMenuItem() - var services = [ - Homebrew.Formulae.php.name, - Homebrew.Formulae.nginx.name, - Homebrew.Formulae.dnsmasq.name - ] - if Preferences.custom.hasServices() { - services += Preferences.custom.services! - } + let services = ServicesManager.shared.services.keys.map({ item in + return item + }) let view = NSHostingView( rootView: Self( @@ -90,9 +85,11 @@ struct CheckmarkView: View { public func toggleService() async { if active()! { await Actions.stopService(name: serviceName) + await delay(seconds: 1.2) busy = false } else { await Actions.startService(name: serviceName) + await delay(seconds: 1.2) busy = false } } From 81183acea8c923ba01e7eaeb14c6be6391a630d8 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 6 Dec 2022 19:28:20 +0100 Subject: [PATCH 094/181] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Valet/Domains/DomainListable.swift | 32 ------------ .../Valet/Domains/ValetInteractable.swift | 20 -------- .../Integrations/Valet/Sites/ValetSite.swift | 50 ++++++++++++++++++- 3 files changed, 49 insertions(+), 53 deletions(-) diff --git a/phpmon/Domain/Integrations/Valet/Domains/DomainListable.swift b/phpmon/Domain/Integrations/Valet/Domains/DomainListable.swift index 81b336d..8a54576 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/DomainListable.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/DomainListable.swift @@ -25,35 +25,3 @@ protocol DomainListable { func getListableUrl() -> URL? } - -extension ValetSite { - - func getListableName() -> String { - return self.name - } - - func getListableSecured() -> Bool { - return self.secured - } - - func getListableAbsolutePath() -> String { - return self.absolutePath - } - - func getListablePhpVersion() -> String { - return self.servingPhpVersion - } - - func getListableKind() -> String { - return (self.aliasPath == nil) ? "linked" : "parked" - } - - func getListableType() -> String { - return self.driver ?? "ZZZ" - } - - func getListableUrl() -> URL? { - return URL(string: "\(self.secured ? "https://" : "http://")\(self.name).\(Valet.shared.config.tld)") - } - -} diff --git a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractable.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractable.swift index 21ecfab..b2bd5c7 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractable.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractable.swift @@ -19,23 +19,3 @@ protocol DomainInteractable { func unlink() async throws } - -extension ValetSite: DomainInteractable { - - func secure() async throws { - try await ValetInteractor.secure(site: self) - } - - func unsecure() async throws { - try await ValetInteractor.unsecure(site: self) - } - - func isolate(version: PhpVersionNumber) async throws { - try await ValetInteractor.isolate(site: self, version: version) - } - - func unlink() async throws { - try await ValetInteractor.unlink(site: self) - } - -} diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift index 7502996..d15d9a5 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift @@ -8,7 +8,7 @@ import Foundation -class ValetSite: DomainListable { +class ValetSite: DomainListable, DomainInteractable { /// Name of the site. Does not include the TLD. var name: String @@ -232,4 +232,52 @@ class ValetSite: DomainListable { return nil } + + // MARK: DomainListable + + func getListableName() -> String { + return self.name + } + + func getListableSecured() -> Bool { + return self.secured + } + + func getListableAbsolutePath() -> String { + return self.absolutePath + } + + func getListablePhpVersion() -> String { + return self.servingPhpVersion + } + + func getListableKind() -> String { + return (self.aliasPath == nil) ? "linked" : "parked" + } + + func getListableType() -> String { + return self.driver ?? "ZZZ" + } + + func getListableUrl() -> URL? { + return URL(string: "\(self.secured ? "https://" : "http://")\(self.name).\(Valet.shared.config.tld)") + } + + // MARK: DomainInteractable + + func secure() async throws { + try await ValetInteractor.secure(site: self) + } + + func unsecure() async throws { + try await ValetInteractor.unsecure(site: self) + } + + func isolate(version: PhpVersionNumber) async throws { + try await ValetInteractor.isolate(site: self, version: version) + } + + func unlink() async throws { + try await ValetInteractor.unlink(site: self) + } } From 31a0bb986fd382b5c8e4fac4f0baf7534b55e68b Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 6 Dec 2022 19:43:45 +0100 Subject: [PATCH 095/181] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Some=20odd=20refac?= =?UTF-8?q?toring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 20 +++++++++---------- phpmon/Domain/DomainList/DomainListVC.swift | 4 ++-- .../Valet/Domains/ValetInteractable.swift | 2 +- .../Valet/Domains/ValetInteractor.swift | 1 + ...mainListable.swift => ValetListable.swift} | 4 ++-- .../Valet/Proxies/ValetProxy.swift | 4 ++-- .../Integrations/Valet/Sites/ValetSite.swift | 6 +++--- phpmon/Domain/Integrations/Valet/Valet.swift | 2 +- 8 files changed, 22 insertions(+), 21 deletions(-) rename phpmon/Domain/Integrations/Valet/Domains/{DomainListable.swift => ValetListable.swift} (89%) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 6b26180..595cc1c 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -110,8 +110,8 @@ C42CFB1627DFDE7900862737 /* nginx-site.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1527DFDE7900862737 /* nginx-site.test */; }; C42CFB1827DFDFDC00862737 /* nginx-site-isolated.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */; }; C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */; }; - C42F26732805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; - C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; + C42F26732805B4B400938AC7 /* ValetListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* ValetListable.swift */; }; + C42F26742805B4B400938AC7 /* ValetListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* ValetListable.swift */; }; C42F26762805FEE200938AC7 /* nginx-secure-proxy.test in Resources */ = {isa = PBXBuildFile; fileRef = C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */; }; C43603A0275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */; }; C43603A1275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */; }; @@ -275,9 +275,9 @@ C471E82728F9BB310021E251 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; }; C471E82828F9BB310021E251 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; }; C471E82928F9BB330021E251 /* Valet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F792754499000D44ED0 /* Valet.swift */; }; - C471E82A28F9BB330021E251 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; + C471E82A28F9BB330021E251 /* ValetListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* ValetListable.swift */; }; C471E82B28F9BB340021E251 /* Valet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F792754499000D44ED0 /* Valet.swift */; }; - C471E82C28F9BB340021E251 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; + C471E82C28F9BB340021E251 /* ValetListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* ValetListable.swift */; }; C471E82D28F9BB650021E251 /* AlertableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */; }; C471E82E28F9BB650021E251 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; }; C471E82F28F9BB650021E251 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; }; @@ -777,7 +777,7 @@ C42CFB1527DFDE7900862737 /* nginx-site.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site.test"; sourceTree = ""; }; C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site-isolated.test"; sourceTree = ""; }; C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationTest.swift; sourceTree = ""; }; - C42F26722805B4B400938AC7 /* DomainListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListable.swift; sourceTree = ""; }; + C42F26722805B4B400938AC7 /* ValetListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetListable.swift; sourceTree = ""; }; C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-secure-proxy.test"; sourceTree = ""; }; C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Notifications.swift"; sourceTree = ""; }; C43A8A1925D9CD1000591B77 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; @@ -1032,7 +1032,7 @@ C40175B629030F7A00763A68 /* Domains */ = { isa = PBXGroup; children = ( - C42F26722805B4B400938AC7 /* DomainListable.swift */, + C42F26722805B4B400938AC7 /* ValetListable.swift */, C40175B129030F4600763A68 /* ValetInteractable.swift */, C40175B72903108900763A68 /* ValetInteractor.swift */, ); @@ -2032,7 +2032,7 @@ 54D9E0B427E4F51E003B9AD9 /* Key.swift in Sources */, C4297F7A28970D59004C4630 /* WarningView.swift in Sources */, C4C0E8E227F88B13002D32A9 /* ValetSiteScanner.swift in Sources */, - C42F26732805B4B400938AC7 /* DomainListable.swift in Sources */, + C42F26732805B4B400938AC7 /* ValetListable.swift in Sources */, 5420395F2613607600FB00FA /* Preferences.swift in Sources */, C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */, 54FCFD2A276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */, @@ -2238,7 +2238,7 @@ C471E82528F9BB2E0021E251 /* ComposerWindow.swift in Sources */, C471E80828F9BAD40021E251 /* PhpExtension.swift in Sources */, C471E7F928F9BACB0021E251 /* PhpSwitcher.swift in Sources */, - C471E82A28F9BB330021E251 /* DomainListable.swift in Sources */, + C471E82A28F9BB330021E251 /* ValetListable.swift in Sources */, C471E82728F9BB310021E251 /* HomebrewDiagnostics.swift in Sources */, C471E81C28F9BB250021E251 /* BetterAlert.swift in Sources */, C471E7DB28F9BA8F0021E251 /* RealShell.swift in Sources */, @@ -2401,7 +2401,7 @@ C4D3660E29113F20006BD146 /* System.swift in Sources */, C471E80428F9BAD40021E251 /* PhpExtension.swift in Sources */, C471E7F728F9BACB0021E251 /* PhpSwitcher.swift in Sources */, - C471E82C28F9BB340021E251 /* DomainListable.swift in Sources */, + C471E82C28F9BB340021E251 /* ValetListable.swift in Sources */, C471E82828F9BB310021E251 /* HomebrewDiagnostics.swift in Sources */, C471E81E28F9BB260021E251 /* BetterAlert.swift in Sources */, C471E7D228F9BA630021E251 /* ActiveFileSystem.swift in Sources */, @@ -2449,7 +2449,7 @@ 54D9E0B727E4F51E003B9AD9 /* HotKey.swift in Sources */, C413E43528DA3EB100AE33C7 /* TestableShellTest.swift in Sources */, C4205A7F27F4D21800191A39 /* ValetProxy.swift in Sources */, - C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */, + C42F26742805B4B400938AC7 /* ValetListable.swift in Sources */, C46EBC4528DB95F0007ACC74 /* ShellProtocol.swift in Sources */, C44B3A4728E5C70100718CB1 /* TimeIntervalExtension.swift in Sources */, C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */, diff --git a/phpmon/Domain/DomainList/DomainListVC.swift b/phpmon/Domain/DomainList/DomainListVC.swift index 964f6ea..19ba610 100644 --- a/phpmon/Domain/DomainList/DomainListVC.swift +++ b/phpmon/Domain/DomainList/DomainListVC.swift @@ -19,7 +19,7 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource // MARK: - Variables /// List of sites that will be displayed in this view. Originates from the `Valet` object. - var domains: [DomainListable] = [] + var domains: [ValetListable] = [] /// Array that contains various apps that might open a particular site directory. var applications: [Application] { @@ -48,7 +48,7 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource return domains[tableView.selectedRow] as? ValetProxy } - var selected: DomainListable? { + var selected: ValetListable? { if tableView.selectedRow == -1 { return nil } diff --git a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractable.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractable.swift index b2bd5c7..38156ec 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractable.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractable.swift @@ -8,7 +8,7 @@ import Foundation -protocol DomainInteractable { +protocol ValetInteractable { func secure() async throws diff --git a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift index 5352aed..7b2067a 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift @@ -8,6 +8,7 @@ import Foundation +#warning("ValetInteractor needs to be implemented and used") class ValetInteractor { public static func secure(site: ValetSite) async throws { // TODO diff --git a/phpmon/Domain/Integrations/Valet/Domains/DomainListable.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetListable.swift similarity index 89% rename from phpmon/Domain/Integrations/Valet/Domains/DomainListable.swift rename to phpmon/Domain/Integrations/Valet/Domains/ValetListable.swift index 8a54576..fb63faa 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/DomainListable.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/ValetListable.swift @@ -1,5 +1,5 @@ // -// DomainListable.swift +// ValetListable.swift // PHP Monitor // // Created by Nico Verbruggen on 12/04/2022. @@ -8,7 +8,7 @@ import Foundation -protocol DomainListable { +protocol ValetListable { func getListableName() -> String diff --git a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift b/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift index 83bb354..3bc067a 100644 --- a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift +++ b/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift @@ -8,7 +8,7 @@ import Foundation -class ValetProxy: DomainListable { +class ValetProxy: ValetListable { var domain: String var tld: String var target: String @@ -21,7 +21,7 @@ class ValetProxy: DomainListable { self.secured = FileSystem.fileExists("~/.config/valet/Certificates/\(self.domain).\(self.tld).key") } - // MARK: - DomainListable Protocol + // MARK: - ValetListable Protocol func getListableName() -> String { return self.domain diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift index d15d9a5..1367aa4 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift @@ -8,7 +8,7 @@ import Foundation -class ValetSite: DomainListable, DomainInteractable { +class ValetSite: ValetListable, ValetInteractable { /// Name of the site. Does not include the TLD. var name: String @@ -233,7 +233,7 @@ class ValetSite: DomainListable, DomainInteractable { return nil } - // MARK: DomainListable + // MARK: ValetListable func getListableName() -> String { return self.name @@ -263,7 +263,7 @@ class ValetSite: DomainListable, DomainInteractable { return URL(string: "\(self.secured ? "https://" : "http://")\(self.name).\(Valet.shared.config.tld)") } - // MARK: DomainInteractable + // MARK: ValetInteractable func secure() async throws { try await ValetInteractor.secure(site: self) diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 9a44bbe..83dca03 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -63,7 +63,7 @@ class Valet { /** Retrieve a list of all domains, including sites & proxies. */ - public static func getDomainListable() -> [DomainListable] { + public static func getDomainListable() -> [ValetListable] { return self.shared.sites + self.shared.proxies } From 073b7cf943bcd28ffeb9842feb87635b68f0c3f5 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 6 Dec 2022 20:53:42 +0100 Subject: [PATCH 096/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Interactions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 10 --------- .../Valet/Domains/ValetInteractable.swift | 21 ------------------- .../Valet/Domains/ValetInteractor.swift | 4 ++-- .../Valet/Proxies/ValetProxy.swift | 6 ++++++ .../Integrations/Valet/Sites/ValetSite.swift | 12 ++++------- 5 files changed, 12 insertions(+), 41 deletions(-) delete mode 100644 phpmon/Domain/Integrations/Valet/Domains/ValetInteractable.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 595cc1c..c195d59 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -35,10 +35,6 @@ 54FCFD2E276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD2C276C8D67004CE748 /* HotkeyPreferenceView.xib */; }; 54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */; }; 54FCFD31276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */; }; - C40175B229030F4600763A68 /* ValetInteractable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B129030F4600763A68 /* ValetInteractable.swift */; }; - C40175B329030F4600763A68 /* ValetInteractable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B129030F4600763A68 /* ValetInteractable.swift */; }; - C40175B429030F4600763A68 /* ValetInteractable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B129030F4600763A68 /* ValetInteractable.swift */; }; - C40175B529030F4600763A68 /* ValetInteractable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B129030F4600763A68 /* ValetInteractable.swift */; }; C40175B82903108900763A68 /* ValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B72903108900763A68 /* ValetInteractor.swift */; }; C40175B92903108900763A68 /* ValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B72903108900763A68 /* ValetInteractor.swift */; }; C40175BA2903108900763A68 /* ValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B72903108900763A68 /* ValetInteractor.swift */; }; @@ -725,7 +721,6 @@ 54FCFD29276C8AA4004CE748 /* CheckboxPreferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxPreferenceView.swift; sourceTree = ""; }; 54FCFD2C276C8D67004CE748 /* HotkeyPreferenceView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HotkeyPreferenceView.xib; sourceTree = ""; }; 54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HotkeyPreferenceView.swift; sourceTree = ""; }; - C40175B129030F4600763A68 /* ValetInteractable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetInteractable.swift; sourceTree = ""; }; C40175B72903108900763A68 /* ValetInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetInteractor.swift; sourceTree = ""; }; C40508AE28ADA23D008FAC1F /* NoDomainResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoDomainResultsView.swift; sourceTree = ""; }; C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSMenuItemExtension.swift; sourceTree = ""; }; @@ -1033,7 +1028,6 @@ isa = PBXGroup; children = ( C42F26722805B4B400938AC7 /* ValetListable.swift */, - C40175B129030F4600763A68 /* ValetInteractable.swift */, C40175B72903108900763A68 /* ValetInteractor.swift */, ); path = Domains; @@ -1960,7 +1954,6 @@ C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */, C4D8016622B1584700C6DA1B /* Startup.swift in Sources */, C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */, - C40175B229030F4600763A68 /* ValetInteractable.swift in Sources */, C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */, C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */, C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */, @@ -2168,7 +2161,6 @@ C471E85C28F9BB650021E251 /* DomainListWindowController.swift in Sources */, C471E85D28F9BB650021E251 /* DomainListVC.swift in Sources */, C471E85E28F9BB650021E251 /* DomainListVC+ContextMenu.swift in Sources */, - C40175B429030F4600763A68 /* ValetInteractable.swift in Sources */, C4E2E86628FC2F1B003B070C /* XCPMApplication.swift in Sources */, C471E85F28F9BB650021E251 /* DomainListVC+Actions.swift in Sources */, C471E86028F9BB650021E251 /* SelectionVC.swift in Sources */, @@ -2277,7 +2269,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C40175B529030F4600763A68 /* ValetInteractable.swift in Sources */, C471E89028F9BB8F0021E251 /* AlertableError.swift in Sources */, C471E89128F9BB8F0021E251 /* Errors.swift in Sources */, C471E89228F9BB8F0021E251 /* Alert.swift in Sources */, @@ -2522,7 +2513,6 @@ C485706D28BF450900539B36 /* NSMenuItemExtension.swift in Sources */, C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */, C495F5B028A42E080087F70A /* EnvironmentCheck.swift in Sources */, - C40175B329030F4600763A68 /* ValetInteractable.swift in Sources */, C41E871B2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */, C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */, C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */, diff --git a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractable.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractable.swift deleted file mode 100644 index 38156ec..0000000 --- a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractable.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// ValetInteractable.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 21/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. -// - -import Foundation - -protocol ValetInteractable { - - func secure() async throws - - func unsecure() async throws - - func isolate(version: PhpVersionNumber) async throws - - func unlink() async throws - -} diff --git a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift index 7b2067a..0e90616 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift @@ -10,11 +10,11 @@ import Foundation #warning("ValetInteractor needs to be implemented and used") class ValetInteractor { - public static func secure(site: ValetSite) async throws { + public static func toggleSecure(site: ValetSite) async throws { // TODO } - public static func unsecure(site: ValetSite) async throws { + public static func toggleSecure(proxy: ValetProxy) async throws { // TODO } diff --git a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift b/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift index 3bc067a..d0ea2ab 100644 --- a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift +++ b/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift @@ -50,4 +50,10 @@ class ValetProxy: ValetListable { func getListableUrl() -> URL? { return URL(string: "\(self.secured ? "https://" : "http://")\(self.domain).\(self.tld)") } + + // MARK: - Interactions + + func toggleSecure() async throws { + try await ValetInteractor.toggleSecure(proxy: self) + } } diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift index 1367aa4..86ddb88 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift @@ -8,7 +8,7 @@ import Foundation -class ValetSite: ValetListable, ValetInteractable { +class ValetSite: ValetListable { /// Name of the site. Does not include the TLD. var name: String @@ -263,14 +263,10 @@ class ValetSite: ValetListable, ValetInteractable { return URL(string: "\(self.secured ? "https://" : "http://")\(self.name).\(Valet.shared.config.tld)") } - // MARK: ValetInteractable + // MARK: - Interactions - func secure() async throws { - try await ValetInteractor.secure(site: self) - } - - func unsecure() async throws { - try await ValetInteractor.unsecure(site: self) + func toggleSecure() async throws { + try await ValetInteractor.toggleSecure(site: self) } func isolate(version: PhpVersionNumber) async throws { From 5f39cd757aceb00c0e2bd94763855f1c780538eb Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 9 Dec 2022 18:05:31 +0100 Subject: [PATCH 097/181] =?UTF-8?q?=F0=9F=93=9D=20Update=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b5c73c8..b5bcef9 100644 --- a/README.md +++ b/README.md @@ -80,19 +80,50 @@ If you're still having issues, here's a few common questions & answers, as well Which versions of PHP are supported?
    -
  • PHP 5.6 (only if you are running Valet 2)
  • -
  • PHP 7.0
  • -
  • PHP 7.1
  • -
  • PHP 7.2
  • -
  • PHP 7.3
  • -
  • PHP 7.4
  • -
  • PHP 8.0
  • -
  • PHP 8.1
  • -
  • PHP 8.2 (experimental)
  • +
  • PHP 5.6 (backport + only if you are running Valet 2)
  • +
  • PHP 7.0 (backport)
  • +
  • PHP 7.1 (backport)
  • +
  • PHP 7.2 (backport)
  • +
  • PHP 7.3 (backport)
  • +
  • PHP 7.4 (backport)
  • +
  • PHP 8.0 (official support)
  • +
  • PHP 8.1 (official support)
  • +
  • PHP 8.2 (latest version)
  • +
  • PHP 8.3-dev (experimental)
For more details, consult the [constants file](https://github.com/nicoverbruggen/phpmon/blob/main/phpmon/Common/Core/Constants.swift#L16) file to see which versions are supported. +Backports are available via [this tap](https://github.com/shivammathur/homebrew-php). For more information about those backports, please see the next FAQ entry. + + + +
+How do I install additional versions of PHP, including legacy versions? + +Assuming you have installed the `php` formula, the latest stable version of PHP is installed. At the time of writing, this is PHP 8.2. + +You can install other supported versions of PHP out of the box, so `php@8.0` and `php@8.1` at the time of writing. + +If you wish to install older (officially unsupported) versions of PHP for local use, you can do so by using [Shivam Mathur's tap](https://github.com/shivammathur/homebrew-php): + +```sh +brew tap shivammathur/php +``` + +You may find that this tap is already in use: if you've used Valet before, it automatically uses this tap for legacy versions of PHP. + +You can then install those older versions: + +```sh +brew install php@7.0 +brew install php@7.1 +... +``` + +**Always make sure to restart PHP Monitor after installing or upgrading PHP versions!** + +> *Note*: Using this tap may cause [temporary alias conflicts](https://github.com/nicoverbruggen/phpmon/issues/54#issuecomment-979789724) while the core tap alias and the tap's alias refer to a different version of PHP, but this is generally speaking a minor inconvenience, since this normally only applies when a new PHP version releases.
From 4bfde7b062290d3c8e9137e3d117101f2478919f Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 9 Dec 2022 18:26:56 +0100 Subject: [PATCH 098/181] =?UTF-8?q?=F0=9F=91=8C=20Add=20warning=20about=20?= =?UTF-8?q?https=20proxy=20subjects?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/App/Base.lproj/Main.storyboard | 27 +++++++++++--------- phpmon/Domain/DomainList/AddProxyVC.swift | 10 ++++++-- phpmon/Localizable.strings | 3 +++ 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/phpmon/Domain/App/Base.lproj/Main.storyboard b/phpmon/Domain/App/Base.lproj/Main.storyboard index 89132db..d7aa2aa 100644 --- a/phpmon/Domain/App/Base.lproj/Main.storyboard +++ b/phpmon/Domain/App/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -1112,17 +1112,17 @@ Gw - + - + - + - + @@ -1149,7 +1149,7 @@ Gw - + @@ -1176,7 +1176,7 @@ Gw - + + + + @@ -1223,7 +1226,7 @@ Gw - + @@ -1239,7 +1242,7 @@ Gw - + diff --git a/phpmon/Domain/DomainList/AddProxyVC.swift b/phpmon/Domain/DomainList/AddProxyVC.swift index ca8bcdb..f2363e0 100644 --- a/phpmon/Domain/DomainList/AddProxyVC.swift +++ b/phpmon/Domain/DomainList/AddProxyVC.swift @@ -158,8 +158,14 @@ class AddProxyVC: NSViewController, NSTextFieldDelegate { return } - previewText.stringValue = "domain_list.add.proxy_available" - .localized( + var translationKey = "domain_list.add.proxy_available" + + if inputProxySubject.stringValue.starts(with: "https://") { + translationKey = "domain_list.add.proxy_https_warning" + } + + previewText.stringValue = + translationKey.localized( inputProxySubject.stringValue, buttonSecure.state == .on ? "https" : "http", inputDomainName.stringValue, diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index fa14f71..468ede5 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -145,6 +145,9 @@ "domain_list.add.domain_name" = "Domain name"; "domain_list.add.create_proxy" = "Create Proxy"; "domain_list.add.proxy_available" = "%@ will be proxied and will be available via: %@://%@.%@"; +"domain_list.add.proxy_https_warning" = "%@ will be proxied and will be available via: %@://%@.%@. + +(!) IMPORTANT: This proxy may not work until you manually add `proxy_ssl_verify off;` to the nginx configuration file for this domain. It is recommended that you use an unsecured domain as the proxy subject."; // ADD SITE TO DOMAINS LIST From 04c78eba35d2f9ca8b6e0fd643f23f1bc934fd82 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 13 Dec 2022 20:24:48 +0100 Subject: [PATCH 099/181] =?UTF-8?q?=F0=9F=91=8C=20Generate=20a=20new=20JSO?= =?UTF-8?q?N=20file=20for=20current=20dev=20env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Shared/TestableConfigurations.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Shared/TestableConfigurations.swift b/tests/Shared/TestableConfigurations.swift index c7aa016..49547f6 100644 --- a/tests/Shared/TestableConfigurations.swift +++ b/tests/Shared/TestableConfigurations.swift @@ -66,7 +66,7 @@ class TestableConfigurations { : .instant("php"), "ls /opt/homebrew/opt | grep php@" : .instant("php@8.1"), - "sudo /opt/homebrew/bin/brew services info nginx --json" + "sudo /opt/homebrew/bin/brew services info dnsmasq --json" : .delayed(0.2, """ [ { From 1f165058b20cde0738bdec618a54f9dbeac00f0f Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 13 Dec 2022 22:56:36 +0100 Subject: [PATCH 100/181] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Rework=20interacti?= =?UTF-8?q?on=20with=20Valet=20commands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcschemes/PHP Monitor DEV.xcscheme | 2 +- .../DomainList/DomainListVC+Actions.swift | 154 ++++++++++-------- .../Valet/Domains/ValetInteractor.swift | 37 ++++- .../Valet/Proxies/ValetProxy.swift | 10 +- 4 files changed, 133 insertions(+), 70 deletions(-) diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index b0a4d86..8642ae0 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -91,7 +91,7 @@ + isEnabled = "NO"> Void) { + let rowToReload = tableView.selectedRow + + waitAndExecute { + await Shell.quiet(command) + } completion: { [self] in + beforeCellReload() + tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0, 1, 2, 3, 4]) + tableView.deselectRow(rowToReload) + tableView.selectRowIndexes([rowToReload], byExtendingSelection: true) } } - func toggleSecureForProxy() { - let originalSecureStatus = selectedProxy!.secured - let selectedProxy = selectedProxy! + private func reloadSelectedRow() { + tableView.reloadData(forRowIndexes: [tableView.selectedRow], columnIndexes: [0, 1, 2, 3, 4]) + tableView.deselectRow(tableView.selectedRow) + tableView.selectRowIndexes([tableView.selectedRow], byExtendingSelection: true) + } - self.waitAndExecute { - // 1. Remove the original proxy - await Shell.quiet("\(Paths.valet) unproxy \(selectedProxy.domain)") + // MARK: - Interactions with `valet` or terminal - // 2. Add a new proxy, which is either secured/unsecured - let secure = originalSecureStatus ? "" : " --secure" - await Shell.quiet("\(Paths.valet) proxy \(selectedProxy.domain) \(selectedProxy.target)\(secure)") + @objc func toggleSecure() { + if selected is ValetSite { + Task { toggleSecureForSite() } + } else { + Task { await toggleSecureForProxy() } + } + } - // 3. Restart nginx - await Actions.restartNginx() + func toggleSecureForProxy() async { + guard let proxy = selectedProxy else { return } - // 4. Reload site list - Task { @MainActor in - App.shared.domainListWindowController?.pressedReload(nil) - } + do { + // Toggle secure + try await proxy.toggleSecure() + + // Reload the UI + self.reloadSelectedRow() + + // Send a notification about the new status (if applicable) + LocalNotification.send( + title: "domain_list.alerts_status_changed.title".localized, + subtitle: "domain_list.alerts_status_changed.desc" + .localized( + "\(proxy.domain).\(Valet.shared.config.tld)", + proxy.secured + ), + preference: .notifyAboutSecureToggle + ) + } catch { + let error = error as! ValetInteractionError + + BetterAlert() + .withInformation( + title: "domain_list.alerts_status_not_changed.title".localized, + subtitle: "domain_list.alerts_status_not_changed.desc".localized(error.command) + ) + .withPrimary(text: "OK") + .show() } } @@ -79,38 +145,6 @@ extension DomainListVC { } } - @objc func openInBrowser() { - guard let selected = self.selected else { - return - } - - guard let url = selected.getListableUrl() else { - BetterAlert() - .withInformation( - title: "domain_list.alert.invalid_folder_name".localized, - subtitle: "domain_list.alert.invalid_folder_name_desc".localized - ) - .withPrimary(text: "OK") - .show() - return - } - - NSWorkspace.shared.open(url) - } - - @objc func openInFinder() async { - await Shell.quiet("open '\(selectedSite!.absolutePath)'") - } - - @objc func openInTerminal() async { - await Shell.quiet("open -b com.apple.terminal '\(selectedSite!.absolutePath)'") - } - - @objc func openWithEditor(sender: EditorMenuItem) async { - guard let editor = sender.editor else { return } - await editor.openDirectory(file: selectedSite!.absolutePath) - } - @objc func isolateSite(sender: PhpMenuItem) { let command = "sudo \(Paths.valet) isolate php@\(sender.version) --site '\(self.selectedSite!.name)' && exit;" @@ -178,25 +212,11 @@ extension DomainListVC { style: .critical, onFirstButtonPressed: { self.waitAndExecute { - Task { await Shell.quiet("valet unproxy '\(proxy.domain)'") } + Task { await proxy.remove() } } completion: { Task { await self.reloadDomains() } } } ) } - - private func performAction(command: String, beforeCellReload: @escaping () -> Void) { - let rowToReload = tableView.selectedRow - - waitAndExecute { - await Shell.quiet(command) - } completion: { [self] in - beforeCellReload() - tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0, 1, 2, 3, 4]) - tableView.deselectRow(rowToReload) - tableView.selectRowIndexes([rowToReload], byExtendingSelection: true) - } - } - } diff --git a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift index 0e90616..318b942 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift @@ -8,6 +8,11 @@ import Foundation +struct ValetInteractionError: Error { + /// The command the user should try (and failed). + var command: String +} + #warning("ValetInteractor needs to be implemented and used") class ValetInteractor { public static func toggleSecure(site: ValetSite) async throws { @@ -15,7 +20,33 @@ class ValetInteractor { } public static func toggleSecure(proxy: ValetProxy) async throws { - // TODO + // Keep track of the original status (secure or not?) + let originalSecureStatus = proxy.secured + + // Build the list of commands we will need to run + let commands: [String] = [ + // Unproxy the given domain + "\(Paths.valet) unproxy \(proxy.domain)", + // Re-create the proxy (with the inverse secured status) + originalSecureStatus + ? "\(Paths.valet) proxy \(proxy.domain) \(proxy.target)" + : "\(Paths.valet) proxy \(proxy.domain) \(proxy.target) --secure" + ] + + for command in commands { + await Shell.quiet(command) + } + + // Check if the secured status has actually changed + proxy.determineSecured() + if proxy.secured == originalSecureStatus { + throw ValetInteractionError( + command: commands.joined(separator: " && ") + ) + } + + // Restart nginx to load the new configuration + await Actions.restartNginx() } public static func isolate(site: ValetSite, version: PhpVersionNumber) async throws { @@ -25,4 +56,8 @@ class ValetInteractor { public static func unlink(site: ValetSite) async throws { // TODO } + + public static func remove(proxy: ValetProxy) async throws { + await Shell.quiet("valet unproxy '\(proxy.domain)'") + } } diff --git a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift b/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift index d0ea2ab..ce118e1 100644 --- a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift +++ b/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift @@ -18,7 +18,7 @@ class ValetProxy: ValetListable { self.domain = configuration.domain self.tld = configuration.tld self.target = configuration.proxy! - self.secured = FileSystem.fileExists("~/.config/valet/Certificates/\(self.domain).\(self.tld).key") + self.determineSecured() } // MARK: - ValetListable Protocol @@ -53,7 +53,15 @@ class ValetProxy: ValetListable { // MARK: - Interactions + func determineSecured() { + self.secured = FileSystem.fileExists("~/.config/valet/Certificates/\(self.domain).\(self.tld).key") + } + func toggleSecure() async throws { try await ValetInteractor.toggleSecure(proxy: self) } + + func remove() async { + try! await ValetInteractor.remove(proxy: self) + } } From cb98d40befc157b08ee2412bd1e060fae21f30b7 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 13 Dec 2022 23:02:30 +0100 Subject: [PATCH 101/181] =?UTF-8?q?=F0=9F=91=8C=20Fix=20crash=20with=20new?= =?UTF-8?q?=20secure/unsecure=20mechanism?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/DomainList/DomainListVC+Actions.swift | 10 +++++++--- phpmon/Localizable.strings | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/phpmon/Domain/DomainList/DomainListVC+Actions.swift b/phpmon/Domain/DomainList/DomainListVC+Actions.swift index cfc8638..389f538 100644 --- a/phpmon/Domain/DomainList/DomainListVC+Actions.swift +++ b/phpmon/Domain/DomainList/DomainListVC+Actions.swift @@ -81,19 +81,23 @@ extension DomainListVC { // Toggle secure try await proxy.toggleSecure() - // Reload the UI - self.reloadSelectedRow() - // Send a notification about the new status (if applicable) LocalNotification.send( title: "domain_list.alerts_status_changed.title".localized, subtitle: "domain_list.alerts_status_changed.desc" .localized( + // 1. The domain that was secured is listed "\(proxy.domain).\(Valet.shared.config.tld)", + // 2. What the domain is is listed (secure / unsecure) proxy.secured + ? "domain_list.alerts_status_secure".localized + : "domain_list.alerts_status_secure".localized ), preference: .notifyAboutSecureToggle ) + + // Reload the UI (do this last so we don't invalidate the proxy) + self.reloadSelectedRow() } catch { let error = error as! ValetInteractionError diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 468ede5..12986a8 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -121,6 +121,8 @@ "domain_list.alerts_status_changed.title" = "SSL Status Changed"; "domain_list.alerts_status_changed.desc" = "The domain '%@' is now %@."; +"domain_list.alerts_status_secure" = "secure"; +"domain_list.alerts_status_unsecure" = "unsecure"; "domain_list.confirm_unlink" = "Are you sure you want to unlink '%@'?"; "domain_list.confirm_unlink_desc" = "No files will be removed. You can always link the folder again by clicking on the + button and selecting the original folder."; From 912d9e7423c14108879ba5d38630cd6afeeb3bc8 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 13 Dec 2022 23:21:15 +0100 Subject: [PATCH 102/181] =?UTF-8?q?=E2=9C=A8=20Add=20FakeValetInteractor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcschemes/PHP Monitor DEV.xcscheme | 2 +- .../Testables/TestableConfiguration.swift | 2 + phpmon/Domain/DomainList/AddProxyVC.swift | 1 + phpmon/Domain/DomainList/AddSiteVC.swift | 1 + .../DomainList/DomainListVC+Actions.swift | 105 ++++++++---------- .../Valet/Domains/ValetInteractor.swift | 47 +++++++- .../Valet/Proxies/ValetProxy.swift | 4 +- .../Integrations/Valet/Sites/ValetSite.swift | 6 +- 8 files changed, 97 insertions(+), 71 deletions(-) diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index 8642ae0..b0a4d86 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -91,7 +91,7 @@ + isEnabled = "YES"> String { diff --git a/phpmon/Domain/DomainList/AddProxyVC.swift b/phpmon/Domain/DomainList/AddProxyVC.swift index f2363e0..6d0fe81 100644 --- a/phpmon/Domain/DomainList/AddProxyVC.swift +++ b/phpmon/Domain/DomainList/AddProxyVC.swift @@ -72,6 +72,7 @@ class AddProxyVC: NSViewController, NSTextFieldDelegate { App.shared.domainListWindowController?.contentVC.setUIBusy() Task { // Ensure we proxy the site asynchronously and reload UI on main thread again + #warning("Creating a proxy should happen via the ValetInteractor") await Shell.quiet("\(Paths.valet) proxy \(domain) \(proxyName)\(secure)") await Actions.restartNginx() diff --git a/phpmon/Domain/DomainList/AddSiteVC.swift b/phpmon/Domain/DomainList/AddSiteVC.swift index 17fd285..b4f5f71 100644 --- a/phpmon/Domain/DomainList/AddSiteVC.swift +++ b/phpmon/Domain/DomainList/AddSiteVC.swift @@ -71,6 +71,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate { // Adding `valet links` is a workaround for Valet malforming the config.json file // TODO: I will have to investigate and report this behaviour if possible + #warning("Linking a site should happen via the ValetInteractor") Task { await Shell.quiet("cd '\(path)' && \(Paths.valet) link '\(name)' && valet links") } dismissView(outcome: .OK) diff --git a/phpmon/Domain/DomainList/DomainListVC+Actions.swift b/phpmon/Domain/DomainList/DomainListVC+Actions.swift index 389f538..2552e1c 100644 --- a/phpmon/Domain/DomainList/DomainListVC+Actions.swift +++ b/phpmon/Domain/DomainList/DomainListVC+Actions.swift @@ -68,7 +68,7 @@ extension DomainListVC { @objc func toggleSecure() { if selected is ValetSite { - Task { toggleSecureForSite() } + Task { await toggleSecureForSite() } } else { Task { await toggleSecureForProxy() } } @@ -78,74 +78,33 @@ extension DomainListVC { guard let proxy = selectedProxy else { return } do { - // Toggle secure + // Recreate proxy as secure or unsecured proxy try await proxy.toggleSecure() - // Send a notification about the new status (if applicable) - LocalNotification.send( - title: "domain_list.alerts_status_changed.title".localized, - subtitle: "domain_list.alerts_status_changed.desc" - .localized( - // 1. The domain that was secured is listed - "\(proxy.domain).\(Valet.shared.config.tld)", - // 2. What the domain is is listed (secure / unsecure) - proxy.secured - ? "domain_list.alerts_status_secure".localized - : "domain_list.alerts_status_secure".localized - ), - preference: .notifyAboutSecureToggle - ) - + self.notifyAboutModifiedSecureStatus(domain: proxy.domain, secured: proxy.secured) // Reload the UI (do this last so we don't invalidate the proxy) self.reloadSelectedRow() } catch { + // Notify the user about a failed command let error = error as! ValetInteractionError - - BetterAlert() - .withInformation( - title: "domain_list.alerts_status_not_changed.title".localized, - subtitle: "domain_list.alerts_status_not_changed.desc".localized(error.command) - ) - .withPrimary(text: "OK") - .show() + self.notifyAboutFailedSecureStatus(command: error.command) } } - func toggleSecureForSite() { - let rowToReload = tableView.selectedRow - let originalSecureStatus = selectedSite!.secured - let action = selectedSite!.secured ? "unsecure" : "secure" - let selectedSite = selectedSite! - let command = "cd '\(selectedSite.absolutePath)' && sudo \(Paths.valet) \(action) && exit;" + func toggleSecureForSite() async { + guard let site = selectedSite else { return } - waitAndExecute { - await Shell.quiet(command) - } completion: { [self] in - selectedSite.determineSecured() - if selectedSite.secured == originalSecureStatus { - BetterAlert() - .withInformation( - title: "domain_list.alerts_status_not_changed.title".localized, - subtitle: "domain_list.alerts_status_not_changed.desc".localized(command) - ) - .withPrimary(text: "OK") - .show() - } else { - let newState = selectedSite.secured ? "secured" : "unsecured" - LocalNotification.send( - title: "domain_list.alerts_status_changed.title".localized, - subtitle: "domain_list.alerts_status_changed.desc" - .localized( - "\(selectedSite.name).\(Valet.shared.config.tld)", - newState - ), - preference: .notifyAboutSecureToggle - ) - } - - tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0, 1, 2, 3, 4]) - tableView.deselectRow(rowToReload) - tableView.selectRowIndexes([rowToReload], byExtendingSelection: true) + do { + // Instruct Valet to secure or unsecure a site + try await site.toggleSecure() + // Send a notification about the new status (if applicable) + self.notifyAboutModifiedSecureStatus(domain: site.name, secured: site.secured) + // Reload the UI (do this last so we don't invalidate the proxy) + self.reloadSelectedRow() + } catch { + // Notify the user about a failed command + let error = error as! ValetInteractionError + self.notifyAboutFailedSecureStatus(command: error.command) } } @@ -223,4 +182,32 @@ extension DomainListVC { } ) } + + // MARK: - Alerts & Modals + + private func notifyAboutModifiedSecureStatus(domain: String, secured: Bool) { + LocalNotification.send( + title: "domain_list.alerts_status_changed.title".localized, + subtitle: "domain_list.alerts_status_changed.desc" + .localized( + // 1. The domain that was secured is listed + "\(domain).\(Valet.shared.config.tld)", + // 2. What the domain is is listed (secure / unsecure) + secured + ? "domain_list.alerts_status_secure".localized + : "domain_list.alerts_status_unsecure".localized + ), + preference: .notifyAboutSecureToggle + ) + } + + private func notifyAboutFailedSecureStatus(command: String) { + BetterAlert() + .withInformation( + title: "domain_list.alerts_status_not_changed.title".localized, + subtitle: "domain_list.alerts_status_not_changed.desc".localized(command) + ) + .withPrimary(text: "OK") + .show() + } } diff --git a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift index 318b942..0e9722d 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift @@ -15,11 +15,31 @@ struct ValetInteractionError: Error { #warning("ValetInteractor needs to be implemented and used") class ValetInteractor { - public static func toggleSecure(site: ValetSite) async throws { - // TODO + static var shared = ValetInteractor() + + public static func useFake() { + ValetInteractor.shared = FakeValetInteractor() } - public static func toggleSecure(proxy: ValetProxy) async throws { + public func toggleSecure(site: ValetSite) async throws { + // Keep track of the original status (secure or not?) + let originalSecureStatus = site.secured + + // Keep track of the command we wish to run + let action = site.secured ? "unsecure" : "secure" + let command = "cd '\(site.absolutePath)' && sudo \(Paths.valet) \(action) && exit;" + + // Run the command + await Shell.quiet(command) + + // Check if the secured status has actually changed + site.determineSecured() + if site.secured == originalSecureStatus { + throw ValetInteractionError(command: command) + } + } + + public func toggleSecure(proxy: ValetProxy) async throws { // Keep track of the original status (secure or not?) let originalSecureStatus = proxy.secured @@ -33,6 +53,7 @@ class ValetInteractor { : "\(Paths.valet) proxy \(proxy.domain) \(proxy.target) --secure" ] + // Run the commands for command in commands { await Shell.quiet(command) } @@ -49,15 +70,29 @@ class ValetInteractor { await Actions.restartNginx() } - public static func isolate(site: ValetSite, version: PhpVersionNumber) async throws { + public func isolate(site: ValetSite, version: PhpVersionNumber) async throws { // TODO } - public static func unlink(site: ValetSite) async throws { + public func unlink(site: ValetSite) async throws { // TODO } - public static func remove(proxy: ValetProxy) async throws { + public func remove(proxy: ValetProxy) async throws { await Shell.quiet("valet unproxy '\(proxy.domain)'") } } + +class FakeValetInteractor: ValetInteractor { + override func toggleSecure(proxy: ValetProxy) async throws { + proxy.secured = !proxy.secured + } + + override func toggleSecure(site: ValetSite) async throws { + site.secured = !site.secured + } + + override func remove(proxy: ValetProxy) async throws { + fatalError("This should remove the proxy") + } +} diff --git a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift b/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift index ce118e1..23d5cf0 100644 --- a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift +++ b/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift @@ -58,10 +58,10 @@ class ValetProxy: ValetListable { } func toggleSecure() async throws { - try await ValetInteractor.toggleSecure(proxy: self) + try await ValetInteractor.shared.toggleSecure(proxy: self) } func remove() async { - try! await ValetInteractor.remove(proxy: self) + try! await ValetInteractor.shared.remove(proxy: self) } } diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift index 86ddb88..b88863e 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift @@ -266,14 +266,14 @@ class ValetSite: ValetListable { // MARK: - Interactions func toggleSecure() async throws { - try await ValetInteractor.toggleSecure(site: self) + try await ValetInteractor.shared.toggleSecure(site: self) } func isolate(version: PhpVersionNumber) async throws { - try await ValetInteractor.isolate(site: self, version: version) + try await ValetInteractor.shared.isolate(site: self, version: version) } func unlink() async throws { - try await ValetInteractor.unlink(site: self) + try await ValetInteractor.shared.unlink(site: self) } } From 837392d6066a3d4de6df22f7fdfbb03062067312 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 13 Dec 2022 23:32:11 +0100 Subject: [PATCH 103/181] =?UTF-8?q?=E2=9C=A8=20Add=20unlinking=20sites=20f?= =?UTF-8?q?or=20fake=20links?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/DomainList/DomainListVC+Actions.swift | 4 +++- .../Integrations/Valet/Domains/ValetInteractor.swift | 11 ++++++++--- .../Valet/Sites/SiteScanner/FakeSiteScanner.swift | 2 +- .../Domain/Integrations/Valet/Sites/ValetSite.swift | 4 ++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/phpmon/Domain/DomainList/DomainListVC+Actions.swift b/phpmon/Domain/DomainList/DomainListVC+Actions.swift index 2552e1c..ce2e386 100644 --- a/phpmon/Domain/DomainList/DomainListVC+Actions.swift +++ b/phpmon/Domain/DomainList/DomainListVC+Actions.swift @@ -108,6 +108,7 @@ extension DomainListVC { } } + #warning("ValetInteractor needs to be used here instead of directly issuing commands to valet") @objc func isolateSite(sender: PhpMenuItem) { let command = "sudo \(Paths.valet) isolate php@\(sender.version) --site '\(self.selectedSite!.name)' && exit;" @@ -128,6 +129,7 @@ extension DomainListVC { } } + #warning("ValetInteractor needs to be used here instead of directly issuing commands to valet") @objc func removeIsolatedSite() { self.performAction(command: "sudo \(Paths.valet) unisolate --site '\(self.selectedSite!.name)' && exit;") { self.selectedSite!.isolatedPhpVersion = nil @@ -153,7 +155,7 @@ extension DomainListVC { style: .critical, onFirstButtonPressed: { self.waitAndExecute { - Task { await Shell.quiet("valet unlink '\(site.name)'") } + Task { await site.unlink() } } completion: { Task { await self.reloadDomains() } } diff --git a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift index 0e9722d..06bb3c4 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift @@ -13,7 +13,6 @@ struct ValetInteractionError: Error { var command: String } -#warning("ValetInteractor needs to be implemented and used") class ValetInteractor { static var shared = ValetInteractor() @@ -75,7 +74,7 @@ class ValetInteractor { } public func unlink(site: ValetSite) async throws { - // TODO + await Shell.quiet("valet unlink '\(site.name)'") } public func remove(proxy: ValetProxy) async throws { @@ -92,7 +91,13 @@ class FakeValetInteractor: ValetInteractor { site.secured = !site.secured } + override func unlink(site: ValetSite) async throws { + if let scanner = ValetScanners.siteScanner as? FakeSiteScanner { + scanner.fakes.removeAll { $0 === site } + } + } + override func remove(proxy: ValetProxy) async throws { - fatalError("This should remove the proxy") + #warning("A fake proxy scanner needs to be added") } } diff --git a/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/FakeSiteScanner.swift b/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/FakeSiteScanner.swift index a7c2cc7..f96f0c2 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/FakeSiteScanner.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/FakeSiteScanner.swift @@ -7,7 +7,7 @@ // class FakeSiteScanner: SiteScanner { - let fakes = [ + var fakes = [ FakeValetSite(fakeWithName: "laravel", tld: "test", secure: true, path: "~/Code/laravel/framework", linked: true), diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift index b88863e..c05a987 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift @@ -273,7 +273,7 @@ class ValetSite: ValetListable { try await ValetInteractor.shared.isolate(site: self, version: version) } - func unlink() async throws { - try await ValetInteractor.shared.unlink(site: self) + func unlink() async { + try! await ValetInteractor.shared.unlink(site: self) } } From a696a9a38683a8544cb45389789fd1a02fcf04c3 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 13 Dec 2022 23:32:50 +0100 Subject: [PATCH 104/181] =?UTF-8?q?=F0=9F=91=8C=20Style=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/App/App.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpmon/Domain/App/App.swift b/phpmon/Domain/App/App.swift index 491bd75..42f27d7 100644 --- a/phpmon/Domain/App/App.swift +++ b/phpmon/Domain/App/App.swift @@ -42,7 +42,7 @@ class App { var systeminfo = utsname() uname(&systeminfo) - let machine = withUnsafeBytes(of: &systeminfo.machine) {bufPtr->String in + let machine = withUnsafeBytes(of: &systeminfo.machine) { bufPtr -> String in let data = Data(bufPtr) if let lastIndex = data.lastIndex(where: {$0 != 0}) { return String(data: data[0...lastIndex], encoding: .isoLatin1)! From e34dadcb9b8caf03c0449daca802963cfc48f1b4 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 13 Dec 2022 23:45:19 +0100 Subject: [PATCH 105/181] =?UTF-8?q?=F0=9F=91=8C=20Reinstate=20UI=20busy=20?= =?UTF-8?q?for=20secure,=20add=20fake=20delay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DomainList/DomainListVC+Actions.swift | 64 ++++++++++--------- .../Valet/Domains/ValetInteractor.swift | 6 ++ 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/phpmon/Domain/DomainList/DomainListVC+Actions.swift b/phpmon/Domain/DomainList/DomainListVC+Actions.swift index ce2e386..162e15e 100644 --- a/phpmon/Domain/DomainList/DomainListVC+Actions.swift +++ b/phpmon/Domain/DomainList/DomainListVC+Actions.swift @@ -68,43 +68,45 @@ extension DomainListVC { @objc func toggleSecure() { if selected is ValetSite { - Task { await toggleSecureForSite() } - } else { - Task { await toggleSecureForProxy() } + Task { await toggleSecure(site: selected as! ValetSite) } + } + + if selected is ValetProxy { + Task { await toggleSecure(proxy: selected as! ValetProxy) } } } - func toggleSecureForProxy() async { - guard let proxy = selectedProxy else { return } - - do { - // Recreate proxy as secure or unsecured proxy - try await proxy.toggleSecure() - // Send a notification about the new status (if applicable) - self.notifyAboutModifiedSecureStatus(domain: proxy.domain, secured: proxy.secured) - // Reload the UI (do this last so we don't invalidate the proxy) - self.reloadSelectedRow() - } catch { - // Notify the user about a failed command - let error = error as! ValetInteractionError - self.notifyAboutFailedSecureStatus(command: error.command) + func toggleSecure(proxy: ValetProxy) async { + waitAndExecute { + do { + // Recreate proxy as secure or unsecured proxy + try await proxy.toggleSecure() + // Send a notification about the new status (if applicable) + self.notifyAboutModifiedSecureStatus(domain: proxy.domain, secured: proxy.secured) + // Reload the UI (do this last so we don't invalidate the proxy) + self.reloadSelectedRow() + } catch { + // Notify the user about a failed command + let error = error as! ValetInteractionError + self.notifyAboutFailedSecureStatus(command: error.command) + } } } - func toggleSecureForSite() async { - guard let site = selectedSite else { return } - - do { - // Instruct Valet to secure or unsecure a site - try await site.toggleSecure() - // Send a notification about the new status (if applicable) - self.notifyAboutModifiedSecureStatus(domain: site.name, secured: site.secured) - // Reload the UI (do this last so we don't invalidate the proxy) - self.reloadSelectedRow() - } catch { - // Notify the user about a failed command - let error = error as! ValetInteractionError - self.notifyAboutFailedSecureStatus(command: error.command) + func toggleSecure(site: ValetSite) async { + waitAndExecute { + do { + // Instruct Valet to secure or unsecure a site + try await site.toggleSecure() + // Send a notification about the new status (if applicable) + self.notifyAboutModifiedSecureStatus(domain: site.name, secured: site.secured) + // Reload the UI (do this last so we don't invalidate the site) + self.reloadSelectedRow() + } catch { + // Notify the user about a failed command + let error = error as! ValetInteractionError + self.notifyAboutFailedSecureStatus(command: error.command) + } } } diff --git a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift index 06bb3c4..98c7132 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift @@ -83,21 +83,27 @@ class ValetInteractor { } class FakeValetInteractor: ValetInteractor { + var delayTime: TimeInterval = 1.0 + override func toggleSecure(proxy: ValetProxy) async throws { + await delay(seconds: delayTime) proxy.secured = !proxy.secured } override func toggleSecure(site: ValetSite) async throws { + await delay(seconds: delayTime) site.secured = !site.secured } override func unlink(site: ValetSite) async throws { + await delay(seconds: delayTime) if let scanner = ValetScanners.siteScanner as? FakeSiteScanner { scanner.fakes.removeAll { $0 === site } } } override func remove(proxy: ValetProxy) async throws { + await delay(seconds: delayTime) #warning("A fake proxy scanner needs to be added") } } From d49e74fab1be86b115842daee0b0b950eb0ee4c2 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 13 Dec 2022 23:47:33 +0100 Subject: [PATCH 106/181] =?UTF-8?q?=F0=9F=9A=9B=20Move=20FakeValetInteract?= =?UTF-8?q?or=20to=20separate=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 10 ++++++ .../Valet/Domains/FakeValetInteractor.swift | 35 +++++++++++++++++++ .../Valet/Domains/ValetInteractor.swift | 26 -------------- 3 files changed, 45 insertions(+), 26 deletions(-) create mode 100644 phpmon/Domain/Integrations/Valet/Domains/FakeValetInteractor.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index c195d59..2359759 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -529,6 +529,10 @@ C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */; }; C4B97B7B275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */; }; C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */; }; + C4BF56AB2949381100379603 /* FakeValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BF56AA2949381100379603 /* FakeValetInteractor.swift */; }; + C4BF56AC2949381100379603 /* FakeValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BF56AA2949381100379603 /* FakeValetInteractor.swift */; }; + C4BF56AD2949381100379603 /* FakeValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BF56AA2949381100379603 /* FakeValetInteractor.swift */; }; + C4BF56AE2949381100379603 /* FakeValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BF56AA2949381100379603 /* FakeValetInteractor.swift */; }; C4BF90C127C57C220054E78C /* MainMenu+FixMyValet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */; }; C4C0E8DF27F88AEB002D32A9 /* FakeSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8DE27F88AEB002D32A9 /* FakeSiteScanner.swift */; }; C4C0E8E027F88AEB002D32A9 /* FakeSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8DE27F88AEB002D32A9 /* FakeSiteScanner.swift */; }; @@ -848,6 +852,7 @@ C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+MenuOutlets.swift"; sourceTree = ""; }; C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+ActivationPolicy.swift"; sourceTree = ""; }; C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+GlobalHotkey.swift"; sourceTree = ""; }; + C4BF56AA2949381100379603 /* FakeValetInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeValetInteractor.swift; sourceTree = ""; }; C4C0E8DE27F88AEB002D32A9 /* FakeSiteScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeSiteScanner.swift; sourceTree = ""; }; C4C0E8E127F88B13002D32A9 /* ValetSiteScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetSiteScanner.swift; sourceTree = ""; }; C4C0E8E627F88B41002D32A9 /* ProxyScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyScanner.swift; sourceTree = ""; }; @@ -1029,6 +1034,7 @@ children = ( C42F26722805B4B400938AC7 /* ValetListable.swift */, C40175B72903108900763A68 /* ValetInteractor.swift */, + C4BF56AA2949381100379603 /* FakeValetInteractor.swift */, ); path = Domains; sourceTree = ""; @@ -2078,6 +2084,7 @@ C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */, C4FACE83288F1F9700FC478F /* OnboardingWindowController.swift in Sources */, C4080FFA27BD956700BF2C6B /* BetterAlertVC.swift in Sources */, + C4BF56AB2949381100379603 /* FakeValetInteractor.swift in Sources */, C4B5635E276AB09000F12CCB /* VersionExtractor.swift in Sources */, 54D9E0B627E4F51E003B9AD9 /* HotKey.swift in Sources */, C4D936C927E3EB6100BD69FE /* PhpHelper.swift in Sources */, @@ -2158,6 +2165,7 @@ C471E85928F9BB650021E251 /* DomainListPhpCell.swift in Sources */, C471E85A28F9BB650021E251 /* DomainListTypeCell.swift in Sources */, C471E85B28F9BB650021E251 /* DomainListKindCell.swift in Sources */, + C4BF56AD2949381100379603 /* FakeValetInteractor.swift in Sources */, C471E85C28F9BB650021E251 /* DomainListWindowController.swift in Sources */, C471E85D28F9BB650021E251 /* DomainListVC.swift in Sources */, C471E85E28F9BB650021E251 /* DomainListVC+ContextMenu.swift in Sources */, @@ -2321,6 +2329,7 @@ C471E8BF28F9BB8F0021E251 /* DomainListWindowController.swift in Sources */, C471E8C028F9BB8F0021E251 /* DomainListVC.swift in Sources */, C471E8C128F9BB8F0021E251 /* DomainListVC+ContextMenu.swift in Sources */, + C4BF56AE2949381100379603 /* FakeValetInteractor.swift in Sources */, C471E8C228F9BB8F0021E251 /* DomainListVC+Actions.swift in Sources */, C4D36618291160A1006BD146 /* WIP.swift in Sources */, C471E8C328F9BB8F0021E251 /* SelectionVC.swift in Sources */, @@ -2562,6 +2571,7 @@ C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */, C449B4F227EE7FC400C47E8A /* DomainListPhpCell.swift in Sources */, C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */, + C4BF56AC2949381100379603 /* FakeValetInteractor.swift in Sources */, C471E79428F9B23B0021E251 /* FileSystemProtocol.swift in Sources */, C4F30B0B278E203C00755FCE /* MainMenu+Startup.swift in Sources */, C485707C28BF459500539B36 /* NoWarningsView.swift in Sources */, diff --git a/phpmon/Domain/Integrations/Valet/Domains/FakeValetInteractor.swift b/phpmon/Domain/Integrations/Valet/Domains/FakeValetInteractor.swift new file mode 100644 index 0000000..d661fe1 --- /dev/null +++ b/phpmon/Domain/Integrations/Valet/Domains/FakeValetInteractor.swift @@ -0,0 +1,35 @@ +// +// FakeValetInteractor.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 13/12/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class FakeValetInteractor: ValetInteractor { + var delayTime: TimeInterval = 1.0 + + override func toggleSecure(proxy: ValetProxy) async throws { + await delay(seconds: delayTime) + proxy.secured = !proxy.secured + } + + override func toggleSecure(site: ValetSite) async throws { + await delay(seconds: delayTime) + site.secured = !site.secured + } + + override func unlink(site: ValetSite) async throws { + await delay(seconds: delayTime) + if let scanner = ValetScanners.siteScanner as? FakeSiteScanner { + scanner.fakes.removeAll { $0 === site } + } + } + + override func remove(proxy: ValetProxy) async throws { + await delay(seconds: delayTime) + #warning("A fake proxy scanner needs to be added") + } +} diff --git a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift index 98c7132..31d9ac2 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift @@ -81,29 +81,3 @@ class ValetInteractor { await Shell.quiet("valet unproxy '\(proxy.domain)'") } } - -class FakeValetInteractor: ValetInteractor { - var delayTime: TimeInterval = 1.0 - - override func toggleSecure(proxy: ValetProxy) async throws { - await delay(seconds: delayTime) - proxy.secured = !proxy.secured - } - - override func toggleSecure(site: ValetSite) async throws { - await delay(seconds: delayTime) - site.secured = !site.secured - } - - override func unlink(site: ValetSite) async throws { - await delay(seconds: delayTime) - if let scanner = ValetScanners.siteScanner as? FakeSiteScanner { - scanner.fakes.removeAll { $0 === site } - } - } - - override func remove(proxy: ValetProxy) async throws { - await delay(seconds: delayTime) - #warning("A fake proxy scanner needs to be added") - } -} From 4b04f7063816dd76169bcb14e68f9392e72dbc0d Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 13 Dec 2022 23:49:16 +0100 Subject: [PATCH 107/181] =?UTF-8?q?=F0=9F=91=8C=20Fix=20phrasing=20("site?= =?UTF-8?q?=20is=20unsecured")?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A site can be either insecure or unsecured. Insecure has a rather negative connotation and is also a human trait. Unsecured is a better term in this particular case. --- phpmon/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 12986a8..915c203 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -122,7 +122,7 @@ "domain_list.alerts_status_changed.title" = "SSL Status Changed"; "domain_list.alerts_status_changed.desc" = "The domain '%@' is now %@."; "domain_list.alerts_status_secure" = "secure"; -"domain_list.alerts_status_unsecure" = "unsecure"; +"domain_list.alerts_status_unsecure" = "unsecured"; "domain_list.confirm_unlink" = "Are you sure you want to unlink '%@'?"; "domain_list.confirm_unlink_desc" = "No files will be removed. You can always link the folder again by clicking on the + button and selecting the original folder."; From d6554ceea9aa863bcaa8d7793d54f6b676e72426 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 14 Dec 2022 20:18:06 +0100 Subject: [PATCH 108/181] =?UTF-8?q?=E2=9C=A8=20Add=20site=20isolation=20to?= =?UTF-8?q?=20ValetInteractor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcschemes/PHP Monitor DEV.xcscheme | 2 +- phpmon/Domain/App/ServicesManager.swift | 2 +- .../DomainList/DomainListVC+Actions.swift | 58 +++++++++++++------ .../Valet/Domains/ValetInteractor.swift | 32 +++++++++- .../Integrations/Valet/Sites/ValetSite.swift | 6 +- phpmon/Localizable.strings | 4 +- 6 files changed, 78 insertions(+), 26 deletions(-) diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index b0a4d86..8642ae0 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -91,7 +91,7 @@ + isEnabled = "NO"> Date: Wed, 14 Dec 2022 20:28:49 +0100 Subject: [PATCH 109/181] =?UTF-8?q?=E2=9C=A8=20Add=20fake=20isolation=20in?= =?UTF-8?q?teraction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcschemes/PHP Monitor DEV.xcscheme | 2 +- .../Valet/Domains/FakeValetInteractor.swift | 14 ++++++++++++++ .../Integrations/Valet/Sites/ValetSite.swift | 3 +++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index 8642ae0..b0a4d86 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -91,7 +91,7 @@ + isEnabled = "YES"> Date: Thu, 15 Dec 2022 22:28:15 +0100 Subject: [PATCH 110/181] =?UTF-8?q?=F0=9F=91=8C=20Unify=20scanners?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 108 +++++++----------- .../Testables/TestableConfiguration.swift | 2 +- .../Valet/Domains/FakeValetInteractor.swift | 10 +- .../ProxyScanner/EmptyProxyScanner.swift | 15 --- .../Proxies/ProxyScanner/ProxyScanner.swift | 15 --- .../ProxyScanner/ValetProxyScanner.swift | 29 ----- .../DomainScanner.swift} | 14 ++- .../FakeDomainScanner.swift} | 23 +++- .../ValetDomainScanner.swift} | 27 ++++- .../Valet/Scanners/ValetScanners.swift | 19 +++ phpmon/Domain/Integrations/Valet/Valet.swift | 11 +- .../Integrations/Valet/ValetScanners.swift | 21 ---- 12 files changed, 125 insertions(+), 169 deletions(-) delete mode 100644 phpmon/Domain/Integrations/Valet/Proxies/ProxyScanner/EmptyProxyScanner.swift delete mode 100644 phpmon/Domain/Integrations/Valet/Proxies/ProxyScanner/ProxyScanner.swift delete mode 100644 phpmon/Domain/Integrations/Valet/Proxies/ProxyScanner/ValetProxyScanner.swift rename phpmon/Domain/Integrations/Valet/{Sites/SiteScanner/SiteScanner.swift => Scanners/DomainScanner.swift} (56%) rename phpmon/Domain/Integrations/Valet/{Sites/SiteScanner/FakeSiteScanner.swift => Scanners/FakeDomainScanner.swift} (78%) rename phpmon/Domain/Integrations/Valet/{Sites/SiteScanner/ValetSiteScanner.swift => Scanners/ValetDomainScanner.swift} (77%) create mode 100644 phpmon/Domain/Integrations/Valet/Scanners/ValetScanners.swift delete mode 100644 phpmon/Domain/Integrations/Valet/ValetScanners.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 2359759..0d14e8b 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -79,9 +79,7 @@ C417DC74277614690015E6EE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; }; C417DC75277614690015E6EE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; }; C4181F1128FAF9330042EA28 /* UITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4181F1028FAF9330042EA28 /* UITestCase.swift */; }; - C41C02A627E60D7A009F26CB /* SiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A527E60D7A009F26CB /* SiteScanner.swift */; }; C41C02A927E61A65009F26CB /* ValetSite+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */; }; - C41C02AA27E61CA3009F26CB /* SiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A527E60D7A009F26CB /* SiteScanner.swift */; }; C41C02AB27E61CB3009F26CB /* ValetSite+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */; }; C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; }; C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3A22B0098000E7CF16 /* Assets.xcassets */; }; @@ -284,13 +282,11 @@ C471E83428F9BB650021E251 /* VersionExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5635D276AB09000F12CCB /* VersionExtractor.swift */; }; C471E83528F9BB650021E251 /* ValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4205A7D27F4D21800191A39 /* ValetProxy.swift */; }; C471E83628F9BB650021E251 /* ValetProxy+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */; }; - C471E83728F9BB650021E251 /* ProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E627F88B41002D32A9 /* ProxyScanner.swift */; }; - C471E83828F9BB650021E251 /* ValetProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C484437A2804BB560041A78A /* ValetProxyScanner.swift */; }; + C471E83728F9BB650021E251 /* DomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E627F88B41002D32A9 /* DomainScanner.swift */; }; C471E83928F9BB650021E251 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; C471E83A28F9BB650021E251 /* ValetSite+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */; }; - C471E83B28F9BB650021E251 /* SiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A527E60D7A009F26CB /* SiteScanner.swift */; }; - C471E83C28F9BB650021E251 /* ValetSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E127F88B13002D32A9 /* ValetSiteScanner.swift */; }; - C471E83D28F9BB650021E251 /* FakeSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8DE27F88AEB002D32A9 /* FakeSiteScanner.swift */; }; + C471E83C28F9BB650021E251 /* ValetDomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E127F88B13002D32A9 /* ValetDomainScanner.swift */; }; + C471E83D28F9BB650021E251 /* FakeDomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8DE27F88AEB002D32A9 /* FakeDomainScanner.swift */; }; C471E83F28F9BB650021E251 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; }; C471E84028F9BB650021E251 /* AppDelegate+MenuOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */; }; C471E84128F9BB650021E251 /* AppDelegate+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */; }; @@ -377,13 +373,11 @@ C471E89728F9BB8F0021E251 /* VersionExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5635D276AB09000F12CCB /* VersionExtractor.swift */; }; C471E89828F9BB8F0021E251 /* ValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4205A7D27F4D21800191A39 /* ValetProxy.swift */; }; C471E89928F9BB8F0021E251 /* ValetProxy+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */; }; - C471E89A28F9BB8F0021E251 /* ProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E627F88B41002D32A9 /* ProxyScanner.swift */; }; - C471E89B28F9BB8F0021E251 /* ValetProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C484437A2804BB560041A78A /* ValetProxyScanner.swift */; }; + C471E89A28F9BB8F0021E251 /* DomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E627F88B41002D32A9 /* DomainScanner.swift */; }; C471E89C28F9BB8F0021E251 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; C471E89D28F9BB8F0021E251 /* ValetSite+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */; }; - C471E89E28F9BB8F0021E251 /* SiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A527E60D7A009F26CB /* SiteScanner.swift */; }; - C471E89F28F9BB8F0021E251 /* ValetSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E127F88B13002D32A9 /* ValetSiteScanner.swift */; }; - C471E8A028F9BB8F0021E251 /* FakeSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8DE27F88AEB002D32A9 /* FakeSiteScanner.swift */; }; + C471E89F28F9BB8F0021E251 /* ValetDomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E127F88B13002D32A9 /* ValetDomainScanner.swift */; }; + C471E8A028F9BB8F0021E251 /* FakeDomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8DE27F88AEB002D32A9 /* FakeDomainScanner.swift */; }; C471E8A228F9BB8F0021E251 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; }; C471E8A328F9BB8F0021E251 /* AppDelegate+MenuOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */; }; C471E8A428F9BB8F0021E251 /* AppDelegate+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */; }; @@ -470,8 +464,6 @@ C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; }; C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; }; C481F79A26164A7C004FBCFF /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; }; - C484437B2804BB560041A78A /* ValetProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C484437A2804BB560041A78A /* ValetProxyScanner.swift */; }; - C484437C2804BB560041A78A /* ValetProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C484437A2804BB560041A78A /* ValetProxyScanner.swift */; }; C485706D28BF450900539B36 /* NSMenuItemExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */; }; C485706E28BF451C00539B36 /* OnboardingWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE82288F1F9700FC478F /* OnboardingWindowController.swift */; }; C485706F28BF452300539B36 /* WarningsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDAC28A2DAC600CEAC97 /* WarningsWindowController.swift */; }; @@ -534,12 +526,12 @@ C4BF56AD2949381100379603 /* FakeValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BF56AA2949381100379603 /* FakeValetInteractor.swift */; }; C4BF56AE2949381100379603 /* FakeValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BF56AA2949381100379603 /* FakeValetInteractor.swift */; }; C4BF90C127C57C220054E78C /* MainMenu+FixMyValet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */; }; - C4C0E8DF27F88AEB002D32A9 /* FakeSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8DE27F88AEB002D32A9 /* FakeSiteScanner.swift */; }; - C4C0E8E027F88AEB002D32A9 /* FakeSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8DE27F88AEB002D32A9 /* FakeSiteScanner.swift */; }; - C4C0E8E227F88B13002D32A9 /* ValetSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E127F88B13002D32A9 /* ValetSiteScanner.swift */; }; - C4C0E8E327F88B13002D32A9 /* ValetSiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E127F88B13002D32A9 /* ValetSiteScanner.swift */; }; - C4C0E8E727F88B41002D32A9 /* ProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E627F88B41002D32A9 /* ProxyScanner.swift */; }; - C4C0E8E827F88B41002D32A9 /* ProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E627F88B41002D32A9 /* ProxyScanner.swift */; }; + C4C0E8DF27F88AEB002D32A9 /* FakeDomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8DE27F88AEB002D32A9 /* FakeDomainScanner.swift */; }; + C4C0E8E027F88AEB002D32A9 /* FakeDomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8DE27F88AEB002D32A9 /* FakeDomainScanner.swift */; }; + C4C0E8E227F88B13002D32A9 /* ValetDomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E127F88B13002D32A9 /* ValetDomainScanner.swift */; }; + C4C0E8E327F88B13002D32A9 /* ValetDomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E127F88B13002D32A9 /* ValetDomainScanner.swift */; }; + C4C0E8E727F88B41002D32A9 /* DomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E627F88B41002D32A9 /* DomainScanner.swift */; }; + C4C0E8E827F88B41002D32A9 /* DomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E627F88B41002D32A9 /* DomainScanner.swift */; }; C4C0E8EA27F88B80002D32A9 /* ValetProxy+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */; }; C4C0E8EB27F88B80002D32A9 /* ValetProxy+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */; }; C4C1019B27C65C6F001FACC2 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C1019A27C65C6F001FACC2 /* Process.swift */; }; @@ -571,10 +563,6 @@ C4D36602291132B7006BD146 /* ValetScanners.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36600291132B7006BD146 /* ValetScanners.swift */; }; C4D36603291132B7006BD146 /* ValetScanners.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36600291132B7006BD146 /* ValetScanners.swift */; }; C4D36604291132B7006BD146 /* ValetScanners.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36600291132B7006BD146 /* ValetScanners.swift */; }; - C4D366062911331E006BD146 /* EmptyProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D366052911331E006BD146 /* EmptyProxyScanner.swift */; }; - C4D366072911331E006BD146 /* EmptyProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D366052911331E006BD146 /* EmptyProxyScanner.swift */; }; - C4D366082911331E006BD146 /* EmptyProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D366052911331E006BD146 /* EmptyProxyScanner.swift */; }; - C4D366092911331E006BD146 /* EmptyProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D366052911331E006BD146 /* EmptyProxyScanner.swift */; }; C4D3660B29113F20006BD146 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3660A29113F20006BD146 /* System.swift */; }; C4D3660C29113F20006BD146 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3660A29113F20006BD146 /* System.swift */; }; C4D3660D29113F20006BD146 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3660A29113F20006BD146 /* System.swift */; }; @@ -751,7 +739,6 @@ C4168F4427ADB4A3003B6C39 /* DEVELOPER.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = DEVELOPER.md; sourceTree = ""; }; C417DC73277614690015E6EE /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; C4181F1028FAF9330042EA28 /* UITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestCase.swift; sourceTree = ""; }; - C41C02A527E60D7A009F26CB /* SiteScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteScanner.swift; sourceTree = ""; }; C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ValetSite+Fake.swift"; sourceTree = ""; }; C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PHP Monitor.app"; sourceTree = BUILT_PRODUCTS_DIR; }; C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -826,7 +813,6 @@ C476FF9722B0DD830098105B /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = ""; }; C4811D2322D70A4700B5F6B3 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = ""; }; - C484437A2804BB560041A78A /* ValetProxyScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetProxyScanner.swift; sourceTree = ""; }; C48D0C9225CC804200CC7490 /* XibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XibLoadable.swift; sourceTree = ""; }; C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpVersionNumber.swift; sourceTree = ""; }; C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpVersionNumberTest.swift; sourceTree = ""; }; @@ -853,9 +839,9 @@ C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+ActivationPolicy.swift"; sourceTree = ""; }; C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+GlobalHotkey.swift"; sourceTree = ""; }; C4BF56AA2949381100379603 /* FakeValetInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeValetInteractor.swift; sourceTree = ""; }; - C4C0E8DE27F88AEB002D32A9 /* FakeSiteScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeSiteScanner.swift; sourceTree = ""; }; - C4C0E8E127F88B13002D32A9 /* ValetSiteScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetSiteScanner.swift; sourceTree = ""; }; - C4C0E8E627F88B41002D32A9 /* ProxyScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyScanner.swift; sourceTree = ""; }; + C4C0E8DE27F88AEB002D32A9 /* FakeDomainScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeDomainScanner.swift; sourceTree = ""; }; + C4C0E8E127F88B13002D32A9 /* ValetDomainScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetDomainScanner.swift; sourceTree = ""; }; + C4C0E8E627F88B41002D32A9 /* DomainScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainScanner.swift; sourceTree = ""; }; C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ValetProxy+Fake.swift"; sourceTree = ""; }; C4C1019A27C65C6F001FACC2 /* Process.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Process.swift; sourceTree = ""; }; C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Items.swift"; sourceTree = ""; }; @@ -872,7 +858,6 @@ C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Switcher.swift"; sourceTree = ""; }; C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerWindow.swift; sourceTree = ""; }; C4D36600291132B7006BD146 /* ValetScanners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetScanners.swift; sourceTree = ""; }; - C4D366052911331E006BD146 /* EmptyProxyScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyProxyScanner.swift; sourceTree = ""; }; C4D3660A29113F20006BD146 /* System.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = System.swift; sourceTree = ""; }; C4D3660F291140BE006BD146 /* TestableFileSystemTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableFileSystemTest.swift; sourceTree = ""; }; C4D36614291160A1006BD146 /* WIP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WIP.swift; sourceTree = ""; }; @@ -1405,8 +1390,8 @@ isa = PBXGroup; children = ( C4AF9F792754499000D44ED0 /* Valet.swift */, - C4D36600291132B7006BD146 /* ValetScanners.swift */, C40175B629030F7A00763A68 /* Domains */, + C4EF72C9294BC6E60088B538 /* Scanners */, C4C0E8D927F887BD002D32A9 /* Proxies */, C4C0E8D827F887A5002D32A9 /* Sites */, ); @@ -1514,7 +1499,6 @@ children = ( C4205A7D27F4D21800191A39 /* ValetProxy.swift */, C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */, - C4C0E8E527F88B36002D32A9 /* ProxyScanner */, ); path = Proxies; sourceTree = ""; @@ -1530,23 +1514,10 @@ C4C0E8E427F88B1F002D32A9 /* SiteScanner */ = { isa = PBXGroup; children = ( - C41C02A527E60D7A009F26CB /* SiteScanner.swift */, - C4C0E8E127F88B13002D32A9 /* ValetSiteScanner.swift */, - C4C0E8DE27F88AEB002D32A9 /* FakeSiteScanner.swift */, ); path = SiteScanner; sourceTree = ""; }; - C4C0E8E527F88B36002D32A9 /* ProxyScanner */ = { - isa = PBXGroup; - children = ( - C4C0E8E627F88B41002D32A9 /* ProxyScanner.swift */, - C484437A2804BB560041A78A /* ValetProxyScanner.swift */, - C4D366052911331E006BD146 /* EmptyProxyScanner.swift */, - ); - path = ProxyScanner; - sourceTree = ""; - }; C4C1019727C65A11001FACC2 /* Parsers */ = { isa = PBXGroup; children = ( @@ -1673,6 +1644,17 @@ path = SwiftUI; sourceTree = ""; }; + C4EF72C9294BC6E60088B538 /* Scanners */ = { + isa = PBXGroup; + children = ( + C4D36600291132B7006BD146 /* ValetScanners.swift */, + C4C0E8E627F88B41002D32A9 /* DomainScanner.swift */, + C4C0E8E127F88B13002D32A9 /* ValetDomainScanner.swift */, + C4C0E8DE27F88AEB002D32A9 /* FakeDomainScanner.swift */, + ); + path = Scanners; + sourceTree = ""; + }; C4F30B01278E169B00755FCE /* Homebrew */ = { isa = PBXGroup; children = ( @@ -1963,7 +1945,6 @@ C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */, C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */, C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */, - C4D366062911331E006BD146 /* EmptyProxyScanner.swift in Sources */, C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */, C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */, C4C0E8EA27F88B80002D32A9 /* ValetProxy+Fake.swift in Sources */, @@ -1991,7 +1972,7 @@ C463E380284930EE00422731 /* PresetHelper.swift in Sources */, C41C02A927E61A65009F26CB /* ValetSite+Fake.swift in Sources */, C4E2E85C28FC282B003B070C /* TestableConfiguration.swift in Sources */, - C4C0E8DF27F88AEB002D32A9 /* FakeSiteScanner.swift in Sources */, + C4C0E8DF27F88AEB002D32A9 /* FakeDomainScanner.swift in Sources */, C44B3A4628E5C70100718CB1 /* TimeIntervalExtension.swift in Sources */, C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */, C4E9D2C02878B336008FFDAD /* OnboardingView.swift in Sources */, @@ -2017,7 +1998,7 @@ C46EBC4A28DB966A007ACC74 /* TestableShell.swift in Sources */, C44C198D276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */, 54D9E0B827E4F51E003B9AD9 /* KeyCombo.swift in Sources */, - C4C0E8E727F88B41002D32A9 /* ProxyScanner.swift in Sources */, + C4C0E8E727F88B41002D32A9 /* DomainScanner.swift in Sources */, C4C3ED4327834C5200AB15D8 /* CustomPrefs.swift in Sources */, 54B48B5F275F66AE006D90C5 /* Application.swift in Sources */, C4B97B78275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */, @@ -2030,7 +2011,7 @@ C4F30B03278E16BA00755FCE /* HomebrewService.swift in Sources */, 54D9E0B427E4F51E003B9AD9 /* Key.swift in Sources */, C4297F7A28970D59004C4630 /* WarningView.swift in Sources */, - C4C0E8E227F88B13002D32A9 /* ValetSiteScanner.swift in Sources */, + C4C0E8E227F88B13002D32A9 /* ValetDomainScanner.swift in Sources */, C42F26732805B4B400938AC7 /* ValetListable.swift in Sources */, 5420395F2613607600FB00FA /* Preferences.swift in Sources */, C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */, @@ -2051,7 +2032,6 @@ C4C8E81B276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */, C417DC74277614690015E6EE /* Helpers.swift in Sources */, C415D3E82770F692005EF286 /* AppDelegate+InterApp.swift in Sources */, - C484437B2804BB560041A78A /* ValetProxyScanner.swift in Sources */, C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */, C42759672627662800093CAE /* NSMenuExtension.swift in Sources */, C422DDAA28A2C49900CEAC97 /* WarningListView.swift in Sources */, @@ -2098,7 +2078,6 @@ C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */, C42337A3281F19F000459A48 /* Xdebug.swift in Sources */, C4B97B75275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */, - C41C02A627E60D7A009F26CB /* SiteScanner.swift in Sources */, C464ADAC275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */, C4CB6E65292C362C002E9027 /* Homebrew.swift in Sources */, C464ADB2275A87CA003FCD53 /* DomainListCellProtocol.swift in Sources */, @@ -2123,14 +2102,12 @@ C471E83428F9BB650021E251 /* VersionExtractor.swift in Sources */, C471E83528F9BB650021E251 /* ValetProxy.swift in Sources */, C471E83628F9BB650021E251 /* ValetProxy+Fake.swift in Sources */, - C471E83728F9BB650021E251 /* ProxyScanner.swift in Sources */, - C471E83828F9BB650021E251 /* ValetProxyScanner.swift in Sources */, + C471E83728F9BB650021E251 /* DomainScanner.swift in Sources */, C471E83928F9BB650021E251 /* ValetSite.swift in Sources */, C471E83A28F9BB650021E251 /* ValetSite+Fake.swift in Sources */, - C471E83B28F9BB650021E251 /* SiteScanner.swift in Sources */, - C471E83C28F9BB650021E251 /* ValetSiteScanner.swift in Sources */, + C471E83C28F9BB650021E251 /* ValetDomainScanner.swift in Sources */, C4E2E86928FC3002003B070C /* Utility.swift in Sources */, - C471E83D28F9BB650021E251 /* FakeSiteScanner.swift in Sources */, + C471E83D28F9BB650021E251 /* FakeDomainScanner.swift in Sources */, C471E83F28F9BB650021E251 /* AppDelegate.swift in Sources */, C471E84028F9BB650021E251 /* AppDelegate+MenuOutlets.swift in Sources */, C4D36603291132B7006BD146 /* ValetScanners.swift in Sources */, @@ -2160,7 +2137,6 @@ C471E85628F9BB650021E251 /* DomainListCellProtocol.swift in Sources */, C4D36617291160A1006BD146 /* WIP.swift in Sources */, C471E85728F9BB650021E251 /* DomainListTLSCell.swift in Sources */, - C4D366082911331E006BD146 /* EmptyProxyScanner.swift in Sources */, C471E85828F9BB650021E251 /* DomainListNameCell.swift in Sources */, C471E85928F9BB650021E251 /* DomainListPhpCell.swift in Sources */, C471E85A28F9BB650021E251 /* DomainListTypeCell.swift in Sources */, @@ -2285,17 +2261,14 @@ C471E89528F9BB8F0021E251 /* MenuBarImageGenerator.swift in Sources */, C471E89628F9BB8F0021E251 /* PMWindowController.swift in Sources */, C471E89728F9BB8F0021E251 /* VersionExtractor.swift in Sources */, - C4D366092911331E006BD146 /* EmptyProxyScanner.swift in Sources */, C4E2E86728FC2F1B003B070C /* XCPMApplication.swift in Sources */, C471E89828F9BB8F0021E251 /* ValetProxy.swift in Sources */, C471E89928F9BB8F0021E251 /* ValetProxy+Fake.swift in Sources */, - C471E89A28F9BB8F0021E251 /* ProxyScanner.swift in Sources */, - C471E89B28F9BB8F0021E251 /* ValetProxyScanner.swift in Sources */, + C471E89A28F9BB8F0021E251 /* DomainScanner.swift in Sources */, C471E89C28F9BB8F0021E251 /* ValetSite.swift in Sources */, C471E89D28F9BB8F0021E251 /* ValetSite+Fake.swift in Sources */, - C471E89E28F9BB8F0021E251 /* SiteScanner.swift in Sources */, - C471E89F28F9BB8F0021E251 /* ValetSiteScanner.swift in Sources */, - C471E8A028F9BB8F0021E251 /* FakeSiteScanner.swift in Sources */, + C471E89F28F9BB8F0021E251 /* ValetDomainScanner.swift in Sources */, + C471E8A028F9BB8F0021E251 /* FakeDomainScanner.swift in Sources */, C471E8A228F9BB8F0021E251 /* AppDelegate.swift in Sources */, C471E8A328F9BB8F0021E251 /* AppDelegate+MenuOutlets.swift in Sources */, C471E8A428F9BB8F0021E251 /* AppDelegate+Notifications.swift in Sources */, @@ -2463,7 +2436,6 @@ C493084B279F331F009C240B /* AddSiteVC.swift in Sources */, C44A874928905BB000498BC4 /* ProgressVC.swift in Sources */, C4D9ADC0277610E1007277F4 /* PhpSwitcher.swift in Sources */, - C41C02AA27E61CA3009F26CB /* SiteScanner.swift in Sources */, C485707528BF454F00539B36 /* StatsView.swift in Sources */, C4080FFB27BD956700BF2C6B /* BetterAlertVC.swift in Sources */, C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */, @@ -2476,9 +2448,8 @@ C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */, C40C7F2927721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */, C485707A28BF457800539B36 /* WarningListView.swift in Sources */, - C4C0E8E827F88B41002D32A9 /* ProxyScanner.swift in Sources */, + C4C0E8E827F88B41002D32A9 /* DomainScanner.swift in Sources */, C449B4F027EE7FB800C47E8A /* DomainListTLSCell.swift in Sources */, - C4D366072911331E006BD146 /* EmptyProxyScanner.swift in Sources */, C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */, C43A8A2425D9D20D00591B77 /* HomebrewPackageTest.swift in Sources */, C485707928BF456C00539B36 /* ArrayExtension.swift in Sources */, @@ -2490,7 +2461,7 @@ C4AF9F7B2754499000D44ED0 /* Valet.swift in Sources */, C4C1019C27C65C6F001FACC2 /* Process.swift in Sources */, C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */, - C4C0E8E327F88B13002D32A9 /* ValetSiteScanner.swift in Sources */, + C4C0E8E327F88B13002D32A9 /* ValetDomainScanner.swift in Sources */, C4CCBA6D275C567B008C7055 /* PMWindowController.swift in Sources */, C4B5635F276AB09000F12CCB /* VersionExtractor.swift in Sources */, C463E381284930EE00422731 /* PresetHelper.swift in Sources */, @@ -2531,7 +2502,6 @@ C40FE738282ABA4F00A302C2 /* AppVersion.swift in Sources */, C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */, C4E49DEE28F764A00026AC4E /* TestableCommand.swift in Sources */, - C484437C2804BB560041A78A /* ValetProxyScanner.swift in Sources */, C4AF9F78275447F100D44ED0 /* ValetConfigurationTest.swift in Sources */, C40175B92903108900763A68 /* ValetInteractor.swift in Sources */, C4CE3BBC27B324250086CA49 /* ComposerWindow.swift in Sources */, @@ -2579,7 +2549,7 @@ C40B24F227A310770018C7D2 /* Events.swift in Sources */, C44AD3F72912EF7100997FF4 /* RealFileSystemTest.swift in Sources */, C4F30B0A278E1A1A00755FCE /* ComposerJson.swift in Sources */, - C4C0E8E027F88AEB002D32A9 /* FakeSiteScanner.swift in Sources */, + C4C0E8E027F88AEB002D32A9 /* FakeDomainScanner.swift in Sources */, C4AF9F7D275454A900D44ED0 /* ValetVersionExtractorTest.swift in Sources */, C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */, C4B585452770FE3900DA4FBE /* RealCommand.swift in Sources */, diff --git a/phpmon/Common/Testables/TestableConfiguration.swift b/phpmon/Common/Testables/TestableConfiguration.swift index 9fccf70..4c05182 100644 --- a/phpmon/Common/Testables/TestableConfiguration.swift +++ b/phpmon/Common/Testables/TestableConfiguration.swift @@ -25,7 +25,7 @@ public struct TestableConfiguration: Codable { Log.info("Applying fake commands...") ActiveCommand.useTestable(commandOutput) Log.info("Applying fake scanner...") - ValetScanners.useFake() + ValetScanner.useFake() Log.info("Applying fake Valet domain interactor...") ValetInteractor.useFake() } diff --git a/phpmon/Domain/Integrations/Valet/Domains/FakeValetInteractor.swift b/phpmon/Domain/Integrations/Valet/Domains/FakeValetInteractor.swift index 12acd29..0dab7b4 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/FakeValetInteractor.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/FakeValetInteractor.swift @@ -23,8 +23,9 @@ class FakeValetInteractor: ValetInteractor { override func unlink(site: ValetSite) async throws { await delay(seconds: delayTime) - if let scanner = ValetScanners.siteScanner as? FakeSiteScanner { - scanner.fakes.removeAll { $0 === site } + + if let scanner = ValetScanner.active as? FakeDomainScanner { + scanner.sites.removeAll { $0 === site } } } @@ -44,6 +45,9 @@ class FakeValetInteractor: ValetInteractor { override func remove(proxy: ValetProxy) async throws { await delay(seconds: delayTime) - #warning("A fake proxy scanner needs to be added") + + if let scanner = ValetScanner.active as? FakeDomainScanner { + scanner.proxies.removeAll { $0 === proxy } + } } } diff --git a/phpmon/Domain/Integrations/Valet/Proxies/ProxyScanner/EmptyProxyScanner.swift b/phpmon/Domain/Integrations/Valet/Proxies/ProxyScanner/EmptyProxyScanner.swift deleted file mode 100644 index abfad2e..0000000 --- a/phpmon/Domain/Integrations/Valet/Proxies/ProxyScanner/EmptyProxyScanner.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// EmptyProxyScanner.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 01/11/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. -// - -import Foundation - -class EmptyProxyScanner: ProxyScanner { - func resolveProxies(directoryPath: String) -> [ValetProxy] { - return [] - } -} diff --git a/phpmon/Domain/Integrations/Valet/Proxies/ProxyScanner/ProxyScanner.swift b/phpmon/Domain/Integrations/Valet/Proxies/ProxyScanner/ProxyScanner.swift deleted file mode 100644 index a0e95e8..0000000 --- a/phpmon/Domain/Integrations/Valet/Proxies/ProxyScanner/ProxyScanner.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// ProxyScanner.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 02/04/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. -// - -import Foundation - -protocol ProxyScanner { - - func resolveProxies(directoryPath: String) -> [ValetProxy] - -} diff --git a/phpmon/Domain/Integrations/Valet/Proxies/ProxyScanner/ValetProxyScanner.swift b/phpmon/Domain/Integrations/Valet/Proxies/ProxyScanner/ValetProxyScanner.swift deleted file mode 100644 index b715fff..0000000 --- a/phpmon/Domain/Integrations/Valet/Proxies/ProxyScanner/ValetProxyScanner.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// ValetProxyScanner.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 11/04/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. -// - -import Foundation - -class ValetProxyScanner: ProxyScanner { - func resolveProxies(directoryPath: String) -> [ValetProxy] { - return try! FileManager - .default - .contentsOfDirectory(atPath: directoryPath) - .filter { - return !$0.starts(with: ".") - } - .compactMap { - return NginxConfigurationFile.from(filePath: "\(directoryPath)/\($0)") - } - .filter { - return $0.proxy != nil - } - .map { - return ValetProxy($0) - } - } -} diff --git a/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/SiteScanner.swift b/phpmon/Domain/Integrations/Valet/Scanners/DomainScanner.swift similarity index 56% rename from phpmon/Domain/Integrations/Valet/Sites/SiteScanner/SiteScanner.swift rename to phpmon/Domain/Integrations/Valet/Scanners/DomainScanner.swift index 8ffbd67..a4a16d8 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/SiteScanner.swift +++ b/phpmon/Domain/Integrations/Valet/Scanners/DomainScanner.swift @@ -1,17 +1,25 @@ // -// ValetSiteScanner.swift +// DomainScanner.swift // PHP Monitor // -// Created by Nico Verbruggen on 19/03/2022. +// Created by Nico Verbruggen on 02/04/2022. // Copyright © 2022 Nico Verbruggen. All rights reserved. // import Foundation -protocol SiteScanner { +protocol DomainScanner { + + // MARK: - Sites + func resolveSiteCount(paths: [String]) -> Int func resolveSitesFrom(paths: [String]) -> [ValetSite] func resolveSite(path: String) -> ValetSite? + + // MARK: - Proxies + + func resolveProxies(directoryPath: String) -> [ValetProxy] + } diff --git a/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/FakeSiteScanner.swift b/phpmon/Domain/Integrations/Valet/Scanners/FakeDomainScanner.swift similarity index 78% rename from phpmon/Domain/Integrations/Valet/Sites/SiteScanner/FakeSiteScanner.swift rename to phpmon/Domain/Integrations/Valet/Scanners/FakeDomainScanner.swift index f96f0c2..2f2342d 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/FakeSiteScanner.swift +++ b/phpmon/Domain/Integrations/Valet/Scanners/FakeDomainScanner.swift @@ -1,13 +1,14 @@ // -// FakeSiteScanner.swift +// FakeDomainScanner.swift // PHP Monitor // // Created by Nico Verbruggen on 02/04/2022. // Copyright © 2022 Nico Verbruggen. All rights reserved. // -class FakeSiteScanner: SiteScanner { - var fakes = [ +class FakeDomainScanner: DomainScanner { + + var sites: [ValetSite] = [ FakeValetSite(fakeWithName: "laravel", tld: "test", secure: true, path: "~/Code/laravel/framework", linked: true), @@ -27,15 +28,27 @@ class FakeSiteScanner: SiteScanner { path: "~/Sites/wordpress", linked: false, driver: "WordPress", constraint: "^7.4", isolated: "7.4") ] + var proxies: [ValetProxy] = [ + // TODO: Add new proxy here + ] + + // MARK: - Sites + func resolveSiteCount(paths: [String]) -> Int { - return fakes.count + return sites.count } func resolveSitesFrom(paths: [String]) -> [ValetSite] { - return fakes + return sites } func resolveSite(path: String) -> ValetSite? { return nil } + + // MARK: - Proxies + + func resolveProxies(directoryPath: String) -> [ValetProxy] { + return proxies + } } diff --git a/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/ValetSiteScanner.swift b/phpmon/Domain/Integrations/Valet/Scanners/ValetDomainScanner.swift similarity index 77% rename from phpmon/Domain/Integrations/Valet/Sites/SiteScanner/ValetSiteScanner.swift rename to phpmon/Domain/Integrations/Valet/Scanners/ValetDomainScanner.swift index 6fe84d3..5cc0e98 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/ValetSiteScanner.swift +++ b/phpmon/Domain/Integrations/Valet/Scanners/ValetDomainScanner.swift @@ -1,5 +1,5 @@ // -// ValetSiteScanner.swift +// ValetDomainScanner.swift // PHP Monitor // // Created by Nico Verbruggen on 02/04/2022. @@ -8,7 +8,10 @@ import Foundation -class ValetSiteScanner: SiteScanner { +class ValetDomainScanner: DomainScanner { + + // MARK: - Sites + func resolveSiteCount(paths: [String]) -> Int { return paths.map { path in @@ -76,4 +79,24 @@ class ValetSiteScanner: SiteScanner { return (FileSystem.isDirectory(siteDir) || FileSystem.isSymlink(siteDir)) } + + // MARK: - Proxies + + func resolveProxies(directoryPath: String) -> [ValetProxy] { + return try! FileManager + .default + .contentsOfDirectory(atPath: directoryPath) + .filter { + return !$0.starts(with: ".") + } + .compactMap { + return NginxConfigurationFile.from(filePath: "\(directoryPath)/\($0)") + } + .filter { + return $0.proxy != nil + } + .map { + return ValetProxy($0) + } + } } diff --git a/phpmon/Domain/Integrations/Valet/Scanners/ValetScanners.swift b/phpmon/Domain/Integrations/Valet/Scanners/ValetScanners.swift new file mode 100644 index 0000000..6db3a1f --- /dev/null +++ b/phpmon/Domain/Integrations/Valet/Scanners/ValetScanners.swift @@ -0,0 +1,19 @@ +// +// Valet+Scanners.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 01/11/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class ValetScanner { + + static var active: DomainScanner = ValetDomainScanner() + + public static func useFake() { + ValetScanner.active = FakeDomainScanner() + } + +} diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 83dca03..e6f422b 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -49,7 +49,7 @@ class Valet { public func checkForMarketingMode() { if ProcessInfo.processInfo.environment["PHPMON_MARKETING_MODE"] != nil { Log.info("Using a fake list of sites for Marketing Mode!") - ValetScanners.useFake() + ValetScanner.useFake() } } @@ -188,8 +188,7 @@ class Valet { Returns a count of how many sites are linked and parked. */ private func countPaths() -> Int { - return ValetScanners.siteScanner - .resolveSiteCount(paths: config.paths) + return ValetScanner.active.resolveSiteCount(paths: config.paths) } /** @@ -198,19 +197,19 @@ class Valet { private func resolvePaths() { isBusy = true - sites = ValetScanners.siteScanner + sites = ValetScanner.active .resolveSitesFrom(paths: config.paths) .sorted { $0.absolutePath < $1.absolutePath } - proxies = ValetScanners.proxyScanner + proxies = ValetScanner.active .resolveProxies( directoryPath: "~/.config/valet/Nginx".replacingTildeWithHomeDirectory ) if let defaultPath = Valet.shared.config.defaultSite, - let site = ValetSiteScanner().resolveSite(path: defaultPath) { + let site = ValetScanner.active.resolveSite(path: defaultPath) { sites.insert(site, at: 0) } diff --git a/phpmon/Domain/Integrations/Valet/ValetScanners.swift b/phpmon/Domain/Integrations/Valet/ValetScanners.swift deleted file mode 100644 index 58b5e93..0000000 --- a/phpmon/Domain/Integrations/Valet/ValetScanners.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// Valet+Scanners.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 01/11/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. -// - -import Foundation - -class ValetScanners { - - static var siteScanner: SiteScanner = ValetSiteScanner() - static var proxyScanner: ProxyScanner = ValetProxyScanner() - - public static func useFake() { - ValetScanners.siteScanner = FakeSiteScanner() - ValetScanners.proxyScanner = EmptyProxyScanner() - } - -} From de6dea066e068ba782acfa28542cb56ea39333e6 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 16 Dec 2022 20:20:31 +0100 Subject: [PATCH 111/181] =?UTF-8?q?=E2=9C=A8=20Use=20ValetInteractor=20to?= =?UTF-8?q?=20add=20links=20and=20proxies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 40 ++++++------ phpmon/Domain/DomainList/AddProxyVC.swift | 10 +-- phpmon/Domain/DomainList/AddSiteVC.swift | 30 ++++----- .../DomainList/DomainListVC+Actions.swift | 10 ++- phpmon/Domain/DomainList/DomainListVC.swift | 14 +++-- .../Valet/Domains/FakeValetInteractor.swift | 63 ++++++++++++++----- .../Valet/Domains/ValetInteractor.swift | 33 +++++++--- .../Valet/Proxies/FakeValetProxy.swift | 15 +++++ .../Valet/Proxies/ValetProxy+Fake.swift | 13 ---- .../Valet/Proxies/ValetProxy.swift | 18 ++++-- .../Valet/Scanners/FakeDomainScanner.swift | 2 +- ...letSite+Fake.swift => FakeValetSite.swift} | 0 12 files changed, 159 insertions(+), 89 deletions(-) create mode 100644 phpmon/Domain/Integrations/Valet/Proxies/FakeValetProxy.swift delete mode 100644 phpmon/Domain/Integrations/Valet/Proxies/ValetProxy+Fake.swift rename phpmon/Domain/Integrations/Valet/Sites/{ValetSite+Fake.swift => FakeValetSite.swift} (100%) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 0d14e8b..8047a72 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -79,8 +79,8 @@ C417DC74277614690015E6EE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; }; C417DC75277614690015E6EE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; }; C4181F1128FAF9330042EA28 /* UITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4181F1028FAF9330042EA28 /* UITestCase.swift */; }; - C41C02A927E61A65009F26CB /* ValetSite+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */; }; - C41C02AB27E61CB3009F26CB /* ValetSite+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */; }; + C41C02A927E61A65009F26CB /* FakeValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* FakeValetSite.swift */; }; + C41C02AB27E61CB3009F26CB /* FakeValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* FakeValetSite.swift */; }; C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; }; C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3A22B0098000E7CF16 /* Assets.xcassets */; }; C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3C22B0098000E7CF16 /* Main.storyboard */; }; @@ -154,6 +154,10 @@ C464ADAF275A7A69003FCD53 /* DomainListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAE275A7A69003FCD53 /* DomainListVC.swift */; }; C464ADB0275A7A6A003FCD53 /* DomainListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAE275A7A69003FCD53 /* DomainListVC.swift */; }; C464ADB2275A87CA003FCD53 /* DomainListCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */; }; + C469E6FE294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */; }; + C469E6FF294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */; }; + C469E700294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */; }; + C469E701294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */; }; C46E206D28299B3800D909D6 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; C46E206E28299B3800D909D6 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; C46E20702829D27F00D909D6 /* AppUpdaterCheckTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206F2829D27F00D909D6 /* AppUpdaterCheckTest.swift */; }; @@ -281,10 +285,9 @@ C471E83328F9BB650021E251 /* PMWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CCBA6B275C567B008C7055 /* PMWindowController.swift */; }; C471E83428F9BB650021E251 /* VersionExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5635D276AB09000F12CCB /* VersionExtractor.swift */; }; C471E83528F9BB650021E251 /* ValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4205A7D27F4D21800191A39 /* ValetProxy.swift */; }; - C471E83628F9BB650021E251 /* ValetProxy+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */; }; C471E83728F9BB650021E251 /* DomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E627F88B41002D32A9 /* DomainScanner.swift */; }; C471E83928F9BB650021E251 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; - C471E83A28F9BB650021E251 /* ValetSite+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */; }; + C471E83A28F9BB650021E251 /* FakeValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* FakeValetSite.swift */; }; C471E83C28F9BB650021E251 /* ValetDomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E127F88B13002D32A9 /* ValetDomainScanner.swift */; }; C471E83D28F9BB650021E251 /* FakeDomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8DE27F88AEB002D32A9 /* FakeDomainScanner.swift */; }; C471E83F28F9BB650021E251 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; }; @@ -372,10 +375,9 @@ C471E89628F9BB8F0021E251 /* PMWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CCBA6B275C567B008C7055 /* PMWindowController.swift */; }; C471E89728F9BB8F0021E251 /* VersionExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5635D276AB09000F12CCB /* VersionExtractor.swift */; }; C471E89828F9BB8F0021E251 /* ValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4205A7D27F4D21800191A39 /* ValetProxy.swift */; }; - C471E89928F9BB8F0021E251 /* ValetProxy+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */; }; C471E89A28F9BB8F0021E251 /* DomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E627F88B41002D32A9 /* DomainScanner.swift */; }; C471E89C28F9BB8F0021E251 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; - C471E89D28F9BB8F0021E251 /* ValetSite+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */; }; + C471E89D28F9BB8F0021E251 /* FakeValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* FakeValetSite.swift */; }; C471E89F28F9BB8F0021E251 /* ValetDomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E127F88B13002D32A9 /* ValetDomainScanner.swift */; }; C471E8A028F9BB8F0021E251 /* FakeDomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8DE27F88AEB002D32A9 /* FakeDomainScanner.swift */; }; C471E8A228F9BB8F0021E251 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; }; @@ -532,8 +534,6 @@ C4C0E8E327F88B13002D32A9 /* ValetDomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E127F88B13002D32A9 /* ValetDomainScanner.swift */; }; C4C0E8E727F88B41002D32A9 /* DomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E627F88B41002D32A9 /* DomainScanner.swift */; }; C4C0E8E827F88B41002D32A9 /* DomainScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E627F88B41002D32A9 /* DomainScanner.swift */; }; - C4C0E8EA27F88B80002D32A9 /* ValetProxy+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */; }; - C4C0E8EB27F88B80002D32A9 /* ValetProxy+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */; }; C4C1019B27C65C6F001FACC2 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C1019A27C65C6F001FACC2 /* Process.swift */; }; C4C1019C27C65C6F001FACC2 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C1019A27C65C6F001FACC2 /* Process.swift */; }; C4C3643928AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */; }; @@ -739,7 +739,7 @@ C4168F4427ADB4A3003B6C39 /* DEVELOPER.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = DEVELOPER.md; sourceTree = ""; }; C417DC73277614690015E6EE /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; C4181F1028FAF9330042EA28 /* UITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestCase.swift; sourceTree = ""; }; - C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ValetSite+Fake.swift"; sourceTree = ""; }; + C41C02A827E61A65009F26CB /* FakeValetSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeValetSite.swift; sourceTree = ""; }; C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PHP Monitor.app"; sourceTree = BUILT_PRODUCTS_DIR; }; C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C41C1B3A22B0098000E7CF16 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -791,6 +791,7 @@ C464ADAB275A7A3F003FCD53 /* DomainListWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListWindowController.swift; sourceTree = ""; }; C464ADAE275A7A69003FCD53 /* DomainListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListVC.swift; sourceTree = ""; }; C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListCellProtocol.swift; sourceTree = ""; }; + C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeValetProxy.swift; sourceTree = ""; }; C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateChecker.swift; sourceTree = ""; }; C46E206F2829D27F00D909D6 /* AppUpdaterCheckTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppUpdaterCheckTest.swift; sourceTree = ""; }; C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellProtocol.swift; sourceTree = ""; }; @@ -842,7 +843,6 @@ C4C0E8DE27F88AEB002D32A9 /* FakeDomainScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeDomainScanner.swift; sourceTree = ""; }; C4C0E8E127F88B13002D32A9 /* ValetDomainScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetDomainScanner.swift; sourceTree = ""; }; C4C0E8E627F88B41002D32A9 /* DomainScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainScanner.swift; sourceTree = ""; }; - C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ValetProxy+Fake.swift"; sourceTree = ""; }; C4C1019A27C65C6F001FACC2 /* Process.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Process.swift; sourceTree = ""; }; C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Items.swift"; sourceTree = ""; }; C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Startup.swift"; sourceTree = ""; }; @@ -1488,7 +1488,7 @@ isa = PBXGroup; children = ( C4E4404527C56F4700D225E1 /* ValetSite.swift */, - C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */, + C41C02A827E61A65009F26CB /* FakeValetSite.swift */, C4C0E8E427F88B1F002D32A9 /* SiteScanner */, ); path = Sites; @@ -1498,7 +1498,7 @@ isa = PBXGroup; children = ( C4205A7D27F4D21800191A39 /* ValetProxy.swift */, - C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */, + C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */, ); path = Proxies; sourceTree = ""; @@ -1947,7 +1947,6 @@ C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */, C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */, C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */, - C4C0E8EA27F88B80002D32A9 /* ValetProxy+Fake.swift in Sources */, C4EB53E728553117006F9937 /* ArrayExtension.swift in Sources */, 5420395926135DC100FB00FA /* PrefsVC.swift in Sources */, C4C8900328F0E28800CE5E97 /* FileSystemProtocol.swift in Sources */, @@ -1970,7 +1969,7 @@ C41E871A2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */, C40C7F2827721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */, C463E380284930EE00422731 /* PresetHelper.swift in Sources */, - C41C02A927E61A65009F26CB /* ValetSite+Fake.swift in Sources */, + C41C02A927E61A65009F26CB /* FakeValetSite.swift in Sources */, C4E2E85C28FC282B003B070C /* TestableConfiguration.swift in Sources */, C4C0E8DF27F88AEB002D32A9 /* FakeDomainScanner.swift in Sources */, C44B3A4628E5C70100718CB1 /* TimeIntervalExtension.swift in Sources */, @@ -2035,6 +2034,7 @@ C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */, C42759672627662800093CAE /* NSMenuExtension.swift in Sources */, C422DDAA28A2C49900CEAC97 /* WarningListView.swift in Sources */, + C469E6FE294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */, C464ADAF275A7A69003FCD53 /* DomainListVC.swift in Sources */, C44CCD4927AFF3B700CE40E5 /* MainMenu+Async.swift in Sources */, C4C1019B27C65C6F001FACC2 /* Process.swift in Sources */, @@ -2101,10 +2101,9 @@ C471E83328F9BB650021E251 /* PMWindowController.swift in Sources */, C471E83428F9BB650021E251 /* VersionExtractor.swift in Sources */, C471E83528F9BB650021E251 /* ValetProxy.swift in Sources */, - C471E83628F9BB650021E251 /* ValetProxy+Fake.swift in Sources */, C471E83728F9BB650021E251 /* DomainScanner.swift in Sources */, C471E83928F9BB650021E251 /* ValetSite.swift in Sources */, - C471E83A28F9BB650021E251 /* ValetSite+Fake.swift in Sources */, + C471E83A28F9BB650021E251 /* FakeValetSite.swift in Sources */, C471E83C28F9BB650021E251 /* ValetDomainScanner.swift in Sources */, C4E2E86928FC3002003B070C /* Utility.swift in Sources */, C471E83D28F9BB650021E251 /* FakeDomainScanner.swift in Sources */, @@ -2199,6 +2198,7 @@ C471E7F328F9BAC70021E251 /* PhpHelper.swift in Sources */, C471E7E728F9BAC20021E251 /* Constants.swift in Sources */, C471E81628F9BAE80021E251 /* DateExtension.swift in Sources */, + C469E700294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */, C471E7D728F9BA8F0021E251 /* TestableFileSystem.swift in Sources */, C471E81A28F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */, C471E7E128F9BAAB0021E251 /* RealCommand.swift in Sources */, @@ -2263,10 +2263,9 @@ C471E89728F9BB8F0021E251 /* VersionExtractor.swift in Sources */, C4E2E86728FC2F1B003B070C /* XCPMApplication.swift in Sources */, C471E89828F9BB8F0021E251 /* ValetProxy.swift in Sources */, - C471E89928F9BB8F0021E251 /* ValetProxy+Fake.swift in Sources */, C471E89A28F9BB8F0021E251 /* DomainScanner.swift in Sources */, C471E89C28F9BB8F0021E251 /* ValetSite.swift in Sources */, - C471E89D28F9BB8F0021E251 /* ValetSite+Fake.swift in Sources */, + C471E89D28F9BB8F0021E251 /* FakeValetSite.swift in Sources */, C471E89F28F9BB8F0021E251 /* ValetDomainScanner.swift in Sources */, C471E8A028F9BB8F0021E251 /* FakeDomainScanner.swift in Sources */, C471E8A228F9BB8F0021E251 /* AppDelegate.swift in Sources */, @@ -2360,6 +2359,7 @@ C471E7D028F9BA630021E251 /* FileSystemProtocol.swift in Sources */, C471E81228F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */, C471E7DF28F9BAAB0021E251 /* RealCommand.swift in Sources */, + C469E701294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */, C471E7E028F9BAAB0021E251 /* ActiveCommand.swift in Sources */, C40175BB2903108900763A68 /* ValetInteractor.swift in Sources */, C471E80928F9BADC0021E251 /* CreatedFromFile.swift in Sources */, @@ -2467,7 +2467,6 @@ C463E381284930EE00422731 /* PresetHelper.swift in Sources */, C46FA98C2822F08F00D78807 /* PhpConfigurationTest.swift in Sources */, C4BF90C127C57C220054E78C /* MainMenu+FixMyValet.swift in Sources */, - C4C0E8EB27F88B80002D32A9 /* ValetProxy+Fake.swift in Sources */, C4E49DEB28F7643D0026AC4E /* CommandProtocol.swift in Sources */, C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */, C485707428BF454E00539B36 /* ServicesView.swift in Sources */, @@ -2507,6 +2506,7 @@ C4CE3BBC27B324250086CA49 /* ComposerWindow.swift in Sources */, C40B24F427A310830018C7D2 /* StatusMenu.swift in Sources */, C417DC75277614690015E6EE /* Helpers.swift in Sources */, + C469E6FF294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */, C4080FF727BD8C6400BF2C6B /* BetterAlert.swift in Sources */, C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */, 5489625928313231004F647A /* CreatedFromFile.swift in Sources */, @@ -2564,7 +2564,7 @@ C48D6C71279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */, C485706F28BF452300539B36 /* WarningsWindowController.swift in Sources */, C46FA9892822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */, - C41C02AB27E61CB3009F26CB /* ValetSite+Fake.swift in Sources */, + C41C02AB27E61CB3009F26CB /* FakeValetSite.swift in Sources */, C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */, C4D9F24C280B69E100DCD39A /* AddProxyVC.swift in Sources */, C4B5853F2770FE3900DA4FBE /* Paths.swift in Sources */, diff --git a/phpmon/Domain/DomainList/AddProxyVC.swift b/phpmon/Domain/DomainList/AddProxyVC.swift index 6d0fe81..b36f45b 100644 --- a/phpmon/Domain/DomainList/AddProxyVC.swift +++ b/phpmon/Domain/DomainList/AddProxyVC.swift @@ -65,16 +65,18 @@ class AddProxyVC: NSViewController, NSTextFieldDelegate { @IBAction func pressedCreateProxy(_ sender: Any) { let domain = self.inputDomainName.stringValue let proxyName = self.inputProxySubject.stringValue - let secure = self.buttonSecure.state == .on ? " --secure" : "" + let secure = (self.buttonSecure.state == .on) dismissView(outcome: .OK) App.shared.domainListWindowController?.contentVC.setUIBusy() Task { // Ensure we proxy the site asynchronously and reload UI on main thread again - #warning("Creating a proxy should happen via the ValetInteractor") - await Shell.quiet("\(Paths.valet) proxy \(domain) \(proxyName)\(secure)") - await Actions.restartNginx() + try! await ValetInteractor.shared.proxy( + domain: domain, + proxy: proxyName, + secure: secure + ) Task { @MainActor in // TODO: Check if this can be removed diff --git a/phpmon/Domain/DomainList/AddSiteVC.swift b/phpmon/Domain/DomainList/AddSiteVC.swift index b4f5f71..9e72f32 100644 --- a/phpmon/Domain/DomainList/AddSiteVC.swift +++ b/phpmon/Domain/DomainList/AddSiteVC.swift @@ -70,24 +70,24 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate { } // Adding `valet links` is a workaround for Valet malforming the config.json file - // TODO: I will have to investigate and report this behaviour if possible - #warning("Linking a site should happen via the ValetInteractor") - Task { await Shell.quiet("cd '\(path)' && \(Paths.valet) link '\(name)' && valet links") } + Task { + try! await ValetInteractor.shared.link(path: path, domain: name) - dismissView(outcome: .OK) + dismissView(outcome: .OK) - // Reset search - App.shared.domainListWindowController? - .searchToolbarItem - .searchField.stringValue = "" + // Reset search + App.shared.domainListWindowController? + .searchToolbarItem + .searchField.stringValue = "" - // Add the new item and scrolls to it - await App.shared.domainListWindowController? - .contentVC - .addedNewSite( - name: name, - secure: buttonSecure.state == .on - ) + // Add the new item and scrolls to it + await App.shared.domainListWindowController? + .contentVC + .addedNewSite( + name: name, + secureAfterLinking: buttonSecure.state == .on + ) + } } @IBAction func pressedCreateLink(_ sender: Any) { diff --git a/phpmon/Domain/DomainList/DomainListVC+Actions.swift b/phpmon/Domain/DomainList/DomainListVC+Actions.swift index dd481c4..236cb96 100644 --- a/phpmon/Domain/DomainList/DomainListVC+Actions.swift +++ b/phpmon/Domain/DomainList/DomainListVC+Actions.swift @@ -166,9 +166,8 @@ extension DomainListVC { style: .critical, onFirstButtonPressed: { self.waitAndExecute { - Task { await site.unlink() } - } completion: { - Task { await self.reloadDomains() } + await site.unlink() + await self.reloadDomainsWithoutUI() } } ) @@ -188,9 +187,8 @@ extension DomainListVC { style: .critical, onFirstButtonPressed: { self.waitAndExecute { - Task { await proxy.remove() } - } completion: { - Task { await self.reloadDomains() } + await proxy.remove() + await self.reloadDomainsWithoutUI() } } ) diff --git a/phpmon/Domain/DomainList/DomainListVC.swift b/phpmon/Domain/DomainList/DomainListVC.swift index 19ba610..43d6632 100644 --- a/phpmon/Domain/DomainList/DomainListVC.swift +++ b/phpmon/Domain/DomainList/DomainListVC.swift @@ -162,6 +162,12 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource } } + func reloadDomainsWithoutUI() async { + await Valet.shared.reloadSites() + domains = Valet.shared.sites + searchedFor(text: lastSearchedFor) + } + func applySortDescriptor(_ descriptor: NSSortDescriptor) { sortDescriptor = descriptor @@ -179,22 +185,22 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource self.domains = descriptor.ascending ? sorted.reversed() : sorted } - func addedNewSite(name: String, secure: Bool) async { + func addedNewSite(name: String, secureAfterLinking: Bool) async { waitAndExecute { await Valet.shared.reloadSites() } completion: { [self] in - find(name, secure) + find(name, secureAfterLinking) } } - private func find(_ name: String, _ secure: Bool = false) { + private func find(_ name: String, _ shouldSecure: Bool = false) { domains = Valet.getDomainListable() searchedFor(text: "") if let site = domains.enumerated().first(where: { $0.element.getListableName() == name }) { Task { @MainActor in self.tableView.selectRowIndexes([site.offset], byExtendingSelection: false) self.tableView.scrollRowToVisible(site.offset) - if secure && !site.element.getListableSecured() { + if shouldSecure && !site.element.getListableSecured() { self.toggleSecure() } } diff --git a/phpmon/Domain/Integrations/Valet/Domains/FakeValetInteractor.swift b/phpmon/Domain/Integrations/Valet/Domains/FakeValetInteractor.swift index 0dab7b4..336da7a 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/FakeValetInteractor.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/FakeValetInteractor.swift @@ -11,14 +11,22 @@ import Foundation class FakeValetInteractor: ValetInteractor { var delayTime: TimeInterval = 1.0 - override func toggleSecure(proxy: ValetProxy) async throws { - await delay(seconds: delayTime) - proxy.secured = !proxy.secured - } + // MARK: - Managing Domains - override func toggleSecure(site: ValetSite) async throws { + override func link(path: String, domain: String) async throws { await delay(seconds: delayTime) - site.secured = !site.secured + + if let scanner = ValetScanner.active as? FakeDomainScanner { + scanner.sites.append( + FakeValetSite( + fakeWithName: domain, + tld: Valet.shared.config.tld, + secure: false, + path: path, + linked: true + ) + ) + } } override func unlink(site: ValetSite) async throws { @@ -29,6 +37,41 @@ class FakeValetInteractor: ValetInteractor { } } + override func proxy(domain: String, proxy: String, secure: Bool) async throws { + await delay(seconds: delayTime) + + if let scanner = ValetScanner.active as? FakeDomainScanner { + scanner.proxies.append( + FakeValetProxy( + domain: domain, + target: proxy, + secure: secure, + tld: Valet.shared.config.tld + ) + ) + } + } + + override func remove(proxy: ValetProxy) async throws { + await delay(seconds: delayTime) + + if let scanner = ValetScanner.active as? FakeDomainScanner { + scanner.proxies.removeAll { $0.domain == proxy.domain } + } + } + + // MARK: - Modifying Domains + + override func toggleSecure(proxy: ValetProxy) async throws { + await delay(seconds: delayTime) + proxy.secured = !proxy.secured + } + + override func toggleSecure(site: ValetSite) async throws { + await delay(seconds: delayTime) + site.secured = !site.secured + } + override func isolate(site: ValetSite, version: String) async throws { await delay(seconds: delayTime) @@ -42,12 +85,4 @@ class FakeValetInteractor: ValetInteractor { site.isolatedPhpVersion = nil site.evaluateCompatibility() } - - override func remove(proxy: ValetProxy) async throws { - await delay(seconds: delayTime) - - if let scanner = ValetScanner.active as? FakeDomainScanner { - scanner.proxies.removeAll { $0 === proxy } - } - } } diff --git a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift index 39b8493..fb1ae3c 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift @@ -20,6 +20,31 @@ class ValetInteractor { ValetInteractor.shared = FakeValetInteractor() } + // MARK: - Managing Domains + + public func link(path: String, domain: String) async throws { + await Shell.quiet("cd '\(path)' && \(Paths.valet) link '\(domain)' && valet links") + } + + public func unlink(site: ValetSite) async throws { + await Shell.quiet("valet unlink '\(site.name)'") + } + + public func proxy(domain: String, proxy: String, secure: Bool) async throws { + let command = secure + ? "\(Paths.valet) proxy \(domain) \(proxy) --secure" + : "\(Paths.valet) proxy \(domain) \(proxy)" + + await Shell.quiet(command) + await Actions.restartNginx() + } + + public func remove(proxy: ValetProxy) async throws { + await Shell.quiet("valet unproxy '\(proxy.domain)'") + } + + // MARK: - Modifying Domains + public func toggleSecure(site: ValetSite) async throws { // Keep track of the original status (secure or not?) let originalSecureStatus = site.secured @@ -100,12 +125,4 @@ class ValetInteractor { throw ValetInteractionError(command: command) } } - - public func unlink(site: ValetSite) async throws { - await Shell.quiet("valet unlink '\(site.name)'") - } - - public func remove(proxy: ValetProxy) async throws { - await Shell.quiet("valet unproxy '\(proxy.domain)'") - } } diff --git a/phpmon/Domain/Integrations/Valet/Proxies/FakeValetProxy.swift b/phpmon/Domain/Integrations/Valet/Proxies/FakeValetProxy.swift new file mode 100644 index 0000000..6591cda --- /dev/null +++ b/phpmon/Domain/Integrations/Valet/Proxies/FakeValetProxy.swift @@ -0,0 +1,15 @@ +// +// FakeValetProxy.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 16/12/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class FakeValetProxy: ValetProxy { + override func determineSecured() { + return + } +} diff --git a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy+Fake.swift b/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy+Fake.swift deleted file mode 100644 index 87bda2c..0000000 --- a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy+Fake.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// ValetProxy+Fake.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 02/04/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. -// - -import Foundation - -extension ValetProxy { - -} diff --git a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift b/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift index 23d5cf0..e9cfbe1 100644 --- a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift +++ b/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift @@ -14,10 +14,20 @@ class ValetProxy: ValetListable { var target: String var secured: Bool = false - init(_ configuration: NginxConfigurationFile) { - self.domain = configuration.domain - self.tld = configuration.tld - self.target = configuration.proxy! + init(domain: String, target: String, secure: Bool, tld: String) { + self.domain = domain + self.tld = tld + self.target = target + self.secured = false + } + + convenience init(_ configuration: NginxConfigurationFile) { + self.init( + domain: configuration.domain, + target: configuration.proxy!, + secure: false, + tld: configuration.tld + ) self.determineSecured() } diff --git a/phpmon/Domain/Integrations/Valet/Scanners/FakeDomainScanner.swift b/phpmon/Domain/Integrations/Valet/Scanners/FakeDomainScanner.swift index 2f2342d..386edc5 100644 --- a/phpmon/Domain/Integrations/Valet/Scanners/FakeDomainScanner.swift +++ b/phpmon/Domain/Integrations/Valet/Scanners/FakeDomainScanner.swift @@ -29,7 +29,7 @@ class FakeDomainScanner: DomainScanner { ] var proxies: [ValetProxy] = [ - // TODO: Add new proxy here + FakeValetProxy(domain: "mailgun", target: "http://127.0.0.1:9999", secure: true, tld: "test") ] // MARK: - Sites diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite+Fake.swift b/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift similarity index 100% rename from phpmon/Domain/Integrations/Valet/Sites/ValetSite+Fake.swift rename to phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift From 15fe5e47168edf2b1488b39b843e86e6ef7a8a13 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 16 Dec 2022 20:50:53 +0100 Subject: [PATCH 112/181] =?UTF-8?q?=E2=9C=85=20Add=20tests=20for=20domain?= =?UTF-8?q?=20list=20interaction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 4 ++ phpmon/Domain/DomainList/AddProxyVC.swift | 1 - tests/ui/DomainsListTest.swift | 61 +++++++++++++++++++++++ tests/ui/StartupTest.swift | 4 +- tests/ui/UITestCase.swift | 17 +++++++ 5 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 tests/ui/DomainsListTest.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 8047a72..9361364 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -158,6 +158,7 @@ C469E6FF294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */; }; C469E700294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */; }; C469E701294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */; }; + C469E706294CFDF700A82AB2 /* DomainsListTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C469E702294CFDF700A82AB2 /* DomainsListTest.swift */; }; C46E206D28299B3800D909D6 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; C46E206E28299B3800D909D6 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; C46E20702829D27F00D909D6 /* AppUpdaterCheckTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206F2829D27F00D909D6 /* AppUpdaterCheckTest.swift */; }; @@ -792,6 +793,7 @@ C464ADAE275A7A69003FCD53 /* DomainListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListVC.swift; sourceTree = ""; }; C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListCellProtocol.swift; sourceTree = ""; }; C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeValetProxy.swift; sourceTree = ""; }; + C469E702294CFDF700A82AB2 /* DomainsListTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainsListTest.swift; sourceTree = ""; }; C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateChecker.swift; sourceTree = ""; }; C46E206F2829D27F00D909D6 /* AppUpdaterCheckTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppUpdaterCheckTest.swift; sourceTree = ""; }; C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellProtocol.swift; sourceTree = ""; }; @@ -1341,6 +1343,7 @@ isa = PBXGroup; children = ( C471E7BE28F9B90F0021E251 /* StartupTest.swift */, + C469E702294CFDF700A82AB2 /* DomainsListTest.swift */, C4181F1028FAF9330042EA28 /* UITestCase.swift */, ); path = ui; @@ -2403,6 +2406,7 @@ C471E7CD28F9BA600021E251 /* ShellProtocol.swift in Sources */, C471E7EC28F9BAC30021E251 /* Events.swift in Sources */, C471E7CE28F9BA600021E251 /* RealShell.swift in Sources */, + C469E706294CFDF700A82AB2 /* DomainsListTest.swift in Sources */, C471E80F28F9BAE80021E251 /* NSMenuExtension.swift in Sources */, C471E80B28F9BAE80021E251 /* XibLoadable.swift in Sources */, C471E7F428F9BAC80021E251 /* PhpVersionNumber.swift in Sources */, diff --git a/phpmon/Domain/DomainList/AddProxyVC.swift b/phpmon/Domain/DomainList/AddProxyVC.swift index b36f45b..d113deb 100644 --- a/phpmon/Domain/DomainList/AddProxyVC.swift +++ b/phpmon/Domain/DomainList/AddProxyVC.swift @@ -79,7 +79,6 @@ class AddProxyVC: NSViewController, NSTextFieldDelegate { ) Task { @MainActor in - // TODO: Check if this can be removed App.shared.domainListWindowController?.contentVC.setUINotBusy() App.shared.domainListWindowController?.pressedReload(nil) } diff --git a/tests/ui/DomainsListTest.swift b/tests/ui/DomainsListTest.swift new file mode 100644 index 0000000..d06d5bc --- /dev/null +++ b/tests/ui/DomainsListTest.swift @@ -0,0 +1,61 @@ +// +// StartupTest.swift +// UI Tests +// +// Created by Nico Verbruggen on 14/10/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import XCTest + +final class DomainsListTest: UITestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + override func tearDownWithError() throws {} + + private func getApp() -> XCPMApplication { + let app = XCPMApplication() + app.withConfiguration(TestableConfigurations.working) + app.launch() + + // Note: If this fails here, make sure the menu bar item can be displayed + // If you use Bartender or something like this, this item may be hidden and tests will fail + app.statusItems.firstMatch.click() + + return app + } + + final func test_can_always_open_domains_list() throws { + let app = getApp() + + app.menuItems["mi_domain_list".localized].click() + } + + final func test_can_filter_domains_list() throws { + let app = getApp() + + app.menuItems["mi_domain_list".localized].click() + + let window = app.windows.allElementsBoundByIndex.first { element in + element.title == "domain_list.title".localized + }! + + let searchField = window.searchFields.firstMatch + + searchField.click() + searchField.typeText("non-existent thing") + XCTAssertTrue(window.tables.tableRows.count == 0) + + searchField.clearText() + searchField.click() + searchField.typeText("concord") + XCTAssertTrue(window.tables.tableRows.count == 1) + + sleep(2) + } + + +} diff --git a/tests/ui/StartupTest.swift b/tests/ui/StartupTest.swift index 5631360..606aa05 100644 --- a/tests/ui/StartupTest.swift +++ b/tests/ui/StartupTest.swift @@ -67,8 +67,8 @@ final class StartupTest: UITestCase { // Note: If this fails here, make sure the menu bar item can be displayed // If you use Bartender or something like this, this item may be hidden and tests will fail - let statusBarItem = app.statusItems.firstMatch - statusBarItem.click() + app.statusItems.firstMatch.click() + assertAllExist([ // "Switch to PHP 8.1 (php)" should be visible since it is aliased to `php` app.menuItems["\("mi_php_switch".localized) 8.1 (php)"], diff --git a/tests/ui/UITestCase.swift b/tests/ui/UITestCase.swift index f8520dc..ee2d8cf 100644 --- a/tests/ui/UITestCase.swift +++ b/tests/ui/UITestCase.swift @@ -28,3 +28,20 @@ class UITestCase: XCTestCase { } } + +extension XCUIElement { + /** + Clears all the text from a given element. + */ + func clearText() { + guard let stringValue = self.value as? String else { + return + } + + var deleteString = String() + for _ in stringValue { + deleteString += XCUIKeyboardKey.delete.rawValue + } + typeText(deleteString) + } +} From c85a8e381852f502e876c478236af5aadb93b285 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sun, 18 Dec 2022 14:50:03 +0100 Subject: [PATCH 113/181] =?UTF-8?q?=E2=9C=85=20Add=20test=20for=20tapping?= =?UTF-8?q?=20on=20"add=20domain"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/ui/DomainsListTest.swift | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/tests/ui/DomainsListTest.swift b/tests/ui/DomainsListTest.swift index d06d5bc..292ae63 100644 --- a/tests/ui/DomainsListTest.swift +++ b/tests/ui/DomainsListTest.swift @@ -16,7 +16,7 @@ final class DomainsListTest: UITestCase { override func tearDownWithError() throws {} - private func getApp() -> XCPMApplication { + private func openMenu() -> XCPMApplication { let app = XCPMApplication() app.withConfiguration(TestableConfigurations.working) app.launch() @@ -29,13 +29,13 @@ final class DomainsListTest: UITestCase { } final func test_can_always_open_domains_list() throws { - let app = getApp() + let app = openMenu() app.menuItems["mi_domain_list".localized].click() } final func test_can_filter_domains_list() throws { - let app = getApp() + let app = openMenu() app.menuItems["mi_domain_list".localized].click() @@ -57,5 +57,22 @@ final class DomainsListTest: UITestCase { sleep(2) } + final func test_can_tap_add_domain_button() throws { + let app = openMenu() + app.menuItems["mi_domain_list".localized].click() + + let window = app.windows.allElementsBoundByIndex.first { element in + element.title == "domain_list.title".localized + }! + + window.buttons["Add Link"].click() + + assertExists(app.staticTexts["selection.title".localized]) + assertExists(app.buttons["selection.create_link".localized]) + assertExists(app.buttons["selection.create_proxy".localized]) + assertExists(app.buttons["selection.cancel".localized]) + + sleep(2) + } } From 0fdd1264ed36f231aa44e8dddd98b18edd7dc783 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 19 Dec 2022 17:30:56 +0100 Subject: [PATCH 114/181] =?UTF-8?q?=F0=9F=91=8C=20Await=20the=20outcome=20?= =?UTF-8?q?of=20cash=20conflict=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 16 ++++++++-------- .../xcschemes/PHP Monitor DEV.xcscheme | 2 +- .../Homebrew/HomebrewDiagnostics.swift | 8 +++----- phpmon/Domain/Menu/MainMenu+Startup.swift | 2 +- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 9361364..26c6d51 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2749,7 +2749,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1000; + CURRENT_PROJECT_VERSION = 900; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2761,7 +2761,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.0; + MARKETING_VERSION = 5.7; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2778,7 +2778,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1000; + CURRENT_PROJECT_VERSION = 900; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2790,7 +2790,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.0; + MARKETING_VERSION = 5.7; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3006,7 +3006,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1000; + CURRENT_PROJECT_VERSION = 900; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3017,7 +3017,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.0; + MARKETING_VERSION = 5.7; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3116,7 +3116,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1000; + CURRENT_PROJECT_VERSION = 900; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3127,7 +3127,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.0; + MARKETING_VERSION = 5.7; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index b0a4d86..8642ae0 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -91,7 +91,7 @@ + isEnabled = "NO"> Date: Mon, 19 Dec 2022 17:35:32 +0100 Subject: [PATCH 115/181] =?UTF-8?q?=F0=9F=91=8C=20Mastodon=20>=20Twitter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Credits.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpmon/Credits.html b/phpmon/Credits.html index 20c0f5c..078adb3 100644 --- a/phpmon/Credits.html +++ b/phpmon/Credits.html @@ -16,7 +16,7 @@

Having issues? Consult the FAQ section, I did my best to ensure everything is documented.

Want to support further development of PHP Monitor? You can financially support the continued development of this app.

-

Get the latest on Twitter Give me a follow on Twitter to learn about the latest and greatest updates of this app.

+

Get the latest on Mastodon. Give me a follow on Mastodon to learn about what's brewing and when new updates drop.


From ff7c68ddfb557fb6ceb0b053f1de5ffc59b44970 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 19 Dec 2022 18:09:10 +0100 Subject: [PATCH 116/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Services=20rewrit?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 2 +- phpmon/Domain/App/ServicesManager.swift | 26 +++++++++++++++++-- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 6 ++--- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 26c6d51..f880a4b 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -3019,7 +3019,7 @@ MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 5.7; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "$(TARGET_NAME) DEV"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; diff --git a/phpmon/Domain/App/ServicesManager.swift b/phpmon/Domain/App/ServicesManager.swift index 4040a2d..8051b33 100644 --- a/phpmon/Domain/App/ServicesManager.swift +++ b/phpmon/Domain/App/ServicesManager.swift @@ -13,9 +13,11 @@ class ServicesManager: ObservableObject { static var shared = ServicesManager() - private var formulae: [HomebrewFormula] + #warning("Only publish the status") - @Published var services: [String: ServiceWrapper] = [:] + private(set) var formulae: [HomebrewFormula] + + private(set) var services: [String: ServiceWrapper] = [:] init() { Log.info("Initializing ServicesManager...") @@ -35,7 +37,27 @@ class ServicesManager: ObservableObject { services = Dictionary(uniqueKeysWithValues: formulae.map { ($0.name, ServiceWrapper(formula: $0)) }) } + public func updateServicesList() async { + Task { @MainActor in + formulae = [ + Homebrew.Formulae.php, + Homebrew.Formulae.nginx, + Homebrew.Formulae.dnsmasq + ] + + let additionalFormulae = (Preferences.custom.services ?? []).map({ item in + return HomebrewFormula(item, elevated: false) + }) + + formulae.append(contentsOf: additionalFormulae) + + services = Dictionary(uniqueKeysWithValues: formulae.map { ($0.name, ServiceWrapper(formula: $0)) }) + } + } + public static func loadHomebrewServices() async { + await Self.shared.updateServicesList() + Task { let rootServiceNames = Self.shared.formulae .filter { $0.elevated } diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 652f42f..bd3ad91 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -17,9 +17,9 @@ struct ServicesView: View { static func asMenuItem(perRow: Int = 3) -> NSMenuItem { let item = NSMenuItem() - let services = ServicesManager.shared.services.keys.map({ item in - return item - }) + let services = ServicesManager.shared.formulae.map { formula in + return formula.name + } let view = NSHostingView( rootView: Self( From 923f0237e893d33ce67a92c936b6bffa2810a3d7 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 20 Dec 2022 19:42:27 +0100 Subject: [PATCH 117/181] =?UTF-8?q?=F0=9F=94=A5=20WIP:=20Removed=20broken?= =?UTF-8?q?=20view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 8 +- phpmon/Domain/App/ServicesManager.swift | 6 +- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 130 ++---------------- 3 files changed, 15 insertions(+), 129 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index f880a4b..cc7f358 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2749,7 +2749,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 900; + CURRENT_PROJECT_VERSION = 1000; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2778,7 +2778,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 900; + CURRENT_PROJECT_VERSION = 1000; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -3006,7 +3006,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 900; + CURRENT_PROJECT_VERSION = 1000; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3116,7 +3116,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 900; + CURRENT_PROJECT_VERSION = 1000; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; diff --git a/phpmon/Domain/App/ServicesManager.swift b/phpmon/Domain/App/ServicesManager.swift index 8051b33..eab61ac 100644 --- a/phpmon/Domain/App/ServicesManager.swift +++ b/phpmon/Domain/App/ServicesManager.swift @@ -13,11 +13,9 @@ class ServicesManager: ObservableObject { static var shared = ServicesManager() - #warning("Only publish the status") + @Published private(set) var formulae: [HomebrewFormula] - private(set) var formulae: [HomebrewFormula] - - private(set) var services: [String: ServiceWrapper] = [:] + @Published private(set) var services: [String: ServiceWrapper] = [:] init() { Log.info("Initializing ServicesManager...") diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index bd3ad91..b142ba8 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -10,154 +10,42 @@ import Foundation import SwiftUI struct ServicesView: View { - @ObservedObject var manager: ServicesManager - @State var servicesToDisplay: [String] - @State var perRow: Int = 3 - static func asMenuItem(perRow: Int = 3) -> NSMenuItem { let item = NSMenuItem() - let services = ServicesManager.shared.formulae.map { formula in - return formula.name - } - let view = NSHostingView( - rootView: Self( - manager: ServicesManager.shared, - servicesToDisplay: services, - perRow: perRow - ) + rootView: Self() ) view.autoresizingMask = [.width, .height] - let height = CGFloat(45 * services.chunked(by: perRow).count) + + let height = CGFloat(45 * ["a", "b", "c", "d", "e", "f"] + .chunked(by: perRow).count) + view.setFrameSize(CGSize(width: view.frame.width, height: height)) item.view = view return item } var body: some View { - VStack(alignment: .leading, spacing: 10) { - ForEach(servicesToDisplay.chunked(by: self.perRow), id: \.self) { chunk in - HStack { - ForEach(0...self.perRow - 1, id: \.self) { index in - if chunk.indices.contains(index) { - // A service exists to fill the cell - let service = chunk[index] - VStack(alignment: .center, spacing: 3) { - SectionHeaderView(text: service.uppercased()) - CheckmarkView(serviceName: service) - .environmentObject(manager) - }.frame(minWidth: 0, maxWidth: .infinity) - } else { - // Empty cell - VStack { - EmptyView() - }.frame(minWidth: 0, maxWidth: .infinity) - } - } - } - } - } + Text("WIP") .padding(10) .frame(minWidth: 0, maxWidth: .infinity) .background(Color.debug) } } -struct CheckmarkView: View { - @State var serviceName: String - @State var busy: Bool = false - @EnvironmentObject var manager: ServicesManager - - public func hasAnyServices() -> Bool { - return !manager.services.isEmpty - } - - public func active() -> Bool? { - if manager.services.keys.contains(serviceName) { - return manager.services[serviceName]!.service?.running ?? nil - } - - return nil - } - - public func toggleService() async { - if active()! { - await Actions.stopService(name: serviceName) - await delay(seconds: 1.2) - busy = false - } else { - await Actions.startService(name: serviceName) - await delay(seconds: 1.2) - busy = false - } - } - - var body: some View { - if !hasAnyServices() { - Image(systemName: "hourglass.circle") - .resizable() - .frame(width: 16.0, height: 16.0) - .foregroundColor(.appSecondary) - } else { - if busy { - ProgressView() - .scaleEffect(x: 0.5, y: 0.5, anchor: .center) - .frame(width: 16.0, height: 20.0) - } else if active() == nil { - Button { } label: { - Text("?") - }.disabled(true) - } else { - Button { - busy = true - Task { await toggleService() } - } label: { - Image(systemName: active()! ? "checkmark" : "xmark") - .resizable() - .frame(width: 12.0, height: 12.0) - .foregroundColor(active()! ? Color.primary : Color("IconColorRed")) - } - } - } - } -} - struct ServicesView_Previews: PreviewProvider { static var previews: some View { - ServicesView( - manager: ServicesManager() - .withDummyServices([:]), - servicesToDisplay: ["php", "nginx", "dnsmasq"] - ) + ServicesView() .frame(width: 330.0) .previewDisplayName("Loading") - ServicesView( - manager: ServicesManager() - .withDummyServices([ - "php": false, - "nginx": true, - "dnsmasq": true - ]), - servicesToDisplay: ["php", "nginx", "dnsmasq"] - ) + ServicesView() .frame(width: 330.0) .previewDisplayName("Light Mode") - ServicesView( - manager: ServicesManager() - .withDummyServices([ - "php": false, - "nginx": true, - "dnsmasq": true, - "mysql": false - ]), - servicesToDisplay: ["php", "nginx", "dnsmasq", - "mysql", "redis", "php@7.4"], - perRow: 3 - ) + ServicesView() .frame(width: 330.0) .previewDisplayName("Dark Mode") .preferredColorScheme(.dark) From 44c1ea7de45610a93463ae1d362ac09b43fc8853 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 23 Dec 2022 19:20:04 +0100 Subject: [PATCH 118/181] =?UTF-8?q?=F0=9F=8F=97=20SwiftUI=20experiments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 40 +++++- .../xcschemes/PHP Monitor.xcscheme | 2 +- phpmon/Common/Core/Actions.swift | 4 +- phpmon/Common/Core/Homebrew.swift | 21 ++- .../Common/PHP/Homebrew/HomebrewService.swift | 8 +- phpmon/Common/PHP/PHP Version/PhpEnv.swift | 2 +- .../App/Services/FakeServicesManager.swift | 33 +++++ .../Domain/App/Services/ServiceWrapper.swift | 59 +++++++++ .../Domain/App/Services/ServicesManager.swift | 57 ++++++++ .../App/Services/ValetServicesManager.swift | 70 ++++++++++ phpmon/Domain/App/ServicesManager.swift | 125 ------------------ phpmon/Domain/Menu/MainMenu+Startup.swift | 2 +- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 125 +++++++++++++++--- 13 files changed, 395 insertions(+), 153 deletions(-) create mode 100644 phpmon/Domain/App/Services/FakeServicesManager.swift create mode 100644 phpmon/Domain/App/Services/ServiceWrapper.swift create mode 100644 phpmon/Domain/App/Services/ServicesManager.swift create mode 100644 phpmon/Domain/App/Services/ValetServicesManager.swift delete mode 100644 phpmon/Domain/App/ServicesManager.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index cc7f358..a948178 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -143,6 +143,18 @@ C4570C3B28FC355300D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C4570C3C28FC355400D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C459B4BD27F6093700E9B4B4 /* nginx-proxy.test in Resources */ = {isa = PBXBuildFile; fileRef = C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */; }; + C45B9149295607F400F4EC78 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* ServiceWrapper.swift */; }; + C45B914A295607F400F4EC78 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* ServiceWrapper.swift */; }; + C45B914B295607F400F4EC78 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* ServiceWrapper.swift */; }; + C45B914C295607F400F4EC78 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* ServiceWrapper.swift */; }; + C45B914E295608E300F4EC78 /* ValetServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B914D295608E300F4EC78 /* ValetServicesManager.swift */; }; + C45B914F295608E300F4EC78 /* ValetServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B914D295608E300F4EC78 /* ValetServicesManager.swift */; }; + C45B9150295608E300F4EC78 /* ValetServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B914D295608E300F4EC78 /* ValetServicesManager.swift */; }; + C45B9151295608E300F4EC78 /* ValetServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B914D295608E300F4EC78 /* ValetServicesManager.swift */; }; + C45B91532956123A00F4EC78 /* FakeServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B91522956123A00F4EC78 /* FakeServicesManager.swift */; }; + C45B91542956123A00F4EC78 /* FakeServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B91522956123A00F4EC78 /* FakeServicesManager.swift */; }; + C45B91552956123A00F4EC78 /* FakeServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B91522956123A00F4EC78 /* FakeServicesManager.swift */; }; + C45B91562956123A00F4EC78 /* FakeServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B91522956123A00F4EC78 /* FakeServicesManager.swift */; }; C45E2A7529199248005C7CFD /* InternalSwitcherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C471E7AF28F9B4940021E251 /* InternalSwitcherTest.swift */; }; C45E2A77291992DA005C7CFD /* FeatureTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E2A76291992DA005C7CFD /* FeatureTestCase.swift */; }; C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; }; @@ -786,6 +798,9 @@ C44F868D2835BD8D005C353A /* phpmon-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "phpmon-config.json"; sourceTree = ""; }; C450C8C528C919EC002A2B4B /* PreferenceName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceName.swift; sourceTree = ""; }; C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-proxy.test"; sourceTree = ""; }; + C45B9148295607F400F4EC78 /* ServiceWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceWrapper.swift; sourceTree = ""; }; + C45B914D295608E300F4EC78 /* ValetServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetServicesManager.swift; sourceTree = ""; }; + C45B91522956123A00F4EC78 /* FakeServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeServicesManager.swift; sourceTree = ""; }; C45E2A76291992DA005C7CFD /* FeatureTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureTestCase.swift; sourceTree = ""; }; C45E76132854A65300B4FE0C /* ServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesManager.swift; sourceTree = ""; }; C463E37F284930EE00422731 /* PresetHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetHelper.swift; sourceTree = ""; }; @@ -1275,6 +1290,17 @@ path = php; sourceTree = ""; }; + C45B9147295607E200F4EC78 /* Services */ = { + isa = PBXGroup; + children = ( + C45E76132854A65300B4FE0C /* ServicesManager.swift */, + C45B914D295608E300F4EC78 /* ValetServicesManager.swift */, + C45B9148295607F400F4EC78 /* ServiceWrapper.swift */, + C45B91522956123A00F4EC78 /* FakeServicesManager.swift */, + ); + path = Services; + sourceTree = ""; + }; C464ADAA275A7A25003FCD53 /* DomainList */ = { isa = PBXGroup; children = ( @@ -1423,6 +1449,7 @@ C4B13B1D25C4915000548C3A /* App */ = { isa = PBXGroup; children = ( + C45B9147295607E200F4EC78 /* Services */, C41C1B3C22B0098000E7CF16 /* Main.storyboard */, C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */, C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */, @@ -1436,7 +1463,6 @@ C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */, C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */, C40FE736282ABA4F00A302C2 /* AppVersion.swift */, - C45E76132854A65300B4FE0C /* ServicesManager.swift */, C4A6957528D23EE300A14CF8 /* EnvironmentManager.swift */, ); path = App; @@ -1954,8 +1980,10 @@ 5420395926135DC100FB00FA /* PrefsVC.swift in Sources */, C4C8900328F0E28800CE5E97 /* FileSystemProtocol.swift in Sources */, C43603A0275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */, + C45B9149295607F400F4EC78 /* ServiceWrapper.swift in Sources */, 5489625828312FAD004F647A /* CreatedFromFile.swift in Sources */, C4068CA727B07A1300544CD5 /* SelectPreferenceView.swift in Sources */, + C45B91532956123A00F4EC78 /* FakeServicesManager.swift in Sources */, C41C708D28AA7F7900E8D498 /* NoWarningsView.swift in Sources */, C4080FF627BD8C6400BF2C6B /* BetterAlert.swift in Sources */, C4E0F7ED27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */, @@ -1967,6 +1995,7 @@ C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */, C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */, C4D9F24B280B69E100DCD39A /* AddProxyVC.swift in Sources */, + C45B914E295608E300F4EC78 /* ValetServicesManager.swift in Sources */, C4E49DED28F764A00026AC4E /* TestableCommand.swift in Sources */, C4A6957628D23EE300A14CF8 /* EnvironmentManager.swift in Sources */, C41E871A2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */, @@ -2155,6 +2184,7 @@ C471E86328F9BB650021E251 /* PMTableView.swift in Sources */, C471E86428F9BB650021E251 /* Warning.swift in Sources */, C40175BA2903108900763A68 /* ValetInteractor.swift in Sources */, + C45B9150295608E300F4EC78 /* ValetServicesManager.swift in Sources */, C471E86528F9BB650021E251 /* WarningManager.swift in Sources */, C471E86628F9BB650021E251 /* WarningsWindowController.swift in Sources */, C471E86728F9BB650021E251 /* OnboardingWindowController.swift in Sources */, @@ -2196,6 +2226,7 @@ C471E88E28F9BB650021E251 /* KeyCombo.swift in Sources */, C471E88F28F9BB650021E251 /* ModifierFlagsExtension.swift in Sources */, C471E7E928F9BAC20021E251 /* Paths.swift in Sources */, + C45B91552956123A00F4EC78 /* FakeServicesManager.swift in Sources */, C471E7FE28F9BACE0021E251 /* HomebrewPackage.swift in Sources */, C471E7D828F9BA8F0021E251 /* FileSystemProtocol.swift in Sources */, C471E7F328F9BAC70021E251 /* PhpHelper.swift in Sources */, @@ -2225,6 +2256,7 @@ C471E7F228F9BAC70021E251 /* PhpEnv.swift in Sources */, C471E7E628F9BAC20021E251 /* Process.swift in Sources */, C471E81928F9BAE80021E251 /* NSMenuItemExtension.swift in Sources */, + C45B914B295607F400F4EC78 /* ServiceWrapper.swift in Sources */, C471E7D928F9BA8F0021E251 /* TestableShell.swift in Sources */, C471E81428F9BAE80021E251 /* NSWindowExtension.swift in Sources */, C471E7D328F9BA8F0021E251 /* ActiveShell.swift in Sources */, @@ -2277,6 +2309,7 @@ C471E8A528F9BB8F0021E251 /* AppDelegate+InterApp.swift in Sources */, C471E8A628F9BB8F0021E251 /* App.swift in Sources */, C471E8A728F9BB8F0021E251 /* App+ActivationPolicy.swift in Sources */, + C45B914C295607F400F4EC78 /* ServiceWrapper.swift in Sources */, C471E8A828F9BB8F0021E251 /* App+GlobalHotkey.swift in Sources */, C471E8A928F9BB8F0021E251 /* InterAppHandler.swift in Sources */, C471E8AA28F9BB8F0021E251 /* Startup.swift in Sources */, @@ -2309,6 +2342,7 @@ C4D36618291160A1006BD146 /* WIP.swift in Sources */, C471E8C328F9BB8F0021E251 /* SelectionVC.swift in Sources */, C471E8C428F9BB8F0021E251 /* AddSiteVC.swift in Sources */, + C45B91562956123A00F4EC78 /* FakeServicesManager.swift in Sources */, C471E8C528F9BB8F0021E251 /* AddProxyVC.swift in Sources */, C471E8C628F9BB8F0021E251 /* PMTableView.swift in Sources */, C471E8C728F9BB8F0021E251 /* Warning.swift in Sources */, @@ -2347,6 +2381,7 @@ C471E8EA28F9BB8F0021E251 /* SectionHeaderView.swift in Sources */, C4D36604291132B7006BD146 /* ValetScanners.swift in Sources */, C471E8EB28F9BB8F0021E251 /* HeaderView.swift in Sources */, + C45B9151295608E300F4EC78 /* ValetServicesManager.swift in Sources */, C471E8EC28F9BB8F0021E251 /* SwiftUIHelper.swift in Sources */, C471E8EE28F9BB8F0021E251 /* HotKey.swift in Sources */, C471E8EF28F9BB8F0021E251 /* HotKeysController.swift in Sources */, @@ -2465,6 +2500,7 @@ C4AF9F7B2754499000D44ED0 /* Valet.swift in Sources */, C4C1019C27C65C6F001FACC2 /* Process.swift in Sources */, C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */, + C45B914A295607F400F4EC78 /* ServiceWrapper.swift in Sources */, C4C0E8E327F88B13002D32A9 /* ValetDomainScanner.swift in Sources */, C4CCBA6D275C567B008C7055 /* PMWindowController.swift in Sources */, C4B5635F276AB09000F12CCB /* VersionExtractor.swift in Sources */, @@ -2530,6 +2566,7 @@ C4D3660C29113F20006BD146 /* System.swift in Sources */, C4D936CA27E3EB6100BD69FE /* PhpHelper.swift in Sources */, C4D36611291140BE006BD146 /* TestableFileSystemTest.swift in Sources */, + C45B91542956123A00F4EC78 /* FakeServicesManager.swift in Sources */, C4E2E84B28FC1E70003B070C /* DataExtension.swift in Sources */, C449B4F127EE7FC200C47E8A /* DomainListNameCell.swift in Sources */, C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */, @@ -2559,6 +2596,7 @@ C4B585452770FE3900DA4FBE /* RealCommand.swift in Sources */, C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */, C4F780B725D80B5D000DBC97 /* App.swift in Sources */, + C45B914F295608E300F4EC78 /* ValetServicesManager.swift in Sources */, C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */, C485707628BF455100539B36 /* SectionHeaderView.swift in Sources */, C46EBC4828DB9644007ACC74 /* RealShell.swift in Sources */, diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme index b7409be..864a93d 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme @@ -109,7 +109,7 @@ + isEnabled = "YES"> diff --git a/phpmon/Common/Core/Actions.swift b/phpmon/Common/Core/Actions.swift index ed62f17..d77f9e7 100644 --- a/phpmon/Common/Core/Actions.swift +++ b/phpmon/Common/Core/Actions.swift @@ -69,7 +69,7 @@ class Actions { public static func stopService(name: String) async { await brew( "services stop \(name)", - sudo: ServicesManager.shared.services[name]?.formula.elevated ?? false + sudo: ServicesManager.shared[name]?.formula.elevated ?? false ) await ServicesManager.loadHomebrewServices() } @@ -77,7 +77,7 @@ class Actions { public static func startService(name: String) async { await brew( "services start \(name)", - sudo: ServicesManager.shared.services[name]?.formula.elevated ?? false + sudo: ServicesManager.shared[name]?.formula.elevated ?? false ) await ServicesManager.loadHomebrewServices() } diff --git a/phpmon/Common/Core/Homebrew.swift b/phpmon/Common/Core/Homebrew.swift index 02f5e0f..a8193e3 100644 --- a/phpmon/Common/Core/Homebrew.swift +++ b/phpmon/Common/Core/Homebrew.swift @@ -9,8 +9,18 @@ import Foundation class Homebrew { + static var fake: Bool = false + struct Formulae { static var php: HomebrewFormula { + if Homebrew.fake { + return HomebrewFormula("php", elevated: true) + } + + if PhpEnv.shared.homebrewPackage == nil { + fatalError("You must either load the HomebrewPackage object or call `fake` on the Homebrew class.") + } + return HomebrewFormula(PhpEnv.phpInstall.formula, elevated: true) } @@ -26,7 +36,7 @@ class Homebrew { } } -class HomebrewFormula { +class HomebrewFormula: Equatable, Hashable { let name: String let elevated: Bool @@ -34,4 +44,13 @@ class HomebrewFormula { self.name = name self.elevated = elevated } + + static func == (lhs: HomebrewFormula, rhs: HomebrewFormula) -> Bool { + return lhs.elevated == rhs.elevated && lhs.name == rhs.name + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(name) + hasher.combine(elevated) + } } diff --git a/phpmon/Common/PHP/Homebrew/HomebrewService.swift b/phpmon/Common/PHP/Homebrew/HomebrewService.swift index 3d7a361..648f781 100644 --- a/phpmon/Common/PHP/Homebrew/HomebrewService.swift +++ b/phpmon/Common/PHP/Homebrew/HomebrewService.swift @@ -8,7 +8,7 @@ import Foundation -struct HomebrewService: Decodable, Equatable { +struct HomebrewService: Decodable, Equatable, Hashable { let name: String let service_name: String let running: Bool @@ -35,4 +35,10 @@ struct HomebrewService: Decodable, Equatable { error_log_path: nil ) } + + public func hash(into hasher: inout Hasher) { + hasher.combine(name) + hasher.combine(service_name) + hasher.combine(pid) + } } diff --git a/phpmon/Common/PHP/PHP Version/PhpEnv.swift b/phpmon/Common/PHP/PHP Version/PhpEnv.swift index 8ec9a68..9d83559 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnv.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnv.swift @@ -45,7 +45,7 @@ class PhpEnv { var cachedPhpInstallations: [String: PhpInstallation] = [:] /** Information about the currently linked PHP installation. */ - var currentInstall: ActivePhpInstallation + var currentInstall: ActivePhpInstallation! /** The version that the `php` formula via Brew is aliased to on the current system. diff --git a/phpmon/Domain/App/Services/FakeServicesManager.swift b/phpmon/Domain/App/Services/FakeServicesManager.swift new file mode 100644 index 0000000..1008bd2 --- /dev/null +++ b/phpmon/Domain/App/Services/FakeServicesManager.swift @@ -0,0 +1,33 @@ +// +// FakeServicesManager.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 23/12/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class FakeServicesManager: ServicesManager { + override init() { + Log.warn("A fake services manager is being used, so Homebrew formula resolver is set to act in fake mode.") + Log.warn("If you do not want this behaviour, never instantiate FakeServicesManager!") + Homebrew.fake = true + } + + override var formulae: [HomebrewFormula] { + var formulae = [ + Homebrew.Formulae.php, + Homebrew.Formulae.nginx, + Homebrew.Formulae.dnsmasq + ] + + let additionalFormulae = ["mailhog", "coolio"].map({ name in + return HomebrewFormula(name, elevated: false) + }) + + formulae.append(contentsOf: additionalFormulae) + + return formulae + } +} diff --git a/phpmon/Domain/App/Services/ServiceWrapper.swift b/phpmon/Domain/App/Services/ServiceWrapper.swift new file mode 100644 index 0000000..c9229ff --- /dev/null +++ b/phpmon/Domain/App/Services/ServiceWrapper.swift @@ -0,0 +1,59 @@ +// +// ServiceWrapper.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 23/12/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +/** + Whether a given service is active, inactive or PHP Monitor is still busy determining the status. + */ +public enum ServiceStatus: String { + case active + case inactive + case loading + case missing +} + +/** + Service wrapper, that contains the Homebrew JSON output (if determined) and the formula. + This helps the app determine whether a service should run as an administrator or not. + */ +public class ServiceWrapper: ObservableObject, Identifiable, Hashable { + var formula: HomebrewFormula + var service: HomebrewService? + + var isBusy: Bool = false + + public var name: String { + return formula.name + } + + public var status: ServiceStatus { + if isBusy { + return .loading + } + + guard let service = self.service else { + return .missing + } + + return service.running ? .active : .inactive + } + + init(formula: HomebrewFormula) { + self.formula = formula + } + + public static func == (lhs: ServiceWrapper, rhs: ServiceWrapper) -> Bool { + return lhs.service == rhs.service && lhs.formula == rhs.formula + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(formula) + hasher.combine(service) + } +} diff --git a/phpmon/Domain/App/Services/ServicesManager.swift b/phpmon/Domain/App/Services/ServicesManager.swift new file mode 100644 index 0000000..944195e --- /dev/null +++ b/phpmon/Domain/App/Services/ServicesManager.swift @@ -0,0 +1,57 @@ +// +// ServicesManager.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 11/06/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation +import SwiftUI + +class ServicesManager: ObservableObject { + + @ObservedObject static var shared: ServicesManager = ValetServicesManager() + + @Published private(set) var services = [ServiceWrapper]() + + subscript(name: String) -> ServiceWrapper? { + return self.services.first { wrapper in + wrapper.name == name + } + } + + @available(*, deprecated, message: "Use a more specific method instead") + static func loadHomebrewServices() { + print(self.shared) + print("This method must be updated") + } + + public func updateServices() { + fatalError("Must be implemented in child class") + } + + var formulae: [HomebrewFormula] { + var formulae = [ + Homebrew.Formulae.php, + Homebrew.Formulae.nginx, + Homebrew.Formulae.dnsmasq + ] + + let additionalFormulae = (Preferences.custom.services ?? []).map({ item in + return HomebrewFormula(item, elevated: false) + }) + + formulae.append(contentsOf: additionalFormulae) + + return formulae + } + + init() { + Log.info("The services manager will determine which Valet services exist on this system.") + + services = formulae.map { + ServiceWrapper(formula: $0) + } + } +} diff --git a/phpmon/Domain/App/Services/ValetServicesManager.swift b/phpmon/Domain/App/Services/ValetServicesManager.swift new file mode 100644 index 0000000..6319169 --- /dev/null +++ b/phpmon/Domain/App/Services/ValetServicesManager.swift @@ -0,0 +1,70 @@ +// +// ValetServicesManager.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 23/12/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class ValetServicesManager: ServicesManager { + override init() { + super.init() + + // Load the initial services state + self.updateServices() + } + + override func updateServices() { + // TODO + } +} + +// TODO + +/* +public static func loadHomebrewServices() async { + await Self.shared.updateServicesList() + + Task { + let rootServiceNames = Self.shared.formulae + .filter { $0.elevated } + .map { $0.name } + + let rootJson = await Shell + .pipe("sudo \(Paths.brew) services info --all --json") + .out.data(using: .utf8)! + + let rootServices = try! JSONDecoder() + .decode([HomebrewService].self, from: rootJson) + .filter({ return rootServiceNames.contains($0.name) }) + + Task { @MainActor in + for service in rootServices { + Self.shared.services[service.name]!.service = service + } + } + } + + Task { + let userServiceNames = Self.shared.formulae + .filter { !$0.elevated } + .map { $0.name } + + let normalJson = await Shell + .pipe("\(Paths.brew) services info --all --json") + .out.data(using: .utf8)! + + let userServices = try! JSONDecoder() + .decode([HomebrewService].self, from: normalJson) + .filter({ return userServiceNames.contains($0.name) }) + + Task { @MainActor in + for service in userServices { + Self.shared.services[service.name]!.service = service + } + } + } +} +*/ diff --git a/phpmon/Domain/App/ServicesManager.swift b/phpmon/Domain/App/ServicesManager.swift deleted file mode 100644 index eab61ac..0000000 --- a/phpmon/Domain/App/ServicesManager.swift +++ /dev/null @@ -1,125 +0,0 @@ -// -// ServicesManager.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 11/06/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. -// - -import Foundation -import SwiftUI - -class ServicesManager: ObservableObject { - - static var shared = ServicesManager() - - @Published private(set) var formulae: [HomebrewFormula] - - @Published private(set) var services: [String: ServiceWrapper] = [:] - - init() { - Log.info("Initializing ServicesManager...") - - formulae = [ - Homebrew.Formulae.php, - Homebrew.Formulae.nginx, - Homebrew.Formulae.dnsmasq - ] - - let additionalFormulae = (Preferences.custom.services ?? []).map({ item in - return HomebrewFormula(item, elevated: false) - }) - - formulae.append(contentsOf: additionalFormulae) - - services = Dictionary(uniqueKeysWithValues: formulae.map { ($0.name, ServiceWrapper(formula: $0)) }) - } - - public func updateServicesList() async { - Task { @MainActor in - formulae = [ - Homebrew.Formulae.php, - Homebrew.Formulae.nginx, - Homebrew.Formulae.dnsmasq - ] - - let additionalFormulae = (Preferences.custom.services ?? []).map({ item in - return HomebrewFormula(item, elevated: false) - }) - - formulae.append(contentsOf: additionalFormulae) - - services = Dictionary(uniqueKeysWithValues: formulae.map { ($0.name, ServiceWrapper(formula: $0)) }) - } - } - - public static func loadHomebrewServices() async { - await Self.shared.updateServicesList() - - Task { - let rootServiceNames = Self.shared.formulae - .filter { $0.elevated } - .map { $0.name } - - let rootJson = await Shell - .pipe("sudo \(Paths.brew) services info --all --json") - .out.data(using: .utf8)! - - let rootServices = try! JSONDecoder() - .decode([HomebrewService].self, from: rootJson) - .filter({ return rootServiceNames.contains($0.name) }) - - Task { @MainActor in - for service in rootServices { - Self.shared.services[service.name]!.service = service - } - } - } - - Task { - let userServiceNames = Self.shared.formulae - .filter { !$0.elevated } - .map { $0.name } - - let normalJson = await Shell - .pipe("\(Paths.brew) services info --all --json") - .out.data(using: .utf8)! - - let userServices = try! JSONDecoder() - .decode([HomebrewService].self, from: normalJson) - .filter({ return userServiceNames.contains($0.name) }) - - Task { @MainActor in - for service in userServices { - Self.shared.services[service.name]!.service = service - } - } - } - } - - /** - Service wrapper, that contains the Homebrew JSON output (if determined) and the formula. - This helps the app determine whether a service should run as an administrator or not. - */ - public struct ServiceWrapper { - public var formula: HomebrewFormula - public var service: HomebrewService? - - init(formula: HomebrewFormula) { - self.formula = formula - } - } - - /** - Dummy data for preview purposes. - */ - func withDummyServices(_ services: [String: Bool]) -> Self { - for (service, enabled) in services { - var item = ServiceWrapper(formula: HomebrewFormula(service)) - item.service = HomebrewService.dummy(named: service, enabled: enabled) - self.services[service] = item - } - - return self - } -} diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 3896868..2698049 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -96,7 +96,7 @@ extension MainMenu { Valet.notifyAboutUnsupportedTLD() // Find out which services are active - await ServicesManager.loadHomebrewServices() + Log.info("The services manager knows about \(ServicesManager.shared.services.count) services.") // Start the background refresh timer startSharedTimer() diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index b142ba8..cc2f0a7 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -10,44 +10,129 @@ import Foundation import SwiftUI struct ServicesView: View { - static func asMenuItem(perRow: Int = 3) -> NSMenuItem { + static func asMenuItem(perRow: Int = 4) -> NSMenuItem { let item = NSMenuItem() - let view = NSHostingView( - rootView: Self() + let manager = ServicesManager.shared + + let rootView = Self( + manager: manager, + perRow: perRow ) + let view = NSHostingView(rootView: rootView) view.autoresizingMask = [.width, .height] + view.setFrameSize( + CGSize(width: view.frame.width, height: rootView.height) + ) + view.focusRingType = .none - let height = CGFloat(45 * ["a", "b", "c", "d", "e", "f"] - .chunked(by: perRow).count) - - view.setFrameSize(CGSize(width: view.frame.width, height: height)) item.view = view return item } + @ObservedObject var manager: ServicesManager + var perRow: Int + var height: CGFloat + var chunkCount: Int + + init(manager: ServicesManager, perRow: Int, height: CGFloat? = nil) { + self.manager = manager + self.perRow = perRow + self.chunkCount = manager.services.chunked(by: perRow).count + self.height = CGFloat((50 * chunkCount) + (5 * perRow)) + } + var body: some View { - Text("WIP") - .padding(10) - .frame(minWidth: 0, maxWidth: .infinity) + GeometryReader { geometry in + VStack { + ForEach(manager.services.chunked(by: perRow), id: \.self) { chunk in + HStack { + ForEach(chunk) { service in + ServiceView(service: service) + .frame(width: abs((geometry.size.width - 15) / CGFloat(perRow))) + } + } + } + } + .padding(.top, 10) + } + .frame(height: self.height) .background(Color.debug) } } +struct ServiceView: View { + @ObservedObject var service: ServiceWrapper + + var body: some View { + VStack(spacing: 0) { + Text(service.name.uppercased()) + .font(.system(size: 10)) + .frame(minWidth: 0, maxWidth: .infinity) + .padding(.bottom, 4) + .background(Color.debug) + if service.status == .loading { + ProgressView() + .scaleEffect(x: 0.5, y: 0.5, anchor: .center) + .frame(width: 16.0, height: 20.0) + } + if service.status == .missing { + Button { print("we pressed da button ")} label: { + Text("?") + } + .buttonStyle(BlueButton()) + } + if service.status == .active { + Button { + // TODO + } label: { + Image(systemName: "checkmark") + .resizable() + .frame(width: 12.0, height: 12.0) + .foregroundColor(Color("IconColorGreen")) + } + } + if service.status == .inactive { + Button { + // TODO + } label: { + Image(systemName: "xmark") + .resizable() + .frame(width: 12.0, height: 12.0) + .foregroundColor(Color("IconColorRed")) + } + } + } + } +} + +public struct BlueButton: ButtonStyle { + public func makeBody(configuration: Configuration) -> some View { + configuration.label + .padding(.bottom, 5) + .padding(.top, 5) + .padding(.leading, 10) + .padding(.trailing, 10) + .background(Color(red: 0, green: 0, blue: 0.5)) + .foregroundColor(.white) + .clipShape(Capsule()) + } +} + struct ServicesView_Previews: PreviewProvider { static var previews: some View { - ServicesView() - .frame(width: 330.0) - .previewDisplayName("Loading") + ServicesView(manager: FakeServicesManager(), perRow: 3) + .frame(width: 330.0) + .previewDisplayName("Loading") - ServicesView() - .frame(width: 330.0) - .previewDisplayName("Light Mode") + ServicesView(manager: FakeServicesManager(), perRow: 3) + .frame(width: 330.0) + .previewDisplayName("Light Mode") - ServicesView() - .frame(width: 330.0) - .previewDisplayName("Dark Mode") - .preferredColorScheme(.dark) + ServicesView(manager: FakeServicesManager(), perRow: 3) + .frame(width: 330.0) + .previewDisplayName("Dark Mode") + .preferredColorScheme(.dark) } } From de2c1aca5d010c94c62824419fba69b686b77bff Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 24 Dec 2022 14:50:50 +0100 Subject: [PATCH 119/181] =?UTF-8?q?=F0=9F=8F=97=20More=20SwiftUI=20experim?= =?UTF-8?q?ents?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcschemes/PHP Monitor.xcscheme | 2 +- .../App/Services/FakeServicesManager.swift | 28 ++++--- .../Domain/App/Services/ServiceWrapper.swift | 1 + phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 77 +++++++++++-------- 4 files changed, 62 insertions(+), 46 deletions(-) diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme index 864a93d..b7409be 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme @@ -109,7 +109,7 @@ + isEnabled = "NO"> diff --git a/phpmon/Domain/App/Services/FakeServicesManager.swift b/phpmon/Domain/App/Services/FakeServicesManager.swift index 1008bd2..83de60d 100644 --- a/phpmon/Domain/App/Services/FakeServicesManager.swift +++ b/phpmon/Domain/App/Services/FakeServicesManager.swift @@ -9,25 +9,23 @@ import Foundation class FakeServicesManager: ServicesManager { - override init() { + var fixedFormulae: [String] = [] + var fixedStatus: ServiceStatus = .loading + + init( + formulae: [String] = ["php", "nginx", "dnsmasq"], + status: ServiceStatus = .loading + ) { Log.warn("A fake services manager is being used, so Homebrew formula resolver is set to act in fake mode.") Log.warn("If you do not want this behaviour, never instantiate FakeServicesManager!") - Homebrew.fake = true + + self.fixedFormulae = formulae + self.fixedStatus = status } override var formulae: [HomebrewFormula] { - var formulae = [ - Homebrew.Formulae.php, - Homebrew.Formulae.nginx, - Homebrew.Formulae.dnsmasq - ] - - let additionalFormulae = ["mailhog", "coolio"].map({ name in - return HomebrewFormula(name, elevated: false) - }) - - formulae.append(contentsOf: additionalFormulae) - - return formulae + return fixedFormulae.map { formula in + return HomebrewFormula.init(formula, elevated: false) + } } } diff --git a/phpmon/Domain/App/Services/ServiceWrapper.swift b/phpmon/Domain/App/Services/ServiceWrapper.swift index c9229ff..716b2ce 100644 --- a/phpmon/Domain/App/Services/ServiceWrapper.swift +++ b/phpmon/Domain/App/Services/ServiceWrapper.swift @@ -46,6 +46,7 @@ public class ServiceWrapper: ObservableObject, Identifiable, Hashable { init(formula: HomebrewFormula) { self.formula = formula + self.isBusy = true } public static func == (lhs: ServiceWrapper, rhs: ServiceWrapper) -> Bool { diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index cc2f0a7..9bce9f3 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -10,6 +10,7 @@ import Foundation import SwiftUI struct ServicesView: View { + static func asMenuItem(perRow: Int = 4) -> NSMenuItem { let item = NSMenuItem() @@ -23,8 +24,9 @@ struct ServicesView: View { let view = NSHostingView(rootView: rootView) view.autoresizingMask = [.width, .height] view.setFrameSize( - CGSize(width: view.frame.width, height: rootView.height) + CGSize(width: view.frame.width, height: rootView.height + 30) ) + view.layer?.backgroundColor = CGColor.init(red: 255, green: 0, blue: 0, alpha: 0) view.focusRingType = .none item.view = view @@ -44,21 +46,33 @@ struct ServicesView: View { } var body: some View { - GeometryReader { geometry in - VStack { + VStack { + VStack(alignment: .leading) { ForEach(manager.services.chunked(by: perRow), id: \.self) { chunk in HStack { ForEach(chunk) { service in - ServiceView(service: service) - .frame(width: abs((geometry.size.width - 15) / CGFloat(perRow))) + ServiceView(service: service).frame(minWidth: 70) } } } + } - .padding(.top, 10) + .frame(height: self.height) + .frame(maxWidth: .infinity, alignment: .center) + // .background(Color.red) + + VStack(alignment: .center) { + HStack { + Circle() + .frame(width: 12, height: 12) + .foregroundColor(.yellow) + Text("Determining services status...") + .font(.system(size: 12)) + } + } + .frame(height: 25) + .frame(maxWidth: .infinity, alignment: .center) } - .frame(height: self.height) - .background(Color.debug) } } @@ -66,22 +80,34 @@ struct ServiceView: View { @ObservedObject var service: ServiceWrapper var body: some View { - VStack(spacing: 0) { + VStack(alignment: .leading, spacing: 0) { Text(service.name.uppercased()) .font(.system(size: 10)) - .frame(minWidth: 0, maxWidth: .infinity) - .padding(.bottom, 4) - .background(Color.debug) + .frame(minWidth: 70, alignment: .center) + .padding(.top, 4) + .padding(.bottom, 2) if service.status == .loading { ProgressView() .scaleEffect(x: 0.5, y: 0.5, anchor: .center) - .frame(width: 16.0, height: 20.0) + .frame(minWidth: 70, alignment: .center) } if service.status == .missing { - Button { print("we pressed da button ")} label: { + Button { + Task { @MainActor in + BetterAlert().withInformation( + title: "alert.warnings.service_missing.title".localized, + subtitle: "alert.warnings.service_missing.subtitle".localized, + description: "alert.warnings.service_missing.description".localized + ) + .withPrimary(text: "OK") + .show() + } + } label: { Text("?") } + .focusable(false) .buttonStyle(BlueButton()) + .frame(minWidth: 70, alignment: .center) } if service.status == .active { Button { @@ -103,18 +129,18 @@ struct ServiceView: View { .foregroundColor(Color("IconColorRed")) } } - } + }.frame(minWidth: 70) } } public struct BlueButton: ButtonStyle { public func makeBody(configuration: Configuration) -> some View { configuration.label - .padding(.bottom, 5) - .padding(.top, 5) - .padding(.leading, 10) - .padding(.trailing, 10) - .background(Color(red: 0, green: 0, blue: 0.5)) + .frame(width: 30, height: 30) + .background(configuration.isPressed + ? Color(red: 0, green: 0, blue: 0.9) + : Color(red: 0, green: 0, blue: 0.5) + ) .foregroundColor(.white) .clipShape(Capsule()) } @@ -122,17 +148,8 @@ public struct BlueButton: ButtonStyle { struct ServicesView_Previews: PreviewProvider { static var previews: some View { - ServicesView(manager: FakeServicesManager(), perRow: 3) + ServicesView(manager: FakeServicesManager(), perRow: 4) .frame(width: 330.0) .previewDisplayName("Loading") - - ServicesView(manager: FakeServicesManager(), perRow: 3) - .frame(width: 330.0) - .previewDisplayName("Light Mode") - - ServicesView(manager: FakeServicesManager(), perRow: 3) - .frame(width: 330.0) - .previewDisplayName("Dark Mode") - .preferredColorScheme(.dark) } } From 59f60b50136c11cd296a3eefc1c224a174580bbd Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 24 Dec 2022 15:04:25 +0100 Subject: [PATCH 120/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Services=20manage?= =?UTF-8?q?r=20main=20status?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Please note that these are computed properties. They should be computed when a service status is modified, and should be `Published`. --- .../Domain/App/Services/ServicesManager.swift | 30 +++++++++++++++++++ phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 4 +-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/phpmon/Domain/App/Services/ServicesManager.swift b/phpmon/Domain/App/Services/ServicesManager.swift index 944195e..57cc1e0 100644 --- a/phpmon/Domain/App/Services/ServicesManager.swift +++ b/phpmon/Domain/App/Services/ServicesManager.swift @@ -21,6 +21,36 @@ class ServicesManager: ObservableObject { } } + public var statusMessage: String { + let statuses = self.services[0...2].map { $0.status } + if statuses.contains(.loading) { + return "Determining Valet status..." + } + if statuses.contains(.missing) { + return "A key service is not installed." + } + if statuses.contains(.inactive) { + return "A key service is not running." + } + + return "All Valet services are OK." + } + + public var statusColor: Color { + let statuses = self.services[0...2].map { $0.status } + if statuses.contains(.loading) { + return .orange + } + if statuses.contains(.missing) { + return .red + } + if statuses.contains(.inactive) { + return .red + } + + return .green + } + @available(*, deprecated, message: "Use a more specific method instead") static func loadHomebrewServices() { print(self.shared) diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 9bce9f3..53e3963 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -65,8 +65,8 @@ struct ServicesView: View { HStack { Circle() .frame(width: 12, height: 12) - .foregroundColor(.yellow) - Text("Determining services status...") + .foregroundColor(self.manager.statusColor) + Text(self.manager.statusMessage) .font(.system(size: 12)) } } From 296bc486c4744e4717620551a1e43765ec97d9fa Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 24 Dec 2022 15:17:47 +0100 Subject: [PATCH 121/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Fake=20services?= =?UTF-8?q?=20manager?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/App/Services/FakeServicesManager.swift | 10 ++++++++++ phpmon/Domain/App/Services/ServicesManager.swift | 10 +++++++++- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 9 ++++++--- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/phpmon/Domain/App/Services/FakeServicesManager.swift b/phpmon/Domain/App/Services/FakeServicesManager.swift index 83de60d..53ae7d2 100644 --- a/phpmon/Domain/App/Services/FakeServicesManager.swift +++ b/phpmon/Domain/App/Services/FakeServicesManager.swift @@ -12,15 +12,25 @@ class FakeServicesManager: ServicesManager { var fixedFormulae: [String] = [] var fixedStatus: ServiceStatus = .loading + override init() {} + init( formulae: [String] = ["php", "nginx", "dnsmasq"], status: ServiceStatus = .loading ) { + super.init() + Log.warn("A fake services manager is being used, so Homebrew formula resolver is set to act in fake mode.") Log.warn("If you do not want this behaviour, never instantiate FakeServicesManager!") self.fixedFormulae = formulae self.fixedStatus = status + + self.services = self.formulae.map { + let wrapper = ServiceWrapper(formula: $0) + wrapper.service = HomebrewService.dummy(named: $0.name, enabled: true) + return wrapper + } } override var formulae: [HomebrewFormula] { diff --git a/phpmon/Domain/App/Services/ServicesManager.swift b/phpmon/Domain/App/Services/ServicesManager.swift index 57cc1e0..da2fad4 100644 --- a/phpmon/Domain/App/Services/ServicesManager.swift +++ b/phpmon/Domain/App/Services/ServicesManager.swift @@ -13,7 +13,7 @@ class ServicesManager: ObservableObject { @ObservedObject static var shared: ServicesManager = ValetServicesManager() - @Published private(set) var services = [ServiceWrapper]() + @Published var services = [ServiceWrapper]() subscript(name: String) -> ServiceWrapper? { return self.services.first { wrapper in @@ -22,6 +22,10 @@ class ServicesManager: ObservableObject { } public var statusMessage: String { + if self.services.isEmpty { + return "Loading..." + } + let statuses = self.services[0...2].map { $0.status } if statuses.contains(.loading) { return "Determining Valet status..." @@ -37,6 +41,10 @@ class ServicesManager: ObservableObject { } public var statusColor: Color { + if self.services.isEmpty { + return .yellow + } + let statuses = self.services[0...2].map { $0.status } if statuses.contains(.loading) { return .orange diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 53e3963..4fd7c23 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -148,8 +148,11 @@ public struct BlueButton: ButtonStyle { struct ServicesView_Previews: PreviewProvider { static var previews: some View { - ServicesView(manager: FakeServicesManager(), perRow: 4) - .frame(width: 330.0) - .previewDisplayName("Loading") + ServicesView(manager: FakeServicesManager( + formulae: ["php", "nginx", "dnsmasq"], + status: .active + ), perRow: 4) + .frame(width: 330.0) + .previewDisplayName("Loading") } } From 3bcf52bf0a498b73302131cae9bd7a3e3f8d92ea Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 3 Jan 2023 16:11:55 +0100 Subject: [PATCH 122/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Use=20`objectWill?= =?UTF-8?q?Change.send()`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcschemes/PHP Monitor DEV.xcscheme | 2 +- phpmon/Common/Core/Actions.swift | 4 +- .../Common/PHP/Homebrew/HomebrewService.swift | 46 +++++++++++++++---- .../Testables/TestableConfiguration.swift | 3 ++ .../App/Services/FakeServicesManager.swift | 17 ++++++- .../Domain/App/Services/ServicesManager.swift | 44 ++++++++++++++---- .../App/Services/ValetServicesManager.swift | 4 +- phpmon/Domain/Menu/MainMenu+Async.swift | 2 +- phpmon/Domain/Menu/MainMenu+Startup.swift | 2 +- phpmon/Domain/Menu/MainMenu.swift | 4 +- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 13 ++++-- 11 files changed, 109 insertions(+), 32 deletions(-) diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index 8642ae0..b0a4d86 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -91,7 +91,7 @@ + isEnabled = "YES"> Self { + public static func dummy(named service: String, enabled: Bool) -> HomebrewService { return HomebrewService( name: service, service_name: service, @@ -41,4 +63,10 @@ struct HomebrewService: Decodable, Equatable, Hashable { hasher.combine(service_name) hasher.combine(pid) } + + static func == (lhs: HomebrewService, rhs: HomebrewService) -> Bool { + return lhs.name == rhs.name + && lhs.pid == rhs.pid + && lhs.status == rhs.status + } } diff --git a/phpmon/Common/Testables/TestableConfiguration.swift b/phpmon/Common/Testables/TestableConfiguration.swift index 4c05182..beaf040 100644 --- a/phpmon/Common/Testables/TestableConfiguration.swift +++ b/phpmon/Common/Testables/TestableConfiguration.swift @@ -26,6 +26,9 @@ public struct TestableConfiguration: Codable { ActiveCommand.useTestable(commandOutput) Log.info("Applying fake scanner...") ValetScanner.useFake() + Log.info("Applying fake services manager...") + Homebrew.fake = true + ServicesManager.useFake() Log.info("Applying fake Valet domain interactor...") ValetInteractor.useFake() } diff --git a/phpmon/Domain/App/Services/FakeServicesManager.swift b/phpmon/Domain/App/Services/FakeServicesManager.swift index 53ae7d2..0601028 100644 --- a/phpmon/Domain/App/Services/FakeServicesManager.swift +++ b/phpmon/Domain/App/Services/FakeServicesManager.swift @@ -21,13 +21,14 @@ class FakeServicesManager: ServicesManager { super.init() Log.warn("A fake services manager is being used, so Homebrew formula resolver is set to act in fake mode.") - Log.warn("If you do not want this behaviour, never instantiate FakeServicesManager!") + Log.warn("If you do not want this behaviour, do not make use of a `FakeServicesManager`!") self.fixedFormulae = formulae self.fixedStatus = status - self.services = self.formulae.map { + self.serviceWrappers = self.formulae.map { let wrapper = ServiceWrapper(formula: $0) + wrapper.isBusy = (status == .loading) wrapper.service = HomebrewService.dummy(named: $0.name, enabled: true) return wrapper } @@ -38,4 +39,16 @@ class FakeServicesManager: ServicesManager { return HomebrewFormula.init(formula, elevated: false) } } + + override func updateServices() async { + await delay(seconds: 0.3) + + for formula in self.serviceWrappers { + formula.service?.running = true + formula.isBusy = false + } + + print("Sending the update!") + broadcastServicesUpdated() + } } diff --git a/phpmon/Domain/App/Services/ServicesManager.swift b/phpmon/Domain/App/Services/ServicesManager.swift index da2fad4..b8d124b 100644 --- a/phpmon/Domain/App/Services/ServicesManager.swift +++ b/phpmon/Domain/App/Services/ServicesManager.swift @@ -13,20 +13,31 @@ class ServicesManager: ObservableObject { @ObservedObject static var shared: ServicesManager = ValetServicesManager() - @Published var services = [ServiceWrapper]() + @Published var serviceWrappers = [ServiceWrapper]() + public static func useFake() { + ServicesManager.shared = FakeServicesManager.init( + formulae: ["php", "nginx", "dnsmasq"], + status: .loading + ) + } + + /** + The order of services is important, so easy access is accomplished + without much fanfare through subscripting. + */ subscript(name: String) -> ServiceWrapper? { - return self.services.first { wrapper in + return self.serviceWrappers.first { wrapper in wrapper.name == name } } public var statusMessage: String { - if self.services.isEmpty { + if self.serviceWrappers.isEmpty { return "Loading..." } - let statuses = self.services[0...2].map { $0.status } + let statuses = self.serviceWrappers[0...2].map { $0.status } if statuses.contains(.loading) { return "Determining Valet status..." } @@ -41,11 +52,11 @@ class ServicesManager: ObservableObject { } public var statusColor: Color { - if self.services.isEmpty { + if self.serviceWrappers.isEmpty { return .yellow } - let statuses = self.services[0...2].map { $0.status } + let statuses = self.serviceWrappers[0...2].map { $0.status } if statuses.contains(.loading) { return .orange } @@ -61,14 +72,29 @@ class ServicesManager: ObservableObject { @available(*, deprecated, message: "Use a more specific method instead") static func loadHomebrewServices() { - print(self.shared) + // print(self.shared) print("This method must be updated") } - public func updateServices() { + public func updateServices() async { fatalError("Must be implemented in child class") } + public func broadcastServicesUpdated() { + Task { @MainActor in + self.serviceWrappers.forEach { wrapper in + guard let service = wrapper.service else { + return + } + + Log.perf("\(service.name): \(wrapper.status)") + wrapper.objectWillChange.send() + } + + self.objectWillChange.send() + } + } + var formulae: [HomebrewFormula] { var formulae = [ Homebrew.Formulae.php, @@ -88,7 +114,7 @@ class ServicesManager: ObservableObject { init() { Log.info("The services manager will determine which Valet services exist on this system.") - services = formulae.map { + serviceWrappers = formulae.map { ServiceWrapper(formula: $0) } } diff --git a/phpmon/Domain/App/Services/ValetServicesManager.swift b/phpmon/Domain/App/Services/ValetServicesManager.swift index 6319169..9ffff96 100644 --- a/phpmon/Domain/App/Services/ValetServicesManager.swift +++ b/phpmon/Domain/App/Services/ValetServicesManager.swift @@ -13,10 +13,10 @@ class ValetServicesManager: ServicesManager { super.init() // Load the initial services state - self.updateServices() + Task { await self.updateServices() } } - override func updateServices() { + override func updateServices() async { // TODO } } diff --git a/phpmon/Domain/Menu/MainMenu+Async.swift b/phpmon/Domain/Menu/MainMenu+Async.swift index 261f25d..ca9b766 100644 --- a/phpmon/Domain/Menu/MainMenu+Async.swift +++ b/phpmon/Domain/Menu/MainMenu+Async.swift @@ -74,7 +74,7 @@ extension MainMenu { } if behaviours.contains(.broadcastServicesUpdate) { - Task { await ServicesManager.loadHomebrewServices() } + Task { await ServicesManager.shared.updateServices() } } if error != nil { diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 2698049..c5a9571 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -96,7 +96,7 @@ extension MainMenu { Valet.notifyAboutUnsupportedTLD() // Find out which services are active - Log.info("The services manager knows about \(ServicesManager.shared.services.count) services.") + Log.info("The services manager knows about \(ServicesManager.shared.serviceWrappers.count) services.") // Start the background refresh timer startSharedTimer() diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index 8ec944e..727e54a 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -94,7 +94,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate self.refreshActiveInstallation() self.refreshIcon() self.rebuild() - await ServicesManager.loadHomebrewServices() + await ServicesManager.shared.updateServices() Log.perf("The menu has been reloaded!") } } @@ -184,7 +184,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate // Make sure the shortcut key does not trigger this when the menu is open App.shared.shortcutHotkey?.isPaused = true Task { // Reload Homebrew services information asynchronously - await ServicesManager.loadHomebrewServices() + await ServicesManager.shared.updateServices() } } diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 4fd7c23..b1b1320 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -41,14 +41,14 @@ struct ServicesView: View { init(manager: ServicesManager, perRow: Int, height: CGFloat? = nil) { self.manager = manager self.perRow = perRow - self.chunkCount = manager.services.chunked(by: perRow).count + self.chunkCount = manager.serviceWrappers.chunked(by: perRow).count self.height = CGFloat((50 * chunkCount) + (5 * perRow)) } var body: some View { VStack { VStack(alignment: .leading) { - ForEach(manager.services.chunked(by: perRow), id: \.self) { chunk in + ForEach(manager.serviceWrappers.chunked(by: perRow), id: \.self) { chunk in HStack { ForEach(chunk) { service in ServiceView(service: service).frame(minWidth: 70) @@ -150,9 +150,16 @@ struct ServicesView_Previews: PreviewProvider { static var previews: some View { ServicesView(manager: FakeServicesManager( formulae: ["php", "nginx", "dnsmasq"], - status: .active + status: .loading ), perRow: 4) .frame(width: 330.0) .previewDisplayName("Loading") + + ServicesView(manager: FakeServicesManager( + formulae: ["php", "nginx", "dnsmasq"], + status: .active + ), perRow: 4) + .frame(width: 330.0) + .previewDisplayName("Active") } } From 61988a141b4aa355bc85b33acc13f0edc6f970a6 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 3 Jan 2023 16:14:45 +0100 Subject: [PATCH 123/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Center=20buttons?= =?UTF-8?q?=20and=20checkmarks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index b1b1320..a3f1f83 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -42,7 +42,7 @@ struct ServicesView: View { self.manager = manager self.perRow = perRow self.chunkCount = manager.serviceWrappers.chunked(by: perRow).count - self.height = CGFloat((50 * chunkCount) + (5 * perRow)) + self.height = CGFloat((30 * chunkCount) + (5 * perRow)) } var body: some View { @@ -80,7 +80,7 @@ struct ServiceView: View { @ObservedObject var service: ServiceWrapper var body: some View { - VStack(alignment: .leading, spacing: 0) { + VStack(alignment: .center, spacing: 0) { Text(service.name.uppercased()) .font(.system(size: 10)) .frame(minWidth: 70, alignment: .center) From be70559d4c25b9bfe9aa58723b004ac3fd80b241 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 3 Jan 2023 16:58:48 +0100 Subject: [PATCH 124/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Adjust=20dimensio?= =?UTF-8?q?ns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index a3f1f83..ade3da5 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -42,7 +42,7 @@ struct ServicesView: View { self.manager = manager self.perRow = perRow self.chunkCount = manager.serviceWrappers.chunked(by: perRow).count - self.height = CGFloat((30 * chunkCount) + (5 * perRow)) + self.height = CGFloat(40 * chunkCount + 25) } var body: some View { @@ -156,7 +156,7 @@ struct ServicesView_Previews: PreviewProvider { .previewDisplayName("Loading") ServicesView(manager: FakeServicesManager( - formulae: ["php", "nginx", "dnsmasq"], + formulae: ["php", "nginx", "dnsmasq", "thing1", "thing2", "thing3", "thing4", "thing5", "thing6", "thing7", "thing8"], status: .active ), perRow: 4) .frame(width: 330.0) From e20d3ffd22ba2388d8f5949219986e93af6fef71 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 3 Jan 2023 19:29:44 +0100 Subject: [PATCH 125/181] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F=20WIP:=20Various?= =?UTF-8?q?=20fixes=20and=20improvements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed `brewPhpAlias` (must be configurable later) - Added TODOs for where the filesystem abstraction is required - Set `Homebrew.fake` early on when applying testable configuration - Evaluate `FakeValetSite` compatibility again - Never display sponsor alert when running tests - Upgrade TestableConfiguration.working to use PHP 8.2 --- PHP Monitor.xcodeproj/project.pbxproj | 8 ---- phpmon/Common/PHP/PHP Version/PhpEnv.swift | 2 + phpmon/Common/PHP/PhpConfigurationFile.swift | 1 + .../Testables/TestableConfiguration.swift | 15 +++++++- .../Valet/Sites/FakeValetSite.swift | 11 +----- phpmon/Domain/Preferences/Stats.swift | 4 ++ phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 6 ++- tests/Shared/TestableConfigurations.swift | 38 ++++++++++--------- 8 files changed, 47 insertions(+), 38 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index a948178..d8a78e6 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -1320,7 +1320,6 @@ C471E6D928F9AFC20021E251 /* Testables */ = { isa = PBXGroup; children = ( - C471E6DB28F9AFD10021E251 /* Command */, C471E6DA28F9AFCB0021E251 /* Filesystem */, C413E43328DA3E8F00AE33C7 /* Shell */, C4E2E84628FC1D8C003B070C /* TestableConfigurationTest.swift */, @@ -1337,13 +1336,6 @@ path = Filesystem; sourceTree = ""; }; - C471E6DB28F9AFD10021E251 /* Command */ = { - isa = PBXGroup; - children = ( - ); - path = Command; - sourceTree = ""; - }; C471E79628F9B4260021E251 /* tests */ = { isa = PBXGroup; children = ( diff --git a/phpmon/Common/PHP/PHP Version/PhpEnv.swift b/phpmon/Common/PHP/PHP Version/PhpEnv.swift index 9d83559..bba11ac 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnv.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnv.swift @@ -57,6 +57,8 @@ class PhpEnv { As such, we take that information from Homebrew. */ static var brewPhpAlias: String { + if Homebrew.fake { return "8.2" } + return Self.shared.homebrewPackage.version } diff --git a/phpmon/Common/PHP/PhpConfigurationFile.swift b/phpmon/Common/PHP/PhpConfigurationFile.swift index a0d4191..f88869d 100644 --- a/phpmon/Common/PHP/PhpConfigurationFile.swift +++ b/phpmon/Common/PHP/PhpConfigurationFile.swift @@ -35,6 +35,7 @@ class PhpConfigurationFile: CreatedFromFile { let path = filePath.replacingOccurrences(of: "~", with: Paths.homePath) do { + // TODO: Use FileSystem abstraction let fileContents = try String(contentsOfFile: path) return Self.init(path: path, contents: fileContents) } catch { diff --git a/phpmon/Common/Testables/TestableConfiguration.swift b/phpmon/Common/Testables/TestableConfiguration.swift index beaf040..f3b69c3 100644 --- a/phpmon/Common/Testables/TestableConfiguration.swift +++ b/phpmon/Common/Testables/TestableConfiguration.swift @@ -17,6 +17,7 @@ public struct TestableConfiguration: Codable { func apply() { Log.separator() Log.info("USING TESTABLE CONFIGURATION...") + Homebrew.fake = true Log.separator() Log.info("Applying fake shell...") ActiveShell.useTestable(shellOutput) @@ -27,7 +28,6 @@ public struct TestableConfiguration: Codable { Log.info("Applying fake scanner...") ValetScanner.useFake() Log.info("Applying fake services manager...") - Homebrew.fake = true ServicesManager.useFake() Log.info("Applying fake Valet domain interactor...") ValetInteractor.useFake() @@ -46,6 +46,19 @@ public struct TestableConfiguration: Codable { static func loadFrom(path: String) -> TestableConfiguration { let url = URL(fileURLWithPath: path.replacingTildeWithHomeDirectory) + if !FileManager.default.fileExists(atPath: url.path) { + /* + You will need to run the `TestableConfigurationTest` test, + which will generate two configuration files you can use. + */ + fatalError("Error: the expected configuration file at \(url.path) is missing!") + } + + /* + If the decoder below fails to decode the configuration file, + the configuration may have been updated. + In that case, you will need to run the test (see above) again. + */ return try! JSONDecoder().decode( TestableConfiguration.self, from: try! String(contentsOf: url, encoding: .utf8).data(using: .utf8)! diff --git a/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift index 5fc8074..e462f46 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift @@ -35,15 +35,6 @@ class FakeValetSite: ValetSite { self.isolatedPhpVersion = PhpInstallation(isolated) } - // TODO: Resolve this at a later time - /* - self.composerPhpCompatibleWithLinked = self.composerPhp.split(separator: "|") - .map { string in - let origin = self.isolatedPhpVersion?.versionNumber.short ?? PhpEnv.phpInstall.version.long - return !PhpVersionNumberCollection.make(from: [origin]) - .matching(constraint: string.trimmingCharacters(in: .whitespacesAndNewlines)) - .isEmpty - }.contains(true) - */ + self.evaluateCompatibility() } } diff --git a/phpmon/Domain/Preferences/Stats.swift b/phpmon/Domain/Preferences/Stats.swift index 229da9f..ab25b9b 100644 --- a/phpmon/Domain/Preferences/Stats.swift +++ b/phpmon/Domain/Preferences/Stats.swift @@ -87,6 +87,10 @@ class Stats { */ public static func evaluateSponsorMessageShouldBeDisplayed() { + if Homebrew.fake { + return Log.info("A fake environment is in use, skipping sponsor alert.") + } + if Bundle.main.bundleIdentifier?.contains("beta") ?? false { return Log.info("Sponsor messages never apply to beta builds.") } diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index ade3da5..9710955 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -156,7 +156,11 @@ struct ServicesView_Previews: PreviewProvider { .previewDisplayName("Loading") ServicesView(manager: FakeServicesManager( - formulae: ["php", "nginx", "dnsmasq", "thing1", "thing2", "thing3", "thing4", "thing5", "thing6", "thing7", "thing8"], + formulae: [ + "php", "nginx", "dnsmasq", "thing1", + "thing2", "thing3", "thing4", "thing5", + "thing6", "thing7", "thing8" + ], status: .active ), perRow: 4) .frame(width: 330.0) diff --git a/tests/Shared/TestableConfigurations.swift b/tests/Shared/TestableConfigurations.swift index 49547f6..90f6ed0 100644 --- a/tests/Shared/TestableConfigurations.swift +++ b/tests/Shared/TestableConfigurations.swift @@ -25,14 +25,14 @@ class TestableConfigurations { "/opt/homebrew/bin/valet" : .fake(.binary), "/opt/homebrew/opt/php" - : .fake(.symlink, "/opt/homebrew/Cellar/php/8.1.10_1"), - "/opt/homebrew/opt/php@8.1/bin/php" - : .fake(.symlink, "/opt/homebrew/Cellar/php/8.1.10_1/bin/php"), - "/opt/homebrew/Cellar/php/8.1.10_1/bin/php" + : .fake(.symlink, "/opt/homebrew/Cellar/php/8.2.0"), + "/opt/homebrew/opt/php@8.2/bin/php" + : .fake(.symlink, "/opt/homebrew/Cellar/php/8.2.0/bin/php"), + "/opt/homebrew/Cellar/php/8.2.0/bin/php" : .fake(.binary), - "/opt/homebrew/Cellar/php/8.1.10_1/bin/php-config" + "/opt/homebrew/Cellar/php/8.2.0/bin/php-config" : .fake(.binary), - "/opt/homebrew/etc/php/8.1/php-fpm.d/www.conf" + "/opt/homebrew/etc/php/8.2/php-fpm.d/www.conf" : .fake(.text), "~/.config/valet/config.json" : .fake(.text, """ @@ -45,7 +45,7 @@ class TestableConfigurations { "loopback": "127.0.0.1" } """), - "/opt/homebrew/etc/php/8.1/php-fpm.d/valet-fpm.conf" + "/opt/homebrew/etc/php/8.2/php-fpm.d/valet-fpm.conf" : .fake(.text), ], shellOutput: [ @@ -57,15 +57,15 @@ class TestableConfigurations { : .instant("/opt/homebrew/bin/node"), "php -v" : .instant(""" - PHP 8.1.10 (cli) (built: Sep 3 2022 12:09:27) (NTS) + PHP 8.2.0 (cli) (built: Dec XX 20XX XX:XX:XX) (NTS) Copyright (c) The PHP Group - Zend Engine v4.1.10, Copyright (c) Zend Technologies - with Zend OPcache v8.1.10, Copyright (c), by Zend Technologies + Zend Engine vX.X, Copyright (c) Zend Technologies + with Zend OPcache vX.X, Copyright (c), by Zend Technologies """), "ls /opt/homebrew/opt | grep php" : .instant("php"), "ls /opt/homebrew/opt | grep php@" - : .instant("php@8.1"), + : .instant("php@8.2"), "sudo /opt/homebrew/bin/brew services info dnsmasq --json" : .delayed(0.2, """ [ @@ -110,7 +110,7 @@ class TestableConfigurations { nicoverbruggen/cask shivammathur/php """), - "chmod +x /Users/nicoverbruggen/.config/phpmon/bin/pm81" + "chmod +x /Users/nicoverbruggen/.config/phpmon/bin/pm82" : .instant(""), "mkdir -p ~/.config/phpmon" : .instant(""), @@ -138,6 +138,8 @@ class TestableConfigurations { : .instant("version '5.6.2_976'"), "/opt/homebrew/bin/brew unlink php" : .delayed(0.2, "OK"), + "/opt/homebrew/bin/brew unlink php@8.2" + : .delayed(0.2, "OK"), "/opt/homebrew/bin/brew link php --overwrite --force" : .delayed(0.2, "OK"), "sudo /opt/homebrew/bin/brew services stop php" @@ -152,20 +154,20 @@ class TestableConfigurations { : .delayed(0.2, "OK"), "sudo /opt/homebrew/bin/brew services start dnsmasq" : .delayed(0.2, "OK"), - "ln -sF ~/.config/valet/valet81.sock ~/.config/valet/valet.sock" + "ln -sF ~/.config/valet/valet82.sock ~/.config/valet/valet.sock" : .instant("OK"), ], commandOutput: [ - "/opt/homebrew/bin/php-config --version": "8.1.10", + "/opt/homebrew/bin/php-config --version": "8.2.0", "/opt/homebrew/bin/php -r echo ini_get('memory_limit');": "512M", "/opt/homebrew/bin/php -r echo ini_get('upload_max_filesize');": "512M", "/opt/homebrew/bin/php -r echo ini_get('post_max_size');": "512M", "/opt/homebrew/bin/php -r echo php_ini_scanned_files();" : """ - /opt/homebrew/etc/php/8.1/conf.d/error_log.ini, - /opt/homebrew/etc/php/8.1/conf.d/ext-opcache.ini, - /opt/homebrew/etc/php/8.1/conf.d/php-memory-limits.ini, - /opt/homebrew/etc/php/8.1/conf.d/xdebug.ini + /opt/homebrew/etc/php/8.2/conf.d/error_log.ini, + /opt/homebrew/etc/php/8.2/conf.d/ext-opcache.ini, + /opt/homebrew/etc/php/8.2/conf.d/php-memory-limits.ini, + /opt/homebrew/etc/php/8.2/conf.d/xdebug.ini """ ] ) From 5af7214320aaebcbf3424f9c0e049fcd1dfbe6f9 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 3 Jan 2023 20:15:44 +0100 Subject: [PATCH 126/181] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F=20Layout=20change?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 9710955..172ca17 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -22,11 +22,11 @@ struct ServicesView: View { ) let view = NSHostingView(rootView: rootView) - view.autoresizingMask = [.width, .height] + view.autoresizingMask = [.width] view.setFrameSize( CGSize(width: view.frame.width, height: rootView.height + 30) ) - view.layer?.backgroundColor = CGColor.init(red: 255, green: 0, blue: 0, alpha: 0) + view.layer?.backgroundColor = CGColor.init(red: 255, green: 0, blue: 0, alpha: 1) view.focusRingType = .none item.view = view @@ -35,27 +35,36 @@ struct ServicesView: View { @ObservedObject var manager: ServicesManager var perRow: Int + var rowCount: Int + var rowSpacing: Int = 2 + var rowHeight: Int = 40 + var statusHeight: Int = 30 var height: CGFloat - var chunkCount: Int - init(manager: ServicesManager, perRow: Int, height: CGFloat? = nil) { + init(manager: ServicesManager, perRow: Int) { self.manager = manager self.perRow = perRow - self.chunkCount = manager.serviceWrappers.chunked(by: perRow).count - self.height = CGFloat(40 * chunkCount + 25) + self.rowCount = manager.serviceWrappers.chunked(by: perRow).count + self.height = CGFloat( + (rowHeight * rowCount) + + ((rowCount - 1) * rowSpacing) + + statusHeight + ) } var body: some View { - VStack { - VStack(alignment: .leading) { + VStack(spacing: 0) { + VStack(alignment: .leading, spacing: CGFloat(self.rowSpacing)) { ForEach(manager.serviceWrappers.chunked(by: perRow), id: \.self) { chunk in HStack { ForEach(chunk) { service in - ServiceView(service: service).frame(minWidth: 70) + ServiceView(service: service) + .frame(minWidth: 70) } } + .frame(height: CGFloat(self.rowHeight)) + .padding(CGFloat(self.rowSpacing)) } - } .frame(height: self.height) .frame(maxWidth: .infinity, alignment: .center) @@ -70,7 +79,7 @@ struct ServicesView: View { .font(.system(size: 12)) } } - .frame(height: 25) + .frame(height: CGFloat(self.statusHeight)) .frame(maxWidth: .infinity, alignment: .center) } } From df1b1c5856fa2e62717426195318a52872afa8d5 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 3 Jan 2023 20:17:29 +0100 Subject: [PATCH 127/181] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F=20Investigate=20e?= =?UTF-8?q?vent=20propagation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 172ca17..606c894 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -26,7 +26,7 @@ struct ServicesView: View { view.setFrameSize( CGSize(width: view.frame.width, height: rootView.height + 30) ) - view.layer?.backgroundColor = CGColor.init(red: 255, green: 0, blue: 0, alpha: 1) + // view.layer?.backgroundColor = CGColor.init(red: 255, green: 0, blue: 0, alpha: 1) view.focusRingType = .none item.view = view @@ -36,8 +36,8 @@ struct ServicesView: View { @ObservedObject var manager: ServicesManager var perRow: Int var rowCount: Int - var rowSpacing: Int = 2 - var rowHeight: Int = 40 + var rowSpacing: Int = 5 + var rowHeight: Int = 30 var statusHeight: Int = 30 var height: CGFloat @@ -97,8 +97,9 @@ struct ServiceView: View { .padding(.bottom, 2) if service.status == .loading { ProgressView() - .scaleEffect(x: 0.5, y: 0.5, anchor: .center) + .scaleEffect(x: 0.4, y: 0.4, anchor: .center) .frame(minWidth: 70, alignment: .center) + .frame(width: 25, height: 25) } if service.status == .missing { Button { @@ -115,18 +116,20 @@ struct ServiceView: View { Text("?") } .focusable(false) - .buttonStyle(BlueButton()) + // .buttonStyle(BlueButton()) .frame(minWidth: 70, alignment: .center) } if service.status == .active { Button { // TODO + print("you pressed the button") } label: { Image(systemName: "checkmark") .resizable() .frame(width: 12.0, height: 12.0) .foregroundColor(Color("IconColorGreen")) } + .frame(width: 25, height: 25) } if service.status == .inactive { Button { @@ -145,13 +148,14 @@ struct ServiceView: View { public struct BlueButton: ButtonStyle { public func makeBody(configuration: Configuration) -> some View { configuration.label - .frame(width: 30, height: 30) + .frame(width: 25, height: 25) .background(configuration.isPressed - ? Color(red: 0, green: 0, blue: 0.9) + ? Color(red: 0, green: 0.5, blue: 0.9) : Color(red: 0, green: 0, blue: 0.5) ) .foregroundColor(.white) .clipShape(Capsule()) + .contentShape(Rectangle()) } } @@ -164,15 +168,21 @@ struct ServicesView_Previews: PreviewProvider { .frame(width: 330.0) .previewDisplayName("Loading") + ServicesView(manager: FakeServicesManager( + formulae: ["php", "nginx", "dnsmasq"], + status: .active + ), perRow: 4) + .frame(width: 330.0) + .previewDisplayName("Active 1") + ServicesView(manager: FakeServicesManager( formulae: [ "php", "nginx", "dnsmasq", "thing1", - "thing2", "thing3", "thing4", "thing5", - "thing6", "thing7", "thing8" + "thing2", "thing3", "thing4", "thing5" ], status: .active ), perRow: 4) .frame(width: 330.0) - .previewDisplayName("Active") + .previewDisplayName("Active 2") } } From a36c9b156364172aa948713025372045cca9c607 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 5 Jan 2023 12:17:31 +0100 Subject: [PATCH 128/181] =?UTF-8?q?=F0=9F=93=9D=20Update=20documentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 32 +++++++++++++------ SECURITY.md | 2 +- phpmon/Common/Core/Actions.swift | 4 +-- .../App/Services/FakeServicesManager.swift | 26 +++++++++++++-- .../Domain/App/Services/ServicesManager.swift | 24 ++++++++++---- .../App/Services/ValetServicesManager.swift | 4 +-- phpmon/Domain/Menu/MainMenu+Async.swift | 2 +- phpmon/Domain/Menu/MainMenu.swift | 4 +-- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 30 +++++++---------- 9 files changed, 83 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index b5bcef9..222e1a0 100644 --- a/README.md +++ b/README.md @@ -79,16 +79,28 @@ If you're still having issues, here's a few common questions & answers, as well
Which versions of PHP are supported? +The following versions of PHP are officially supported: + +
    +
  • PHP 7.4
  • +
  • PHP 8.0
  • +
  • PHP 8.1
  • +
  • PHP 8.2
  • +
+ +The following versions have some support via backport and/or dev version: + +
    +
  • PHP 5.6 (Valet 2 only)
  • +
  • PHP 7.0 (Valet 2 and 3 only)
  • +
  • PHP 7.1 (Valet 2 and 3 only)
  • +
  • PHP 7.2 (Valet 2 and 3 only)
  • +
  • PHP 7.3 (Valet 2 and 3 only)
  • +
+ +Additionally, the following dev version is also available: +
    -
  • PHP 5.6 (backport + only if you are running Valet 2)
  • -
  • PHP 7.0 (backport)
  • -
  • PHP 7.1 (backport)
  • -
  • PHP 7.2 (backport)
  • -
  • PHP 7.3 (backport)
  • -
  • PHP 7.4 (backport)
  • -
  • PHP 8.0 (official support)
  • -
  • PHP 8.1 (official support)
  • -
  • PHP 8.2 (latest version)
  • PHP 8.3-dev (experimental)
@@ -96,6 +108,8 @@ For more details, consult the [constants file](https://github.com/nicoverbruggen Backports are available via [this tap](https://github.com/shivammathur/homebrew-php). For more information about those backports, please see the next FAQ entry. +For maximum compatibility with older PHP versions, you may wish to keep using Valet 2 or 3. +
diff --git a/SECURITY.md b/SECURITY.md index e9a4382..4543d3e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,7 +6,7 @@ Generally speaking, only the latest version of **PHP Monitor** is supported, exc | Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Recommended Valet Version | | ------- | ------------- | ------------------ | ----- | ----- | ----- | ---- -| 6.x | ✅ Universal binary | ✅ Yes | Big Sur (11.0)
Monterey (12.0)
Ventura (13.0) | macOS 11+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)
PHP 7.0—PHP 8.2 (w/ Valet 3.x) | 3.0 recommended
2.16.2 minimum | +| 6.x | ✅ Universal binary | ✅ Yes | Big Sur (11.0)
Monterey (12.0)
Ventura (13.0) | macOS 11+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)
PHP 7.0—PHP 8.2 (w/ Valet 3.x)
PHP 7.4-PHP 8.2 (w/ Valet 4.x) | 3.0 or higher recommended
2.16.2 minimum | ## Legacy versions diff --git a/phpmon/Common/Core/Actions.swift b/phpmon/Common/Core/Actions.swift index 4851993..072463c 100644 --- a/phpmon/Common/Core/Actions.swift +++ b/phpmon/Common/Core/Actions.swift @@ -71,7 +71,7 @@ class Actions { "services stop \(name)", sudo: ServicesManager.shared[name]?.formula.elevated ?? false ) - await ServicesManager.shared.updateServices() + await ServicesManager.shared.reloadServicesStatus() } public static func startService(name: String) async { @@ -79,7 +79,7 @@ class Actions { "services start \(name)", sudo: ServicesManager.shared[name]?.formula.elevated ?? false ) - await ServicesManager.shared.updateServices() + await ServicesManager.shared.reloadServicesStatus() } // MARK: - Finding Config Files diff --git a/phpmon/Domain/App/Services/FakeServicesManager.swift b/phpmon/Domain/App/Services/FakeServicesManager.swift index 0601028..0c4b9dc 100644 --- a/phpmon/Domain/App/Services/FakeServicesManager.swift +++ b/phpmon/Domain/App/Services/FakeServicesManager.swift @@ -40,7 +40,7 @@ class FakeServicesManager: ServicesManager { } } - override func updateServices() async { + override func reloadServicesStatus() async { await delay(seconds: 0.3) for formula in self.serviceWrappers { @@ -48,7 +48,29 @@ class FakeServicesManager: ServicesManager { formula.isBusy = false } - print("Sending the update!") broadcastServicesUpdated() } + + override func toggleService(named: String) async { + guard let wrapper = self[named] else { + return Log.err("The wrapper for service \(named) does not exist.") + } + + wrapper.isBusy = true + Task { @MainActor in + wrapper.objectWillChange.send() + } + + guard let service = wrapper.service else { + return Log.err("The actual service for wrapper \(named) is nil.") + } + + await delay(seconds: 2) + service.running = !service.running + wrapper.isBusy = false + Task { @MainActor in + wrapper.objectWillChange.send() + self.objectWillChange.send() + } + } } diff --git a/phpmon/Domain/App/Services/ServicesManager.swift b/phpmon/Domain/App/Services/ServicesManager.swift index b8d124b..0cd58ca 100644 --- a/phpmon/Domain/App/Services/ServicesManager.swift +++ b/phpmon/Domain/App/Services/ServicesManager.swift @@ -17,7 +17,7 @@ class ServicesManager: ObservableObject { public static func useFake() { ServicesManager.shared = FakeServicesManager.init( - formulae: ["php", "nginx", "dnsmasq"], + formulae: ["php", "nginx", "dnsmasq", "mysql"], status: .loading ) } @@ -38,6 +38,7 @@ class ServicesManager: ObservableObject { } let statuses = self.serviceWrappers[0...2].map { $0.status } + if statuses.contains(.loading) { return "Determining Valet status..." } @@ -70,16 +71,25 @@ class ServicesManager: ObservableObject { return .green } - @available(*, deprecated, message: "Use a more specific method instead") - static func loadHomebrewServices() { - // print(self.shared) - print("This method must be updated") + /** + This method is called when the system configuration has changed + and all the status of one or more services may need to be determined. + */ + public func reloadServicesStatus() async { + fatalError("This method `\(#function)` has not been implemented") } - public func updateServices() async { - fatalError("Must be implemented in child class") + /** + This method is called when a service needs to be toggled (on/off). + */ + public func toggleService(named: String) async { + fatalError("This method `\(#function)` has not been implemented") } + /** + This method will notify all publishers that subscribe to notifiable objects. + The notified objects include this very ServicesManager as well as any individual service instances. + */ public func broadcastServicesUpdated() { Task { @MainActor in self.serviceWrappers.forEach { wrapper in diff --git a/phpmon/Domain/App/Services/ValetServicesManager.swift b/phpmon/Domain/App/Services/ValetServicesManager.swift index 9ffff96..be5531c 100644 --- a/phpmon/Domain/App/Services/ValetServicesManager.swift +++ b/phpmon/Domain/App/Services/ValetServicesManager.swift @@ -13,10 +13,10 @@ class ValetServicesManager: ServicesManager { super.init() // Load the initial services state - Task { await self.updateServices() } + Task { await self.reloadServicesStatus() } } - override func updateServices() async { + override func reloadServicesStatus() async { // TODO } } diff --git a/phpmon/Domain/Menu/MainMenu+Async.swift b/phpmon/Domain/Menu/MainMenu+Async.swift index ca9b766..013913a 100644 --- a/phpmon/Domain/Menu/MainMenu+Async.swift +++ b/phpmon/Domain/Menu/MainMenu+Async.swift @@ -74,7 +74,7 @@ extension MainMenu { } if behaviours.contains(.broadcastServicesUpdate) { - Task { await ServicesManager.shared.updateServices() } + Task { await ServicesManager.shared.reloadServicesStatus() } } if error != nil { diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index 727e54a..b411efc 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -94,7 +94,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate self.refreshActiveInstallation() self.refreshIcon() self.rebuild() - await ServicesManager.shared.updateServices() + await ServicesManager.shared.reloadServicesStatus() Log.perf("The menu has been reloaded!") } } @@ -184,7 +184,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate // Make sure the shortcut key does not trigger this when the menu is open App.shared.shortcutHotkey?.isPaused = true Task { // Reload Homebrew services information asynchronously - await ServicesManager.shared.updateServices() + await ServicesManager.shared.reloadServicesStatus() } } diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 606c894..566dffc 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -119,27 +119,19 @@ struct ServiceView: View { // .buttonStyle(BlueButton()) .frame(minWidth: 70, alignment: .center) } - if service.status == .active { + if service.status == .active || service.status == .inactive { Button { - // TODO - print("you pressed the button") + Task { await ServicesManager.shared.toggleService(named: service.name) } } label: { - Image(systemName: "checkmark") - .resizable() - .frame(width: 12.0, height: 12.0) - .foregroundColor(Color("IconColorGreen")) - } - .frame(width: 25, height: 25) - } - if service.status == .inactive { - Button { - // TODO - } label: { - Image(systemName: "xmark") - .resizable() - .frame(width: 12.0, height: 12.0) - .foregroundColor(Color("IconColorRed")) - } + Image( + systemName: service.status == .active ? "checkmark" : "xmark" + ) + .resizable() + .frame(width: 12.0, height: 12.0) + .foregroundColor( + service.status == .active ? Color("IconColorGreen") : Color("IconColorRed") + ) + }.frame(width: 25, height: 25) } }.frame(minWidth: 70) } From 67e85898344f469dc463aec2778a04c4f44ea719 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 5 Jan 2023 17:45:23 +0100 Subject: [PATCH 129/181] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Alter=20how=20PHP?= =?UTF-8?q?=20version=20support=20is=20handled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 20 ++++---- phpmon/Common/Core/Constants.swift | 40 +++++++-------- phpmon/Common/PHP/ActivePhpInstallation.swift | 4 +- phpmon/Common/PHP/PHP Version/PhpEnv.swift | 9 ++-- ...ersionNumber.swift => VersionNumber.swift} | 50 ++++++++++--------- phpmon/Common/PHP/PhpInstallation.swift | 6 +-- phpmon/Domain/App/EnvironmentManager.swift | 2 +- phpmon/Domain/App/Startup.swift | 2 +- .../DomainList/Cells/DomainListPhpCell.swift | 2 +- phpmon/Domain/Integrations/Valet/Valet.swift | 36 ++++++++----- phpmon/Domain/Menu/MainMenu+Startup.swift | 2 +- phpmon/Domain/Menu/StatusMenu+Items.swift | 2 +- .../SwiftUI/Domains/VersionPopoverView.swift | 6 +-- phpmon/Localizable.strings | 6 +-- 14 files changed, 98 insertions(+), 89 deletions(-) rename phpmon/Common/PHP/PHP Version/{PhpVersionNumber.swift => VersionNumber.swift} (81%) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index d8a78e6..c4d5736 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -229,10 +229,10 @@ C471E7EE28F9BAC30021E251 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; }; C471E7EF28F9BAC30021E251 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; }; C471E7F028F9BAC30021E251 /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; }; - C471E7F128F9BAC70021E251 /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; }; + C471E7F128F9BAC70021E251 /* VersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* VersionNumber.swift */; }; C471E7F228F9BAC70021E251 /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; }; C471E7F328F9BAC70021E251 /* PhpHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D936C827E3EB6100BD69FE /* PhpHelper.swift */; }; - C471E7F428F9BAC80021E251 /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; }; + C471E7F428F9BAC80021E251 /* VersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* VersionNumber.swift */; }; C471E7F528F9BAC80021E251 /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; }; C471E7F628F9BAC80021E251 /* PhpHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D936C827E3EB6100BD69FE /* PhpHelper.swift */; }; C471E7F728F9BACB0021E251 /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */; }; @@ -497,8 +497,8 @@ C485707C28BF459500539B36 /* NoWarningsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C708C28AA7F7900E8D498 /* NoWarningsView.swift */; }; C485707D28BF45A200539B36 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.swift */; }; C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9225CC804200CC7490 /* XibLoadable.swift */; }; - C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; }; - C48D6C71279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; }; + C48D6C70279CD2AC00F26D7E /* VersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* VersionNumber.swift */; }; + C48D6C71279CD2AC00F26D7E /* VersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* VersionNumber.swift */; }; C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */; }; C4927F0B27B2DFC200C55AFD /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; }; C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; }; @@ -832,7 +832,7 @@ C4811D2322D70A4700B5F6B3 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = ""; }; C48D0C9225CC804200CC7490 /* XibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XibLoadable.swift; sourceTree = ""; }; - C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpVersionNumber.swift; sourceTree = ""; }; + C48D6C6F279CD2AC00F26D7E /* VersionNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionNumber.swift; sourceTree = ""; }; C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpVersionNumberTest.swift; sourceTree = ""; }; C4927F0A27B2DFC200C55AFD /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; C4930849279F331F009C240B /* AddSiteVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSiteVC.swift; sourceTree = ""; }; @@ -1401,7 +1401,7 @@ isa = PBXGroup; children = ( C40C7F1D2772136000DDDCDC /* PhpEnv.swift */, - C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */, + C48D6C6F279CD2AC00F26D7E /* VersionNumber.swift */, C4D936C827E3EB6100BD69FE /* PhpHelper.swift */, ); path = "PHP Version"; @@ -1963,7 +1963,7 @@ C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */, C4D8016622B1584700C6DA1B /* Startup.swift in Sources */, C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */, - C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */, + C48D6C70279CD2AC00F26D7E /* VersionNumber.swift in Sources */, C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */, C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */, C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */, @@ -2271,7 +2271,7 @@ C471E81728F9BAE80021E251 /* NSMenuExtension.swift in Sources */, C471E81328F9BAE80021E251 /* XibLoadable.swift in Sources */, C4D3661C291173EA006BD146 /* DictionaryExtension.swift in Sources */, - C471E7F128F9BAC70021E251 /* PhpVersionNumber.swift in Sources */, + C471E7F128F9BAC70021E251 /* VersionNumber.swift in Sources */, C471E7DC28F9BA8F0021E251 /* ShellProtocol.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2436,7 +2436,7 @@ C469E706294CFDF700A82AB2 /* DomainsListTest.swift in Sources */, C471E80F28F9BAE80021E251 /* NSMenuExtension.swift in Sources */, C471E80B28F9BAE80021E251 /* XibLoadable.swift in Sources */, - C471E7F428F9BAC80021E251 /* PhpVersionNumber.swift in Sources */, + C471E7F428F9BAC80021E251 /* VersionNumber.swift in Sources */, C471E7CB28F9BA5B0021E251 /* TestableCommand.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2595,7 +2595,7 @@ C4E4404727C56F4700D225E1 /* ValetSite.swift in Sources */, C44CCD4A27AFF3BC00CE40E5 /* MainMenu+Async.swift in Sources */, C449B4F327EE7FC600C47E8A /* DomainListTypeCell.swift in Sources */, - C48D6C71279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */, + C48D6C71279CD2AC00F26D7E /* VersionNumber.swift in Sources */, C485706F28BF452300539B36 /* WarningsWindowController.swift in Sources */, C46FA9892822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */, C41C02AB27E61CB3009F26CB /* FakeValetSite.swift in Sources */, diff --git a/phpmon/Common/Core/Constants.swift b/phpmon/Common/Core/Constants.swift index 64d13fa..1c595ba 100644 --- a/phpmon/Common/Core/Constants.swift +++ b/phpmon/Common/Core/Constants.swift @@ -20,29 +20,25 @@ struct Constants { /** * The PHP versions supported by this application. - * Versions that do not appear in this array are omitted from the list. + * Depends on what version of Valet is installed. */ - static let SupportedPhpVersions = [ - // ==================== - // STABLE RELEASES - // ==================== - // Versions of PHP that are stable and are supported. - "5.6", // only supported when Valet 2.x is active - "7.0", - "7.1", - "7.2", - "7.3", - "7.4", - "8.0", - "8.1", - "8.2", - - // ==================== - // EXPERIMENTAL SUPPORT - // ==================== - // Every release that supports the next release will always support the next - // dev release. In this case, that means that the version below is detected. - "8.3" + static let ValetSupportedPhpVersionMatrix = [ + 2: // Valet v2 has the broadest legacy support + [ + "5.6", + "7.0", "7.1", "7.2", "7.3", "7.4", + "8.0", "8.1", "8.2" + ], + 3: // Valet v3 dropped support for v5.6 + [ + "7.0", "7.1", "7.2", "7.3", "7.4", + "8.0", "8.1", "8.2", "8.3" + ], + 4: // Valet v4 dropped support for [String] { var output: [String] = [] - var supported = Constants.SupportedPhpVersions + let valetMajor = Valet.shared.version.major + let supported = Constants.ValetSupportedPhpVersionMatrix[valetMajor] ?? [] - if !Valet.enabled(feature: .supportForPhp56) { - supported.removeAll { $0 == "5.6" } - } + print(supported) versions.filter { (version) -> Bool in // Omit everything that doesn't start with php@ @@ -161,7 +160,7 @@ class PhpEnv { return output } - public func validVersions(for constraint: String) -> [PhpVersionNumber] { + public func validVersions(for constraint: String) -> [VersionNumber] { constraint.split(separator: "|").flatMap { return PhpVersionNumberCollection .make(from: self.availablePhpVersions) diff --git a/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift b/phpmon/Common/PHP/PHP Version/VersionNumber.swift similarity index 81% rename from phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift rename to phpmon/Common/PHP/PHP Version/VersionNumber.swift index c262b2c..b3c4cc3 100644 --- a/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift +++ b/phpmon/Common/PHP/PHP Version/VersionNumber.swift @@ -9,19 +9,19 @@ import Foundation public struct PhpVersionNumberCollection: Equatable { - let versions: [PhpVersionNumber] + let versions: [VersionNumber] public static func make(from versions: [String]) -> Self { return PhpVersionNumberCollection( - versions: versions.map { try! PhpVersionNumber.parse($0) } + versions: versions.map { try! VersionNumber.parse($0) } ) } - public var first: PhpVersionNumber? { + public var first: VersionNumber? { return self.versions.first } - public var all: [PhpVersionNumber] { + public var all: [VersionNumber] { return self.versions } @@ -56,19 +56,19 @@ public struct PhpVersionNumberCollection: Equatable { If checking compatibility with general Homebrew versions of PHP, do NOT use strict mode, since the patch version there is not used. (The formula php@8.0 suffices for ^8.0.1.) */ - public func matching(constraint: String, strict: Bool = false) -> [PhpVersionNumber] { - if let version = PhpVersionNumber.make(from: constraint, type: .versionOnly) { + public func matching(constraint: String, strict: Bool = false) -> [VersionNumber] { + if let version = VersionNumber.make(from: constraint, type: .versionOnly) { // Strict constraint (e.g. "7.0") -> returns specific version return self.versions.filter { $0.isSameAs(version, strict) } } - if let version = PhpVersionNumber.make(from: constraint, type: .caretVersionRange) { + if let version = VersionNumber.make(from: constraint, type: .caretVersionRange) { // 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 return self.versions.filter { $0.hasNewerMinorVersionOrPatch(version, strict) } } - if let version = PhpVersionNumber.make(from: constraint, type: .tildeVersionRange) { + if let version = VersionNumber.make(from: constraint, type: .tildeVersionRange) { // Tilde range means that most specific digit is used as the basis. return self.versions.filter { version.patch != nil @@ -79,19 +79,19 @@ public struct PhpVersionNumberCollection: Equatable { } } - if let version = PhpVersionNumber.make(from: constraint, type: .greaterThanOrEqual) { + if let version = VersionNumber.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) { + if let version = VersionNumber.make(from: constraint, type: .greaterThan) { return self.versions.filter { $0.isNewerThan(version, strict) } } - if let version = PhpVersionNumber.make(from: constraint, type: .smallerThanOrEqual) { + if let version = VersionNumber.make(from: constraint, type: .smallerThanOrEqual) { return self.versions.filter { $0.isSameAs(version, strict) || $0.isOlderThan(version, strict)} } - if let version = PhpVersionNumber.make(from: constraint, type: .smallerThan) { + if let version = VersionNumber.make(from: constraint, type: .smallerThan) { return self.versions.filter { $0.isOlderThan(version, strict)} } @@ -99,18 +99,18 @@ public struct PhpVersionNumberCollection: Equatable { } } -public struct PhpVersionNumber: Equatable, Hashable { +public struct VersionNumber: Equatable, Hashable { let major: Int let minor: Int let patch: Int? - public func toString() -> String { + var text: String { return self.patch == nil - ? "\(major).\(minor)" - : "\(major).\(minor).\(patch!)" + ? "\(major).\(minor)" + : "\(major).\(minor).\(patch!)" } - public func patch(_ strictFallback: Bool = true, _ constraint: PhpVersionNumber? = nil) -> Int { + public func patch(_ strictFallback: Bool = true, _ constraint: VersionNumber? = nil) -> Int { return patch ?? (strictFallback ? 0 : constraint?.patch ?? 999) } @@ -168,13 +168,17 @@ public struct PhpVersionNumber: Equatable, Hashable { // MARK: Comparison Logic - internal func isSameAs(_ version: PhpVersionNumber, _ strict: Bool) -> Bool { + internal func isSameMajorVersionAs(_ version: VersionNumber) -> Bool { + return self.major == version.major + } + + internal func isSameAs(_ version: VersionNumber, _ 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 { + internal func isNewerThan(_ version: VersionNumber, _ strict: Bool) -> Bool { return ( self.major > version.major || self.major == version.major && self.minor > version.minor || @@ -183,7 +187,7 @@ public struct PhpVersionNumber: Equatable, Hashable { ) } - internal func isOlderThan(_ version: PhpVersionNumber, _ strict: Bool) -> Bool { + internal func isOlderThan(_ version: VersionNumber, _ strict: Bool) -> Bool { return ( self.major < version.major || self.major == version.major && self.minor < version.minor || @@ -192,7 +196,7 @@ public struct PhpVersionNumber: Equatable, Hashable { ) } - internal func hasNewerMinorVersionOrPatch(_ version: PhpVersionNumber, _ strict: Bool) -> Bool { + internal func hasNewerMinorVersionOrPatch(_ version: VersionNumber, _ strict: Bool) -> Bool { return self.major == version.major && ( (self.minor == version.minor && self.patch(strict) >= version.patch(strict, self)) @@ -200,12 +204,12 @@ public struct PhpVersionNumber: Equatable, Hashable { ) } - internal func hasSameMajorAndMinorButNewerOrSamePatch(_ version: PhpVersionNumber, _ strict: Bool) -> Bool { + internal func hasSameMajorAndMinorButNewerOrSamePatch(_ version: VersionNumber, _ 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 { + internal func hasSameMajorButNewerOrSameMinor(_ version: VersionNumber, _ strict: Bool) -> Bool { return self.major == version.major && self.minor >= version.minor } diff --git a/phpmon/Common/PHP/PhpInstallation.swift b/phpmon/Common/PHP/PhpInstallation.swift index c8abf99..f044be9 100644 --- a/phpmon/Common/PHP/PhpInstallation.swift +++ b/phpmon/Common/PHP/PhpInstallation.swift @@ -10,7 +10,7 @@ import Foundation class PhpInstallation { - var versionNumber: PhpVersionNumber + var versionNumber: VersionNumber /** In order to determine details about a PHP installation, we’ll simply run `php-config --version` @@ -19,7 +19,7 @@ class PhpInstallation { init(_ version: String) { let phpConfigExecutablePath = "\(Paths.optPath)/php@\(version)/bin/php-config" - self.versionNumber = PhpVersionNumber.make(from: version)! + self.versionNumber = VersionNumber.make(from: version)! if FileSystem.fileExists(phpConfigExecutablePath) { let longVersionString = Command.execute( @@ -30,7 +30,7 @@ class PhpInstallation { // The parser should always work, or the string has to be very unusual. // If so, the app SHOULD crash, so that the users report what's up. - self.versionNumber = try! PhpVersionNumber.parse(longVersionString) + self.versionNumber = try! VersionNumber.parse(longVersionString) } } diff --git a/phpmon/Domain/App/EnvironmentManager.swift b/phpmon/Domain/App/EnvironmentManager.swift index effcc9d..52f3ca7 100644 --- a/phpmon/Domain/App/EnvironmentManager.swift +++ b/phpmon/Domain/App/EnvironmentManager.swift @@ -21,7 +21,7 @@ public class EnvironmentManager { } // Extract the version number - Valet.shared.version = VersionExtractor.from(output) + Valet.shared.version = try! VersionNumber.parse(VersionExtractor.from(output)!) // Get the actual version return Valet.shared.version == nil diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index 5d53b78..7bfbe0e 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -242,7 +242,7 @@ class Startup { .components(separatedBy: "Laravel Valet")[1] .trimmingCharacters(in: .whitespaces) // Extract the version number - Valet.shared.version = VersionExtractor.from(output) + Valet.shared.version = try! VersionNumber.parse(VersionExtractor.from(output)!) // Get the actual version return Valet.shared.version == nil }, diff --git a/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift b/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift index a39d54b..bc0d039 100644 --- a/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift +++ b/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift @@ -53,7 +53,7 @@ class DomainListPhpCell: NSTableCellView, DomainListCellProtocol { @IBAction func pressedPhpVersion(_ sender: Any) { guard let site = self.site else { return } - var validPhpSuggestions: [PhpVersionNumber] { + var validPhpSuggestions: [VersionNumber] { if site.isolatedPhpVersion != nil { return [] } diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index e6f422b..50c8ca7 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -11,14 +11,13 @@ import Foundation class Valet { enum FeatureFlag { - case isolatedSites, - supportForPhp56 + case isolatedSites } static let shared = Valet() /// The version of Valet that was detected. - var version: String! = nil + var version: VersionNumber! = nil /// The Valet configuration file. var config: Valet.Configuration! @@ -138,13 +137,18 @@ class Valet { in use. This allows PHP Monitor to do different things when Valet 3.0 is enabled. */ public func evaluateFeatureSupport() { - let isOlderThanVersionThree = version.versionCompare("3.0") == .orderedAscending + let isVersion2 = version.isSameMajorVersionAs(try! VersionNumber.parse("2.0")) + let isVersion3 = version.isSameMajorVersionAs(try! VersionNumber.parse("3.0")) + let isVersion4 = version.isSameMajorVersionAs(try! VersionNumber.parse("4.0")) - if isOlderThanVersionThree { - self.features.append(.supportForPhp56) - } else { - Log.info("This version of Valet supports isolation.") + if isVersion2 { + Log.info("You are running Valet v2. Support for site isolation is disabled.") + } else if isVersion3 || isVersion4 { + Log.info("You are running Valet v3 or v4. Support for site isolation is available.") self.features.append(.isolatedSites) + } else { + // TODO: Show an alert and notify that some features might not work + Log.err("This version of Valet is not supported.") } } @@ -158,15 +162,16 @@ class Valet { Valet.shared.evaluateFeatureSupport() // 2. Notify user if the version is too old - if version.versionCompare(Constants.MinimumRecommendedValetVersion) == .orderedAscending { - let version = version - Log.warn("Valet version \(version!) is too old! (recommended: \(Constants.MinimumRecommendedValetVersion))") + if version.text.versionCompare(Constants.MinimumRecommendedValetVersion) == .orderedAscending { + let version = version! + let recommended = Constants.MinimumRecommendedValetVersion + Log.warn("Valet version \(version.text) is too old! (recommended: \(recommended))") Task { @MainActor in BetterAlert() .withInformation( title: "alert.min_valet_version.title".localized, subtitle: "alert.min_valet_version.info".localized( - version!, + version.text, Constants.MinimumRecommendedValetVersion ) ) @@ -174,9 +179,14 @@ class Valet { .show() } } else { - Log.info("Valet version \(version!) is recent enough, OK " + + Log.info("Valet version \(version.text) is recent enough, OK " + "(recommended: \(Constants.MinimumRecommendedValetVersion))") } + + // 3. Notify user if the version is too high + if version.major > 4 { + // TODO: + } } public func hasPlatformIssues() async -> Bool { diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index c5a9571..4f54592 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -50,7 +50,7 @@ extension MainMenu { // Attempt to find out more info about Valet if Valet.shared.version != nil { - Log.info("PHP Monitor has extracted the version number of Valet: \(Valet.shared.version!)") + Log.info("PHP Monitor has extracted the version number of Valet: \(Valet.shared.version!.text)") } // Validate the version (this will enforce which versions of PHP are supported) diff --git a/phpmon/Domain/Menu/StatusMenu+Items.swift b/phpmon/Domain/Menu/StatusMenu+Items.swift index 561a4e3..0d4c189 100644 --- a/phpmon/Domain/Menu/StatusMenu+Items.swift +++ b/phpmon/Domain/Menu/StatusMenu+Items.swift @@ -56,7 +56,7 @@ extension StatusMenu { let longVersion = PhpEnv.shared.cachedPhpInstallations[shortVersion]!.versionNumber let long = Preferences.preferences[.fullPhpVersionDynamicIcon] as! Bool - let versionString = long ? longVersion.toString() : shortVersion + let versionString = long ? longVersion.text : shortVersion let action = #selector(MainMenu.switchToPhpVersion(sender:)) let brew = (shortVersion == PhpEnv.brewPhpAlias) ? "php" : "php@\(shortVersion)" diff --git a/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift b/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift index 9ce4f11..25be569 100644 --- a/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift +++ b/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift @@ -12,7 +12,7 @@ struct VersionPopoverView: View { @State var site: ValetSite - @State var validPhpVersions: [PhpVersionNumber] + @State var validPhpVersions: [VersionNumber] @State var parent: NSPopover! @@ -185,8 +185,8 @@ struct VersionPopoverView_Previews: PreviewProvider { constraint: "^8.0" ), validPhpVersions: [ - PhpVersionNumber(major: 8, minor: 0, patch: 0), - PhpVersionNumber(major: 8, minor: 1, patch: 0) + VersionNumber(major: 8, minor: 0, patch: 0), + VersionNumber(major: 8, minor: 1, patch: 0) ], parent: nil ) diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index fadeff1..0f86088 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -427,12 +427,12 @@ problem manually, using your own Terminal app (this just shows you the output)." "alert.php_alias_conflict.title" = "Homebrew `php` formula alias conflict detected"; "alert.php_alias_conflict.info" = "PHP Monitor has detected conflicting `php` aliases in your Homebrew setup, both of which have been detected as installed.\n\nThis will likely result in failed linking when switching PHP versions, and will break PHP Monitor functionality.\n\nFor more information, please visit: https://github.com/nicoverbruggen/phpmon/issues/54"; -"alert.min_valet_version.title" = "Your version of Valet is outdated, please upgrade!"; +"alert.min_valet_version.title" = "The installed version of Valet does not meet the minimum version requirement. PHP Monitor may not function as expected!"; "alert.min_valet_version.info" = "You are currently running Valet %@. -For optimal support of the latest versions of PHP and proper version switching, it is recommended that you update to version %@. +For optimal support of the latest versions of PHP and proper version switching, it is recommended that you update to version %@, which is the minimum requirement for this version of PHP Monitor. -You can do this by running `composer global update` in your terminal. After that, run `valet install` again. For best results, restart PHP Monitor after that."; +You can do this by running `composer global update` in your terminal. After that, run `valet install` again. For best results, restart PHP Monitor after that. Until this is resolved, PHP Monitor may not behave as expected."; // Preset text description "alert.preset_description.switcher_version" = "Switches to PHP %@.\n\n"; From d854dee2a109d3bcfa70ef133b06eba151fa3520 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 5 Jan 2023 17:58:55 +0100 Subject: [PATCH 130/181] =?UTF-8?q?=E2=9C=85=20Fix=20tests=20related=20to?= =?UTF-8?q?=20version=20number=20parsing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/PHP/PHP Version/PhpEnv.swift | 12 ++++---- .../Versions/PhpVersionDetectionTest.swift | 29 +++++++++++-------- .../unit/Versions/PhpVersionNumberTest.swift | 24 +++++++-------- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/phpmon/Common/PHP/PHP Version/PhpEnv.swift b/phpmon/Common/PHP/PHP Version/PhpEnv.swift index 77d291f..06e1368 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnv.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnv.swift @@ -90,7 +90,10 @@ class PhpEnv { public func detectPhpVersions() async -> [String] { let files = await Shell.pipe("ls \(Paths.optPath) | grep php@").out - var versionsOnly = await extractPhpVersions(from: files.components(separatedBy: "\n")) + var versionsOnly = await extractPhpVersions( + from: files.components(separatedBy: "\n"), + supported: Constants.ValetSupportedPhpVersionMatrix[Valet.shared.version.major]! + ) // Make sure the aliased version is detected // The user may have `php` installed, but not e.g. `php@8.0` @@ -126,16 +129,11 @@ class PhpEnv { */ public func extractPhpVersions( from versions: [String], + supported: [String], checkBinaries: Bool = true, generateHelpers: Bool = true ) async -> [String] { var output: [String] = [] - - let valetMajor = Valet.shared.version.major - let supported = Constants.ValetSupportedPhpVersionMatrix[valetMajor] ?? [] - - print(supported) - versions.filter { (version) -> Bool in // Omit everything that doesn't start with php@ // (e.g. something-php@8.0 won't be detected) diff --git a/tests/unit/Versions/PhpVersionDetectionTest.swift b/tests/unit/Versions/PhpVersionDetectionTest.swift index d9f636e..a137741 100644 --- a/tests/unit/Versions/PhpVersionDetectionTest.swift +++ b/tests/unit/Versions/PhpVersionDetectionTest.swift @@ -11,18 +11,23 @@ import XCTest class PhpVersionDetectionTest: XCTestCase { func test_can_detect_valid_php_versions() async throws { - let outcome = await PhpEnv.shared.extractPhpVersions(from: [ - "", // empty lines should be omitted - "php@8.0", - "php@8.0", // should only be detected once - "meta-php@8.0", // should be omitted, invalid - "php@8.0-coolio", // should be omitted, invalid - "php@7.0", - "", - "unrelatedphp@1.0", // should be omitted, invalid - "php@5.6", - "php@5.4" // should be omitted, not supported - ], checkBinaries: false, generateHelpers: false) + let outcome = await PhpEnv.shared.extractPhpVersions( + from: [ + "", // empty lines should be omitted + "php@8.0", + "php@8.0", // should only be detected once + "meta-php@8.0", // should be omitted, invalid + "php@8.0-coolio", // should be omitted, invalid + "php@7.0", + "", + "unrelatedphp@1.0", // should be omitted, invalid + "php@5.6", // should be omitted, not supported + "php@5.4" // should be omitted, not supported + ], + supported: ["7.0", "8.0", "8.1", "8.2"], + checkBinaries: false, + generateHelpers: false + ) XCTAssertEqual(outcome, ["8.0", "7.0"]) } diff --git a/tests/unit/Versions/PhpVersionNumberTest.swift b/tests/unit/Versions/PhpVersionNumberTest.swift index 17fbbae..a9f3e4b 100644 --- a/tests/unit/Versions/PhpVersionNumberTest.swift +++ b/tests/unit/Versions/PhpVersionNumberTest.swift @@ -13,33 +13,33 @@ class PhpVersionNumberTest: XCTestCase { func test_can_deconstruct_php_version() throws { XCTAssertEqual( - try! PhpVersionNumber.parse("PHP 8.2.0-dev"), - PhpVersionNumber(major: 8, minor: 2, patch: 0) + try! VersionNumber.parse("PHP 8.2.0-dev"), + VersionNumber(major: 8, minor: 2, patch: 0) ) XCTAssertEqual( - try! PhpVersionNumber.parse("PHP 8.1.0RC5-dev"), - PhpVersionNumber(major: 8, minor: 1, patch: 0) + try! VersionNumber.parse("PHP 8.1.0RC5-dev"), + VersionNumber(major: 8, minor: 1, patch: 0) ) XCTAssertEqual( - try! PhpVersionNumber.parse("8.0.11"), - PhpVersionNumber(major: 8, minor: 0, patch: 11) + try! VersionNumber.parse("8.0.11"), + VersionNumber(major: 8, minor: 0, patch: 11) ) XCTAssertEqual( - try! PhpVersionNumber.parse("7.4.2"), - PhpVersionNumber(major: 7, minor: 4, patch: 2) + try! VersionNumber.parse("7.4.2"), + VersionNumber(major: 7, minor: 4, patch: 2) ) XCTAssertEqual( - try! PhpVersionNumber.parse("7.4"), - PhpVersionNumber(major: 7, minor: 4, patch: nil) + try! VersionNumber.parse("7.4"), + VersionNumber(major: 7, minor: 4, patch: nil) ) XCTAssertEqual( - PhpVersionNumber.make(from: "7"), + VersionNumber.make(from: "7"), nil ) } func test_php_version_number_parse() throws { - XCTAssertThrowsError(try PhpVersionNumber.parse("OOF")) { error in + XCTAssertThrowsError(try VersionNumber.parse("OOF")) { error in XCTAssertTrue(error is VersionParseError) } } From 3d505aebde2176320022c4a8da5807ccb613d807 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 6 Jan 2023 12:41:37 +0100 Subject: [PATCH 131/181] =?UTF-8?q?=F0=9F=9A=9B=20VersionNumber,=20PhpVers?= =?UTF-8?q?ionNumberCollection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 12 ++- .../PhpVersionNumberCollection.swift | 100 ++++++++++++++++++ .../PHP/PHP Version/VersionNumber.swift | 95 +---------------- phpmon/Domain/Integrations/Valet/Valet.swift | 8 ++ 4 files changed, 124 insertions(+), 91 deletions(-) create mode 100644 phpmon/Common/PHP/PHP Version/PhpVersionNumberCollection.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index c4d5736..32055c8 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -572,6 +572,10 @@ C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */; }; C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; }; C4CE3BBC27B324250086CA49 /* ComposerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */; }; + C4CE7F9629683B43000102CF /* PhpVersionNumberCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE7F9529683B43000102CF /* PhpVersionNumberCollection.swift */; }; + C4CE7F9729683B43000102CF /* PhpVersionNumberCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE7F9529683B43000102CF /* PhpVersionNumberCollection.swift */; }; + C4CE7F9829683B43000102CF /* PhpVersionNumberCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE7F9529683B43000102CF /* PhpVersionNumberCollection.swift */; }; + C4CE7F9929683B43000102CF /* PhpVersionNumberCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE7F9529683B43000102CF /* PhpVersionNumberCollection.swift */; }; C4D36601291132B7006BD146 /* ValetScanners.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36600291132B7006BD146 /* ValetScanners.swift */; }; C4D36602291132B7006BD146 /* ValetScanners.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36600291132B7006BD146 /* ValetScanners.swift */; }; C4D36603291132B7006BD146 /* ValetScanners.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36600291132B7006BD146 /* ValetScanners.swift */; }; @@ -874,6 +878,7 @@ C4CDA892288F1A71007CE25F /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = ""; }; C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Switcher.swift"; sourceTree = ""; }; C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerWindow.swift; sourceTree = ""; }; + C4CE7F9529683B43000102CF /* PhpVersionNumberCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpVersionNumberCollection.swift; sourceTree = ""; }; C4D36600291132B7006BD146 /* ValetScanners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetScanners.swift; sourceTree = ""; }; C4D3660A29113F20006BD146 /* System.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = System.swift; sourceTree = ""; }; C4D3660F291140BE006BD146 /* TestableFileSystemTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableFileSystemTest.swift; sourceTree = ""; }; @@ -1400,8 +1405,9 @@ C48D6C6E279CD29C00F26D7E /* PHP Version */ = { isa = PBXGroup; children = ( - C40C7F1D2772136000DDDCDC /* PhpEnv.swift */, C48D6C6F279CD2AC00F26D7E /* VersionNumber.swift */, + C4CE7F9529683B43000102CF /* PhpVersionNumberCollection.swift */, + C40C7F1D2772136000DDDCDC /* PhpEnv.swift */, C4D936C827E3EB6100BD69FE /* PhpHelper.swift */, ); path = "PHP Version"; @@ -2067,6 +2073,7 @@ C4C8900528F0E3D100CE5E97 /* RealFileSystem.swift in Sources */, C4A81CA428C67101008DD9D1 /* PMTableView.swift in Sources */, C4927F0B27B2DFC200C55AFD /* Errors.swift in Sources */, + C4CE7F9629683B43000102CF /* PhpVersionNumberCollection.swift in Sources */, C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */, C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */, C4FE011128084FC200D1DE6D /* SelectionVC.swift in Sources */, @@ -2190,6 +2197,7 @@ C4E2E84C28FC1E70003B070C /* DataExtension.swift in Sources */, C471E86E28F9BB650021E251 /* MenuBarIcons.swift in Sources */, C471E86F28F9BB650021E251 /* Stats.swift in Sources */, + C4CE7F9829683B43000102CF /* PhpVersionNumberCollection.swift in Sources */, C471E87028F9BB650021E251 /* GlobalKeybindPreference.swift in Sources */, C471E87228F9BB650021E251 /* CheckboxPreferenceView.swift in Sources */, C471E87428F9BB650021E251 /* SelectPreferenceView.swift in Sources */, @@ -2381,6 +2389,7 @@ C471E8F128F9BB8F0021E251 /* KeyCombo.swift in Sources */, C471E8F228F9BB8F0021E251 /* ModifierFlagsExtension.swift in Sources */, C471E7F028F9BAC30021E251 /* Paths.swift in Sources */, + C4CE7F9929683B43000102CF /* PhpVersionNumberCollection.swift in Sources */, C471E7FC28F9BACE0021E251 /* HomebrewPackage.swift in Sources */, C471E7CF28F9BA600021E251 /* ActiveShell.swift in Sources */, C471E7F628F9BAC80021E251 /* PhpHelper.swift in Sources */, @@ -2529,6 +2538,7 @@ C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */, C4AD38B328ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */, C40C5C9D2846A40600E28255 /* Preset.swift in Sources */, + C4CE7F9729683B43000102CF /* PhpVersionNumberCollection.swift in Sources */, C4F30B09278E1A0E00755FCE /* CustomPrefs.swift in Sources */, C40FE738282ABA4F00A302C2 /* AppVersion.swift in Sources */, C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */, diff --git a/phpmon/Common/PHP/PHP Version/PhpVersionNumberCollection.swift b/phpmon/Common/PHP/PHP Version/PhpVersionNumberCollection.swift new file mode 100644 index 0000000..029e262 --- /dev/null +++ b/phpmon/Common/PHP/PHP Version/PhpVersionNumberCollection.swift @@ -0,0 +1,100 @@ +// +// PhpVersionNumberCollection.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 06/01/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import Foundation + +public struct PhpVersionNumberCollection: Equatable { + let versions: [VersionNumber] + + public static func make(from versions: [String]) -> Self { + return PhpVersionNumberCollection( + versions: versions.map { try! VersionNumber.parse($0) } + ) + } + + public var first: VersionNumber? { + return self.versions.first + } + + public var all: [VersionNumber] { + return self.versions + } + + /** + Checks if any versions of PHP are valid for the constraint provided. + Due to the complexity of evaluating these, a important test is maintained. + More information on these constraints can be found here: + https://getcomposer.org/doc/articles/versions.md#writing-version-constraints + + - Parameter constraint: The full constraint as a string (e.g. "^7.0") + - 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. + + Strict mode assumes that any PHP version lacking precise patch information, e.g. inferred + from Homebrew corresponds to the .0 patch version of that version. The default, which is imprecise, + assumes that the patch version is .999, which means that in all cases the patch version check is + always going to pass. + + **STRICT MODE (= patch precision on)** + + 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). + When checking against actual PHP versions installed by the user (with patch precision), use + strict mode. + + **NON-STRICT MODE (= patch precision off)** + + Given versions 8.0.? and 8.1.?, but the requirement is ^8.0.1, in non-strict mode version 8.0 + is assumed to be equal to version 8.0.999, which is actually fine if 8.0.1 is the required version. + In non-strict mode, the patch version is ignored for regular version checks (no caret / tilde). + If checking compatibility with general Homebrew versions of PHP, do NOT use strict mode, since + the patch version there is not used. (The formula php@8.0 suffices for ^8.0.1.) + */ + public func matching(constraint: String, strict: Bool = false) -> [VersionNumber] { + if let version = VersionNumber.make(from: constraint, type: .versionOnly) { + // Strict constraint (e.g. "7.0") -> returns specific version + return self.versions.filter { $0.isSameAs(version, strict) } + } + + if let version = VersionNumber.make(from: constraint, type: .caretVersionRange) { + // 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 + return self.versions.filter { $0.hasNewerMinorVersionOrPatch(version, strict) } + } + + if let version = VersionNumber.make(from: constraint, type: .tildeVersionRange) { + // Tilde range means that most specific digit is used as the basis. + return self.versions.filter { + version.patch != nil + // If a patch is provided then the minor version cannot be bumped. + ? $0.hasSameMajorAndMinorButNewerOrSamePatch(version, strict) + // If a patch is not provided then the major version cannot be bumped. + : $0.hasSameMajorButNewerOrSameMinor(version, strict) + } + } + + if let version = VersionNumber.make(from: constraint, type: .greaterThanOrEqual) { + return self.versions.filter { $0.isSameAs(version, strict) || $0.isNewerThan(version, strict) } + } + + if let version = VersionNumber.make(from: constraint, type: .greaterThan) { + return self.versions.filter { $0.isNewerThan(version, strict) } + } + + if let version = VersionNumber.make(from: constraint, type: .smallerThanOrEqual) { + return self.versions.filter { $0.isSameAs(version, strict) || $0.isOlderThan(version, strict)} + } + + if let version = VersionNumber.make(from: constraint, type: .smallerThan) { + return self.versions.filter { $0.isOlderThan(version, strict)} + } + + return [] + } +} diff --git a/phpmon/Common/PHP/PHP Version/VersionNumber.swift b/phpmon/Common/PHP/PHP Version/VersionNumber.swift index b3c4cc3..3b07c52 100644 --- a/phpmon/Common/PHP/PHP Version/VersionNumber.swift +++ b/phpmon/Common/PHP/PHP Version/VersionNumber.swift @@ -8,97 +8,12 @@ import Foundation -public struct PhpVersionNumberCollection: Equatable { - let versions: [VersionNumber] - - public static func make(from versions: [String]) -> Self { - return PhpVersionNumberCollection( - versions: versions.map { try! VersionNumber.parse($0) } - ) - } - - public var first: VersionNumber? { - return self.versions.first - } - - public var all: [VersionNumber] { - return self.versions - } - - /** - Checks if any versions of PHP are valid for the constraint provided. - Due to the complexity of evaluating these, a important test is maintained. - More information on these constraints can be found here: - https://getcomposer.org/doc/articles/versions.md#writing-version-constraints - - - Parameter constraint: The full constraint as a string (e.g. "^7.0") - - 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. - - Strict mode assumes that any PHP version lacking precise patch information, e.g. inferred - from Homebrew corresponds to the .0 patch version of that version. The default, which is imprecise, - assumes that the patch version is .999, which means that in all cases the patch version check is - always going to pass. - - **STRICT MODE (= patch precision on)** - - 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). - When checking against actual PHP versions installed by the user (with patch precision), use - strict mode. - - **NON-STRICT MODE (= patch precision off)** - - Given versions 8.0.? and 8.1.?, but the requirement is ^8.0.1, in non-strict mode version 8.0 - is assumed to be equal to version 8.0.999, which is actually fine if 8.0.1 is the required version. - In non-strict mode, the patch version is ignored for regular version checks (no caret / tilde). - If checking compatibility with general Homebrew versions of PHP, do NOT use strict mode, since - the patch version there is not used. (The formula php@8.0 suffices for ^8.0.1.) - */ - public func matching(constraint: String, strict: Bool = false) -> [VersionNumber] { - if let version = VersionNumber.make(from: constraint, type: .versionOnly) { - // Strict constraint (e.g. "7.0") -> returns specific version - return self.versions.filter { $0.isSameAs(version, strict) } - } - - if let version = VersionNumber.make(from: constraint, type: .caretVersionRange) { - // 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 - return self.versions.filter { $0.hasNewerMinorVersionOrPatch(version, strict) } - } - - if let version = VersionNumber.make(from: constraint, type: .tildeVersionRange) { - // Tilde range means that most specific digit is used as the basis. - return self.versions.filter { - version.patch != nil - // If a patch is provided then the minor version cannot be bumped. - ? $0.hasSameMajorAndMinorButNewerOrSamePatch(version, strict) - // If a patch is not provided then the major version cannot be bumped. - : $0.hasSameMajorButNewerOrSameMinor(version, strict) - } - } - - if let version = VersionNumber.make(from: constraint, type: .greaterThanOrEqual) { - return self.versions.filter { $0.isSameAs(version, strict) || $0.isNewerThan(version, strict) } - } - - if let version = VersionNumber.make(from: constraint, type: .greaterThan) { - return self.versions.filter { $0.isNewerThan(version, strict) } - } - - if let version = VersionNumber.make(from: constraint, type: .smallerThanOrEqual) { - return self.versions.filter { $0.isSameAs(version, strict) || $0.isOlderThan(version, strict)} - } - - if let version = VersionNumber.make(from: constraint, type: .smallerThan) { - return self.versions.filter { $0.isOlderThan(version, strict)} - } - - return [] - } -} +/** + A version number that is (mostly) compatible with the semantic versioning standard. + For more information about semantic versioning, see: https://semver.org/ + - Note: If you want to check version constraints for PHP versions, please see `PhpVersionNumberCollection`. + */ public struct VersionNumber: Equatable, Hashable { let major: Int let minor: Int diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 50c8ca7..f3689df 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -8,6 +8,11 @@ import Foundation +/** + This class is responsible for handling the state of Valet throughout PHP Monitor. A singleton instance is created + and accessible throughout the lifecycle of the app, unless the user has decided to not use Valet. In that case, + only a restricted subset of functionality is available in the app. + */ class Valet { enum FeatureFlag { @@ -189,6 +194,9 @@ class Valet { } } + /** + Determine if any platform issues are detected when running `valet --version`. + */ public func hasPlatformIssues() async -> Bool { return await Shell.pipe("valet --version") .out.contains("Composer detected issues in your platform") From 291d20b2b30e7c12a74586f014d6e7d177962b0a Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 6 Jan 2023 12:59:15 +0100 Subject: [PATCH 132/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Functional=20serv?= =?UTF-8?q?ices=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcschemes/PHP Monitor DEV.xcscheme | 2 +- .../App/Services/ValetServicesManager.swift | 94 +++++++++---------- 2 files changed, 46 insertions(+), 50 deletions(-) diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index b0a4d86..8642ae0 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -91,7 +91,7 @@ + isEnabled = "NO"> Date: Fri, 6 Jan 2023 13:07:02 +0100 Subject: [PATCH 133/181] =?UTF-8?q?=F0=9F=91=8C=20Add=20comments=20about?= =?UTF-8?q?=20concurrency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/PHP/Homebrew/HomebrewService.swift | 2 ++ .../Domain/App/Services/ServiceWrapper.swift | 3 ++- .../App/Services/ValetServicesManager.swift | 19 ++++++++++++++----- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/phpmon/Common/PHP/Homebrew/HomebrewService.swift b/phpmon/Common/PHP/Homebrew/HomebrewService.swift index 6cc1362..963d3d4 100644 --- a/phpmon/Common/PHP/Homebrew/HomebrewService.swift +++ b/phpmon/Common/PHP/Homebrew/HomebrewService.swift @@ -62,10 +62,12 @@ class HomebrewService: Decodable, Equatable, Hashable { hasher.combine(name) hasher.combine(service_name) hasher.combine(pid) + hasher.combine(status) } static func == (lhs: HomebrewService, rhs: HomebrewService) -> Bool { return lhs.name == rhs.name + && lhs.service_name == rhs.service_name && lhs.pid == rhs.pid && lhs.status == rhs.status } diff --git a/phpmon/Domain/App/Services/ServiceWrapper.swift b/phpmon/Domain/App/Services/ServiceWrapper.swift index 716b2ce..6ea9b3f 100644 --- a/phpmon/Domain/App/Services/ServiceWrapper.swift +++ b/phpmon/Domain/App/Services/ServiceWrapper.swift @@ -50,7 +50,8 @@ public class ServiceWrapper: ObservableObject, Identifiable, Hashable { } public static func == (lhs: ServiceWrapper, rhs: ServiceWrapper) -> Bool { - return lhs.service == rhs.service && lhs.formula == rhs.formula + return lhs.service == rhs.service + && lhs.formula == rhs.formula } public func hash(into hasher: inout Hasher) { diff --git a/phpmon/Domain/App/Services/ValetServicesManager.swift b/phpmon/Domain/App/Services/ValetServicesManager.swift index 541bbb0..3f52b24 100644 --- a/phpmon/Domain/App/Services/ValetServicesManager.swift +++ b/phpmon/Domain/App/Services/ValetServicesManager.swift @@ -16,6 +16,11 @@ class ValetServicesManager: ServicesManager { Task { await self.reloadServicesStatus() } } + /** + This method allows us to reload the Homebrew services, but we run this command + twice (once for user services, and once for root services). Please note that + these two commands are executed concurrently. + */ override func reloadServicesStatus() async { await withTaskGroup(of: [HomebrewService].self, body: { group in group.addTask { @@ -46,21 +51,25 @@ class ValetServicesManager: ServicesManager { .filter({ return userServiceNames.contains($0.name) }) } + // Ensure both commands complete (but run concurrently) for await services in group { + // For both groups (user and root services), set the service to the wrapper object for service in services { - guard let wrapper = self[service.name] else { - break - } - - wrapper.service = service + self[service.name]?.service = service } } + // Ensure that every wrapper is considered no longer busy for wrapper in serviceWrappers { wrapper.isBusy = false } + // Broadcast that all services have been updated self.broadcastServicesUpdated() }) } + + override func toggleService(named: String) async { + // TODO + } } From 7b07520440ba91ef7a276dee5e87f5f56bd7e7f3 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 6 Jan 2023 18:33:55 +0100 Subject: [PATCH 134/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Service=20togglin?= =?UTF-8?q?g?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Core/Actions.swift | 17 ------------- .../Common/PHP/Homebrew/HomebrewService.swift | 7 +++--- .../Domain/App/Services/ServiceWrapper.swift | 17 ++++++------- .../App/Services/ValetServicesManager.swift | 25 ++++++++++++++----- 4 files changed, 29 insertions(+), 37 deletions(-) diff --git a/phpmon/Common/Core/Actions.swift b/phpmon/Common/Core/Actions.swift index 072463c..1db1b7c 100644 --- a/phpmon/Common/Core/Actions.swift +++ b/phpmon/Common/Core/Actions.swift @@ -65,23 +65,6 @@ class Actions { } } - // MARK: - Third Party Services - public static func stopService(name: String) async { - await brew( - "services stop \(name)", - sudo: ServicesManager.shared[name]?.formula.elevated ?? false - ) - await ServicesManager.shared.reloadServicesStatus() - } - - public static func startService(name: String) async { - await brew( - "services start \(name)", - sudo: ServicesManager.shared[name]?.formula.elevated ?? false - ) - await ServicesManager.shared.reloadServicesStatus() - } - // MARK: - Finding Config Files public static func openGenericPhpConfigFolder() { diff --git a/phpmon/Common/PHP/Homebrew/HomebrewService.swift b/phpmon/Common/PHP/Homebrew/HomebrewService.swift index 963d3d4..da287d5 100644 --- a/phpmon/Common/PHP/Homebrew/HomebrewService.swift +++ b/phpmon/Common/PHP/Homebrew/HomebrewService.swift @@ -63,12 +63,11 @@ class HomebrewService: Decodable, Equatable, Hashable { hasher.combine(service_name) hasher.combine(pid) hasher.combine(status) + hasher.combine(running) + hasher.combine(user) } static func == (lhs: HomebrewService, rhs: HomebrewService) -> Bool { - return lhs.name == rhs.name - && lhs.service_name == rhs.service_name - && lhs.pid == rhs.pid - && lhs.status == rhs.status + return lhs.hashValue == rhs.hashValue } } diff --git a/phpmon/Domain/App/Services/ServiceWrapper.swift b/phpmon/Domain/App/Services/ServiceWrapper.swift index 6ea9b3f..5c72d34 100644 --- a/phpmon/Domain/App/Services/ServiceWrapper.swift +++ b/phpmon/Domain/App/Services/ServiceWrapper.swift @@ -14,7 +14,6 @@ import Foundation public enum ServiceStatus: String { case active case inactive - case loading case missing } @@ -26,17 +25,11 @@ public class ServiceWrapper: ObservableObject, Identifiable, Hashable { var formula: HomebrewFormula var service: HomebrewService? - var isBusy: Bool = false - public var name: String { return formula.name } public var status: ServiceStatus { - if isBusy { - return .loading - } - guard let service = self.service else { return .missing } @@ -46,16 +39,20 @@ public class ServiceWrapper: ObservableObject, Identifiable, Hashable { init(formula: HomebrewFormula) { self.formula = formula - self.isBusy = true } public static func == (lhs: ServiceWrapper, rhs: ServiceWrapper) -> Bool { - return lhs.service == rhs.service - && lhs.formula == rhs.formula + return lhs.hashValue == rhs.hashValue } public func hash(into hasher: inout Hasher) { hasher.combine(formula) hasher.combine(service) } + + public func broadcastChanged() { + Task { @MainActor in + self.objectWillChange.send() + } + } } diff --git a/phpmon/Domain/App/Services/ValetServicesManager.swift b/phpmon/Domain/App/Services/ValetServicesManager.swift index 3f52b24..ac02c25 100644 --- a/phpmon/Domain/App/Services/ValetServicesManager.swift +++ b/phpmon/Domain/App/Services/ValetServicesManager.swift @@ -23,6 +23,7 @@ class ValetServicesManager: ServicesManager { */ override func reloadServicesStatus() async { await withTaskGroup(of: [HomebrewService].self, body: { group in + // First, retrieve the status of the formulae that run as root group.addTask { let rootServiceNames = self.formulae .filter { $0.elevated } @@ -37,6 +38,7 @@ class ValetServicesManager: ServicesManager { .filter({ return rootServiceNames.contains($0.name) }) } + // At the same time, retrieve the status of the formulae that run as user group.addTask { let userServiceNames = self.formulae .filter { !$0.elevated } @@ -59,17 +61,28 @@ class ValetServicesManager: ServicesManager { } } - // Ensure that every wrapper is considered no longer busy - for wrapper in serviceWrappers { - wrapper.isBusy = false - } - // Broadcast that all services have been updated self.broadcastServicesUpdated() }) } override func toggleService(named: String) async { - // TODO + guard let wrapper = self[named] else { + return Log.err("The wrapper for '\(named)' is missing.") + } + + if wrapper.service == nil { + return Log.err("The Homebrew service for \(named) is missing.") + } + + // Prepare the appropriate command to stop or start a service + let action = wrapper.service!.running ? "stop" : "start" + let command = "services \(action) \(wrapper.formula.name)" + + // Run the command + await brew(command, sudo: wrapper.formula.elevated) + + // Reload the services status to confirm this worked + await ServicesManager.shared.reloadServicesStatus() } } From a090cbc20bf5f80c0822aae3ac3a522283a2130c Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 6 Jan 2023 18:34:39 +0100 Subject: [PATCH 135/181] =?UTF-8?q?=F0=9F=91=8C=20Revert=20loading?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/App/Services/ServiceWrapper.swift | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/phpmon/Domain/App/Services/ServiceWrapper.swift b/phpmon/Domain/App/Services/ServiceWrapper.swift index 5c72d34..6ea9b3f 100644 --- a/phpmon/Domain/App/Services/ServiceWrapper.swift +++ b/phpmon/Domain/App/Services/ServiceWrapper.swift @@ -14,6 +14,7 @@ import Foundation public enum ServiceStatus: String { case active case inactive + case loading case missing } @@ -25,11 +26,17 @@ public class ServiceWrapper: ObservableObject, Identifiable, Hashable { var formula: HomebrewFormula var service: HomebrewService? + var isBusy: Bool = false + public var name: String { return formula.name } public var status: ServiceStatus { + if isBusy { + return .loading + } + guard let service = self.service else { return .missing } @@ -39,20 +46,16 @@ public class ServiceWrapper: ObservableObject, Identifiable, Hashable { init(formula: HomebrewFormula) { self.formula = formula + self.isBusy = true } public static func == (lhs: ServiceWrapper, rhs: ServiceWrapper) -> Bool { - return lhs.hashValue == rhs.hashValue + return lhs.service == rhs.service + && lhs.formula == rhs.formula } public func hash(into hasher: inout Hasher) { hasher.combine(formula) hasher.combine(service) } - - public func broadcastChanged() { - Task { @MainActor in - self.objectWillChange.send() - } - } } From 456948ffd99735ba03a9ff9efa37c1bee10d9125 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 6 Jan 2023 19:14:17 +0100 Subject: [PATCH 136/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Broken=20services?= =?UTF-8?q?=20toggling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit You can break it by opening the menu twice. --- PHP Monitor.xcodeproj/project.pbxproj | 2 +- phpmon/Domain/App/Services/ServicesManager.swift | 5 ----- phpmon/Domain/App/Services/ValetServicesManager.swift | 4 ++++ 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 32055c8..d0f44a2 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -1298,9 +1298,9 @@ C45B9147295607E200F4EC78 /* Services */ = { isa = PBXGroup; children = ( + C45B9148295607F400F4EC78 /* ServiceWrapper.swift */, C45E76132854A65300B4FE0C /* ServicesManager.swift */, C45B914D295608E300F4EC78 /* ValetServicesManager.swift */, - C45B9148295607F400F4EC78 /* ServiceWrapper.swift */, C45B91522956123A00F4EC78 /* FakeServicesManager.swift */, ); path = Services; diff --git a/phpmon/Domain/App/Services/ServicesManager.swift b/phpmon/Domain/App/Services/ServicesManager.swift index 0cd58ca..be7c746 100644 --- a/phpmon/Domain/App/Services/ServicesManager.swift +++ b/phpmon/Domain/App/Services/ServicesManager.swift @@ -93,11 +93,6 @@ class ServicesManager: ObservableObject { public func broadcastServicesUpdated() { Task { @MainActor in self.serviceWrappers.forEach { wrapper in - guard let service = wrapper.service else { - return - } - - Log.perf("\(service.name): \(wrapper.status)") wrapper.objectWillChange.send() } diff --git a/phpmon/Domain/App/Services/ValetServicesManager.swift b/phpmon/Domain/App/Services/ValetServicesManager.swift index ac02c25..baca249 100644 --- a/phpmon/Domain/App/Services/ValetServicesManager.swift +++ b/phpmon/Domain/App/Services/ValetServicesManager.swift @@ -59,6 +59,10 @@ class ValetServicesManager: ServicesManager { for service in services { self[service.name]?.service = service } + + for wrapper in serviceWrappers { + wrapper.isBusy = false + } } // Broadcast that all services have been updated From 684a53fc4a65d5ade1d840922a602c38f7a817ba Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 6 Jan 2023 20:30:25 +0100 Subject: [PATCH 137/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Functional=20serv?= =?UTF-8?q?ice=20toggling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../App/Services/FakeServicesManager.swift | 39 ++------ .../Domain/App/Services/ServiceWrapper.swift | 31 ++----- .../Domain/App/Services/ServicesManager.swift | 12 +-- .../App/Services/ValetServicesManager.swift | 36 ++++---- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 88 +++++++++---------- 5 files changed, 77 insertions(+), 129 deletions(-) diff --git a/phpmon/Domain/App/Services/FakeServicesManager.swift b/phpmon/Domain/App/Services/FakeServicesManager.swift index 0c4b9dc..4cb733f 100644 --- a/phpmon/Domain/App/Services/FakeServicesManager.swift +++ b/phpmon/Domain/App/Services/FakeServicesManager.swift @@ -10,13 +10,13 @@ import Foundation class FakeServicesManager: ServicesManager { var fixedFormulae: [String] = [] - var fixedStatus: ServiceStatus = .loading + var fixedStatus: ServiceStatus = .active override init() {} init( formulae: [String] = ["php", "nginx", "dnsmasq"], - status: ServiceStatus = .loading + status: ServiceStatus = .active ) { super.init() @@ -27,9 +27,10 @@ class FakeServicesManager: ServicesManager { self.fixedStatus = status self.serviceWrappers = self.formulae.map { - let wrapper = ServiceWrapper(formula: $0) - wrapper.isBusy = (status == .loading) - wrapper.service = HomebrewService.dummy(named: $0.name, enabled: true) + let wrapper = ServiceWrapper( + formula: $0, + service: HomebrewService.dummy(named: $0.name, enabled: true) + ) return wrapper } } @@ -42,35 +43,9 @@ class FakeServicesManager: ServicesManager { override func reloadServicesStatus() async { await delay(seconds: 0.3) - - for formula in self.serviceWrappers { - formula.service?.running = true - formula.isBusy = false - } - - broadcastServicesUpdated() } override func toggleService(named: String) async { - guard let wrapper = self[named] else { - return Log.err("The wrapper for service \(named) does not exist.") - } - - wrapper.isBusy = true - Task { @MainActor in - wrapper.objectWillChange.send() - } - - guard let service = wrapper.service else { - return Log.err("The actual service for wrapper \(named) is nil.") - } - - await delay(seconds: 2) - service.running = !service.running - wrapper.isBusy = false - Task { @MainActor in - wrapper.objectWillChange.send() - self.objectWillChange.send() - } + await delay(seconds: 0.3) } } diff --git a/phpmon/Domain/App/Services/ServiceWrapper.swift b/phpmon/Domain/App/Services/ServiceWrapper.swift index 6ea9b3f..deafaf4 100644 --- a/phpmon/Domain/App/Services/ServiceWrapper.swift +++ b/phpmon/Domain/App/Services/ServiceWrapper.swift @@ -14,7 +14,6 @@ import Foundation public enum ServiceStatus: String { case active case inactive - case loading case missing } @@ -22,40 +21,28 @@ public enum ServiceStatus: String { Service wrapper, that contains the Homebrew JSON output (if determined) and the formula. This helps the app determine whether a service should run as an administrator or not. */ -public class ServiceWrapper: ObservableObject, Identifiable, Hashable { +public struct ServiceWrapper: Hashable { var formula: HomebrewFormula - var service: HomebrewService? - - var isBusy: Bool = false + var status: ServiceStatus = .missing public var name: String { return formula.name } - public var status: ServiceStatus { - if isBusy { - return .loading - } - - guard let service = self.service else { - return .missing - } - - return service.running ? .active : .inactive - } - - init(formula: HomebrewFormula) { + init(formula: HomebrewFormula, service: HomebrewService? = nil) { self.formula = formula - self.isBusy = true + + if service != nil { + self.status = service!.running ? .active : .inactive + } } public static func == (lhs: ServiceWrapper, rhs: ServiceWrapper) -> Bool { - return lhs.service == rhs.service - && lhs.formula == rhs.formula + return lhs.hashValue == rhs.hashValue } public func hash(into hasher: inout Hasher) { hasher.combine(formula) - hasher.combine(service) + hasher.combine(status) } } diff --git a/phpmon/Domain/App/Services/ServicesManager.swift b/phpmon/Domain/App/Services/ServicesManager.swift index be7c746..c5f6f60 100644 --- a/phpmon/Domain/App/Services/ServicesManager.swift +++ b/phpmon/Domain/App/Services/ServicesManager.swift @@ -18,7 +18,7 @@ class ServicesManager: ObservableObject { public static func useFake() { ServicesManager.shared = FakeServicesManager.init( formulae: ["php", "nginx", "dnsmasq", "mysql"], - status: .loading + status: .active ) } @@ -39,9 +39,6 @@ class ServicesManager: ObservableObject { let statuses = self.serviceWrappers[0...2].map { $0.status } - if statuses.contains(.loading) { - return "Determining Valet status..." - } if statuses.contains(.missing) { return "A key service is not installed." } @@ -58,9 +55,6 @@ class ServicesManager: ObservableObject { } let statuses = self.serviceWrappers[0...2].map { $0.status } - if statuses.contains(.loading) { - return .orange - } if statuses.contains(.missing) { return .red } @@ -92,10 +86,6 @@ class ServicesManager: ObservableObject { */ public func broadcastServicesUpdated() { Task { @MainActor in - self.serviceWrappers.forEach { wrapper in - wrapper.objectWillChange.send() - } - self.objectWillChange.send() } } diff --git a/phpmon/Domain/App/Services/ValetServicesManager.swift b/phpmon/Domain/App/Services/ValetServicesManager.swift index baca249..3fad3bf 100644 --- a/phpmon/Domain/App/Services/ValetServicesManager.swift +++ b/phpmon/Domain/App/Services/ValetServicesManager.swift @@ -16,6 +16,11 @@ class ValetServicesManager: ServicesManager { Task { await self.reloadServicesStatus() } } + /** + The last known state of all Homebrew services. + */ + var homebrewServices: [HomebrewService] = [] + /** This method allows us to reload the Homebrew services, but we run this command twice (once for user services, and once for root services). Please note that @@ -53,20 +58,23 @@ class ValetServicesManager: ServicesManager { .filter({ return userServiceNames.contains($0.name) }) } - // Ensure both commands complete (but run concurrently) - for await services in group { - // For both groups (user and root services), set the service to the wrapper object - for service in services { - self[service.name]?.service = service - } + self.homebrewServices = [] - for wrapper in serviceWrappers { - wrapper.isBusy = false - } + for await services in group { + homebrewServices.append(contentsOf: services) } - // Broadcast that all services have been updated - self.broadcastServicesUpdated() + Task { @MainActor in + // Ensure both commands complete (but run concurrently) + serviceWrappers = formulae.map { formula in + ServiceWrapper(formula: formula, service: homebrewServices.first(where: { service in + service.name == formula.name + })) + } + + // Broadcast that all services have been updated + self.broadcastServicesUpdated() + } }) } @@ -75,12 +83,8 @@ class ValetServicesManager: ServicesManager { return Log.err("The wrapper for '\(named)' is missing.") } - if wrapper.service == nil { - return Log.err("The Homebrew service for \(named) is missing.") - } - // Prepare the appropriate command to stop or start a service - let action = wrapper.service!.running ? "stop" : "start" + let action = wrapper.status == .active ? "stop" : "start" let command = "services \(action) \(wrapper.formula.name)" // Run the command diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 566dffc..0a547a6 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -57,7 +57,7 @@ struct ServicesView: View { VStack(alignment: .leading, spacing: CGFloat(self.rowSpacing)) { ForEach(manager.serviceWrappers.chunked(by: perRow), id: \.self) { chunk in HStack { - ForEach(chunk) { service in + ForEach(chunk, id: \.self) { service in ServiceView(service: service) .frame(minWidth: 70) } @@ -86,7 +86,8 @@ struct ServicesView: View { } struct ServiceView: View { - @ObservedObject var service: ServiceWrapper + var service: ServiceWrapper + @State var isBusy: Bool = false var body: some View { VStack(alignment: .center, spacing: 0) { @@ -95,67 +96,58 @@ struct ServiceView: View { .frame(minWidth: 70, alignment: .center) .padding(.top, 4) .padding(.bottom, 2) - if service.status == .loading { + if isBusy { ProgressView() .scaleEffect(x: 0.4, y: 0.4, anchor: .center) .frame(minWidth: 70, alignment: .center) .frame(width: 25, height: 25) - } - if service.status == .missing { - Button { - Task { @MainActor in - BetterAlert().withInformation( - title: "alert.warnings.service_missing.title".localized, - subtitle: "alert.warnings.service_missing.subtitle".localized, - description: "alert.warnings.service_missing.description".localized - ) - .withPrimary(text: "OK") - .show() + } else { + if service.status == .missing { + Button { + Task { @MainActor in + BetterAlert().withInformation( + title: "alert.warnings.service_missing.title".localized, + subtitle: "alert.warnings.service_missing.subtitle".localized, + description: "alert.warnings.service_missing.description".localized + ) + .withPrimary(text: "OK") + .show() + } + } label: { + Text("?") } - } label: { - Text("?") + .focusable(false) + // .buttonStyle(BlueButton()) + .frame(minWidth: 70, alignment: .center) + } + if service.status == .active || service.status == .inactive { + Button { + Task { + isBusy = true + await ServicesManager.shared.toggleService(named: service.name) + isBusy = false + } + } label: { + Image( + systemName: service.status == .active ? "checkmark" : "xmark" + ) + .resizable() + .frame(width: 12.0, height: 12.0) + .foregroundColor( + service.status == .active ? Color("IconColorGreen") : Color("IconColorRed") + ) + }.frame(width: 25, height: 25) } - .focusable(false) - // .buttonStyle(BlueButton()) - .frame(minWidth: 70, alignment: .center) - } - if service.status == .active || service.status == .inactive { - Button { - Task { await ServicesManager.shared.toggleService(named: service.name) } - } label: { - Image( - systemName: service.status == .active ? "checkmark" : "xmark" - ) - .resizable() - .frame(width: 12.0, height: 12.0) - .foregroundColor( - service.status == .active ? Color("IconColorGreen") : Color("IconColorRed") - ) - }.frame(width: 25, height: 25) } }.frame(minWidth: 70) } } -public struct BlueButton: ButtonStyle { - public func makeBody(configuration: Configuration) -> some View { - configuration.label - .frame(width: 25, height: 25) - .background(configuration.isPressed - ? Color(red: 0, green: 0.5, blue: 0.9) - : Color(red: 0, green: 0, blue: 0.5) - ) - .foregroundColor(.white) - .clipShape(Capsule()) - .contentShape(Rectangle()) - } -} - struct ServicesView_Previews: PreviewProvider { static var previews: some View { ServicesView(manager: FakeServicesManager( formulae: ["php", "nginx", "dnsmasq"], - status: .loading + status: .active ), perRow: 4) .frame(width: 330.0) .previewDisplayName("Loading") From 0beda388ebe8a6bc7d5a43e617466af88db08251 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 6 Jan 2023 21:33:51 +0100 Subject: [PATCH 138/181] =?UTF-8?q?=F0=9F=91=8C=20Cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 20 ++++++------- .../Common/PHP/Homebrew/HomebrewService.swift | 29 +++++-------------- .../App/Services/FakeServicesManager.swift | 6 ++-- .../{ServiceWrapper.swift => Service.swift} | 8 ++--- .../Domain/App/Services/ServicesManager.swift | 20 +++++++------ .../App/Services/ValetServicesManager.swift | 19 ++++++++---- phpmon/Domain/Menu/MainMenu+Startup.swift | 2 +- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 11 +++---- 8 files changed, 55 insertions(+), 60 deletions(-) rename phpmon/Domain/App/Services/{ServiceWrapper.swift => Service.swift} (74%) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index d0f44a2..5f43860 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -143,10 +143,10 @@ C4570C3B28FC355300D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C4570C3C28FC355400D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C459B4BD27F6093700E9B4B4 /* nginx-proxy.test in Resources */ = {isa = PBXBuildFile; fileRef = C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */; }; - C45B9149295607F400F4EC78 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* ServiceWrapper.swift */; }; - C45B914A295607F400F4EC78 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* ServiceWrapper.swift */; }; - C45B914B295607F400F4EC78 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* ServiceWrapper.swift */; }; - C45B914C295607F400F4EC78 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* ServiceWrapper.swift */; }; + C45B9149295607F400F4EC78 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* Service.swift */; }; + C45B914A295607F400F4EC78 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* Service.swift */; }; + C45B914B295607F400F4EC78 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* Service.swift */; }; + C45B914C295607F400F4EC78 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* Service.swift */; }; C45B914E295608E300F4EC78 /* ValetServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B914D295608E300F4EC78 /* ValetServicesManager.swift */; }; C45B914F295608E300F4EC78 /* ValetServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B914D295608E300F4EC78 /* ValetServicesManager.swift */; }; C45B9150295608E300F4EC78 /* ValetServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B914D295608E300F4EC78 /* ValetServicesManager.swift */; }; @@ -802,7 +802,7 @@ C44F868D2835BD8D005C353A /* phpmon-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "phpmon-config.json"; sourceTree = ""; }; C450C8C528C919EC002A2B4B /* PreferenceName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceName.swift; sourceTree = ""; }; C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-proxy.test"; sourceTree = ""; }; - C45B9148295607F400F4EC78 /* ServiceWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceWrapper.swift; sourceTree = ""; }; + C45B9148295607F400F4EC78 /* Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Service.swift; sourceTree = ""; }; C45B914D295608E300F4EC78 /* ValetServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetServicesManager.swift; sourceTree = ""; }; C45B91522956123A00F4EC78 /* FakeServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeServicesManager.swift; sourceTree = ""; }; C45E2A76291992DA005C7CFD /* FeatureTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureTestCase.swift; sourceTree = ""; }; @@ -1298,7 +1298,7 @@ C45B9147295607E200F4EC78 /* Services */ = { isa = PBXGroup; children = ( - C45B9148295607F400F4EC78 /* ServiceWrapper.swift */, + C45B9148295607F400F4EC78 /* Service.swift */, C45E76132854A65300B4FE0C /* ServicesManager.swift */, C45B914D295608E300F4EC78 /* ValetServicesManager.swift */, C45B91522956123A00F4EC78 /* FakeServicesManager.swift */, @@ -1978,7 +1978,7 @@ 5420395926135DC100FB00FA /* PrefsVC.swift in Sources */, C4C8900328F0E28800CE5E97 /* FileSystemProtocol.swift in Sources */, C43603A0275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */, - C45B9149295607F400F4EC78 /* ServiceWrapper.swift in Sources */, + C45B9149295607F400F4EC78 /* Service.swift in Sources */, 5489625828312FAD004F647A /* CreatedFromFile.swift in Sources */, C4068CA727B07A1300544CD5 /* SelectPreferenceView.swift in Sources */, C45B91532956123A00F4EC78 /* FakeServicesManager.swift in Sources */, @@ -2256,7 +2256,7 @@ C471E7F228F9BAC70021E251 /* PhpEnv.swift in Sources */, C471E7E628F9BAC20021E251 /* Process.swift in Sources */, C471E81928F9BAE80021E251 /* NSMenuItemExtension.swift in Sources */, - C45B914B295607F400F4EC78 /* ServiceWrapper.swift in Sources */, + C45B914B295607F400F4EC78 /* Service.swift in Sources */, C471E7D928F9BA8F0021E251 /* TestableShell.swift in Sources */, C471E81428F9BAE80021E251 /* NSWindowExtension.swift in Sources */, C471E7D328F9BA8F0021E251 /* ActiveShell.swift in Sources */, @@ -2309,7 +2309,7 @@ C471E8A528F9BB8F0021E251 /* AppDelegate+InterApp.swift in Sources */, C471E8A628F9BB8F0021E251 /* App.swift in Sources */, C471E8A728F9BB8F0021E251 /* App+ActivationPolicy.swift in Sources */, - C45B914C295607F400F4EC78 /* ServiceWrapper.swift in Sources */, + C45B914C295607F400F4EC78 /* Service.swift in Sources */, C471E8A828F9BB8F0021E251 /* App+GlobalHotkey.swift in Sources */, C471E8A928F9BB8F0021E251 /* InterAppHandler.swift in Sources */, C471E8AA28F9BB8F0021E251 /* Startup.swift in Sources */, @@ -2501,7 +2501,7 @@ C4AF9F7B2754499000D44ED0 /* Valet.swift in Sources */, C4C1019C27C65C6F001FACC2 /* Process.swift in Sources */, C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */, - C45B914A295607F400F4EC78 /* ServiceWrapper.swift in Sources */, + C45B914A295607F400F4EC78 /* Service.swift in Sources */, C4C0E8E327F88B13002D32A9 /* ValetDomainScanner.swift in Sources */, C4CCBA6D275C567B008C7055 /* PMWindowController.swift in Sources */, C4B5635F276AB09000F12CCB /* VersionExtractor.swift in Sources */, diff --git a/phpmon/Common/PHP/Homebrew/HomebrewService.swift b/phpmon/Common/PHP/Homebrew/HomebrewService.swift index da287d5..028dfe0 100644 --- a/phpmon/Common/PHP/Homebrew/HomebrewService.swift +++ b/phpmon/Common/PHP/Homebrew/HomebrewService.swift @@ -8,16 +8,16 @@ import Foundation -class HomebrewService: Decodable, Equatable, Hashable { +final class HomebrewService: Sendable, Decodable { let name: String let service_name: String - var running: Bool - var loaded: Bool - var pid: Int? - var user: String? - var status: String? - var log_path: String? - var error_log_path: String? + let running: Bool + let loaded: Bool + let pid: Int? + let user: String? + let status: String? + let log_path: String? + let error_log_path: String? init( name: String, @@ -57,17 +57,4 @@ class HomebrewService: Decodable, Equatable, Hashable { error_log_path: nil ) } - - public func hash(into hasher: inout Hasher) { - hasher.combine(name) - hasher.combine(service_name) - hasher.combine(pid) - hasher.combine(status) - hasher.combine(running) - hasher.combine(user) - } - - static func == (lhs: HomebrewService, rhs: HomebrewService) -> Bool { - return lhs.hashValue == rhs.hashValue - } } diff --git a/phpmon/Domain/App/Services/FakeServicesManager.swift b/phpmon/Domain/App/Services/FakeServicesManager.swift index 4cb733f..486ce8c 100644 --- a/phpmon/Domain/App/Services/FakeServicesManager.swift +++ b/phpmon/Domain/App/Services/FakeServicesManager.swift @@ -26,13 +26,15 @@ class FakeServicesManager: ServicesManager { self.fixedFormulae = formulae self.fixedStatus = status - self.serviceWrappers = self.formulae.map { - let wrapper = ServiceWrapper( + self.services = self.formulae.map { + let wrapper = Service( formula: $0, service: HomebrewService.dummy(named: $0.name, enabled: true) ) return wrapper } + + self.firstRunComplete = true } override var formulae: [HomebrewFormula] { diff --git a/phpmon/Domain/App/Services/ServiceWrapper.swift b/phpmon/Domain/App/Services/Service.swift similarity index 74% rename from phpmon/Domain/App/Services/ServiceWrapper.swift rename to phpmon/Domain/App/Services/Service.swift index deafaf4..cf9b021 100644 --- a/phpmon/Domain/App/Services/ServiceWrapper.swift +++ b/phpmon/Domain/App/Services/Service.swift @@ -17,11 +17,7 @@ public enum ServiceStatus: String { case missing } -/** - Service wrapper, that contains the Homebrew JSON output (if determined) and the formula. - This helps the app determine whether a service should run as an administrator or not. - */ -public struct ServiceWrapper: Hashable { +public struct Service: Hashable { var formula: HomebrewFormula var status: ServiceStatus = .missing @@ -37,7 +33,7 @@ public struct ServiceWrapper: Hashable { } } - public static func == (lhs: ServiceWrapper, rhs: ServiceWrapper) -> Bool { + public static func == (lhs: Service, rhs: Service) -> Bool { return lhs.hashValue == rhs.hashValue } diff --git a/phpmon/Domain/App/Services/ServicesManager.swift b/phpmon/Domain/App/Services/ServicesManager.swift index c5f6f60..ae20f7c 100644 --- a/phpmon/Domain/App/Services/ServicesManager.swift +++ b/phpmon/Domain/App/Services/ServicesManager.swift @@ -13,7 +13,9 @@ class ServicesManager: ObservableObject { @ObservedObject static var shared: ServicesManager = ValetServicesManager() - @Published var serviceWrappers = [ServiceWrapper]() + @Published var services = [Service]() + + @Published var firstRunComplete: Bool = false public static func useFake() { ServicesManager.shared = FakeServicesManager.init( @@ -26,18 +28,18 @@ class ServicesManager: ObservableObject { The order of services is important, so easy access is accomplished without much fanfare through subscripting. */ - subscript(name: String) -> ServiceWrapper? { - return self.serviceWrappers.first { wrapper in + subscript(name: String) -> Service? { + return self.services.first { wrapper in wrapper.name == name } } public var statusMessage: String { - if self.serviceWrappers.isEmpty { + if self.services.isEmpty { return "Loading..." } - let statuses = self.serviceWrappers[0...2].map { $0.status } + let statuses = self.services[0...2].map { $0.status } if statuses.contains(.missing) { return "A key service is not installed." @@ -50,11 +52,11 @@ class ServicesManager: ObservableObject { } public var statusColor: Color { - if self.serviceWrappers.isEmpty { + if self.services.isEmpty { return .yellow } - let statuses = self.serviceWrappers[0...2].map { $0.status } + let statuses = self.services[0...2].map { $0.status } if statuses.contains(.missing) { return .red } @@ -109,8 +111,8 @@ class ServicesManager: ObservableObject { init() { Log.info("The services manager will determine which Valet services exist on this system.") - serviceWrappers = formulae.map { - ServiceWrapper(formula: $0) + services = formulae.map { + Service(formula: $0) } } } diff --git a/phpmon/Domain/App/Services/ValetServicesManager.swift b/phpmon/Domain/App/Services/ValetServicesManager.swift index 3fad3bf..01a7085 100644 --- a/phpmon/Domain/App/Services/ValetServicesManager.swift +++ b/phpmon/Domain/App/Services/ValetServicesManager.swift @@ -13,7 +13,10 @@ class ValetServicesManager: ServicesManager { super.init() // Load the initial services state - Task { await self.reloadServicesStatus() } + Task { + await self.reloadServicesStatus() + firstRunComplete = true + } } /** @@ -58,18 +61,22 @@ class ValetServicesManager: ServicesManager { .filter({ return userServiceNames.contains($0.name) }) } + // Ensure that Homebrew services' output is stored self.homebrewServices = [] - for await services in group { homebrewServices.append(contentsOf: services) } + // Dispatch the update of the new service wrappers Task { @MainActor in // Ensure both commands complete (but run concurrently) - serviceWrappers = formulae.map { formula in - ServiceWrapper(formula: formula, service: homebrewServices.first(where: { service in - service.name == formula.name - })) + services = formulae.map { formula in + Service( + formula: formula, + service: homebrewServices.first(where: { service in + service.name == formula.name + }) + ) } // Broadcast that all services have been updated diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 4f54592..487d259 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -96,7 +96,7 @@ extension MainMenu { Valet.notifyAboutUnsupportedTLD() // Find out which services are active - Log.info("The services manager knows about \(ServicesManager.shared.serviceWrappers.count) services.") + Log.info("The services manager knows about \(ServicesManager.shared.services.count) services.") // Start the background refresh timer startSharedTimer() diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 0a547a6..52170a4 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -44,7 +44,7 @@ struct ServicesView: View { init(manager: ServicesManager, perRow: Int) { self.manager = manager self.perRow = perRow - self.rowCount = manager.serviceWrappers.chunked(by: perRow).count + self.rowCount = manager.services.chunked(by: perRow).count self.height = CGFloat( (rowHeight * rowCount) + ((rowCount - 1) * rowSpacing) @@ -55,7 +55,7 @@ struct ServicesView: View { var body: some View { VStack(spacing: 0) { VStack(alignment: .leading, spacing: CGFloat(self.rowSpacing)) { - ForEach(manager.serviceWrappers.chunked(by: perRow), id: \.self) { chunk in + ForEach(manager.services.chunked(by: perRow), id: \.self) { chunk in HStack { ForEach(chunk, id: \.self) { service in ServiceView(service: service) @@ -86,7 +86,7 @@ struct ServicesView: View { } struct ServiceView: View { - var service: ServiceWrapper + var service: Service @State var isBusy: Bool = false var body: some View { @@ -117,7 +117,6 @@ struct ServiceView: View { Text("?") } .focusable(false) - // .buttonStyle(BlueButton()) .frame(minWidth: 70, alignment: .center) } if service.status == .active || service.status == .inactive { @@ -134,7 +133,9 @@ struct ServiceView: View { .resizable() .frame(width: 12.0, height: 12.0) .foregroundColor( - service.status == .active ? Color("IconColorGreen") : Color("IconColorRed") + service.status == .active + ? Color("IconColorGreen") + : Color("IconColorRed") ) }.frame(width: 25, height: 25) } From 422a7738bd997bf8c7c390aa0a9134743f7af43f Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 6 Jan 2023 21:50:34 +0100 Subject: [PATCH 139/181] =?UTF-8?q?=F0=9F=91=8C=20Add=20fake=20services=20?= =?UTF-8?q?system?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcschemes/PHP Monitor DEV.xcscheme | 2 +- .../App/Services/FakeServicesManager.swift | 39 ++++++++++++++++--- phpmon/Domain/App/Services/Service.swift | 22 ++++++----- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index 8642ae0..b0a4d86 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -91,7 +91,7 @@ + isEnabled = "YES"> becomes inactive) + : service.status == .active // service remains unmodified if it's not the named one we change + + return Service( + formula: service.formula, + service: HomebrewService.dummy( + named: service.name, + enabled: newServiceEnabled + ) + ) + }) + + Task { @MainActor in + self.services = services + } } } diff --git a/phpmon/Domain/App/Services/Service.swift b/phpmon/Domain/App/Services/Service.swift index cf9b021..8fca526 100644 --- a/phpmon/Domain/App/Services/Service.swift +++ b/phpmon/Domain/App/Services/Service.swift @@ -8,18 +8,10 @@ import Foundation -/** - Whether a given service is active, inactive or PHP Monitor is still busy determining the status. - */ -public enum ServiceStatus: String { - case active - case inactive - case missing -} - +/** Service linked to a Homebrew formula and whether it is currently (in)active or missing. */ public struct Service: Hashable { var formula: HomebrewFormula - var status: ServiceStatus = .missing + var status: Status = .missing public var name: String { return formula.name @@ -33,6 +25,8 @@ public struct Service: Hashable { } } + // MARK: - Protocols + public static func == (lhs: Service, rhs: Service) -> Bool { return lhs.hashValue == rhs.hashValue } @@ -41,4 +35,12 @@ public struct Service: Hashable { hasher.combine(formula) hasher.combine(status) } + + // MARK: - Status + + public enum Status: String { + case active + case inactive + case missing + } } From 61ecefb6e74865ec705e7c994b19b4f6d81d0d8a Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 7 Jan 2023 12:53:07 +0100 Subject: [PATCH 140/181] =?UTF-8?q?=F0=9F=91=8C=20Use=20filesystem=20abstr?= =?UTF-8?q?action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Core/Actions.swift | 4 +--- phpmon/Common/PHP/PhpConfigurationFile.swift | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/phpmon/Common/Core/Actions.swift b/phpmon/Common/Core/Actions.swift index 1db1b7c..6655222 100644 --- a/phpmon/Common/Core/Actions.swift +++ b/phpmon/Common/Core/Actions.swift @@ -95,9 +95,7 @@ class Actions { // MARK: - Other Actions public static func createTempPhpInfoFile() async -> URL { - // Write a file called `phpmon_phpinfo.php` to /tmp - // TODO: Use FileSystem abstraction - try! " /tmp/phpmon_phpinfo.html") diff --git a/phpmon/Common/PHP/PhpConfigurationFile.swift b/phpmon/Common/PHP/PhpConfigurationFile.swift index f88869d..8f789ea 100644 --- a/phpmon/Common/PHP/PhpConfigurationFile.swift +++ b/phpmon/Common/PHP/PhpConfigurationFile.swift @@ -35,8 +35,7 @@ class PhpConfigurationFile: CreatedFromFile { let path = filePath.replacingOccurrences(of: "~", with: Paths.homePath) do { - // TODO: Use FileSystem abstraction - let fileContents = try String(contentsOfFile: path) + let fileContents = try FileSystem.getStringFromFile(path) return Self.init(path: path, contents: fileContents) } catch { Log.warn("Could not read the PHP configuration file at: `\(filePath)`") From 71e1ed1b93752182b5473cab2a16e865d1af5fe8 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 7 Jan 2023 12:53:27 +0100 Subject: [PATCH 141/181] =?UTF-8?q?=F0=9F=91=8C=20Async=20switcher=20(Swif?= =?UTF-8?q?t=20concurrency)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Core/Actions.swift | 14 ++--- phpmon/Common/PHP/ActivePhpInstallation.swift | 4 +- phpmon/Common/PHP/PhpExtension.swift | 2 +- .../PHP/Switcher/InternalSwitcher.swift | 56 +++++++++---------- phpmon/Common/PHP/Switcher/PhpSwitcher.swift | 2 +- .../App/Services/FakeServicesManager.swift | 4 +- .../Domain/App/Services/ServicesManager.swift | 2 +- .../App/Services/ValetServicesManager.swift | 5 +- phpmon/Domain/Integrations/Valet/Valet.swift | 15 ++--- phpmon/Domain/Menu/MainMenu+Actions.swift | 35 ++++-------- phpmon/Domain/Menu/MainMenu+FixMyValet.swift | 14 ++--- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 4 +- 12 files changed, 72 insertions(+), 85 deletions(-) diff --git a/phpmon/Common/Core/Actions.swift b/phpmon/Common/Core/Actions.swift index 6655222..28ed5c3 100644 --- a/phpmon/Common/Core/Actions.swift +++ b/phpmon/Common/Core/Actions.swift @@ -117,14 +117,10 @@ class Actions { If this does not solve the issue, the user may need to install additional extensions and/or run `composer global update`. */ - public static func fixMyValet(completed: @escaping () -> Void) { - InternalSwitcher().performSwitch(to: PhpEnv.brewPhpAlias, completion: { - Task { // Restart all services asynchronously and fire callback upon completion - await brew("services restart \(Homebrew.Formulae.dnsmasq)", sudo: Homebrew.Formulae.dnsmasq.elevated) - await brew("services restart \(Homebrew.Formulae.php)", sudo: Homebrew.Formulae.php.elevated) - await brew("services restart \(Homebrew.Formulae.nginx)", sudo: Homebrew.Formulae.nginx.elevated) - completed() - } - }) + public static func fixMyValet() async { + await InternalSwitcher().performSwitch(to: PhpEnv.brewPhpAlias) + await brew("services restart \(Homebrew.Formulae.dnsmasq)", sudo: Homebrew.Formulae.dnsmasq.elevated) + await brew("services restart \(Homebrew.Formulae.php)", sudo: Homebrew.Formulae.php.elevated) + await brew("services restart \(Homebrew.Formulae.nginx)", sudo: Homebrew.Formulae.nginx.elevated) } } diff --git a/phpmon/Common/PHP/ActivePhpInstallation.swift b/phpmon/Common/PHP/ActivePhpInstallation.swift index 9435f59..dd38a7f 100644 --- a/phpmon/Common/PHP/ActivePhpInstallation.swift +++ b/phpmon/Common/PHP/ActivePhpInstallation.swift @@ -42,8 +42,8 @@ class ActivePhpInstallation { do { try determineVersion() } catch { - // TODO: Throw up an alert if the PHP version cannot be parsed - fatalError("Could not determine or parse PHP version") + #warning("In future versions of PHP Monitor, this should not crash") + fatalError("Could not determine or parse PHP version; aborting") } // Initialize the list of ini files that are loaded diff --git a/phpmon/Common/PHP/PhpExtension.swift b/phpmon/Common/PHP/PhpExtension.swift index bd7b71e..91b6079 100644 --- a/phpmon/Common/PHP/PhpExtension.swift +++ b/phpmon/Common/PHP/PhpExtension.swift @@ -88,7 +88,7 @@ class PhpExtension { if !isRunningTests { // When running unit tests, the MainMenu will not be available - // TODO: Fix this dependency issue, set up a notification mechanism + // TODO: Investigate an alternate approach w/ notification or publishable Task { @MainActor in MainMenu.shared.rebuild() } diff --git a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift index f6d9374..76669a9 100644 --- a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift +++ b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift @@ -15,45 +15,43 @@ class InternalSwitcher: PhpSwitcher { - unlinking the current version - stopping the active services - linking the new desired version - + Please note that depending on which version is installed, the version that is switched to may or may not be identical to `php` (without @version). - - TODO: Use `async` and use structured concurrency: https://www.hackingwithswift.com/swift/5.5/structured-concurrency */ - func performSwitch(to version: String, completion: @escaping () -> Void) { + func performSwitch(to version: String) async { Log.info("Switching to \(version), unlinking all versions...") let versions = getVersionsToBeHandled(version) - let group = DispatchGroup() - PhpEnv.shared.availablePhpVersions.forEach { (available) in - group.enter() - - Task { - await self.disableDefaultPhpFpmPool(available) - await self.stopPhpVersion(available) - group.leave() - } - } - - group.notify(queue: .global(qos: .userInitiated)) { - Task { - Log.info("All versions have been unlinked!") - Log.info("Linking the new version!") - - for formula in versions { - await self.startPhpVersion(formula, primary: (version == formula)) + await withTaskGroup(of: String.self, body: { group in + for available in PhpEnv.shared.availablePhpVersions { + group.addTask { + await self.disableDefaultPhpFpmPool(available) + await self.stopPhpVersion(available) + return available } - - Log.info("Restarting nginx, just to be sure!") - await brew("services restart nginx", sudo: true) - - Log.info("The new version(s) have been linked!") - completion() } - } + + var unlinked: [String] = [] + for await version in group { + unlinked.append(version) + } + + Log.info("These versions have been unlinked: \(unlinked)") + Log.info("Linking the new version \(version)!") + + for formula in versions { + Log.info("Will start PHP \(version)... (primary: \(version == formula))") + await self.startPhpVersion(formula, primary: (version == formula)) + } + + Log.info("Restarting nginx, just to be sure!") + await brew("services restart nginx", sudo: true) + + Log.info("The new version(s) have been linked!") + }) } func getVersionsToBeHandled(_ primary: String) -> Set { diff --git a/phpmon/Common/PHP/Switcher/PhpSwitcher.swift b/phpmon/Common/PHP/Switcher/PhpSwitcher.swift index d2e2bb1..c706fe7 100644 --- a/phpmon/Common/PHP/Switcher/PhpSwitcher.swift +++ b/phpmon/Common/PHP/Switcher/PhpSwitcher.swift @@ -18,6 +18,6 @@ protocol PhpSwitcherDelegate: AnyObject { protocol PhpSwitcher { - func performSwitch(to version: String, completion: @escaping () -> Void) + func performSwitch(to version: String) async } diff --git a/phpmon/Domain/App/Services/FakeServicesManager.swift b/phpmon/Domain/App/Services/FakeServicesManager.swift index 9c109f7..a1ec1ba 100644 --- a/phpmon/Domain/App/Services/FakeServicesManager.swift +++ b/phpmon/Domain/App/Services/FakeServicesManager.swift @@ -29,7 +29,9 @@ class FakeServicesManager: ServicesManager { self.services = [] self.reapplyServices() - self.firstRunComplete = true + Task { @MainActor in + self.firstRunComplete = true + } } private func reapplyServices() { diff --git a/phpmon/Domain/App/Services/ServicesManager.swift b/phpmon/Domain/App/Services/ServicesManager.swift index ae20f7c..3d5bdda 100644 --- a/phpmon/Domain/App/Services/ServicesManager.swift +++ b/phpmon/Domain/App/Services/ServicesManager.swift @@ -15,7 +15,7 @@ class ServicesManager: ObservableObject { @Published var services = [Service]() - @Published var firstRunComplete: Bool = false + @Published @MainActor var firstRunComplete: Bool = false public static func useFake() { ServicesManager.shared = FakeServicesManager.init( diff --git a/phpmon/Domain/App/Services/ValetServicesManager.swift b/phpmon/Domain/App/Services/ValetServicesManager.swift index 01a7085..445c4c0 100644 --- a/phpmon/Domain/App/Services/ValetServicesManager.swift +++ b/phpmon/Domain/App/Services/ValetServicesManager.swift @@ -15,7 +15,10 @@ class ValetServicesManager: ServicesManager { // Load the initial services state Task { await self.reloadServicesStatus() - firstRunComplete = true + + Task { @MainActor in + firstRunComplete = true + } } } diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index f3689df..2deffca 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -142,17 +142,14 @@ class Valet { in use. This allows PHP Monitor to do different things when Valet 3.0 is enabled. */ public func evaluateFeatureSupport() { - let isVersion2 = version.isSameMajorVersionAs(try! VersionNumber.parse("2.0")) - let isVersion3 = version.isSameMajorVersionAs(try! VersionNumber.parse("3.0")) - let isVersion4 = version.isSameMajorVersionAs(try! VersionNumber.parse("4.0")) - - if isVersion2 { + switch version.major { + case 2: Log.info("You are running Valet v2. Support for site isolation is disabled.") - } else if isVersion3 || isVersion4 { - Log.info("You are running Valet v3 or v4. Support for site isolation is available.") + case 3, 4: + Log.info("You are running Valet v\(version.major). Support for site isolation is available.") self.features.append(.isolatedSites) - } else { - // TODO: Show an alert and notify that some features might not work + default: + #warning("An alert should be presented here") Log.err("This version of Valet is not supported.") } } diff --git a/phpmon/Domain/Menu/MainMenu+Actions.swift b/phpmon/Domain/Menu/MainMenu+Actions.swift index 880e0e3..6cd6dda 100644 --- a/phpmon/Domain/Menu/MainMenu+Actions.swift +++ b/phpmon/Domain/Menu/MainMenu+Actions.swift @@ -240,14 +240,11 @@ extension MainMenu { Task(priority: .userInitiated) { [unowned self] in updatePhpVersionInStatusBar() rebuild() - PhpEnv.switcher.performSwitch( - to: version, - completion: { - PhpEnv.shared.currentInstall = ActivePhpInstallation() - App.shared.handlePhpConfigWatcher() - PhpEnv.shared.delegate?.switcherDidCompleteSwitch(to: version) - } - ) + await PhpEnv.switcher.performSwitch(to: version) + + PhpEnv.shared.currentInstall = ActivePhpInstallation() + App.shared.handlePhpConfigWatcher() + PhpEnv.shared.delegate?.switcherDidCompleteSwitch(to: version) } } @@ -259,8 +256,6 @@ extension MainMenu { await MainMenu.shared.switchToPhp("8.1") // thing to do after the switch ``` - Since this async function uses `withCheckedContinuation` - any code after will run only after the switcher is done. */ func switchToPhp(_ version: String) async { Task { @MainActor [self] in @@ -270,19 +265,13 @@ extension MainMenu { PhpEnv.shared.delegate?.switcherDidStartSwitching(to: version) } - return await withCheckedContinuation({ continuation in - updatePhpVersionInStatusBar() - rebuild() - PhpEnv.switcher.performSwitch( - to: version, - completion: { - PhpEnv.shared.currentInstall = ActivePhpInstallation() - App.shared.handlePhpConfigWatcher() - PhpEnv.shared.delegate?.switcherDidCompleteSwitch(to: version) - continuation.resume() - } - ) - }) + updatePhpVersionInStatusBar() + rebuild() + await PhpEnv.switcher.performSwitch(to: version) + + PhpEnv.shared.currentInstall = ActivePhpInstallation() + App.shared.handlePhpConfigWatcher() + PhpEnv.shared.delegate?.switcherDidCompleteSwitch(to: version) } } diff --git a/phpmon/Domain/Menu/MainMenu+FixMyValet.swift b/phpmon/Domain/Menu/MainMenu+FixMyValet.swift index e2c79e9..5255499 100644 --- a/phpmon/Domain/Menu/MainMenu+FixMyValet.swift +++ b/phpmon/Domain/Menu/MainMenu+FixMyValet.swift @@ -31,13 +31,13 @@ extension MainMenu { return } - Actions.fixMyValet { - Task { @MainActor in - if previousVersion == PhpEnv.brewPhpAlias { - self.presentAlertForSameVersion() - } else { - self.presentAlertForDifferentVersion(version: previousVersion) - } + Task { @MainActor in + await Actions.fixMyValet() + + if previousVersion == PhpEnv.brewPhpAlias { + self.presentAlertForSameVersion() + } else { + self.presentAlertForDifferentVersion(version: previousVersion) } } } diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 52170a4..15aac6a 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -137,7 +137,9 @@ struct ServiceView: View { ? Color("IconColorGreen") : Color("IconColorRed") ) - }.frame(width: 25, height: 25) + } + .focusable(false) + .frame(width: 25, height: 25) } } }.frame(minWidth: 70) From f153fee05c8e17a12ba97d1340485700a5e25fde Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 7 Jan 2023 12:57:45 +0100 Subject: [PATCH 142/181] =?UTF-8?q?=E2=9C=85=20Fix=20broken=20tests=20afte?= =?UTF-8?q?r=20test=20config=20using=208.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/ui/StartupTest.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ui/StartupTest.swift b/tests/ui/StartupTest.swift index 606aa05..9f99cd5 100644 --- a/tests/ui/StartupTest.swift +++ b/tests/ui/StartupTest.swift @@ -50,7 +50,7 @@ final class StartupTest: UITestCase { final func test_get_warning_about_missing_fpm_symlink() throws { var configuration = TestableConfigurations.working - configuration.filesystem["/opt/homebrew/etc/php/8.1/php-fpm.d/valet-fpm.conf"] = nil + configuration.filesystem["/opt/homebrew/etc/php/8.2/php-fpm.d/valet-fpm.conf"] = nil let app = XCPMApplication() app.withConfiguration(configuration) @@ -71,7 +71,7 @@ final class StartupTest: UITestCase { assertAllExist([ // "Switch to PHP 8.1 (php)" should be visible since it is aliased to `php` - app.menuItems["\("mi_php_switch".localized) 8.1 (php)"], + app.menuItems["\("mi_php_switch".localized) 8.2 (php)"], // We should see the about and quit items app.menuItems["mi_about".localized], app.menuItems["mi_quit".localized] From 27894e48847dd407565ba460db87760f752e0dd1 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 7 Jan 2023 13:03:27 +0100 Subject: [PATCH 143/181] =?UTF-8?q?=F0=9F=91=8C=20Handle=20TODOs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/Integrations/Valet/Valet.swift | 2 +- phpmon/Domain/SwiftUI/Warning/WarningListView.swift | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 2deffca..57bee4f 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -187,7 +187,7 @@ class Valet { // 3. Notify user if the version is too high if version.major > 4 { - // TODO: + // TODO: Notify user if the version is too high } } diff --git a/phpmon/Domain/SwiftUI/Warning/WarningListView.swift b/phpmon/Domain/SwiftUI/Warning/WarningListView.swift index 4f0380f..87a55a4 100644 --- a/phpmon/Domain/SwiftUI/Warning/WarningListView.swift +++ b/phpmon/Domain/SwiftUI/Warning/WarningListView.swift @@ -80,12 +80,10 @@ struct WarningListView_Previews: PreviewProvider { static var previews: some View { WarningListView(empty: true) .frame(width: 600, height: 480) + .previewDisplayName("Empty List") - /* - WarningListView() - // TODO: Figure out how the empty() only applies to this single instance - // .empty() + WarningListView(empty: false) .frame(width: 600, height: 480) - */ + .previewDisplayName("List With All Warnings") } } From d05f39efe77abebdaec08a55717bca2e7330eccb Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 7 Jan 2023 13:24:52 +0100 Subject: [PATCH 144/181] =?UTF-8?q?=F0=9F=91=8C=20Check=20if=20Valet=20ver?= =?UTF-8?q?sion=20is=20supported?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/PHP/ActivePhpInstallation.swift | 2 +- phpmon/Common/PHP/PHP Version/PhpEnv.swift | 2 +- phpmon/Domain/App/Startup.swift | 14 ++++++++++++++ phpmon/Domain/Integrations/Valet/Valet.swift | 1 - phpmon/Localizable.strings | 5 +++++ tests/ui/StartupTest.swift | 12 ++++++++++++ 6 files changed, 33 insertions(+), 3 deletions(-) diff --git a/phpmon/Common/PHP/ActivePhpInstallation.swift b/phpmon/Common/PHP/ActivePhpInstallation.swift index dd38a7f..bd15f09 100644 --- a/phpmon/Common/PHP/ActivePhpInstallation.swift +++ b/phpmon/Common/PHP/ActivePhpInstallation.swift @@ -42,7 +42,7 @@ class ActivePhpInstallation { do { try determineVersion() } catch { - #warning("In future versions of PHP Monitor, this should not crash") + // TODO: In future versions of PHP Monitor, this should not crash fatalError("Could not determine or parse PHP version; aborting") } diff --git a/phpmon/Common/PHP/PHP Version/PhpEnv.swift b/phpmon/Common/PHP/PHP Version/PhpEnv.swift index 06e1368..d3332c9 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnv.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnv.swift @@ -92,7 +92,7 @@ class PhpEnv { var versionsOnly = await extractPhpVersions( from: files.components(separatedBy: "\n"), - supported: Constants.ValetSupportedPhpVersionMatrix[Valet.shared.version.major]! + supported: Constants.ValetSupportedPhpVersionMatrix[Valet.shared.version.major] ?? [] ) // Make sure the aliased version is detected diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index 7bfbe0e..cdacbb0 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -250,6 +250,20 @@ class Startup { titleText: "startup.errors.valet_version_unknown.title".localized, subtitleText: "startup.errors.valet_version_unknown.subtitle".localized, descriptionText: "startup.errors.valet_version_unknown.desc".localized + ), + // ================================================================================= + // Ensure the Valet version is supported. + // ================================================================================= + EnvironmentCheck( + command: { + // We currently support Valet 2, 3 or 4. Any other version should get an alert. + return ![2, 3, 4].contains(Valet.shared.version.major) + }, + name: "valet version is supported", + titleText: "startup.errors.valet_version_not_supported.title".localized, + subtitleText: "startup.errors.valet_version_not_supported.subtitle".localized(Valet.shared.version.text), + descriptionText: "startup.errors.valet_version_not_supported.desc".localized ) + ] } diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 57bee4f..9b614ae 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -149,7 +149,6 @@ class Valet { Log.info("You are running Valet v\(version.major). Support for site isolation is available.") self.features.append(.isolatedSites) default: - #warning("An alert should be presented here") Log.err("This version of Valet is not supported.") } } diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 0f86088..23c9938 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -501,6 +501,11 @@ You can do this by running `composer global update` in your terminal. After that If you are seeing this message but are confused why this folder has gone missing, then you may want to investigate why it is gone—it shouldn't just disappear and it means your Valet installation is broken."; +// Valet version too new or old +"startup.errors.valet_version_not_supported.title" = "This version of Valet is not supported"; +"startup.errors.valet_version_not_supported.subtitle" = "You are running a version of Valet that is currently not supported (%@). PHP Monitor currently works with Valet v2, v3 and v4. In order to avoid causing issues on your system, PHP Monitor cannot start."; +"startup.errors.valet_version_not_supported.desc" = "You must install a version of Valet that is compatible with PHP Monitor, or you may need to upgrade to a newer version of PHP Monitor which may include compatibility for this version of Valet (consult the latest release notes for more info)."; + /// Brew & sudoers "startup.errors.sudoers_brew.title" = "Brew has not been added to sudoers.d"; "startup.errors.sudoers_brew.subtitle" = "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue."; diff --git a/tests/ui/StartupTest.swift b/tests/ui/StartupTest.swift index 9f99cd5..090f2c9 100644 --- a/tests/ui/StartupTest.swift +++ b/tests/ui/StartupTest.swift @@ -60,6 +60,18 @@ final class StartupTest: UITestCase { click(app.buttons["generic.ok".localized]) } + final func test_get_warning_about_unsupported_valet_version() throws { + var configuration = TestableConfigurations.working + configuration.shellOutput["valet --version"] = .instant("Laravel Valet 5.0") + + let app = XCPMApplication() + app.withConfiguration(configuration) + app.launch() + + assertExists(app.staticTexts["startup.errors.valet_version_not_supported.title".localized], 3.0) + click(app.buttons["generic.ok".localized]) + } + final func test_can_open_status_menu_item() throws { let app = XCPMApplication() app.withConfiguration(TestableConfigurations.working) From 6e7c0d827cf4775564ec732b2eb3e3ec6f5c5e15 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 7 Jan 2023 17:16:51 +0100 Subject: [PATCH 145/181] =?UTF-8?q?=F0=9F=91=8C=20Initial=20loading=20stat?= =?UTF-8?q?e=20for=20services=20manager?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/App/Services/ServicesManager.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phpmon/Domain/App/Services/ServicesManager.swift b/phpmon/Domain/App/Services/ServicesManager.swift index 3d5bdda..ebc6f74 100644 --- a/phpmon/Domain/App/Services/ServicesManager.swift +++ b/phpmon/Domain/App/Services/ServicesManager.swift @@ -15,7 +15,7 @@ class ServicesManager: ObservableObject { @Published var services = [Service]() - @Published @MainActor var firstRunComplete: Bool = false + @Published var firstRunComplete: Bool = false public static func useFake() { ServicesManager.shared = FakeServicesManager.init( @@ -35,7 +35,7 @@ class ServicesManager: ObservableObject { } public var statusMessage: String { - if self.services.isEmpty { + if self.services.isEmpty || !self.firstRunComplete { return "Loading..." } @@ -52,7 +52,7 @@ class ServicesManager: ObservableObject { } public var statusColor: Color { - if self.services.isEmpty { + if self.services.isEmpty || !self.firstRunComplete { return .yellow } From 0b3a83c1e4f652ee1462708ad6855a84d02b4b0a Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 7 Jan 2023 18:10:31 +0100 Subject: [PATCH 146/181] =?UTF-8?q?=F0=9F=91=8C=20Cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/PHP/PhpExtension.swift | 2 -- phpmon/Domain/Integrations/Valet/Valet.swift | 7 +------ phpmon/Domain/Preferences/CustomPrefs.swift | 17 +++++++++-------- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 7 ++++++- tests/unit/Parsers/HomebrewPackageTest.swift | 4 ++-- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/phpmon/Common/PHP/PhpExtension.swift b/phpmon/Common/PHP/PhpExtension.swift index 91b6079..d20330a 100644 --- a/phpmon/Common/PHP/PhpExtension.swift +++ b/phpmon/Common/PHP/PhpExtension.swift @@ -87,8 +87,6 @@ class PhpExtension { enabled.toggle() if !isRunningTests { - // When running unit tests, the MainMenu will not be available - // TODO: Investigate an alternate approach w/ notification or publishable Task { @MainActor in MainMenu.shared.rebuild() } diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 9b614ae..9d67ba7 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -162,7 +162,7 @@ class Valet { // 1. Evaluate feature support Valet.shared.evaluateFeatureSupport() - // 2. Notify user if the version is too old + // 2. Notify user if the version is too old (but major version is OK) if version.text.versionCompare(Constants.MinimumRecommendedValetVersion) == .orderedAscending { let version = version! let recommended = Constants.MinimumRecommendedValetVersion @@ -183,11 +183,6 @@ class Valet { Log.info("Valet version \(version.text) is recent enough, OK " + "(recommended: \(Constants.MinimumRecommendedValetVersion))") } - - // 3. Notify user if the version is too high - if version.major > 4 { - // TODO: Notify user if the version is too high - } } /** diff --git a/phpmon/Domain/Preferences/CustomPrefs.swift b/phpmon/Domain/Preferences/CustomPrefs.swift index d327c26..6ca241e 100644 --- a/phpmon/Domain/Preferences/CustomPrefs.swift +++ b/phpmon/Domain/Preferences/CustomPrefs.swift @@ -14,6 +14,14 @@ struct CustomPrefs: Decodable { let services: [String]? let environmentVariables: [String: String]? + var exportAsString: String { + return self.environmentVariables! + .map { (key, value) in + return "export \(key)=\(value)" + } + .joined(separator: "&&") + } + public func hasPresets() -> Bool { return self.presets != nil && !self.presets!.isEmpty } @@ -26,13 +34,6 @@ struct CustomPrefs: Decodable { return self.environmentVariables != nil && !self.environmentVariables!.keys.isEmpty } - // TODO: Rework this - public func getEnvironmentVariables() -> String { - return self.environmentVariables!.map { (key, value) in - return "export \(key)=\(value)" - }.joined(separator: "&&") - } - private enum CodingKeys: String, CodingKey { case scanApps = "scan_apps" case presets = "presets" @@ -88,7 +89,7 @@ extension Preferences { if customPreferences.hasEnvironmentVariables() { Log.info("Configuring the additional exports...") if let shell = Shell as? RealShell { - shell.exports = customPreferences.getEnvironmentVariables() + shell.exports = customPreferences.exportAsString } } } catch { diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 15aac6a..8a8fa2f 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -77,6 +77,11 @@ struct ServicesView: View { .foregroundColor(self.manager.statusColor) Text(self.manager.statusMessage) .font(.system(size: 12)) + Button { + + } label: { + Text("Learn more").font(.system(size: 12)) + } } } .frame(height: CGFloat(self.statusHeight)) @@ -152,7 +157,7 @@ struct ServicesView_Previews: PreviewProvider { formulae: ["php", "nginx", "dnsmasq"], status: .active ), perRow: 4) - .frame(width: 330.0) + .frame(width: 330.0, height: 150) .previewDisplayName("Loading") ServicesView(manager: FakeServicesManager( diff --git a/tests/unit/Parsers/HomebrewPackageTest.swift b/tests/unit/Parsers/HomebrewPackageTest.swift index 81c9718..2ca09f5 100644 --- a/tests/unit/Parsers/HomebrewPackageTest.swift +++ b/tests/unit/Parsers/HomebrewPackageTest.swift @@ -47,9 +47,9 @@ class HomebrewPackageTest: XCTestCase { XCTAssertEqual(services.first?.service_name, "homebrew.mxcl.dnsmasq") } + /* // - MARK: LIVE TESTS - /// TODO: Use fake data or make this an integration test /// This test requires that you have a valid Homebrew installation set up, /// and requires the Valet services to be installed: php, nginx and dnsmasq. /// If this test fails, there is an issue with your Homebrew installation @@ -72,7 +72,6 @@ class HomebrewPackageTest: XCTestCase { XCTAssertEqual(services.count, 3) } - /// TODO: Use fake data or make this an integration test /// This test requires that you have a valid Homebrew installation set up, /// and requires the `php` formula to be installed. /// If this test fails, there is an issue with your Homebrew installation @@ -87,4 +86,5 @@ class HomebrewPackageTest: XCTestCase { XCTAssertTrue(package.name == "php") } + */ } From e5c80ab52f917c92064be068b7cb51eba107e8a8 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 7 Jan 2023 18:54:07 +0100 Subject: [PATCH 147/181] =?UTF-8?q?=F0=9F=91=8C=20Resolve=20height=20issue?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 10 +++++ phpmon/Domain/SwiftUI/Common/HelpButton.swift | 39 +++++++++++++++++++ phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 28 ++++++------- 3 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 phpmon/Domain/SwiftUI/Common/HelpButton.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 5f43860..80a6a97 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -139,6 +139,10 @@ C44F868E2835BD8D005C353A /* phpmon-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C44F868D2835BD8D005C353A /* phpmon-config.json */; }; C450C8C628C919EC002A2B4B /* PreferenceName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C450C8C528C919EC002A2B4B /* PreferenceName.swift */; }; C450C8C728C919EC002A2B4B /* PreferenceName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C450C8C528C919EC002A2B4B /* PreferenceName.swift */; }; + C451AFF62969E40F0078E617 /* HelpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C451AFF52969E40F0078E617 /* HelpButton.swift */; }; + C451AFF72969E40F0078E617 /* HelpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C451AFF52969E40F0078E617 /* HelpButton.swift */; }; + C451AFF82969E40F0078E617 /* HelpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C451AFF52969E40F0078E617 /* HelpButton.swift */; }; + C451AFF92969E40F0078E617 /* HelpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C451AFF52969E40F0078E617 /* HelpButton.swift */; }; C4570C3A28FC355300D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C4570C3B28FC355300D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C4570C3C28FC355400D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; @@ -801,6 +805,7 @@ C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Async.swift"; sourceTree = ""; }; C44F868D2835BD8D005C353A /* phpmon-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "phpmon-config.json"; sourceTree = ""; }; C450C8C528C919EC002A2B4B /* PreferenceName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceName.swift; sourceTree = ""; }; + C451AFF52969E40F0078E617 /* HelpButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpButton.swift; sourceTree = ""; }; C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-proxy.test"; sourceTree = ""; }; C45B9148295607F400F4EC78 /* Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Service.swift; sourceTree = ""; }; C45B914D295608E300F4EC78 /* ValetServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetServicesManager.swift; sourceTree = ""; }; @@ -1487,6 +1492,7 @@ isa = PBXGroup; children = ( C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */, + C451AFF52969E40F0078E617 /* HelpButton.swift */, ); path = Common; sourceTree = ""; @@ -2097,6 +2103,7 @@ C4080FFA27BD956700BF2C6B /* BetterAlertVC.swift in Sources */, C4BF56AB2949381100379603 /* FakeValetInteractor.swift in Sources */, C4B5635E276AB09000F12CCB /* VersionExtractor.swift in Sources */, + C451AFF62969E40F0078E617 /* HelpButton.swift in Sources */, 54D9E0B627E4F51E003B9AD9 /* HotKey.swift in Sources */, C4D936C927E3EB6100BD69FE /* PhpHelper.swift in Sources */, C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */, @@ -2217,6 +2224,7 @@ C471E88428F9BB650021E251 /* NoDomainResultsView.swift in Sources */, C471E88528F9BB650021E251 /* ServicesView.swift in Sources */, C471E88628F9BB650021E251 /* StatsView.swift in Sources */, + C451AFF82969E40F0078E617 /* HelpButton.swift in Sources */, C471E88728F9BB650021E251 /* SectionHeaderView.swift in Sources */, C471E88828F9BB650021E251 /* HeaderView.swift in Sources */, C471E88928F9BB650021E251 /* SwiftUIHelper.swift in Sources */, @@ -2304,6 +2312,7 @@ C471E89F28F9BB8F0021E251 /* ValetDomainScanner.swift in Sources */, C471E8A028F9BB8F0021E251 /* FakeDomainScanner.swift in Sources */, C471E8A228F9BB8F0021E251 /* AppDelegate.swift in Sources */, + C451AFF92969E40F0078E617 /* HelpButton.swift in Sources */, C471E8A328F9BB8F0021E251 /* AppDelegate+MenuOutlets.swift in Sources */, C471E8A428F9BB8F0021E251 /* AppDelegate+Notifications.swift in Sources */, C471E8A528F9BB8F0021E251 /* AppDelegate+InterApp.swift in Sources */, @@ -2500,6 +2509,7 @@ 54D9E0B527E4F51E003B9AD9 /* Key.swift in Sources */, C4AF9F7B2754499000D44ED0 /* Valet.swift in Sources */, C4C1019C27C65C6F001FACC2 /* Process.swift in Sources */, + C451AFF72969E40F0078E617 /* HelpButton.swift in Sources */, C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */, C45B914A295607F400F4EC78 /* Service.swift in Sources */, C4C0E8E327F88B13002D32A9 /* ValetDomainScanner.swift in Sources */, diff --git a/phpmon/Domain/SwiftUI/Common/HelpButton.swift b/phpmon/Domain/SwiftUI/Common/HelpButton.swift new file mode 100644 index 0000000..609c5fa --- /dev/null +++ b/phpmon/Domain/SwiftUI/Common/HelpButton.swift @@ -0,0 +1,39 @@ +// +// HelpButton.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 07/01/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import Foundation +import SwiftUI + +struct HelpButton: View { + var action: () -> Void + + var body: some View { + Button(action: action, label: { + ZStack { + Circle() + .strokeBorder(Color(NSColor.separatorColor), lineWidth: 0.5) + .background(Circle().foregroundColor(Color(NSColor.controlColor))) + .shadow(color: Color(NSColor.separatorColor).opacity(0.3), radius: 1) + .frame(width: 20, height: 20) + Text("?").font(.system(size: 15, weight: .medium )) + } + }) + .buttonStyle(PlainButtonStyle()) + } + + struct HelpButton_Previews: PreviewProvider { + static var previews: some View { + Group { + HelpButton(action: {}).padding() + .previewDisplayName("Light Mode") + HelpButton(action: {}).padding().preferredColorScheme(.dark) + .previewDisplayName("Dark Mode") + } + } + } +} diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 8a8fa2f..94a0fb2 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -36,20 +36,20 @@ struct ServicesView: View { @ObservedObject var manager: ServicesManager var perRow: Int var rowCount: Int - var rowSpacing: Int = 5 - var rowHeight: Int = 30 + var rowSpacing: Int = 0 + var rowHeight: Int = 50 var statusHeight: Int = 30 + var allRowHeight: CGFloat var height: CGFloat init(manager: ServicesManager, perRow: Int) { self.manager = manager self.perRow = perRow - self.rowCount = manager.services.chunked(by: perRow).count - self.height = CGFloat( - (rowHeight * rowCount) - + ((rowCount - 1) * rowSpacing) - + statusHeight + self.rowCount = manager.formulae.chunked(by: perRow).count + self.allRowHeight = CGFloat( + (rowHeight * rowCount) + ((rowCount - 1) * rowSpacing) ) + self.height = allRowHeight + CGFloat(statusHeight) } var body: some View { @@ -66,7 +66,7 @@ struct ServicesView: View { .padding(CGFloat(self.rowSpacing)) } } - .frame(height: self.height) + .frame(height: CGFloat(self.height - CGFloat(self.statusHeight))) .frame(maxWidth: .infinity, alignment: .center) // .background(Color.red) @@ -77,10 +77,10 @@ struct ServicesView: View { .foregroundColor(self.manager.statusColor) Text(self.manager.statusMessage) .font(.system(size: 12)) - Button { - - } label: { - Text("Learn more").font(.system(size: 12)) + if self.manager.statusColor == .red { + HelpButton { + print("oof") + } } } } @@ -157,7 +157,7 @@ struct ServicesView_Previews: PreviewProvider { formulae: ["php", "nginx", "dnsmasq"], status: .active ), perRow: 4) - .frame(width: 330.0, height: 150) + .frame(width: 330.0) .previewDisplayName("Loading") ServicesView(manager: FakeServicesManager( @@ -172,7 +172,7 @@ struct ServicesView_Previews: PreviewProvider { "php", "nginx", "dnsmasq", "thing1", "thing2", "thing3", "thing4", "thing5" ], - status: .active + status: .inactive ), perRow: 4) .frame(width: 330.0) .previewDisplayName("Active 2") From 18dd597d3847d88055aba0170d35094ec9b42d08 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 7 Jan 2023 19:01:31 +0100 Subject: [PATCH 148/181] =?UTF-8?q?=F0=9F=91=8C=20SwiftUI=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/App/Services/FakeServicesManager.swift | 7 ++++++- .../Integrations/Valet/Sites/FakeValetSite.swift | 4 +++- .../Domain/SwiftUI/Domains/VersionPopoverView.swift | 11 ++++++++++- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 9 +-------- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/phpmon/Domain/App/Services/FakeServicesManager.swift b/phpmon/Domain/App/Services/FakeServicesManager.swift index a1ec1ba..e522ac3 100644 --- a/phpmon/Domain/App/Services/FakeServicesManager.swift +++ b/phpmon/Domain/App/Services/FakeServicesManager.swift @@ -16,7 +16,8 @@ class FakeServicesManager: ServicesManager { init( formulae: [String] = ["php", "nginx", "dnsmasq"], - status: Service.Status = .active + status: Service.Status = .active, + loading: Bool = false ) { super.init() @@ -29,6 +30,10 @@ class FakeServicesManager: ServicesManager { self.services = [] self.reapplyServices() + if loading { + return + } + Task { @MainActor in self.firstRunComplete = true } diff --git a/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift index e462f46..6cb22a8 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift @@ -35,6 +35,8 @@ class FakeValetSite: ValetSite { self.isolatedPhpVersion = PhpInstallation(isolated) } - self.evaluateCompatibility() + if PhpEnv.shared.currentInstall != nil { + self.evaluateCompatibility() + } } } diff --git a/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift b/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift index 25be569..457be84 100644 --- a/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift +++ b/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift @@ -77,8 +77,17 @@ struct VersionPopoverView: View { return "alert.composer_php_requirement.unable_to_determine".localized } + + let suffix = { + if isRunningTests || isRunningSwiftUIPreview { + return "test" + } + + return Valet.shared.config.tld + }() + return "alert.composer_php_requirement.title".localized( - "\(site.name).\(Valet.shared.config.tld)", + "\(site.name).\(suffix)", site.composerPhp ) } diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 94a0fb2..50d4348 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -24,7 +24,7 @@ struct ServicesView: View { let view = NSHostingView(rootView: rootView) view.autoresizingMask = [.width] view.setFrameSize( - CGSize(width: view.frame.width, height: rootView.height + 30) + CGSize(width: view.frame.width, height: rootView.height) ) // view.layer?.backgroundColor = CGColor.init(red: 255, green: 0, blue: 0, alpha: 1) view.focusRingType = .none @@ -153,13 +153,6 @@ struct ServiceView: View { struct ServicesView_Previews: PreviewProvider { static var previews: some View { - ServicesView(manager: FakeServicesManager( - formulae: ["php", "nginx", "dnsmasq"], - status: .active - ), perRow: 4) - .frame(width: 330.0) - .previewDisplayName("Loading") - ServicesView(manager: FakeServicesManager( formulae: ["php", "nginx", "dnsmasq"], status: .active From 4cbfbeb4e5bc9727f6e3be921cba5604e3322461 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 7 Jan 2023 23:54:01 +0100 Subject: [PATCH 149/181] =?UTF-8?q?=F0=9F=91=8C=20Add=20copy=20about=20ina?= =?UTF-8?q?ctive=20services?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/SwiftUI/Common/HelpButton.swift | 8 +------- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 10 +++++++++- phpmon/Localizable.strings | 10 ++++++++++ tests/Shared/TestableConfigurations.swift | 7 ++++--- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/phpmon/Domain/SwiftUI/Common/HelpButton.swift b/phpmon/Domain/SwiftUI/Common/HelpButton.swift index 609c5fa..8fa7c46 100644 --- a/phpmon/Domain/SwiftUI/Common/HelpButton.swift +++ b/phpmon/Domain/SwiftUI/Common/HelpButton.swift @@ -15,15 +15,9 @@ struct HelpButton: View { var body: some View { Button(action: action, label: { ZStack { - Circle() - .strokeBorder(Color(NSColor.separatorColor), lineWidth: 0.5) - .background(Circle().foregroundColor(Color(NSColor.controlColor))) - .shadow(color: Color(NSColor.separatorColor).opacity(0.3), radius: 1) - .frame(width: 20, height: 20) - Text("?").font(.system(size: 15, weight: .medium )) + Text("?").font(.system(size: 15, weight: .medium)) } }) - .buttonStyle(PlainButtonStyle()) } struct HelpButton_Previews: PreviewProvider { diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 50d4348..482c4d7 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -34,6 +34,7 @@ struct ServicesView: View { } @ObservedObject var manager: ServicesManager + var perRow: Int var rowCount: Int var rowSpacing: Int = 0 @@ -79,7 +80,14 @@ struct ServicesView: View { .font(.system(size: 12)) if self.manager.statusColor == .red { HelpButton { - print("oof") + // Show an alert with more information + BetterAlert().withInformation( + title: "alert.key_service_not_running.title".localized, + subtitle: "alert.key_service_not_running.subtitle".localized, + description: "alert.key_service_not_running.desc".localized + ) + .withPrimary(text: "generic.ok".localized) + .show() } } } diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 23c9938..41395c0 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -546,6 +546,16 @@ If you are seeing this message but are confused why this folder has gone missing "alert.errors.homebrew_permissions.applescript_returned_nil.title" = "Restore Homebrew Permissions has been cancelled."; "alert.errors.homebrew_permissions.applescript_returned_nil.description" = "The outcome of the script that is executed to adjust the permissions returned nil, which usually means that you did not grant administrative permissions to PHP Monitor.\n\nIf you clicked on Cancel during the authentication prompt, this is normal. If you did actually authenticate and you are still seeing this message, something probably went wrong."; +"alert.key_service_not_running.title" = "Due to issues with the Homebrew services required, Valet is currently not working correctly"; +"alert.key_service_not_running.subtitle" = "For Valet to work properly, at least three key services need to be running correctly. + +PHP Monitor is reporting that this isn't the case. You can try to fix this by pressing the button with the 'X' in the menu below the affected service to (re)start the service that is currently inactive."; +"alert.key_service_not_running.desc" = "If clicking on the button below the service doesn't work (i.e. the spinner appears but remains an 'X' after some time), you may need to run Fix My Valet. You can do this via the menu First Aid > Fix My Valet. + +Alternatively, you can use `valet stop` and `valet start` in the terminal, which may also fix the issue (as an alternative to Fix My Valet). + +For further debugging, you may wish to check the GitHub issue tracker, where others may have had similar issues. As the developer, I attempt to make sure every question gets answered :)"; + // CHECK FOR UPDATES "updater.alerts.newer_version_available.title" = "PHP Monitor v%@ is now available!"; diff --git a/tests/Shared/TestableConfigurations.swift b/tests/Shared/TestableConfigurations.swift index 90f6ed0..6bdfd63 100644 --- a/tests/Shared/TestableConfigurations.swift +++ b/tests/Shared/TestableConfigurations.swift @@ -47,6 +47,10 @@ class TestableConfigurations { """), "/opt/homebrew/etc/php/8.2/php-fpm.d/valet-fpm.conf" : .fake(.text), + "/opt/homebrew/etc/php/8.2/php.ini" + : .fake(.text), + "/opt/homebrew/etc/php/8.2/conf.d/php-memory-limits.ini" + : .fake(.text) ], shellOutput: [ "sysctl -n sysctl.proc_translated" @@ -164,10 +168,7 @@ class TestableConfigurations { "/opt/homebrew/bin/php -r echo ini_get('post_max_size');": "512M", "/opt/homebrew/bin/php -r echo php_ini_scanned_files();" : """ - /opt/homebrew/etc/php/8.2/conf.d/error_log.ini, - /opt/homebrew/etc/php/8.2/conf.d/ext-opcache.ini, /opt/homebrew/etc/php/8.2/conf.d/php-memory-limits.ini, - /opt/homebrew/etc/php/8.2/conf.d/xdebug.ini """ ] ) From a50eb04f3cee332eb4ed90f097a93260c6f5dff3 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sun, 8 Jan 2023 11:50:01 +0100 Subject: [PATCH 150/181] =?UTF-8?q?=F0=9F=91=8C=20Services=20now=20report?= =?UTF-8?q?=20error=20status?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcschemes/PHP Monitor DEV.xcscheme | 2 +- phpmon/Domain/App/Services/Service.swift | 13 ++++++++-- .../Domain/App/Services/ServicesManager.swift | 21 +++++++++++++--- .../App/Services/ValetServicesManager.swift | 20 +++++++++++++++ phpmon/Domain/SwiftUI/Common/HelpButton.swift | 4 +-- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 25 ++++++++++++++++--- phpmon/Localizable.strings | 20 ++++++++++++++- 7 files changed, 91 insertions(+), 14 deletions(-) diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index b0a4d86..8642ae0 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -91,7 +91,7 @@ + isEnabled = "NO"> Date: Sun, 8 Jan 2023 12:48:47 +0100 Subject: [PATCH 151/181] =?UTF-8?q?=F0=9F=91=8C=20Improved=20error=20handl?= =?UTF-8?q?ing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../App/Services/ValetServicesManager.swift | 49 ++++++++++++++++--- phpmon/Domain/SwiftUI/Common/HelpButton.swift | 1 + phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 31 ++++++------ phpmon/Localizable.strings | 9 ++++ 4 files changed, 66 insertions(+), 24 deletions(-) diff --git a/phpmon/Domain/App/Services/ValetServicesManager.swift b/phpmon/Domain/App/Services/ValetServicesManager.swift index f36c594..38f2cfa 100644 --- a/phpmon/Domain/App/Services/ValetServicesManager.swift +++ b/phpmon/Domain/App/Services/ValetServicesManager.swift @@ -7,6 +7,7 @@ // import Foundation +import Cocoa class ValetServicesManager: ServicesManager { override init() { @@ -98,7 +99,14 @@ class ValetServicesManager: ServicesManager { return service.name == named } - let action = wrapper.status == .active ? "stop" : "start" + // Normally, we allow starting and stopping + var action = wrapper.status == .active ? "stop" : "start" + + // However, if we've encountered an error, attempt to restart + if wrapper.status == .error { + action = "restart" + } + let command = "services \(action) \(wrapper.formula.name)" // Run the command @@ -107,19 +115,46 @@ class ValetServicesManager: ServicesManager { // Reload the services status to confirm this worked await ServicesManager.shared.reloadServicesStatus() + Task { + + } + } + + func presentTroubleshootingForService(named: String) { Task { @MainActor in let after = self.homebrewServices.first { service in return service.name == named } - guard let before else { return } guard let after else { return } - if before.running == after.running { - // The status has not changed, report this to the user - Log.err("The service '\(named)' status has not changed. Its status is: \(after.status ?? "empty")") - } else { - Log.info("The service '\(named)' has been successfully toggled.") + if after.status == "error" { + Log.err("The service '\(named)' is now reporting an error.") + + let hasErrorPath = after.error_log_path != nil + + if hasErrorPath { + BetterAlert().withInformation( + title: "alert.service_error.title".localized(named), + subtitle: "alert.service_error.subtitle.error_log".localized(named), + description: "alert.service_error.extra".localized + ) + .withPrimary(text: "alert.service_error.button.close".localized) + .withSecondary(text: "alert.service_error.button.show_log", action: { alert in + let url = URL(fileURLWithPath: after.error_log_path!) + NSWorkspace.shared.activateFileViewerSelecting([url]) + alert.close(with: .OK) + }) + .show() + } else { + BetterAlert().withInformation( + title: "alert.service_error.title".localized(named), + subtitle: "alert.service_error.subtitle.no_error_log".localized(named), + description: "alert.service_error.extra".localized + ) + .withPrimary(text: "alert.service_error.button.close".localized) + .show() + } } } } diff --git a/phpmon/Domain/SwiftUI/Common/HelpButton.swift b/phpmon/Domain/SwiftUI/Common/HelpButton.swift index 20a5266..8fea3d0 100644 --- a/phpmon/Domain/SwiftUI/Common/HelpButton.swift +++ b/phpmon/Domain/SwiftUI/Common/HelpButton.swift @@ -16,6 +16,7 @@ struct HelpButton: View { Button(action: action, label: { Text("?").font(.system(size: 12, weight: .medium)) }) + .focusable(false) } struct HelpButton_Previews: PreviewProvider { diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index a820b7a..29495d4 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -12,25 +12,22 @@ import SwiftUI struct ServicesView: View { static func asMenuItem(perRow: Int = 4) -> NSMenuItem { - let item = NSMenuItem() + let view = { + let rootView = Self(manager: ServicesManager.shared, perRow: perRow) + let view = NSHostingView(rootView: rootView) + view.autoresizingMask = [.width] + view.setFrameSize(CGSize(width: view.frame.width, height: rootView.height)) + view.focusRingType = .none + return view + }() - let manager = ServicesManager.shared + let menuItem = { + let item = NSMenuItem() + item.view = view + return item + }() - let rootView = Self( - manager: manager, - perRow: perRow - ) - - let view = NSHostingView(rootView: rootView) - view.autoresizingMask = [.width] - view.setFrameSize( - CGSize(width: view.frame.width, height: rootView.height) - ) - // view.layer?.backgroundColor = CGColor.init(red: 255, green: 0, blue: 0, alpha: 1) - view.focusRingType = .none - - item.view = view - return item + return menuItem } @ObservedObject var manager: ServicesManager diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 7ad381c..bc1317a 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -448,6 +448,15 @@ You can do this by running `composer global update` in your terminal. After that "alert.php_switch_unavailable.info" = "Please make sure PHP %@ is installed and you can switch to it in the dropdown. Currently supported versions include PHP: %@."; "alert.php_switch_unavailable.ok" = "OK"; +// Service error +"alert.service_error.title" = "The service '%@' is reporting an error!"; +"alert.service_error.subtitle.error_log" = "This means that the service '%@' isn't running. This may prevent Valet from working correctly. This service has an associated log file that you might want to check, however."; +"alert.service_error.subtitle.no_error_log" = "This means that the service '%@' isn't running. This may prevent Valet from working correctly. Unfortunately, there is no associated log file for this service."; +"alert.service_error.extra" = "You may also wish to follow common troubleshooting steps. To learn more, press the '?' button in the services section in PHP Monitor."; + +"alert.service_error.button.show_log" = "View Error Log"; +"alert.service_error.button.close" = "Close"; + // Composer issues "alert.global_composer_platform_issues.title" = "Composer detected issues in your platform"; "alert.global_composer_platform_issues.subtitle" = "The version of PHP you switched to is too old for the global Composer dependencies you have installed. These dependencies will need to be updated."; From 55fc90bcf5b55158f73080dc59309c165d616594 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sun, 8 Jan 2023 13:14:18 +0100 Subject: [PATCH 152/181] =?UTF-8?q?=F0=9F=91=8C=20Better=20alerts=20for=20?= =?UTF-8?q?error=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../App/Services/ValetServicesManager.swift | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/phpmon/Domain/App/Services/ValetServicesManager.swift b/phpmon/Domain/App/Services/ValetServicesManager.swift index 38f2cfa..fe0a590 100644 --- a/phpmon/Domain/App/Services/ValetServicesManager.swift +++ b/phpmon/Domain/App/Services/ValetServicesManager.swift @@ -116,46 +116,46 @@ class ValetServicesManager: ServicesManager { await ServicesManager.shared.reloadServicesStatus() Task { - + await presentTroubleshootingForService(named: named) } } - func presentTroubleshootingForService(named: String) { - Task { @MainActor in - let after = self.homebrewServices.first { service in - return service.name == named + @MainActor func presentTroubleshootingForService(named: String) { + let after = self.homebrewServices.first { service in + return service.name == named + } + + guard let after else { return } + + if after.status == "error" { + Log.err("The service '\(named)' is now reporting an error.") + + guard let errorLogPath = after.error_log_path else { + return BetterAlert().withInformation( + title: "alert.service_error.title".localized(named), + subtitle: "alert.service_error.subtitle.no_error_log".localized(named), + description: "alert.service_error.extra".localized + ) + .withPrimary(text: "alert.service_error.button.close".localized) + .show() } - guard let after else { return } - - if after.status == "error" { - Log.err("The service '\(named)' is now reporting an error.") - - let hasErrorPath = after.error_log_path != nil - - if hasErrorPath { - BetterAlert().withInformation( - title: "alert.service_error.title".localized(named), - subtitle: "alert.service_error.subtitle.error_log".localized(named), - description: "alert.service_error.extra".localized - ) - .withPrimary(text: "alert.service_error.button.close".localized) - .withSecondary(text: "alert.service_error.button.show_log", action: { alert in - let url = URL(fileURLWithPath: after.error_log_path!) - NSWorkspace.shared.activateFileViewerSelecting([url]) - alert.close(with: .OK) - }) - .show() + BetterAlert().withInformation( + title: "alert.service_error.title".localized(named), + subtitle: "alert.service_error.subtitle.error_log".localized(named), + description: "alert.service_error.extra".localized + ) + .withPrimary(text: "alert.service_error.button.close".localized) + .withTertiary(text: "alert.service_error.button.show_log".localized, action: { alert in + let url = URL(fileURLWithPath: errorLogPath) + if errorLogPath.hasSuffix(".log") { + NSWorkspace.shared.open(url) } else { - BetterAlert().withInformation( - title: "alert.service_error.title".localized(named), - subtitle: "alert.service_error.subtitle.no_error_log".localized(named), - description: "alert.service_error.extra".localized - ) - .withPrimary(text: "alert.service_error.button.close".localized) - .show() + NSWorkspace.shared.activateFileViewerSelecting([url]) } - } + alert.close(with: .OK) + }) + .show() } } } From d8738b685f8852230cdecafe121b01f5a6f1f87c Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 9 Jan 2023 18:10:51 +0100 Subject: [PATCH 153/181] =?UTF-8?q?=F0=9F=91=8C=20Get=20rid=20of=20warning?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/App/Services/ValetServicesManager.swift | 12 ++++-------- .../Domain/SwiftUI/Domains/VersionPopoverView.swift | 1 - 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/phpmon/Domain/App/Services/ValetServicesManager.swift b/phpmon/Domain/App/Services/ValetServicesManager.swift index fe0a590..d018b3c 100644 --- a/phpmon/Domain/App/Services/ValetServicesManager.swift +++ b/phpmon/Domain/App/Services/ValetServicesManager.swift @@ -94,11 +94,6 @@ class ValetServicesManager: ServicesManager { return Log.err("The wrapper for '\(named)' is missing.") } - // Prepare the appropriate command to stop or start a service - let before = self.homebrewServices.first { service in - return service.name == named - } - // Normally, we allow starting and stopping var action = wrapper.status == .active ? "stop" : "start" @@ -107,10 +102,11 @@ class ValetServicesManager: ServicesManager { action = "restart" } - let command = "services \(action) \(wrapper.formula.name)" - // Run the command - await brew(command, sudo: wrapper.formula.elevated) + await brew( + "services \(action) \(wrapper.formula.name)", + sudo: wrapper.formula.elevated + ) // Reload the services status to confirm this worked await ServicesManager.shared.reloadServicesStatus() diff --git a/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift b/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift index 457be84..e95b03f 100644 --- a/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift +++ b/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift @@ -77,7 +77,6 @@ struct VersionPopoverView: View { return "alert.composer_php_requirement.unable_to_determine".localized } - let suffix = { if isRunningTests || isRunningSwiftUIPreview { return "test" From 3c946a53e85575d50896513b3ef5db908eebe33b Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 9 Jan 2023 19:19:18 +0100 Subject: [PATCH 154/181] =?UTF-8?q?=F0=9F=91=8C=20Keep=20track=20of=20last?= =?UTF-8?q?=20used=20global=20PHP=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PHP/Switcher/InternalSwitcher.swift | 3 ++ phpmon/Domain/App/Startup.swift | 1 - phpmon/Domain/Menu/MainMenu+Startup.swift | 3 ++ .../Domain/Preferences/PreferenceName.swift | 1 + phpmon/Domain/Preferences/Preferences.swift | 3 +- phpmon/Domain/Preferences/Stats.swift | 43 +++++++++++++++++++ 6 files changed, 52 insertions(+), 2 deletions(-) diff --git a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift index 76669a9..d86b1a6 100644 --- a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift +++ b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift @@ -51,6 +51,9 @@ class InternalSwitcher: PhpSwitcher { await brew("services restart nginx", sudo: true) Log.info("The new version(s) have been linked!") + + // Persist which formula is linked so we can check at launch + Stats.persistCurrentGlobalPhpVersion(version: PhpEnv.phpInstall.formula) }) } diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index cdacbb0..a3525fe 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -264,6 +264,5 @@ class Startup { subtitleText: "startup.errors.valet_version_not_supported.subtitle".localized(Valet.shared.version.text), descriptionText: "startup.errors.valet_version_not_supported.desc".localized ) - ] } diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 487d259..03a4545 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -121,6 +121,9 @@ extension MainMenu { // Check for updates await AppUpdateChecker.checkIfNewerVersionIsAvailable() + // Check if the linked version has changed between launches of phpmon + Stats.evaluateLastLinkedPhpVersion() + // We are ready! Log.info("PHP Monitor is ready to serve!") } diff --git a/phpmon/Domain/Preferences/PreferenceName.swift b/phpmon/Domain/Preferences/PreferenceName.swift index 221e8ce..656d22c 100644 --- a/phpmon/Domain/Preferences/PreferenceName.swift +++ b/phpmon/Domain/Preferences/PreferenceName.swift @@ -107,4 +107,5 @@ enum InternalStats: String { case launchCount = "times_launched" case switchCount = "times_switched_versions" case didSeeSponsorEncouragement = "did_see_sponsor_encouragement" + case lastGlobalPhpVersion = "last_global_php_version" } diff --git a/phpmon/Domain/Preferences/Preferences.swift b/phpmon/Domain/Preferences/Preferences.swift index c410e37..504c870 100644 --- a/phpmon/Domain/Preferences/Preferences.swift +++ b/phpmon/Domain/Preferences/Preferences.swift @@ -80,7 +80,8 @@ class Preferences { /// Stats InternalStats.switchCount.rawValue: 0, InternalStats.launchCount.rawValue: 0, - InternalStats.didSeeSponsorEncouragement.rawValue: false + InternalStats.didSeeSponsorEncouragement.rawValue: false, + InternalStats.lastGlobalPhpVersion.rawValue: "" ]) if UserDefaults.standard.bool(forKey: PreferenceName.wasLaunchedBefore.rawValue) { diff --git a/phpmon/Domain/Preferences/Stats.swift b/phpmon/Domain/Preferences/Stats.swift index ab25b9b..3155bd9 100644 --- a/phpmon/Domain/Preferences/Stats.swift +++ b/phpmon/Domain/Preferences/Stats.swift @@ -48,6 +48,10 @@ class Stats { ) } + public static var lastGlobalPhpVersion: String { + UserDefaults.standard.string(forKey: InternalStats.lastGlobalPhpVersion.rawValue) ?? "" + } + /** Increment the successful launch count. This should only be called when the user has not encountered ANY issues starting @@ -70,6 +74,16 @@ class Stats { ) } + /** + Persist which PHP version was active when you last used the app. + */ + public static func persistCurrentGlobalPhpVersion(version: String) { + UserDefaults.standard.set( + version, + forKey: InternalStats.lastGlobalPhpVersion.rawValue + ) + } + /** Determine if the sponsor message should be displayed. @@ -127,4 +141,33 @@ class Stats { } } + public static func evaluateLastLinkedPhpVersion() { + let currentVersion = PhpEnv.phpInstall.version.short + let previousVersion = Stats.lastGlobalPhpVersion + + // TODO: Add a preference to disable this + + // Save the PHP version that is currently in use (only if unknown) + if Stats.lastGlobalPhpVersion == "" { + Stats.persistCurrentGlobalPhpVersion(version: currentVersion) + Log.info("Persisting the currently linked PHP version (first time only).") + } else { + Log.info("Previously, the globally linked PHP version was: \(previousVersion).") + if previousVersion != currentVersion { + Log.info("Currently, that version is: \(currentVersion). This is a mismatch.") + Task { @MainActor in + BetterAlert() + .withInformation( + title: "startup.version_mismatch.title".localized, + subtitle: "startup.version_mismatch.subtitle".localized, + description: "startup.version_mismatch.desc".localized + ) + .withPrimary(text: "OK") + // TODO: Add secondary button to switch to that version (if possible) + .show() + } + } + } + } + } From 6eea08cd4fb04366945a8273e83d0f31b618fc91 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 10 Jan 2023 17:53:55 +0100 Subject: [PATCH 155/181] =?UTF-8?q?=F0=9F=91=8C=20Finalize=20PHP=20Guard?= =?UTF-8?q?=20functionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 8 +++---- phpmon/Common/PHP/PHP Version/PhpEnv.swift | 3 +++ .../PHP/Switcher/InternalSwitcher.swift | 3 --- phpmon/Domain/App/InterAppHandler.swift | 14 +---------- phpmon/Domain/Menu/MainMenu+Actions.swift | 15 ++++++++++++ phpmon/Domain/Preferences/Stats.swift | 24 +++++++++++++------ phpmon/Localizable.strings | 7 ++++++ 7 files changed, 47 insertions(+), 27 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 80a6a97..9221fca 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2799,7 +2799,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1000; + CURRENT_PROJECT_VERSION = 1020; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2828,7 +2828,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1000; + CURRENT_PROJECT_VERSION = 1020; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -3056,7 +3056,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1000; + CURRENT_PROJECT_VERSION = 1020; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3166,7 +3166,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1000; + CURRENT_PROJECT_VERSION = 1020; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; diff --git a/phpmon/Common/PHP/PHP Version/PhpEnv.swift b/phpmon/Common/PHP/PHP Version/PhpEnv.swift index d3332c9..bbd78e8 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnv.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnv.swift @@ -172,6 +172,9 @@ class PhpEnv { public func validate(_ version: String) -> Bool { if self.currentInstall.version.short == version { Log.info("Switching to version \(version) seems to have succeeded. Validation passed.") + Log.info("Keeping track that this is the new version!") + Stats.persistCurrentGlobalPhpVersion(version: version) + return true } diff --git a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift index d86b1a6..76669a9 100644 --- a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift +++ b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift @@ -51,9 +51,6 @@ class InternalSwitcher: PhpSwitcher { await brew("services restart nginx", sudo: true) Log.info("The new version(s) have been linked!") - - // Persist which formula is linked so we can check at launch - Stats.persistCurrentGlobalPhpVersion(version: PhpEnv.phpInstall.formula) }) } diff --git a/phpmon/Domain/App/InterAppHandler.swift b/phpmon/Domain/App/InterAppHandler.swift index c8bb0a5..dd2816b 100644 --- a/phpmon/Domain/App/InterAppHandler.swift +++ b/phpmon/Domain/App/InterAppHandler.swift @@ -53,19 +53,7 @@ class InterApp { Task { MainMenu.shared.openPhpInfo() } }), InterApp.Action(command: "switch/php/", action: { version in - if PhpEnv.shared.availablePhpVersions.contains(version) { - Task { MainMenu.shared.switchToPhpVersion(version) } - } else { - Task { - BetterAlert().withInformation( - title: "alert.php_switch_unavailable.title".localized, - subtitle: "alert.php_switch_unavailable.subtitle".localized(version) - ).withPrimary( - text: "alert.php_switch_unavailable.ok".localized - ).show() - } - } + Task { MainMenu.shared.switchToAnyPhpVersion(version) } }) ]} - } diff --git a/phpmon/Domain/Menu/MainMenu+Actions.swift b/phpmon/Domain/Menu/MainMenu+Actions.swift index 6cd6dda..da27abf 100644 --- a/phpmon/Domain/Menu/MainMenu+Actions.swift +++ b/phpmon/Domain/Menu/MainMenu+Actions.swift @@ -231,6 +231,21 @@ extension MainMenu { self.switchToPhpVersion(sender.version) } + public func switchToAnyPhpVersion(_ version: String) { + if PhpEnv.shared.availablePhpVersions.contains(version) { + Task { MainMenu.shared.switchToPhpVersion(version) } + } else { + Task { + BetterAlert().withInformation( + title: "alert.php_switch_unavailable.title".localized, + subtitle: "alert.php_switch_unavailable.subtitle".localized(version) + ).withPrimary( + text: "alert.php_switch_unavailable.ok".localized + ).show() + } + } + } + @objc func switchToPhpVersion(_ version: String) { setBusyImage() PhpEnv.shared.isBusy = true diff --git a/phpmon/Domain/Preferences/Stats.swift b/phpmon/Domain/Preferences/Stats.swift index 3155bd9..4ceb701 100644 --- a/phpmon/Domain/Preferences/Stats.swift +++ b/phpmon/Domain/Preferences/Stats.swift @@ -145,8 +145,6 @@ class Stats { let currentVersion = PhpEnv.phpInstall.version.short let previousVersion = Stats.lastGlobalPhpVersion - // TODO: Add a preference to disable this - // Save the PHP version that is currently in use (only if unknown) if Stats.lastGlobalPhpVersion == "" { Stats.persistCurrentGlobalPhpVersion(version: currentVersion) @@ -159,15 +157,27 @@ class Stats { BetterAlert() .withInformation( title: "startup.version_mismatch.title".localized, - subtitle: "startup.version_mismatch.subtitle".localized, - description: "startup.version_mismatch.desc".localized + subtitle: "startup.version_mismatch.subtitle".localized( + currentVersion, + previousVersion + ), + description: "startup.version_mismatch.desc".localized() ) - .withPrimary(text: "OK") - // TODO: Add secondary button to switch to that version (if possible) + .withPrimary(text: "startup.version_mismatch.button_switch_back".localized( + previousVersion + ), action: { alert in + alert.close(with: .OK) + Task { MainMenu.shared.switchToAnyPhpVersion(previousVersion) } + }) + .withTertiary(text: "startup.version_mismatch.button_stay".localized( + currentVersion + ), action: { alert in + Stats.persistCurrentGlobalPhpVersion(version: currentVersion) + alert.close(with: .OK) + }) .show() } } } } - } diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index bc1317a..7fef6f9 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -540,6 +540,13 @@ If you are seeing this message but are confused why this folder has gone missing "startup.errors.which_alias_issue.subtitle" = "It appears that there's a file in `/usr/local/bin/which`. This is usually set up by NodeJS, but `node` isn't in the PATH in `/usr/local/bin`. To fix this, keep reading."; "startup.errors.which_alias_issue.desc" = "You will need to symlink `node` into the `/usr/local/bin` directory to make sure PHP Monitor can start successfully. For more info, see: https://github.com/nicoverbruggen/phpmon/issues/174"; +// Warning about a different PHP version linked than last time +"startup.version_mismatch.title" = "Your active PHP version has changed."; +"startup.version_mismatch.subtitle" = "Since PHP Monitor was last active, your linked PHP version has been changed to PHP %@. Would you like to switch back to PHP %@, or do you want to keep using the current version?"; +"startup.version_mismatch.desc" = "PHP Monitor keeps track of which version of PHP is globally linked. The global version may have been changed due to some other program or Homebrew may have linked a different formula after upgrades."; +"startup.version_mismatch.button_switch_back" = "Switch back to PHP %@"; +"startup.version_mismatch.button_stay" = "Keep using PHP %@"; + // SPONSOR ENCOURAGEMENT "startup.sponsor_encouragement.title" = "If PHP Monitor has been useful to you or your company, please consider leaving a tip."; From 300b10c5d869edc61e6eb9ad4668a064ba3495a5 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 11 Jan 2023 17:50:47 +0100 Subject: [PATCH 156/181] =?UTF-8?q?=F0=9F=90=9B=20Fix=20crash=20with=20che?= =?UTF-8?q?ck=20for=20updates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/Menu/MainMenu.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index b411efc..0dc8fef 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -174,8 +174,8 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate NSApplication.shared.terminate(nil) } - @objc func checkForUpdates() async { - await AppUpdateChecker.checkIfNewerVersionIsAvailable(initiatedFromBackground: false) + @objc func checkForUpdates() { + Task { await AppUpdateChecker.checkIfNewerVersionIsAvailable(initiatedFromBackground: false) } } // MARK: - Menu Delegate From e6d2c873a5d867fa879301d87905636c4574e354 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 11 Jan 2023 18:03:10 +0100 Subject: [PATCH 157/181] =?UTF-8?q?=F0=9F=91=8C=20Add=20modal=20to=20infor?= =?UTF-8?q?m=20about=20helper=20scripts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Helpers/Alert.swift | 4 +-- phpmon/Domain/App/AppUpdateChecker.swift | 4 +-- phpmon/Domain/App/Startup.swift | 2 +- .../DomainList/DomainListVC+Actions.swift | 32 +++++++++++++++++-- .../DomainList/DomainListVC+ContextMenu.swift | 7 ++++ .../Composer/ComposerWindow.swift | 2 +- .../Homebrew/HomebrewDiagnostics.swift | 2 +- phpmon/Domain/Integrations/Valet/Valet.swift | 4 +-- phpmon/Domain/Menu/MainMenu+Actions.swift | 4 +-- phpmon/Domain/Menu/MainMenu+FixMyValet.swift | 4 +-- phpmon/Domain/Notice/BetterAlert.swift | 2 +- .../PHP/ActivePhpInstallation+Checks.swift | 2 +- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 2 +- phpmon/Localizable.strings | 15 ++++++++- 14 files changed, 66 insertions(+), 20 deletions(-) diff --git a/phpmon/Common/Helpers/Alert.swift b/phpmon/Common/Helpers/Alert.swift index 8b6534a..4f46ec2 100644 --- a/phpmon/Common/Helpers/Alert.swift +++ b/phpmon/Common/Helpers/Alert.swift @@ -13,8 +13,8 @@ class Alert { onWindow window: NSWindow, messageText: String, informativeText: String, - buttonTitle: String = "OK", - secondButtonTitle: String = "Cancel", + buttonTitle: String = "generic.ok".localized, + secondButtonTitle: String = "generic.cancel".localized, style: NSAlert.Style = .warning, onFirstButtonPressed: @escaping (() -> Void) ) { diff --git a/phpmon/Domain/App/AppUpdateChecker.swift b/phpmon/Domain/App/AppUpdateChecker.swift index 51c0300..0018989 100644 --- a/phpmon/Domain/App/AppUpdateChecker.swift +++ b/phpmon/Domain/App/AppUpdateChecker.swift @@ -125,7 +125,7 @@ class AppUpdateChecker { subtitle: "updater.alerts.is_latest_version.subtitle".localized(App.shortVersion), description: "" ) - .withPrimary(text: "OK") + .withPrimary(text: "generic.ok".localized) .show() } } @@ -174,7 +174,7 @@ class AppUpdateChecker { NSWorkspace.shared.open(Constants.Urls.GitHubReleases) } ) - .withPrimary(text: "OK") + .withPrimary(text: "generic.ok".localized) .show() } } diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index a3525fe..f93d7ec 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -64,7 +64,7 @@ class Startup { subtitle: check.subtitleText, description: check.descriptionText ) - .withPrimary(text: "OK") + .withPrimary(text: "generic.ok".localized) .show() } diff --git a/phpmon/Domain/DomainList/DomainListVC+Actions.swift b/phpmon/Domain/DomainList/DomainListVC+Actions.swift index 236cb96..877b0b6 100644 --- a/phpmon/Domain/DomainList/DomainListVC+Actions.swift +++ b/phpmon/Domain/DomainList/DomainListVC+Actions.swift @@ -22,7 +22,7 @@ extension DomainListVC { title: "domain_list.alert.invalid_folder_name".localized, subtitle: "domain_list.alert.invalid_folder_name_desc".localized ) - .withPrimary(text: "OK") + .withPrimary(text: "generic.ok".localized) .show() return } @@ -194,6 +194,18 @@ extension DomainListVC { ) } + @objc func useInTerminal() { + guard let site = selectedSite else { + return + } + + guard let version = site.isolatedPhpVersion?.versionNumber else { + return + } + + self.notifyAboutUsingIsolatedPhpVersionInTerminal(version: version) + } + // MARK: - Alerts & Modals private func notifyAboutModifiedSecureStatus(domain: String, secured: Bool) { @@ -212,13 +224,27 @@ extension DomainListVC { ) } + private func notifyAboutUsingIsolatedPhpVersionInTerminal(version: VersionNumber) { + BetterAlert() + .withInformation( + title: "domain_list.alerts_isolated_php_terminal.title".localized(version.short), + subtitle: "domain_list.alerts_isolated_php_terminal.subtitle".localized( + "\(version.major)\(version.minor)", + version.short + ), + description: "domain_list.alerts_isolated_php_terminal.desc".localized + ) + .withPrimary(text: "generic.ok".localized) + .show() + } + private func notifyAboutFailedSecureStatus(command: String) { BetterAlert() .withInformation( title: "domain_list.alerts_status_not_changed.title".localized, subtitle: "domain_list.alerts_status_not_changed.desc".localized(command) ) - .withPrimary(text: "OK") + .withPrimary(text: "generic.ok".localized) .show() } @@ -229,7 +255,7 @@ extension DomainListVC { subtitle: "domain_list.alerts_isolation_failed.subtitle".localized, description: "domain_list.alerts_isolation_failed.desc".localized(command) ) - .withPrimary(text: "OK") + .withPrimary(text: "generic.ok".localized) .show() } } diff --git a/phpmon/Domain/DomainList/DomainListVC+ContextMenu.swift b/phpmon/Domain/DomainList/DomainListVC+ContextMenu.swift index eb77a80..9d4b0c3 100644 --- a/phpmon/Domain/DomainList/DomainListVC+ContextMenu.swift +++ b/phpmon/Domain/DomainList/DomainListVC+ContextMenu.swift @@ -130,6 +130,13 @@ extension DomainListVC { menu.addItem(HeaderView.asMenuItem(text: "domain_list.site_isolation".localized)) menu.addItem(NSMenuItem(title: "domain_list.isolate".localized, submenu: items)) + + if site.isolatedPhpVersion != nil { + menu.addItem(NSMenuItem( + title: "domain_list.use_in_terminal".localized(site.servingPhpVersion), + action: #selector(self.useInTerminal) + )) + } menu.addItem(NSMenuItem.separator()) } diff --git a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift index 9753905..754b2e3 100644 --- a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift +++ b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift @@ -120,7 +120,7 @@ import Foundation subtitle: "alert.composer_missing.subtitle".localized, description: "alert.composer_missing.desc".localized ) - .withPrimary(text: "OK") + .withPrimary(text: "generic.ok".localized) .show() } diff --git a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift index fe80a25..83fe469 100644 --- a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift +++ b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift @@ -140,7 +140,7 @@ class HomebrewDiagnostics { title: "alert.php_alias_conflict.title".localized, subtitle: "alert.php_alias_conflict.info".localized ) - .withPrimary(text: "OK") + .withPrimary(text: "generic.ok".localized) .show() } } diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 9d67ba7..0b84e5e 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -82,7 +82,7 @@ class Valet { subtitle: "alert.warnings.tld_issue.subtitle".localized, description: "alert.warnings.tld_issue.description".localized ) - .withPrimary(text: "OK") + .withPrimary(text: "generic.ok".localized) .withTertiary(text: "alert.do_not_tell_again".localized, action: { alert in Preferences.update(.warnAboutNonStandardTLD, value: false) alert.close(with: .alertThirdButtonReturn) @@ -176,7 +176,7 @@ class Valet { Constants.MinimumRecommendedValetVersion ) ) - .withPrimary(text: "OK") + .withPrimary(text: "generic.ok".localized) .show() } } else { diff --git a/phpmon/Domain/Menu/MainMenu+Actions.swift b/phpmon/Domain/Menu/MainMenu+Actions.swift index da27abf..bac4889 100644 --- a/phpmon/Domain/Menu/MainMenu+Actions.swift +++ b/phpmon/Domain/Menu/MainMenu+Actions.swift @@ -34,7 +34,7 @@ extension MainMenu { subtitle: "alert.fix_homebrew_permissions_done.subtitle".localized, description: "alert.fix_homebrew_permissions_done.desc".localized ) - .withPrimary(text: "OK") + .withPrimary(text: "generic.ok".localized) .show() } failure: { error in BetterAlert.show(for: error as! HomebrewPermissionError) @@ -182,7 +182,7 @@ extension MainMenu { subtitle: "preset_help_info".localized, description: "preset_help_desc".localized ) - .withPrimary(text: "OK") + .withPrimary(text: "generic.ok".localized) .withTertiary(text: "", action: { alert in NSWorkspace.shared.open(Constants.Urls.FrequentlyAskedQuestions) alert.close(with: .OK) diff --git a/phpmon/Domain/Menu/MainMenu+FixMyValet.swift b/phpmon/Domain/Menu/MainMenu+FixMyValet.swift index 5255499..fbc36e9 100644 --- a/phpmon/Domain/Menu/MainMenu+FixMyValet.swift +++ b/phpmon/Domain/Menu/MainMenu+FixMyValet.swift @@ -48,7 +48,7 @@ extension MainMenu { title: "alert.php_formula_missing.title".localized, subtitle: "alert.php_formula_missing.info".localized ) - .withPrimary(text: "OK") + .withPrimary(text: "generic.ok".localized) .show() } @@ -59,7 +59,7 @@ extension MainMenu { subtitle: "alert.fix_my_valet_done.subtitle".localized, description: "alert.fix_my_valet_done.desc".localized ) - .withPrimary(text: "OK") + .withPrimary(text: "generic.ok".localized) .show() } diff --git a/phpmon/Domain/Notice/BetterAlert.swift b/phpmon/Domain/Notice/BetterAlert.swift index 3013415..a4b8d2c 100644 --- a/phpmon/Domain/Notice/BetterAlert.swift +++ b/phpmon/Domain/Notice/BetterAlert.swift @@ -115,6 +115,6 @@ class BetterAlert { return BetterAlert().withInformation( title: "\(key).title".localized, subtitle: "\(key).description".localized - ).withPrimary(text: "OK").show() + ).withPrimary(text: "generic.ok".localized).show() } } diff --git a/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift b/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift index 75bfb02..65dfeb9 100644 --- a/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift +++ b/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift @@ -33,7 +33,7 @@ extension ActivePhpInstallation { subtitle: "alert.php_fpm_broken.info".localized, description: "alert.php_fpm_broken.description".localized ) - .withPrimary(text: "OK") + .withPrimary(text: "generic.ok".localized) .show() } } diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 29495d4..99371cd 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -124,7 +124,7 @@ struct ServiceView: View { subtitle: "alert.warnings.service_missing.subtitle".localized, description: "alert.warnings.service_missing.description".localized ) - .withPrimary(text: "OK") + .withPrimary(text: "generic.ok".localized) .show() } } label: { diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 7fef6f9..6b3f3ff 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -86,6 +86,7 @@ // GENERIC "generic.ok" = "OK"; +"generic.cancel" = "Cancel"; "generic.retry" = "Retry"; "generic.notice" = "Notice"; @@ -194,6 +195,18 @@ "domain_list.detected_apps" = "Detected Applications"; "domain_list.system_apps" = "System Applications"; "domain_list.unproxy" = "Remove Proxy"; +"domain_list.use_in_terminal" = "Open PHP %@ in Terminal"; + +"domain_list.alerts_isolated_php_terminal.title" = "You can use PHP %@ in a specific terminal!"; +"domain_list.alerts_isolated_php_terminal.subtitle" = "Sadly, PHP Monitor cannot open a terminal for you (and type in the appropriate commands). You'll have to manually source the helper script in order to use this specific version of PHP. To do this, you can type the following in a terminal of choice: + +. pm%@ + +This will source the helper script as generated by PHP Monitor and enable the use of PHP %@ for that specific terminal. + +This has no effect on other terminals, only for the particular terminal session that you are using it on. (i.e. if you have multiple tabs in your terminal app, other tabs & windows are unaffected.)"; +"domain_list.alerts_isolated_php_terminal.desc" = "If this doesn't work, you may wish to check PHP Doctor via the First Aid menu here in PHP Monitor. More information about this feature can also be found on GitHub (on the wiki on PHP Monitor's repository). This alert is included to improve visibility of this feature."; + "domain_list.warning.spaces" = "Warning! This site has a space in its folder.\nThe site will not be reachable via the browser."; @@ -623,7 +636,7 @@ COMMON TROUBLESHOOTING TIPS "warnings.refresh.button.description" = "Press this button once you've fixed an issue. This will cause PHP Monitor to re-evaluate your environment. If it's really fixed, the recommendation should disappear."; "warnings.helper_permissions.title" = "PHP Monitor’s helpers are currently unavailable."; -"warnings.helper_permissions.description" = "PHP Monitor comes with various helper binaries. Using these binaries allows you to easily invoke a specific version of PHP without switching the linked PHP version."; +"warnings.helper_permissions.description" = "PHP Monitor comes with various helper scripts. Using these scripts allows you to easily invoke a specific version of PHP without switching the linked PHP version."; "warnings.helper_permissions.unavailable" = "However, these helpers are potentially *unavailable* because PHP Monitor cannot currently create or update the required symlinks."; "warnings.helper_permissions.symlink" = "If you do not wish to make `/usr/local/bin` writable, you can add PHP Monitor's helper directory to your `PATH` variable to make this warning go away. (Click on ”Learn More” to find out how to fix this issue.)"; From d6258f54a9cfcfe8e8cb5cf1e2689b6e3efb442f Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 11 Jan 2023 22:23:39 +0100 Subject: [PATCH 158/181] =?UTF-8?q?=F0=9F=90=9B=20Fix=20crash=20when=20Val?= =?UTF-8?q?et.shared=20is=20nil?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/App/Startup.swift | 2 +- phpmon/Localizable.strings | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index f93d7ec..7824ac2 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -261,7 +261,7 @@ class Startup { }, name: "valet version is supported", titleText: "startup.errors.valet_version_not_supported.title".localized, - subtitleText: "startup.errors.valet_version_not_supported.subtitle".localized(Valet.shared.version.text), + subtitleText: "startup.errors.valet_version_not_supported.subtitle".localized, descriptionText: "startup.errors.valet_version_not_supported.desc".localized ) ] diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 6b3f3ff..e84842e 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -525,7 +525,7 @@ If you are seeing this message but are confused why this folder has gone missing // Valet version too new or old "startup.errors.valet_version_not_supported.title" = "This version of Valet is not supported"; -"startup.errors.valet_version_not_supported.subtitle" = "You are running a version of Valet that is currently not supported (%@). PHP Monitor currently works with Valet v2, v3 and v4. In order to avoid causing issues on your system, PHP Monitor cannot start."; +"startup.errors.valet_version_not_supported.subtitle" = "You are running a version of Valet that is currently not supported. PHP Monitor currently works with Valet v2, v3 and v4. In order to avoid causing issues on your system, PHP Monitor cannot start."; "startup.errors.valet_version_not_supported.desc" = "You must install a version of Valet that is compatible with PHP Monitor, or you may need to upgrade to a newer version of PHP Monitor which may include compatibility for this version of Valet (consult the latest release notes for more info)."; /// Brew & sudoers From c82ea1fac10886a288f2ada36c8961ac4bf34eea Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 12 Jan 2023 17:19:28 +0100 Subject: [PATCH 159/181] =?UTF-8?q?=F0=9F=90=9B=20Fix=20concurrency=20cras?= =?UTF-8?q?hes=20with=20@objc=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Helpers/Application.swift | 4 ++-- phpmon/Domain/DomainList/DomainListVC+Actions.swift | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/phpmon/Common/Helpers/Application.swift b/phpmon/Common/Helpers/Application.swift index 0f1b1fe..b0f0370 100644 --- a/phpmon/Common/Helpers/Application.swift +++ b/phpmon/Common/Helpers/Application.swift @@ -33,8 +33,8 @@ class Application { Attempt to open a specific directory in the app of choice. (This will open the app if it isn't open yet.) */ - @objc public func openDirectory(file: String) async { - return await Shell.quiet("/usr/bin/open -a \"\(name)\" \"\(file)\"") + @objc public func openDirectory(file: String) { + Task { await Shell.quiet("/usr/bin/open -a \"\(name)\" \"\(file)\"") } } /** Checks if the app is installed. */ diff --git a/phpmon/Domain/DomainList/DomainListVC+Actions.swift b/phpmon/Domain/DomainList/DomainListVC+Actions.swift index 877b0b6..f12da19 100644 --- a/phpmon/Domain/DomainList/DomainListVC+Actions.swift +++ b/phpmon/Domain/DomainList/DomainListVC+Actions.swift @@ -30,17 +30,17 @@ extension DomainListVC { NSWorkspace.shared.open(url) } - @objc func openInFinder() async { - await Shell.quiet("open '\(selectedSite!.absolutePath)'") + @objc func openInFinder() { + Task { return await Shell.quiet("open '\(selectedSite!.absolutePath)'") } } - @objc func openInTerminal() async { - await Shell.quiet("open -b com.apple.terminal '\(selectedSite!.absolutePath)'") + @objc func openInTerminal() { + Task { await Shell.quiet("open -b com.apple.terminal '\(selectedSite!.absolutePath)'") } } - @objc func openWithEditor(sender: EditorMenuItem) async { + @objc func openWithEditor(sender: EditorMenuItem) { guard let editor = sender.editor else { return } - await editor.openDirectory(file: selectedSite!.absolutePath) + editor.openDirectory(file: selectedSite!.absolutePath) } // MARK: - UI interaction From ae7e13de9b92a11682d2d2a63672b6025367ce03 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 12 Jan 2023 17:20:58 +0100 Subject: [PATCH 160/181] =?UTF-8?q?=F0=9F=94=A7=20Bump=20build=20for=20new?= =?UTF-8?q?=20pre-release=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 9221fca..24970bb 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2799,7 +2799,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1020; + CURRENT_PROJECT_VERSION = 1022; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2828,7 +2828,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1020; + CURRENT_PROJECT_VERSION = 1022; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -3056,7 +3056,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1020; + CURRENT_PROJECT_VERSION = 1022; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3166,7 +3166,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1020; + CURRENT_PROJECT_VERSION = 1022; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; From 3ce7e8f48b6b10ecf85fb1ce268927b8d85c8e08 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 13 Jan 2023 19:13:43 +0100 Subject: [PATCH 161/181] =?UTF-8?q?=F0=9F=94=A7=20Keep=20track=20of=20unsu?= =?UTF-8?q?pported=20(but=20installed)=20PHP=20versions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Core/Constants.swift | 13 +++++-- phpmon/Common/PHP/PHP Version/PhpEnv.swift | 36 +++++++++++-------- .../Versions/PhpVersionDetectionTest.swift | 3 +- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/phpmon/Common/Core/Constants.swift b/phpmon/Common/Core/Constants.swift index 1c595ba..7739fea 100644 --- a/phpmon/Common/Core/Constants.swift +++ b/phpmon/Common/Core/Constants.swift @@ -20,9 +20,18 @@ struct Constants { /** * The PHP versions supported by this application. - * Depends on what version of Valet is installed. + * Any other PHP versions are considered invalid. */ - static let ValetSupportedPhpVersionMatrix = [ + static let DetectedPhpVersions: Set = [ + "5.6", + "7.0", "7.1", "7.2", "7.3", "7.4", + "8.0", "8.1", "8.2", "8.3" + ] + + /** + The PHP versions supported by each version of Valet. + */ + static let ValetSupportedPhpVersionMatrix: [Int: Set] = [ 2: // Valet v2 has the broadest legacy support [ "5.6", diff --git a/phpmon/Common/PHP/PHP Version/PhpEnv.swift b/phpmon/Common/PHP/PHP Version/PhpEnv.swift index bbd78e8..55f939d 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnv.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnv.swift @@ -38,9 +38,12 @@ class PhpEnv { /** Whether the switcher is busy performing any actions. */ var isBusy: Bool = false - /** All available versions of PHP. */ + /** All versions of PHP that are currently supported. */ var availablePhpVersions: [String] = [] + /** All versions of PHP that are currently installed but not compatible. */ + var incompatiblePhpVersions: [String] = [] + /** Cached information about the PHP installations. */ var cachedPhpInstallations: [String: PhpInstallation] = [:] @@ -87,13 +90,14 @@ class PhpEnv { /** Detects which versions of PHP are installed. */ - public func detectPhpVersions() async -> [String] { + public func detectPhpVersions() async -> Set { let files = await Shell.pipe("ls \(Paths.optPath) | grep php@").out - var versionsOnly = await extractPhpVersions( - from: files.components(separatedBy: "\n"), - supported: Constants.ValetSupportedPhpVersionMatrix[Valet.shared.version.major] ?? [] - ) + let versions = await extractPhpVersions(from: files.components(separatedBy: "\n")) + + let supportedByValet = Constants.ValetSupportedPhpVersionMatrix[Valet.shared.version.major] ?? [] + + var supportedVersions = versions.intersection(supportedByValet) // Make sure the aliased version is detected // The user may have `php` installed, but not e.g. `php@8.0` @@ -101,13 +105,15 @@ class PhpEnv { let phpAlias = homebrewPackage.version // Avoid inserting a duplicate - if !versionsOnly.contains(phpAlias) && FileSystem.fileExists("\(Paths.optPath)/php/bin/php") { - versionsOnly.append(phpAlias) + if !supportedVersions.contains(phpAlias) && FileSystem.fileExists("\(Paths.optPath)/php/bin/php") { + supportedVersions.insert(phpAlias) } - Log.info("The PHP versions that were detected are: \(versionsOnly)") + availablePhpVersions = Array(supportedVersions) + incompatiblePhpVersions = Array(versions.subtracting(supportedByValet)) - availablePhpVersions = versionsOnly + Log.info("The PHP versions that were detected are: \(availablePhpVersions)") + Log.info("The PHP versions that were unsupported are: \(incompatiblePhpVersions)") var mappedVersions: [String: PhpInstallation] = [:] @@ -117,7 +123,7 @@ class PhpEnv { cachedPhpInstallations = mappedVersions - return versionsOnly + return supportedVersions } /** @@ -129,11 +135,11 @@ class PhpEnv { */ public func extractPhpVersions( from versions: [String], - supported: [String], checkBinaries: Bool = true, generateHelpers: Bool = true - ) async -> [String] { - var output: [String] = [] + ) async -> Set { + let supported = Constants.DetectedPhpVersions + var output: Set = [] versions.filter { (version) -> Bool in // Omit everything that doesn't start with php@ // (e.g. something-php@8.0 won't be detected) @@ -145,7 +151,7 @@ class PhpEnv { if !output.contains(version) && supported.contains(version) && (checkBinaries ? FileSystem.fileExists("\(Paths.optPath)/php@\(version)/bin/php") : true) { - output.append(version) + output.insert(version) } } diff --git a/tests/unit/Versions/PhpVersionDetectionTest.swift b/tests/unit/Versions/PhpVersionDetectionTest.swift index a137741..fc77d78 100644 --- a/tests/unit/Versions/PhpVersionDetectionTest.swift +++ b/tests/unit/Versions/PhpVersionDetectionTest.swift @@ -24,11 +24,10 @@ class PhpVersionDetectionTest: XCTestCase { "php@5.6", // should be omitted, not supported "php@5.4" // should be omitted, not supported ], - supported: ["7.0", "8.0", "8.1", "8.2"], checkBinaries: false, generateHelpers: false ) - XCTAssertEqual(outcome, ["8.0", "7.0"]) + XCTAssertEqual(outcome, ["8.0", "7.0", "5.6"]) } } From 020a0260f1ebe09454a777c845159bed527e95a6 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 13 Jan 2023 19:46:25 +0100 Subject: [PATCH 162/181] =?UTF-8?q?=E2=9C=A8=20Tell=20users=20about=20olde?= =?UTF-8?q?r=20unsupported=20PHP=20versions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/Menu/MainMenu.swift | 18 ++++++++++++++++++ phpmon/Domain/Menu/StatusMenu+Items.swift | 13 +++++++++++-- phpmon/Localizable.strings | 11 ++++++++++- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index 0dc8fef..cfd8ae8 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -109,6 +109,24 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate } } + @objc func showIncompatiblePhpVersionsAlert() { + Task { @MainActor in + BetterAlert().withInformation( + title: "startup.unsupported_versions_explanation.title".localized, + subtitle: "startup.unsupported_versions_explanation.subtitle".localized( + PhpEnv.shared.incompatiblePhpVersions + .map({ version in + return "• PHP \(version)" + }) + .joined(separator: "\n") + ), + description: "startup.unsupported_versions_explanation.desc".localized + ) + .withPrimary(text: "generic.ok".localized) + .show() + } + } + /** Reloads the menu in the background, using `asyncExecution`. */ @objc func reloadPhpMonitorMenuInBackground() { asyncExecution({ diff --git a/phpmon/Domain/Menu/StatusMenu+Items.swift b/phpmon/Domain/Menu/StatusMenu+Items.swift index 0d4c189..88147bc 100644 --- a/phpmon/Domain/Menu/StatusMenu+Items.swift +++ b/phpmon/Domain/Menu/StatusMenu+Items.swift @@ -30,7 +30,7 @@ extension StatusMenu { return } - if PhpEnv.shared.availablePhpVersions.isEmpty { return } + if PhpEnv.shared.availablePhpVersions.isEmpty && PhpEnv.shared.incompatiblePhpVersions.isEmpty { return } addSwitchToPhpMenuItems() self.addItem(NSMenuItem.separator()) @@ -50,7 +50,6 @@ extension StatusMenu { func addSwitchToPhpMenuItems() { var shortcutKey = 1 for index in (0.. Date: Fri, 13 Jan 2023 20:01:09 +0100 Subject: [PATCH 163/181] =?UTF-8?q?=F0=9F=91=8C=20Update=20support=20matri?= =?UTF-8?q?x?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Core/Constants.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/phpmon/Common/Core/Constants.swift b/phpmon/Common/Core/Constants.swift index 7739fea..ad3a85e 100644 --- a/phpmon/Common/Core/Constants.swift +++ b/phpmon/Common/Core/Constants.swift @@ -41,12 +41,14 @@ struct Constants { 3: // Valet v3 dropped support for v5.6 [ "7.0", "7.1", "7.2", "7.3", "7.4", - "8.0", "8.1", "8.2", "8.3" + "8.0", "8.1", "8.2", + "8.3" // dev ], - 4: // Valet v4 dropped support for Date: Mon, 16 Jan 2023 21:09:25 +0100 Subject: [PATCH 164/181] =?UTF-8?q?=F0=9F=91=8C=20Sort=20PHP=20versions,?= =?UTF-8?q?=20amend=20message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/PHP/PHP Version/PhpEnv.swift | 3 +++ phpmon/Domain/Menu/StatusMenu+Items.swift | 2 +- phpmon/Localizable.strings | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/phpmon/Common/PHP/PHP Version/PhpEnv.swift b/phpmon/Common/PHP/PHP Version/PhpEnv.swift index 55f939d..9f5b7bd 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnv.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnv.swift @@ -110,7 +110,10 @@ class PhpEnv { } availablePhpVersions = Array(supportedVersions) + .sorted(by: { $0.versionCompare($1) == .orderedDescending }) + incompatiblePhpVersions = Array(versions.subtracting(supportedByValet)) + .sorted(by: { $0.versionCompare($1) == .orderedDescending }) Log.info("The PHP versions that were detected are: \(availablePhpVersions)") Log.info("The PHP versions that were unsupported are: \(incompatiblePhpVersions)") diff --git a/phpmon/Domain/Menu/StatusMenu+Items.swift b/phpmon/Domain/Menu/StatusMenu+Items.swift index 88147bc..315da7f 100644 --- a/phpmon/Domain/Menu/StatusMenu+Items.swift +++ b/phpmon/Domain/Menu/StatusMenu+Items.swift @@ -49,7 +49,7 @@ extension StatusMenu { func addSwitchToPhpMenuItems() { var shortcutKey = 1 - for index in (0.. Date: Mon, 16 Jan 2023 21:10:58 +0100 Subject: [PATCH 165/181] =?UTF-8?q?=F0=9F=94=A7=20Bump=20build=20for=20new?= =?UTF-8?q?=20pre-release=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 24970bb..e878c5a 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2799,7 +2799,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1022; + CURRENT_PROJECT_VERSION = 1025; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2828,7 +2828,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1022; + CURRENT_PROJECT_VERSION = 1025; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -3056,7 +3056,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1022; + CURRENT_PROJECT_VERSION = 1025; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3166,7 +3166,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1022; + CURRENT_PROJECT_VERSION = 1025; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; From 35b19efc3e7680091588c9aa794940188d2fb00c Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 17 Jan 2023 18:05:22 +0100 Subject: [PATCH 166/181] =?UTF-8?q?=F0=9F=91=8C=20Use=20specific=20theme?= =?UTF-8?q?=20colors=20for=20services=20status?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StatusColorGreen.colorset/Contents.json | 38 +++++++++++++++++++ .../StatusColorRed.colorset/Contents.json | 38 +++++++++++++++++++ .../StatusColorYellow.colorset/Contents.json | 38 +++++++++++++++++++ .../Domain/App/Services/ServicesManager.swift | 6 +-- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 6 +-- 5 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 phpmon/Assets.xcassets/StatusColorGreen.colorset/Contents.json create mode 100644 phpmon/Assets.xcassets/StatusColorRed.colorset/Contents.json create mode 100644 phpmon/Assets.xcassets/StatusColorYellow.colorset/Contents.json diff --git a/phpmon/Assets.xcassets/StatusColorGreen.colorset/Contents.json b/phpmon/Assets.xcassets/StatusColorGreen.colorset/Contents.json new file mode 100644 index 0000000..fdb176a --- /dev/null +++ b/phpmon/Assets.xcassets/StatusColorGreen.colorset/Contents.json @@ -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 + } +} diff --git a/phpmon/Assets.xcassets/StatusColorRed.colorset/Contents.json b/phpmon/Assets.xcassets/StatusColorRed.colorset/Contents.json new file mode 100644 index 0000000..7274621 --- /dev/null +++ b/phpmon/Assets.xcassets/StatusColorRed.colorset/Contents.json @@ -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 + } +} diff --git a/phpmon/Assets.xcassets/StatusColorYellow.colorset/Contents.json b/phpmon/Assets.xcassets/StatusColorYellow.colorset/Contents.json new file mode 100644 index 0000000..2705f6e --- /dev/null +++ b/phpmon/Assets.xcassets/StatusColorYellow.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.180", + "green" : "0.841", + "red" : "1.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.426", + "green" : "0.809", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phpmon/Domain/App/Services/ServicesManager.swift b/phpmon/Domain/App/Services/ServicesManager.swift index 579b78d..e0b3a54 100644 --- a/phpmon/Domain/App/Services/ServicesManager.swift +++ b/phpmon/Domain/App/Services/ServicesManager.swift @@ -66,7 +66,7 @@ class ServicesManager: ObservableObject { public var statusColor: Color { if self.services.isEmpty || !self.firstRunComplete { - return .yellow + return Color("StatusColorYellow") } let statuses = self.services[0...2].map { $0.status } @@ -74,10 +74,10 @@ class ServicesManager: ObservableObject { if statuses.contains(.missing) || statuses.contains(.inactive) || statuses.contains(.error) { - return .red + return Color("StatusColorRed") } - return .green + return Color("StatusColorGreen") } /** diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 99371cd..b08fc6c 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -143,7 +143,7 @@ struct ServiceView: View { } label: { Text("E") .frame(width: 12.0, height: 12.0) - .foregroundColor(Color("IconColorRed")) + .foregroundColor(Color("StatusColorRed")) } .focusable(false) .frame(width: 25, height: 25) @@ -163,8 +163,8 @@ struct ServiceView: View { .frame(width: 12.0, height: 12.0) .foregroundColor( service.status == .active - ? Color("IconColorGreen") - : Color("IconColorRed") + ? Color("StatusColorGreen") + : Color("StatusColorRed") ) } .focusable(false) From a0d80423e9c2f0309af996bc7ab3f00f4208e3aa Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 18 Jan 2023 19:10:43 +0100 Subject: [PATCH 167/181] =?UTF-8?q?=F0=9F=93=9D=20Update=20documentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updated README * Updated SECURITY * Revert the minimum Valet version to v2.16 --- README.md | 34 +++++++++++++++++++++--------- SECURITY.md | 4 +++- phpmon/Common/Core/Constants.swift | 4 ++-- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 222e1a0..392146d 100644 --- a/README.md +++ b/README.md @@ -27,22 +27,36 @@ PHP Monitor is a universal application that runs natively on Apple Silicon **and * macOS 11 Big Sur or later * Homebrew is installed in `/usr/local/homebrew` or `/opt/homebrew` * Homebrew `php` formula is installed -* Laravel Valet 3 recommended (but compatible with Valet 2) +* Laravel Valet (works with Valet v2, v3 and v4) _You may need to update your Valet installation to keep everything working if a major version update of PHP has been released. You can do this by running `composer global update && valet install`. Some features are not supported when running Valet 2._ +For more information, please see [SECURITY.md](./SECURITY.md) to find out which version of the app is currently supported. + ## 🚀 How to install -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. +Again, make sure you have **[Laravel Valet](https://laravel.com/docs/master/valet)** installed first: + +```sh +composer global require laravel/valet +valet install +valet trust +``` + +Once that's done, you can install PHP Monitor via Homebrew (recommended), or (alternatively) you may download the latest release on GitHub. To install via Homebrew, run: - brew tap nicoverbruggen/homebrew-cask - brew install --cask phpmon +```sh +brew tap nicoverbruggen/homebrew-cask +brew install --cask phpmon +``` To upgrade your existing installation, run: - brew upgrade phpmon +```sh +brew upgrade phpmon +``` (You may need to run `brew update` or `brew update-reset` first in order to update the cask file if you ran a Homebrew operation recently.) @@ -127,12 +141,12 @@ brew tap shivammathur/php You may find that this tap is already in use: if you've used Valet before, it automatically uses this tap for legacy versions of PHP. -You can then install those older versions: - ```sh -brew install php@7.0 -brew install php@7.1 -... +brew install shivammathur/php/php@7.4 +brew install shivammathur/php/php@7.3 +brew install shivammathur/php/php@7.2 +brew install shivammathur/php/php@7.1 +brew install shivammathur/php/php@7.0 ``` **Always make sure to restart PHP Monitor after installing or upgrading PHP versions!** diff --git a/SECURITY.md b/SECURITY.md index 4543d3e..3e50d8b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,7 +6,9 @@ Generally speaking, only the latest version of **PHP Monitor** is supported, exc | Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Recommended Valet Version | | ------- | ------------- | ------------------ | ----- | ----- | ----- | ---- -| 6.x | ✅ Universal binary | ✅ Yes | Big Sur (11.0)
Monterey (12.0)
Ventura (13.0) | macOS 11+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)
PHP 7.0—PHP 8.2 (w/ Valet 3.x)
PHP 7.4-PHP 8.2 (w/ Valet 4.x) | 3.0 or higher recommended
2.16.2 minimum | +| 5.7 | ✅ Universal binary | ✅ Yes | Big Sur (11.0)
Monterey (12.0)
Ventura (13.0) | macOS 11+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)
PHP 7.0—PHP 8.2 (w/ Valet 3.x)
PHP 7.4-PHP 8.2 (w/ Valet 4.x*) | 3.0 or higher recommended
2.16.2 minimum | + +(*) Preliminary listing. Valet 4 hasn't been released yet and the versions of PHP Valet can work with might still change. ## Legacy versions diff --git a/phpmon/Common/Core/Constants.swift b/phpmon/Common/Core/Constants.swift index ad3a85e..1b069c0 100644 --- a/phpmon/Common/Core/Constants.swift +++ b/phpmon/Common/Core/Constants.swift @@ -14,9 +14,9 @@ struct Constants { If the installed version is older, a notification will be shown every time the app launches (with a recommendation to upgrade). - See also: https://github.com/laravel/valet/releases/tag/v3.1.10 + See also: https://github.com/laravel/valet/releases/tag/v2.16.2 */ - static let MinimumRecommendedValetVersion = "3.1.10" + static let MinimumRecommendedValetVersion = "2.16.2" /** * The PHP versions supported by this application. From 601432044188b32ea3681cd0ecc32fbbaccae380 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 18 Jan 2023 19:18:25 +0100 Subject: [PATCH 168/181] =?UTF-8?q?=F0=9F=93=9D=20Update=20information=20a?= =?UTF-8?q?bout=20supported=20PHP=20versions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 392146d..14a8148 100644 --- a/README.md +++ b/README.md @@ -93,36 +93,14 @@ If you're still having issues, here's a few common questions & answers, as well
Which versions of PHP are supported? -The following versions of PHP are officially supported: +All stable and supported PHP versions are also supported by PHP Monitor. However, depending on which version of Valet you have installed, which versions of PHP that are made available for switching purposes may differ. -
    -
  • PHP 7.4
  • -
  • PHP 8.0
  • -
  • PHP 8.1
  • -
  • PHP 8.2
  • -
- -The following versions have some support via backport and/or dev version: - -
    -
  • PHP 5.6 (Valet 2 only)
  • -
  • PHP 7.0 (Valet 2 and 3 only)
  • -
  • PHP 7.1 (Valet 2 and 3 only)
  • -
  • PHP 7.2 (Valet 2 and 3 only)
  • -
  • PHP 7.3 (Valet 2 and 3 only)
  • -
- -Additionally, the following dev version is also available: - -
    -
  • PHP 8.3-dev (experimental)
  • -
- -For more details, consult the [constants file](https://github.com/nicoverbruggen/phpmon/blob/main/phpmon/Common/Core/Constants.swift#L16) file to see which versions are supported. +> **Note** +> If you have versions of PHP installed that can be detected by PHP Monitor but is *not* supported by the currently active version of Valet, you will be alerted by an item in the menu with an exclamation mark emoji. (⚠️) Backports are available via [this tap](https://github.com/shivammathur/homebrew-php). For more information about those backports, please see the next FAQ entry. -For maximum compatibility with older PHP versions, you may wish to keep using Valet 2 or 3. +For maximum compatibility with older PHP versions, you may wish to keep using Valet 2 or 3. For more information, please see [SECURITY.md](./SECURITY.md) to find out which versions of PHP are supported with different versions of Valet.
From e509f6b59d9618aab955877c602aa286a1dcd14d Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 18 Jan 2023 19:22:45 +0100 Subject: [PATCH 169/181] =?UTF-8?q?=F0=9F=93=9D=20TL=20QC=20Pass=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Localizable.strings | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 503998c..642b919 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -10,7 +10,7 @@ "mi_busy" = "PHP Monitor is busy..."; "mi_unsure" = "We are not sure what version of PHP you are running."; -"mi_php_version" = "Global PHP version: PHP"; +"mi_php_version" = "Global version: PHP"; "mi_php_switch" = "Switch to PHP"; "mi_php_unsupported" = "Some installed PHP versions are not displayed."; "mi_php_broken_1" = "Oof! It appears your PHP installation is broken..."; @@ -562,13 +562,13 @@ If you are seeing this message but are confused why this folder has gone missing "startup.version_mismatch.button_stay" = "Keep using PHP %@"; // Warning about unsupported PHP versions -"startup.unsupported_versions_explanation.title" = "One or more PHP installed versions are not supported."; -"startup.unsupported_versions_explanation.subtitle" = "The following versions of PHP are installed on your system but are not supported by this version of Valet. +"startup.unsupported_versions_explanation.title" = "PHP installation(s) not supported by Valet detected!"; +"startup.unsupported_versions_explanation.subtitle" = "The following PHP versions are installed on your system but are not supported by this version of Valet. %@ Valet might break if you link these PHP versions so PHP Monitor won't let you switch to them."; -"startup.unsupported_versions_explanation.desc" = "If you need support for older versions of PHP, you may need to downgrade to an older versions of Valet. Otherwise, it might be a good idea to uninstall any outdated versions that are not in use. This message will only be removed after restarting PHP Monitor."; +"startup.unsupported_versions_explanation.desc" = "If you need support for older versions of PHP, you may need to downgrade to an older versions of Valet. Otherwise, it might be a good idea to uninstall any outdated versions that are not in use. It can also be that this version of Valet is too old. This message will only be removed after restarting PHP Monitor."; // Sponsor encouragement "startup.sponsor_encouragement.title" = "If PHP Monitor has been useful to you or your company, please consider leaving a tip."; From b6b1174ca335233c69144c4f0f1b3d8e0946015b Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 18 Jan 2023 19:56:54 +0100 Subject: [PATCH 170/181] =?UTF-8?q?=F0=9F=91=8C=20More=20compact=20`Servic?= =?UTF-8?q?esView`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Assets.xcassets/AppColor.colorset/Contents.json | 2 +- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/phpmon/Assets.xcassets/AppColor.colorset/Contents.json b/phpmon/Assets.xcassets/AppColor.colorset/Contents.json index 4759aba..4fc8e42 100644 --- a/phpmon/Assets.xcassets/AppColor.colorset/Contents.json +++ b/phpmon/Assets.xcassets/AppColor.colorset/Contents.json @@ -7,7 +7,7 @@ "alpha" : "1.000", "blue" : "0.988", "green" : "0.580", - "red" : "0.277" + "red" : "0.278" } }, "idiom" : "universal" diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index b08fc6c..8f71fc6 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -35,8 +35,8 @@ struct ServicesView: View { var perRow: Int var rowCount: Int var rowSpacing: Int = 0 - var rowHeight: Int = 50 - var statusHeight: Int = 30 + var rowHeight: Int = 48 + var statusHeight: Int = 20 var allRowHeight: CGFloat var height: CGFloat @@ -66,15 +66,14 @@ struct ServicesView: View { } .frame(height: CGFloat(self.height - CGFloat(self.statusHeight))) .frame(maxWidth: .infinity, alignment: .center) - // .background(Color.red) VStack(alignment: .center) { HStack { Circle() - .frame(width: 12, height: 12) + .frame(width: 10, height: 10) .foregroundColor(self.manager.statusColor) Text(self.manager.statusMessage) - .font(.system(size: 12)) + .font(.system(size: 11)) if self.manager.statusColor == .red { HelpButton { let type = manager.hasError @@ -108,8 +107,6 @@ struct ServiceView: View { Text(service.name.uppercased()) .font(.system(size: 10)) .frame(minWidth: 70, alignment: .center) - .padding(.top, 4) - .padding(.bottom, 2) if isBusy { ProgressView() .scaleEffect(x: 0.4, y: 0.4, anchor: .center) @@ -163,7 +160,7 @@ struct ServiceView: View { .frame(width: 12.0, height: 12.0) .foregroundColor( service.status == .active - ? Color("StatusColorGreen") + ? Color.primary : Color("StatusColorRed") ) } From 450d7ec001bfddf44b96262d8d0e03e5f14115c5 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 18 Jan 2023 20:27:02 +0100 Subject: [PATCH 171/181] =?UTF-8?q?=F0=9F=91=8C=20Fixed=20onboarding=20for?= =?UTF-8?q?=20initial=20launch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DEVELOPER.md | 10 ++++++++++ phpmon/Common/PHP/Homebrew/HomebrewService.swift | 4 ++-- phpmon/Domain/App/Services/FakeServicesManager.swift | 7 ++++++- phpmon/Domain/Menu/MainMenu+Startup.swift | 2 +- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/DEVELOPER.md b/DEVELOPER.md index f8a7396..ff43b26 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -14,6 +14,16 @@ It also automatically runs when you try to build the project. You'll get a warni swiftlint --fix ``` +## ⚙️ Preferences + +You can find the persisted configuration file in `~/Library/Preferences/com.nicoverbruggen.phpmon.plist` + +These values are cached by the OS. You can clear this cache by running: + +``` +defaults delete com.nicoverbruggen.phpmon && killall cfprefsd +``` + ## 🔧 Build instructions build button in Xcode diff --git a/phpmon/Common/PHP/Homebrew/HomebrewService.swift b/phpmon/Common/PHP/Homebrew/HomebrewService.swift index 028dfe0..d46cbc7 100644 --- a/phpmon/Common/PHP/Homebrew/HomebrewService.swift +++ b/phpmon/Common/PHP/Homebrew/HomebrewService.swift @@ -44,7 +44,7 @@ final class HomebrewService: Sendable, Decodable { /** Dummy data for preview purposes. */ - public static func dummy(named service: String, enabled: Bool) -> HomebrewService { + public static func dummy(named service: String, enabled: Bool, status: String? = nil) -> HomebrewService { return HomebrewService( name: service, service_name: service, @@ -52,7 +52,7 @@ final class HomebrewService: Sendable, Decodable { loaded: enabled, pid: nil, user: nil, - status: nil, + status: status, log_path: nil, error_log_path: nil ) diff --git a/phpmon/Domain/App/Services/FakeServicesManager.swift b/phpmon/Domain/App/Services/FakeServicesManager.swift index e522ac3..b18998f 100644 --- a/phpmon/Domain/App/Services/FakeServicesManager.swift +++ b/phpmon/Domain/App/Services/FakeServicesManager.swift @@ -41,9 +41,14 @@ class FakeServicesManager: ServicesManager { private func reapplyServices() { let services = self.formulae.map { + let dummy = HomebrewService.dummy( + named: $0.name, + enabled: self.fixedStatus == .active, + status: self.fixedStatus == .error ? "error" : nil + ) let wrapper = Service( formula: $0, - service: HomebrewService.dummy(named: $0.name, enabled: self.fixedStatus == .active) + service: dummy ) return wrapper } diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 03a4545..e51d2e9 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -111,7 +111,7 @@ extension MainMenu { #endif // Present first launch screen if needed - if Stats.successfulLaunchCount == 0 && !isRunningSwiftUIPreview { + if Stats.successfulLaunchCount == 1 && !isRunningSwiftUIPreview { Log.info("Should present the first launch screen!") Task { @MainActor in OnboardingWindowController.show() From 2d59b8c6e891e62803b19cbb0a9a7faffc4b76b4 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 18 Jan 2023 20:42:24 +0100 Subject: [PATCH 172/181] =?UTF-8?q?=F0=9F=91=8C=20Improve=20onboarding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/Menu/MainMenu+Startup.swift | 24 +++++++------------ .../SwiftUI/Onboarding/OnboardingView.swift | 4 +++- phpmon/Localizable.strings | 4 ++-- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index e51d2e9..2d9dd06 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -101,25 +101,19 @@ extension MainMenu { // Start the background refresh timer startSharedTimer() - // Update the stats - Stats.incrementSuccessfulLaunchCount() - - #if SPONSOR - Log.info("Sponsor encouragement messages are omitted in SE builds.") - #else + if !isRunningSwiftUIPreview { + Stats.incrementSuccessfulLaunchCount() Stats.evaluateSponsorMessageShouldBeDisplayed() - #endif - // Present first launch screen if needed - if Stats.successfulLaunchCount == 1 && !isRunningSwiftUIPreview { - Log.info("Should present the first launch screen!") - Task { @MainActor in - OnboardingWindowController.show() + if Stats.successfulLaunchCount == 1 { + Log.info("Should present the first launch screen!") + Task { @MainActor in + OnboardingWindowController.show() + } } - } - // Check for updates - await AppUpdateChecker.checkIfNewerVersionIsAvailable() + await AppUpdateChecker.checkIfNewerVersionIsAvailable() + } // Check if the linked version has changed between launches of phpmon Stats.evaluateLastLinkedPhpVersion() diff --git a/phpmon/Domain/SwiftUI/Onboarding/OnboardingView.swift b/phpmon/Domain/SwiftUI/Onboarding/OnboardingView.swift index 80df681..36ff786 100644 --- a/phpmon/Domain/SwiftUI/Onboarding/OnboardingView.swift +++ b/phpmon/Domain/SwiftUI/Onboarding/OnboardingView.swift @@ -101,11 +101,13 @@ struct OnboardingView: View { } VStack { Text("onboarding.tour.once".localized) + .fixedSize(horizontal: false, vertical: true) .font(.subheadline) .foregroundColor(.gray) .padding(.top, 5) .padding(.bottom, 5) - .lineLimit(5) + .lineLimit(3) + .frame(height: 35) Button("onboarding.tour.close".localized) { App.shared.onboardingWindowController?.close() } diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 642b919..7e9cb9b 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -658,7 +658,7 @@ COMMON TROUBLESHOOTING TIPS "onboarding.title" = "Welcome Tour"; "onboarding.welcome" = "Welcome to PHP Monitor!"; -"onboarding.explore" = "Learn more about some of the features that PHP Monitor has to offer. You can find a more comprehensive list of features on GitHub."; +"onboarding.explore" = "Congrats, you now have access to PHP Monitor's entire suite of functionality. You can learn more about some of the features that PHP Monitor has to offer on this screen."; "onboarding.tour.menu_bar.title" = "Power In Your Menu Bar"; "onboarding.tour.menu_bar" = "PHP Monitor lives in your menu bar. From this menu, you can access most of PHP Monitor's key functionality, including switching the globally linked PHP version, locating config files, and much more."; "onboarding.tour.faq_hint" = "I recommend that you check out the [README](https://github.com/nicoverbruggen/phpmon/blob/main/README.md) on GitHub: it contains a comprehensive FAQ with various tips and common questions and answers."; @@ -668,5 +668,5 @@ COMMON TROUBLESHOOTING TIPS "onboarding.tour.domains" = "By opening the Domains window via the menu bar item, you can view which domains are linked and parked, as well as active nginx proxies."; "onboarding.tour.isolation.title" = "Isolate Domains"; "onboarding.tour.isolation" = "If you have Valet 3 installed, you can even use domain isolation by right-clicking on a given domain in the Domains window. This allows you to pick a specific version of PHP to use for that domain, and that domain only."; -"onboarding.tour.once" = "You will only see the Welcome Tour once. You can re-open the Welcome Tour later via the menu bar icon (under First Aid & Services)."; +"onboarding.tour.once" = "You will only see the Welcome Tour once. You can re-open the Welcome Tour later via the menu bar icon (available in the menu, under First Aid & Services)."; "onboarding.tour.close" = "Close Tour"; From 18b62ecc3f5117c88d8bbdfdcbfcf78d810ad814 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 19 Jan 2023 17:30:21 +0100 Subject: [PATCH 173/181] =?UTF-8?q?=F0=9F=91=8C=20Adjust=20SECURITY=20and?= =?UTF-8?q?=20support=20matrix=20for=20Valet=204?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit ensures that PHP Monitor knows about which versions of PHP are supported by the upcoming Valet 4.0: PHP 7.1 and higher. Ensures compatibility with https://github.com/laravel/valet/pull/1318 --- SECURITY.md | 2 +- phpmon/Common/Core/Constants.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 3e50d8b..2255dca 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,7 +6,7 @@ Generally speaking, only the latest version of **PHP Monitor** is supported, exc | Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Recommended Valet Version | | ------- | ------------- | ------------------ | ----- | ----- | ----- | ---- -| 5.7 | ✅ Universal binary | ✅ Yes | Big Sur (11.0)
Monterey (12.0)
Ventura (13.0) | macOS 11+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)
PHP 7.0—PHP 8.2 (w/ Valet 3.x)
PHP 7.4-PHP 8.2 (w/ Valet 4.x*) | 3.0 or higher recommended
2.16.2 minimum | +| 5.7 | ✅ Universal binary | ✅ Yes | Big Sur (11.0)
Monterey (12.0)
Ventura (13.0) | macOS 11+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)
PHP 7.0—PHP 8.2 (w/ Valet 3.x)
PHP 7.1-PHP 8.2 (w/ Valet 4.x*) | 3.0 or higher recommended
2.16.2 minimum | (*) Preliminary listing. Valet 4 hasn't been released yet and the versions of PHP Valet can work with might still change. diff --git a/phpmon/Common/Core/Constants.swift b/phpmon/Common/Core/Constants.swift index 1b069c0..00ef039 100644 --- a/phpmon/Common/Core/Constants.swift +++ b/phpmon/Common/Core/Constants.swift @@ -44,9 +44,9 @@ struct Constants { "8.0", "8.1", "8.2", "8.3" // dev ], - 4: // Valet v4 dropped support for v7.0-v7.3 + 4: // Valet v4 dropped support for v7.0 [ - "7.4", + "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3" // dev ] From 2fa50a7dc4875cc30c45cf57c45c226d42454715 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 19 Jan 2023 17:33:55 +0100 Subject: [PATCH 174/181] =?UTF-8?q?=F0=9F=94=A7=20Bump=20build=20for=20new?= =?UTF-8?q?=20pre-release=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index e878c5a..02fec9c 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2799,7 +2799,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1025; + CURRENT_PROJECT_VERSION = 1026; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2828,7 +2828,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1025; + CURRENT_PROJECT_VERSION = 1026; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -3056,7 +3056,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1025; + CURRENT_PROJECT_VERSION = 1026; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3166,7 +3166,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1025; + CURRENT_PROJECT_VERSION = 1026; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; From d3b1afe9fd16a3a51e8f03382907ed3e4a5cf985 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 19 Jan 2023 17:50:13 +0100 Subject: [PATCH 175/181] =?UTF-8?q?=F0=9F=90=9B=20Fix=20bug=20related=20to?= =?UTF-8?q?=20"=3F"=20not=20showing=20up?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 8 ++++---- phpmon/Domain/SwiftUI/Common/HelpButton.swift | 11 ++++++++++- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 02fec9c..99832e6 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2799,7 +2799,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1026; + CURRENT_PROJECT_VERSION = 1027; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2828,7 +2828,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1026; + CURRENT_PROJECT_VERSION = 1027; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -3056,7 +3056,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1026; + CURRENT_PROJECT_VERSION = 1027; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3166,7 +3166,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1026; + CURRENT_PROJECT_VERSION = 1027; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; diff --git a/phpmon/Domain/SwiftUI/Common/HelpButton.swift b/phpmon/Domain/SwiftUI/Common/HelpButton.swift index 8fea3d0..5c9dc85 100644 --- a/phpmon/Domain/SwiftUI/Common/HelpButton.swift +++ b/phpmon/Domain/SwiftUI/Common/HelpButton.swift @@ -14,8 +14,17 @@ struct HelpButton: View { var body: some View { Button(action: action, label: { - Text("?").font(.system(size: 12, weight: .medium)) + ZStack { + Circle() + .strokeBorder(Color(NSColor.separatorColor), lineWidth: 0.5) + .background(Circle().foregroundColor(Color(NSColor.controlColor)).opacity(0.7)) + .shadow(color: Color(NSColor.separatorColor).opacity(0.3), radius: 1) + .frame(width: 14, height: 14) + Text("?").font(.system(size: 12, weight: .medium)) + .foregroundColor(Color(NSColor.labelColor)) + } }) + .buttonStyle(BorderlessButtonStyle()) .focusable(false) } diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 8f71fc6..b9fcd13 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -74,7 +74,7 @@ struct ServicesView: View { .foregroundColor(self.manager.statusColor) Text(self.manager.statusMessage) .font(.system(size: 11)) - if self.manager.statusColor == .red { + if self.manager.statusColor == Color("StatusColorRed") { HelpButton { let type = manager.hasError ? "key_service_has_error" From 93790f39513a785347be3fdd0de8198c98896c08 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 19 Jan 2023 18:09:42 +0100 Subject: [PATCH 176/181] =?UTF-8?q?=F0=9F=91=8C=20Update=20copyright=20mes?= =?UTF-8?q?sage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 2 +- phpmon/Common/Command/ActiveCommand.swift | 2 +- phpmon/Common/Command/CommandProtocol.swift | 2 +- phpmon/Common/Command/RealCommand.swift | 2 +- phpmon/Common/Core/Actions.swift | 2 +- phpmon/Common/Core/Constants.swift | 2 +- phpmon/Common/Core/Events.swift | 2 +- phpmon/Common/Core/Helpers.swift | 2 +- phpmon/Common/Core/Homebrew.swift | 2 +- phpmon/Common/Core/Logger.swift | 2 +- phpmon/Common/Core/Paths.swift | 2 +- phpmon/Common/Core/Process.swift | 2 +- phpmon/Common/Errors/AlertableError.swift | 2 +- phpmon/Common/Errors/Errors.swift | 2 +- phpmon/Common/Extensions/ArrayExtension.swift | 2 +- phpmon/Common/Extensions/DataExtension.swift | 2 +- phpmon/Common/Extensions/DateExtension.swift | 2 +- phpmon/Common/Extensions/DictionaryExtension.swift | 2 +- phpmon/Common/Extensions/NSMenuExtension.swift | 2 +- phpmon/Common/Extensions/NSMenuItemExtension.swift | 2 +- phpmon/Common/Extensions/NSWindowExtension.swift | 2 +- phpmon/Common/Extensions/StringExtension.swift | 2 +- phpmon/Common/Extensions/TimeIntervalExtension.swift | 2 +- phpmon/Common/Extensions/XibLoadable.swift | 2 +- phpmon/Common/Filesystem/ActiveFileSystem.swift | 2 +- phpmon/Common/Filesystem/FileSystemProtocol.swift | 2 +- phpmon/Common/Filesystem/RealFileSystem.swift | 2 +- phpmon/Common/Helpers/Alert.swift | 2 +- phpmon/Common/Helpers/Application.swift | 2 +- phpmon/Common/Helpers/LocalNotification.swift | 2 +- phpmon/Common/Helpers/MenuBarImageGenerator.swift | 2 +- phpmon/Common/Helpers/PMWindowController.swift | 2 +- phpmon/Common/Helpers/System.swift | 2 +- phpmon/Common/Helpers/VersionExtractor.swift | 2 +- phpmon/Common/Helpers/WIP.swift | 2 +- phpmon/Common/PHP/ActivePhpInstallation.swift | 2 +- phpmon/Common/PHP/Extensions/Xdebug.swift | 2 +- phpmon/Common/PHP/Homebrew/HomebrewPackage.swift | 2 +- phpmon/Common/PHP/Homebrew/HomebrewService.swift | 2 +- phpmon/Common/PHP/PHP Version/PhpEnv.swift | 2 +- phpmon/Common/PHP/PHP Version/PhpHelper.swift | 2 +- phpmon/Common/PHP/PHP Version/VersionNumber.swift | 2 +- phpmon/Common/PHP/PhpConfigurationFile.swift | 2 +- phpmon/Common/PHP/PhpExtension.swift | 2 +- phpmon/Common/PHP/PhpInstallation.swift | 2 +- phpmon/Common/PHP/Switcher/InternalSwitcher.swift | 2 +- phpmon/Common/PHP/Switcher/PhpSwitcher.swift | 2 +- phpmon/Common/Protocols/CreatedFromFile.swift | 2 +- phpmon/Common/Shell/ActiveShell.swift | 2 +- phpmon/Common/Shell/RealShell.swift | 2 +- phpmon/Common/Shell/ShellProtocol.swift | 2 +- phpmon/Common/Testables/TestableCommand.swift | 2 +- phpmon/Common/Testables/TestableConfiguration.swift | 2 +- phpmon/Common/Testables/TestableFileSystem.swift | 2 +- phpmon/Common/Testables/TestableShell.swift | 2 +- phpmon/Domain/App/App+ActivationPolicy.swift | 2 +- phpmon/Domain/App/App+GlobalHotkey.swift | 2 +- phpmon/Domain/App/App.swift | 2 +- phpmon/Domain/App/AppDelegate+InterApp.swift | 2 +- phpmon/Domain/App/AppDelegate+MenuOutlets.swift | 2 +- phpmon/Domain/App/AppDelegate+Notifications.swift | 2 +- phpmon/Domain/App/AppDelegate.swift | 2 +- phpmon/Domain/App/AppUpdateChecker.swift | 2 +- phpmon/Domain/App/AppVersion.swift | 2 +- phpmon/Domain/App/EnvironmentCheck.swift | 2 +- phpmon/Domain/App/EnvironmentManager.swift | 2 +- phpmon/Domain/App/InterAppHandler.swift | 2 +- phpmon/Domain/App/Services/FakeServicesManager.swift | 2 +- phpmon/Domain/App/Services/Service.swift | 2 +- phpmon/Domain/App/Services/ServicesManager.swift | 2 +- phpmon/Domain/App/Services/ValetServicesManager.swift | 2 +- phpmon/Domain/App/Startup.swift | 2 +- phpmon/Domain/DomainList/AddProxyVC.swift | 2 +- phpmon/Domain/DomainList/AddSiteVC.swift | 2 +- phpmon/Domain/DomainList/Cells/DomainListCellProtocol.swift | 2 +- phpmon/Domain/DomainList/Cells/DomainListKindCell.swift | 2 +- phpmon/Domain/DomainList/Cells/DomainListNameCell.swift | 2 +- phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift | 2 +- phpmon/Domain/DomainList/Cells/DomainListTLSCell.swift | 2 +- phpmon/Domain/DomainList/Cells/DomainListTypeCell.swift | 2 +- phpmon/Domain/DomainList/DomainListVC+Actions.swift | 2 +- phpmon/Domain/DomainList/DomainListVC+ContextMenu.swift | 2 +- phpmon/Domain/DomainList/DomainListVC.swift | 2 +- phpmon/Domain/DomainList/DomainListWindowController.swift | 2 +- phpmon/Domain/DomainList/PMTableView.swift | 2 +- phpmon/Domain/DomainList/SelectionVC.swift | 2 +- phpmon/Domain/Integrations/Composer/ComposerJson.swift | 2 +- phpmon/Domain/Integrations/Composer/ComposerWindow.swift | 2 +- phpmon/Domain/Integrations/Composer/PhpFrameworks.swift | 2 +- phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift | 2 +- phpmon/Domain/Integrations/Nginx/NginxConfigurationFile.swift | 2 +- .../Domain/Integrations/Valet/Domains/FakeValetInteractor.swift | 2 +- phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift | 2 +- phpmon/Domain/Integrations/Valet/Domains/ValetListable.swift | 2 +- phpmon/Domain/Integrations/Valet/Proxies/FakeValetProxy.swift | 2 +- phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift | 2 +- phpmon/Domain/Integrations/Valet/Scanners/DomainScanner.swift | 2 +- .../Domain/Integrations/Valet/Scanners/FakeDomainScanner.swift | 2 +- .../Domain/Integrations/Valet/Scanners/ValetDomainScanner.swift | 2 +- phpmon/Domain/Integrations/Valet/Scanners/ValetScanners.swift | 2 +- phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift | 2 +- phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift | 2 +- phpmon/Domain/Integrations/Valet/Valet.swift | 2 +- phpmon/Domain/Menu/MainMenu+Actions.swift | 2 +- phpmon/Domain/Menu/MainMenu+Async.swift | 2 +- phpmon/Domain/Menu/MainMenu+FixMyValet.swift | 2 +- phpmon/Domain/Menu/MainMenu+Startup.swift | 2 +- phpmon/Domain/Menu/MainMenu+Switcher.swift | 2 +- phpmon/Domain/Menu/MainMenu.swift | 2 +- phpmon/Domain/Menu/StatusMenu+Items.swift | 2 +- phpmon/Domain/Menu/StatusMenu.swift | 2 +- phpmon/Domain/Notice/BetterAlert.swift | 2 +- phpmon/Domain/Notice/BetterAlertVC.swift | 2 +- phpmon/Domain/Onboarding/OnboardingWindowController.swift | 2 +- phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift | 2 +- phpmon/Domain/Preferences/CustomPrefs.swift | 2 +- .../Domain/Preferences/Keybinds/GlobalKeybindPreference.swift | 2 +- phpmon/Domain/Preferences/Keys.swift | 2 +- phpmon/Domain/Preferences/MenuBarIcons.swift | 2 +- phpmon/Domain/Preferences/PreferenceName.swift | 2 +- phpmon/Domain/Preferences/Preferences.swift | 2 +- .../Domain/Preferences/PreferencesWindowController+Hotkey.swift | 2 +- phpmon/Domain/Preferences/PreferencesWindowController.swift | 2 +- phpmon/Domain/Preferences/PrefsVC.swift | 2 +- phpmon/Domain/Preferences/Stats.swift | 2 +- phpmon/Domain/Preferences/Views/CheckboxPreferenceView.swift | 2 +- phpmon/Domain/Preferences/Views/HotkeyPreferenceView.swift | 2 +- phpmon/Domain/Preferences/Views/SelectPreferenceView.swift | 2 +- phpmon/Domain/Presets/Preset.swift | 2 +- phpmon/Domain/Presets/PresetHelper.swift | 2 +- phpmon/Domain/Progress/ProgressVC.swift | 2 +- phpmon/Domain/Progress/TerminalProgressWindowController.swift | 2 +- phpmon/Domain/SwiftUI/Common/SwiftUIHelper.swift | 2 +- phpmon/Domain/SwiftUI/Domains/NoDomainResultsView.swift | 2 +- phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift | 2 +- phpmon/Domain/SwiftUI/Menu/HeaderView.swift | 2 +- phpmon/Domain/SwiftUI/Menu/SectionHeaderView.swift | 2 +- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 2 +- phpmon/Domain/SwiftUI/Menu/StatsView.swift | 2 +- phpmon/Domain/SwiftUI/Onboarding/OnboardingView.swift | 2 +- phpmon/Domain/SwiftUI/Warning/NoWarningsView.swift | 2 +- phpmon/Domain/SwiftUI/Warning/WarningListView.swift | 2 +- phpmon/Domain/SwiftUI/Warning/WarningView.swift | 2 +- phpmon/Domain/Warnings/Warning.swift | 2 +- phpmon/Domain/Warnings/WarningManager.swift | 2 +- phpmon/Domain/Warnings/WarningsWindowController.swift | 2 +- phpmon/Domain/Watcher/App+ConfigWatch.swift | 2 +- phpmon/Domain/Watcher/PhpConfigWatcher.swift | 2 +- phpmon/Localizable.strings | 2 +- tests/Shared/TestableConfigurations.swift | 2 +- tests/Shared/Utility.swift | 2 +- tests/Shared/XCPMApplication.swift | 2 +- tests/feature/FeatureTestCase.swift | 2 +- tests/feature/InternalSwitcherTest.swift | 2 +- tests/ui/DomainsListTest.swift | 2 +- tests/ui/StartupTest.swift | 2 +- tests/ui/UITestCase.swift | 2 +- tests/unit/Commands/CommandTest.swift | 2 +- tests/unit/Parsers/HomebrewPackageTest.swift | 2 +- tests/unit/Parsers/NginxConfigurationTest.swift | 2 +- tests/unit/Parsers/PhpConfigurationTest.swift | 2 +- tests/unit/Parsers/PhpExtensionTest.swift | 2 +- tests/unit/Parsers/ValetConfigurationTest.swift | 2 +- tests/unit/Testables/Filesystem/RealFileSystemTest.swift | 2 +- tests/unit/Testables/Filesystem/TestableFileSystemTest.swift | 2 +- tests/unit/Testables/Shell/RealShellTest.swift | 2 +- tests/unit/Testables/Shell/TestableShellTest.swift | 2 +- tests/unit/Testables/TestableConfigurationTest.swift | 2 +- tests/unit/Versions/AppUpdaterCheckTest.swift | 2 +- tests/unit/Versions/AppVersionTest.swift | 2 +- tests/unit/Versions/PhpVersionDetectionTest.swift | 2 +- tests/unit/Versions/PhpVersionNumberTest.swift | 2 +- tests/unit/Versions/ValetVersionExtractorTest.swift | 2 +- tests/unit/Versions/VersionExtractorTest.swift | 2 +- 174 files changed, 174 insertions(+), 174 deletions(-) diff --git a/LICENSE b/LICENSE index 07b0628..3f2c5e4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2022 Nico Verbruggen +Copyright (c) 2019-2023 Nico Verbruggen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/phpmon/Common/Command/ActiveCommand.swift b/phpmon/Common/Command/ActiveCommand.swift index 190e4e7..79ae17b 100644 --- a/phpmon/Common/Command/ActiveCommand.swift +++ b/phpmon/Common/Command/ActiveCommand.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 12/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Command/CommandProtocol.swift b/phpmon/Common/Command/CommandProtocol.swift index 49411f0..9784ae8 100644 --- a/phpmon/Common/Command/CommandProtocol.swift +++ b/phpmon/Common/Command/CommandProtocol.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 12/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Command/RealCommand.swift b/phpmon/Common/Command/RealCommand.swift index f9a0001..36cde64 100644 --- a/phpmon/Common/Command/RealCommand.swift +++ b/phpmon/Common/Command/RealCommand.swift @@ -2,7 +2,7 @@ // Command.swift // PHP Monitor // -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Common/Core/Actions.swift b/phpmon/Common/Core/Actions.swift index 28ed5c3..da87c12 100644 --- a/phpmon/Common/Core/Actions.swift +++ b/phpmon/Common/Core/Actions.swift @@ -2,7 +2,7 @@ // Services.swift // PHP Monitor // -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Core/Constants.swift b/phpmon/Common/Core/Constants.swift index 00ef039..96b5e26 100644 --- a/phpmon/Common/Core/Constants.swift +++ b/phpmon/Common/Core/Constants.swift @@ -2,7 +2,7 @@ // Constants.swift // PHP Monitor // -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Common/Core/Events.swift b/phpmon/Common/Core/Events.swift index d15270a..972f019 100644 --- a/phpmon/Common/Core/Events.swift +++ b/phpmon/Common/Core/Events.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 23/01/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Core/Helpers.swift b/phpmon/Common/Core/Helpers.swift index a53c14f..e35af77 100644 --- a/phpmon/Common/Core/Helpers.swift +++ b/phpmon/Common/Core/Helpers.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 24/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // // MARK: Common Shell Commands diff --git a/phpmon/Common/Core/Homebrew.swift b/phpmon/Common/Core/Homebrew.swift index a8193e3..c570f2a 100644 --- a/phpmon/Common/Core/Homebrew.swift +++ b/phpmon/Common/Core/Homebrew.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 21/11/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Core/Logger.swift b/phpmon/Common/Core/Logger.swift index 7ab6592..e1bbd30 100644 --- a/phpmon/Common/Core/Logger.swift +++ b/phpmon/Common/Core/Logger.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 21/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Core/Paths.swift b/phpmon/Common/Core/Paths.swift index 9cb8c2d..3c25bb4 100644 --- a/phpmon/Common/Core/Paths.swift +++ b/phpmon/Common/Core/Paths.swift @@ -2,7 +2,7 @@ // Paths.swift // PHP Monitor // -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Core/Process.swift b/phpmon/Common/Core/Process.swift index 68e9de3..24b6ec4 100644 --- a/phpmon/Common/Core/Process.swift +++ b/phpmon/Common/Core/Process.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 23/02/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Errors/AlertableError.swift b/phpmon/Common/Errors/AlertableError.swift index f32540b..d05e8a8 100644 --- a/phpmon/Common/Errors/AlertableError.swift +++ b/phpmon/Common/Errors/AlertableError.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 06/02/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Errors/Errors.swift b/phpmon/Common/Errors/Errors.swift index 5cdad29..74ce17b 100644 --- a/phpmon/Common/Errors/Errors.swift +++ b/phpmon/Common/Errors/Errors.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 08/02/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Extensions/ArrayExtension.swift b/phpmon/Common/Extensions/ArrayExtension.swift index e2f067d..cad6c96 100644 --- a/phpmon/Common/Extensions/ArrayExtension.swift +++ b/phpmon/Common/Extensions/ArrayExtension.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 11/06/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Extensions/DataExtension.swift b/phpmon/Common/Extensions/DataExtension.swift index ce241b7..b4c38e9 100644 --- a/phpmon/Common/Extensions/DataExtension.swift +++ b/phpmon/Common/Extensions/DataExtension.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 16/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Extensions/DateExtension.swift b/phpmon/Common/Extensions/DateExtension.swift index 5481ecb..6321c15 100644 --- a/phpmon/Common/Extensions/DateExtension.swift +++ b/phpmon/Common/Extensions/DateExtension.swift @@ -2,7 +2,7 @@ // Date.swift // PHP Monitor // -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Common/Extensions/DictionaryExtension.swift b/phpmon/Common/Extensions/DictionaryExtension.swift index d7a2f14..65e357a 100644 --- a/phpmon/Common/Extensions/DictionaryExtension.swift +++ b/phpmon/Common/Extensions/DictionaryExtension.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 01/11/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Extensions/NSMenuExtension.swift b/phpmon/Common/Extensions/NSMenuExtension.swift index 198daab..33142ee 100644 --- a/phpmon/Common/Extensions/NSMenuExtension.swift +++ b/phpmon/Common/Extensions/NSMenuExtension.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 14/04/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Common/Extensions/NSMenuItemExtension.swift b/phpmon/Common/Extensions/NSMenuItemExtension.swift index ed655cd..9a5461e 100644 --- a/phpmon/Common/Extensions/NSMenuItemExtension.swift +++ b/phpmon/Common/Extensions/NSMenuItemExtension.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 18/08/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Common/Extensions/NSWindowExtension.swift b/phpmon/Common/Extensions/NSWindowExtension.swift index 1556eff..73b4782 100644 --- a/phpmon/Common/Extensions/NSWindowExtension.swift +++ b/phpmon/Common/Extensions/NSWindowExtension.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 17/02/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Extensions/StringExtension.swift b/phpmon/Common/Extensions/StringExtension.swift index 38ac652..d98ac58 100644 --- a/phpmon/Common/Extensions/StringExtension.swift +++ b/phpmon/Common/Extensions/StringExtension.swift @@ -2,7 +2,7 @@ // StringExtension.swift // PHP Monitor // -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation import SwiftUI diff --git a/phpmon/Common/Extensions/TimeIntervalExtension.swift b/phpmon/Common/Extensions/TimeIntervalExtension.swift index 19d3854..bbf8990 100644 --- a/phpmon/Common/Extensions/TimeIntervalExtension.swift +++ b/phpmon/Common/Extensions/TimeIntervalExtension.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 29/09/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Extensions/XibLoadable.swift b/phpmon/Common/Extensions/XibLoadable.swift index e1cdbf9..c799567 100644 --- a/phpmon/Common/Extensions/XibLoadable.swift +++ b/phpmon/Common/Extensions/XibLoadable.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 04/02/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Filesystem/ActiveFileSystem.swift b/phpmon/Common/Filesystem/ActiveFileSystem.swift index 295ebc7..1e16355 100644 --- a/phpmon/Common/Filesystem/ActiveFileSystem.swift +++ b/phpmon/Common/Filesystem/ActiveFileSystem.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 08/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Filesystem/FileSystemProtocol.swift b/phpmon/Common/Filesystem/FileSystemProtocol.swift index fbf54b9..98e6b0e 100644 --- a/phpmon/Common/Filesystem/FileSystemProtocol.swift +++ b/phpmon/Common/Filesystem/FileSystemProtocol.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 08/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Filesystem/RealFileSystem.swift b/phpmon/Common/Filesystem/RealFileSystem.swift index 221ecd9..e7b87f3 100644 --- a/phpmon/Common/Filesystem/RealFileSystem.swift +++ b/phpmon/Common/Filesystem/RealFileSystem.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 08/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Helpers/Alert.swift b/phpmon/Common/Helpers/Alert.swift index 4f46ec2..bc6c8c1 100644 --- a/phpmon/Common/Helpers/Alert.swift +++ b/phpmon/Common/Helpers/Alert.swift @@ -2,7 +2,7 @@ // Alert.swift // PHP Monitor // -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Common/Helpers/Application.swift b/phpmon/Common/Helpers/Application.swift index b0f0370..37a8858 100644 --- a/phpmon/Common/Helpers/Application.swift +++ b/phpmon/Common/Helpers/Application.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 07/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Helpers/LocalNotification.swift b/phpmon/Common/Helpers/LocalNotification.swift index 0c61e40..6e4f7bc 100644 --- a/phpmon/Common/Helpers/LocalNotification.swift +++ b/phpmon/Common/Helpers/LocalNotification.swift @@ -2,7 +2,7 @@ // LocalNotification.swift // PHP Monitor // -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Helpers/MenuBarImageGenerator.swift b/phpmon/Common/Helpers/MenuBarImageGenerator.swift index 97843dc..f699c42 100644 --- a/phpmon/Common/Helpers/MenuBarImageGenerator.swift +++ b/phpmon/Common/Helpers/MenuBarImageGenerator.swift @@ -2,7 +2,7 @@ // ImageGenerator.swift // PHP Monitor // -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Common/Helpers/PMWindowController.swift b/phpmon/Common/Helpers/PMWindowController.swift index 24684dc..e35ae7e 100644 --- a/phpmon/Common/Helpers/PMWindowController.swift +++ b/phpmon/Common/Helpers/PMWindowController.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 05/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Common/Helpers/System.swift b/phpmon/Common/Helpers/System.swift index 92b8ba2..332bba2 100644 --- a/phpmon/Common/Helpers/System.swift +++ b/phpmon/Common/Helpers/System.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 01/11/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Helpers/VersionExtractor.swift b/phpmon/Common/Helpers/VersionExtractor.swift index 3f6cb06..00e865b 100644 --- a/phpmon/Common/Helpers/VersionExtractor.swift +++ b/phpmon/Common/Helpers/VersionExtractor.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 16/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Helpers/WIP.swift b/phpmon/Common/Helpers/WIP.swift index dbe89f4..9e425d8 100644 --- a/phpmon/Common/Helpers/WIP.swift +++ b/phpmon/Common/Helpers/WIP.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 01/11/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/PHP/ActivePhpInstallation.swift b/phpmon/Common/PHP/ActivePhpInstallation.swift index bd15f09..97c363e 100644 --- a/phpmon/Common/PHP/ActivePhpInstallation.swift +++ b/phpmon/Common/PHP/ActivePhpInstallation.swift @@ -2,7 +2,7 @@ // ActivePhpInstallation.swift // PHP Monitor // -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/PHP/Extensions/Xdebug.swift b/phpmon/Common/PHP/Extensions/Xdebug.swift index 3ef19c8..615d71e 100644 --- a/phpmon/Common/PHP/Extensions/Xdebug.swift +++ b/phpmon/Common/PHP/Extensions/Xdebug.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 01/05/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/PHP/Homebrew/HomebrewPackage.swift b/phpmon/Common/PHP/Homebrew/HomebrewPackage.swift index 79bebaa..100a6a3 100644 --- a/phpmon/Common/PHP/Homebrew/HomebrewPackage.swift +++ b/phpmon/Common/PHP/Homebrew/HomebrewPackage.swift @@ -2,7 +2,7 @@ // HomebrewPackage.swift // PHP Monitor // -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/PHP/Homebrew/HomebrewService.swift b/phpmon/Common/PHP/Homebrew/HomebrewService.swift index d46cbc7..aa85404 100644 --- a/phpmon/Common/PHP/Homebrew/HomebrewService.swift +++ b/phpmon/Common/PHP/Homebrew/HomebrewService.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 11/01/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/PHP/PHP Version/PhpEnv.swift b/phpmon/Common/PHP/PHP Version/PhpEnv.swift index 9f5b7bd..49dccaa 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnv.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnv.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 21/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/PHP/PHP Version/PhpHelper.swift b/phpmon/Common/PHP/PHP Version/PhpHelper.swift index 1ed99e1..75aa912 100644 --- a/phpmon/Common/PHP/PHP Version/PhpHelper.swift +++ b/phpmon/Common/PHP/PHP Version/PhpHelper.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 17/03/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/PHP/PHP Version/VersionNumber.swift b/phpmon/Common/PHP/PHP Version/VersionNumber.swift index 3b07c52..b01e6c0 100644 --- a/phpmon/Common/PHP/PHP Version/VersionNumber.swift +++ b/phpmon/Common/PHP/PHP Version/VersionNumber.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 23/01/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/PHP/PhpConfigurationFile.swift b/phpmon/Common/PHP/PhpConfigurationFile.swift index 8f789ea..9ce7907 100644 --- a/phpmon/Common/PHP/PhpConfigurationFile.swift +++ b/phpmon/Common/PHP/PhpConfigurationFile.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 04/05/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/PHP/PhpExtension.swift b/phpmon/Common/PHP/PhpExtension.swift index d20330a..44b8360 100644 --- a/phpmon/Common/PHP/PhpExtension.swift +++ b/phpmon/Common/PHP/PhpExtension.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 31/01/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/PHP/PhpInstallation.swift b/phpmon/Common/PHP/PhpInstallation.swift index f044be9..4b19881 100644 --- a/phpmon/Common/PHP/PhpInstallation.swift +++ b/phpmon/Common/PHP/PhpInstallation.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 28/11/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift index 76669a9..9526b93 100644 --- a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift +++ b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 24/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/PHP/Switcher/PhpSwitcher.swift b/phpmon/Common/PHP/Switcher/PhpSwitcher.swift index c706fe7..d0ffe8e 100644 --- a/phpmon/Common/PHP/Switcher/PhpSwitcher.swift +++ b/phpmon/Common/PHP/Switcher/PhpSwitcher.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 24/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Protocols/CreatedFromFile.swift b/phpmon/Common/Protocols/CreatedFromFile.swift index 0fb46b5..c36e16c 100644 --- a/phpmon/Common/Protocols/CreatedFromFile.swift +++ b/phpmon/Common/Protocols/CreatedFromFile.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 15/05/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Shell/ActiveShell.swift b/phpmon/Common/Shell/ActiveShell.swift index 99a50a0..e0aa4b3 100644 --- a/phpmon/Common/Shell/ActiveShell.swift +++ b/phpmon/Common/Shell/ActiveShell.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 20/09/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Shell/RealShell.swift b/phpmon/Common/Shell/RealShell.swift index d8b5a2b..28fd203 100644 --- a/phpmon/Common/Shell/RealShell.swift +++ b/phpmon/Common/Shell/RealShell.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 21/09/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Shell/ShellProtocol.swift b/phpmon/Common/Shell/ShellProtocol.swift index 7cb77c6..14927a5 100644 --- a/phpmon/Common/Shell/ShellProtocol.swift +++ b/phpmon/Common/Shell/ShellProtocol.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 21/09/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Testables/TestableCommand.swift b/phpmon/Common/Testables/TestableCommand.swift index a8295fb..b7e8503 100644 --- a/phpmon/Common/Testables/TestableCommand.swift +++ b/phpmon/Common/Testables/TestableCommand.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 12/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Testables/TestableConfiguration.swift b/phpmon/Common/Testables/TestableConfiguration.swift index f3b69c3..9e53505 100644 --- a/phpmon/Common/Testables/TestableConfiguration.swift +++ b/phpmon/Common/Testables/TestableConfiguration.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 16/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Testables/TestableFileSystem.swift b/phpmon/Common/Testables/TestableFileSystem.swift index 9492260..f0fe533 100644 --- a/phpmon/Common/Testables/TestableFileSystem.swift +++ b/phpmon/Common/Testables/TestableFileSystem.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 04/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Common/Testables/TestableShell.swift b/phpmon/Common/Testables/TestableShell.swift index f982b30..696f9f3 100644 --- a/phpmon/Common/Testables/TestableShell.swift +++ b/phpmon/Common/Testables/TestableShell.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 21/09/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/App/App+ActivationPolicy.swift b/phpmon/Domain/App/App+ActivationPolicy.swift index 31db8d6..8f01606 100644 --- a/phpmon/Domain/App/App+ActivationPolicy.swift +++ b/phpmon/Domain/App/App+ActivationPolicy.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 05/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/App/App+GlobalHotkey.swift b/phpmon/Domain/App/App+GlobalHotkey.swift index 5f3e237..8e2e129 100644 --- a/phpmon/Domain/App/App+GlobalHotkey.swift +++ b/phpmon/Domain/App/App+GlobalHotkey.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 05/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/App/App.swift b/phpmon/Domain/App/App.swift index 42f27d7..bfb5659 100644 --- a/phpmon/Domain/App/App.swift +++ b/phpmon/Domain/App/App.swift @@ -2,7 +2,7 @@ // StateManager.swift // PHP Monitor // -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/App/AppDelegate+InterApp.swift b/phpmon/Domain/App/AppDelegate+InterApp.swift index 715502b..7296e76 100644 --- a/phpmon/Domain/App/AppDelegate+InterApp.swift +++ b/phpmon/Domain/App/AppDelegate+InterApp.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 20/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/App/AppDelegate+MenuOutlets.swift b/phpmon/Domain/App/AppDelegate+MenuOutlets.swift index 0d30925..52fb01a 100644 --- a/phpmon/Domain/App/AppDelegate+MenuOutlets.swift +++ b/phpmon/Domain/App/AppDelegate+MenuOutlets.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 05/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/App/AppDelegate+Notifications.swift b/phpmon/Domain/App/AppDelegate+Notifications.swift index 50dc36b..f97e9e3 100644 --- a/phpmon/Domain/App/AppDelegate+Notifications.swift +++ b/phpmon/Domain/App/AppDelegate+Notifications.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 06/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/App/AppDelegate.swift b/phpmon/Domain/App/AppDelegate.swift index 4a2d268..a24e238 100644 --- a/phpmon/Domain/App/AppDelegate.swift +++ b/phpmon/Domain/App/AppDelegate.swift @@ -2,7 +2,7 @@ // AppDelegate.swift // PHP Monitor // -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/App/AppUpdateChecker.swift b/phpmon/Domain/App/AppUpdateChecker.swift index 0018989..0593b2b 100644 --- a/phpmon/Domain/App/AppUpdateChecker.swift +++ b/phpmon/Domain/App/AppUpdateChecker.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 09/05/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/App/AppVersion.swift b/phpmon/Domain/App/AppVersion.swift index 928858a..62231b4 100644 --- a/phpmon/Domain/App/AppVersion.swift +++ b/phpmon/Domain/App/AppVersion.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 10/05/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/App/EnvironmentCheck.swift b/phpmon/Domain/App/EnvironmentCheck.swift index a044ddf..cb9bf5a 100644 --- a/phpmon/Domain/App/EnvironmentCheck.swift +++ b/phpmon/Domain/App/EnvironmentCheck.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 10/08/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/App/EnvironmentManager.swift b/phpmon/Domain/App/EnvironmentManager.swift index 52f3ca7..9275084 100644 --- a/phpmon/Domain/App/EnvironmentManager.swift +++ b/phpmon/Domain/App/EnvironmentManager.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 14/09/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/App/InterAppHandler.swift b/phpmon/Domain/App/InterAppHandler.swift index dd2816b..8bf4bbc 100644 --- a/phpmon/Domain/App/InterAppHandler.swift +++ b/phpmon/Domain/App/InterAppHandler.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 28/01/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/App/Services/FakeServicesManager.swift b/phpmon/Domain/App/Services/FakeServicesManager.swift index b18998f..68a4a30 100644 --- a/phpmon/Domain/App/Services/FakeServicesManager.swift +++ b/phpmon/Domain/App/Services/FakeServicesManager.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 23/12/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/App/Services/Service.swift b/phpmon/Domain/App/Services/Service.swift index f6bd7ea..9b82261 100644 --- a/phpmon/Domain/App/Services/Service.swift +++ b/phpmon/Domain/App/Services/Service.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 23/12/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/App/Services/ServicesManager.swift b/phpmon/Domain/App/Services/ServicesManager.swift index e0b3a54..d2d51f4 100644 --- a/phpmon/Domain/App/Services/ServicesManager.swift +++ b/phpmon/Domain/App/Services/ServicesManager.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 11/06/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/App/Services/ValetServicesManager.swift b/phpmon/Domain/App/Services/ValetServicesManager.swift index d018b3c..3496e17 100644 --- a/phpmon/Domain/App/Services/ValetServicesManager.swift +++ b/phpmon/Domain/App/Services/ValetServicesManager.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 23/12/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index 7824ac2..0a20e60 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -2,7 +2,7 @@ // Environment.swift // PHP Monitor // -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/DomainList/AddProxyVC.swift b/phpmon/Domain/DomainList/AddProxyVC.swift index d113deb..697e30c 100644 --- a/phpmon/Domain/DomainList/AddProxyVC.swift +++ b/phpmon/Domain/DomainList/AddProxyVC.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 24/01/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/DomainList/AddSiteVC.swift b/phpmon/Domain/DomainList/AddSiteVC.swift index 9e72f32..a6f3bb7 100644 --- a/phpmon/Domain/DomainList/AddSiteVC.swift +++ b/phpmon/Domain/DomainList/AddSiteVC.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 24/01/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/DomainList/Cells/DomainListCellProtocol.swift b/phpmon/Domain/DomainList/Cells/DomainListCellProtocol.swift index e5a3bab..7da9234 100644 --- a/phpmon/Domain/DomainList/Cells/DomainListCellProtocol.swift +++ b/phpmon/Domain/DomainList/Cells/DomainListCellProtocol.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 03/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/DomainList/Cells/DomainListKindCell.swift b/phpmon/Domain/DomainList/Cells/DomainListKindCell.swift index 7a6b65e..359574f 100644 --- a/phpmon/Domain/DomainList/Cells/DomainListKindCell.swift +++ b/phpmon/Domain/DomainList/Cells/DomainListKindCell.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 16/03/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/DomainList/Cells/DomainListNameCell.swift b/phpmon/Domain/DomainList/Cells/DomainListNameCell.swift index a9ee4d5..663e885 100644 --- a/phpmon/Domain/DomainList/Cells/DomainListNameCell.swift +++ b/phpmon/Domain/DomainList/Cells/DomainListNameCell.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 16/03/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift b/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift index bc0d039..6f75e53 100644 --- a/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift +++ b/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 16/03/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/DomainList/Cells/DomainListTLSCell.swift b/phpmon/Domain/DomainList/Cells/DomainListTLSCell.swift index a4e27af..54d261e 100644 --- a/phpmon/Domain/DomainList/Cells/DomainListTLSCell.swift +++ b/phpmon/Domain/DomainList/Cells/DomainListTLSCell.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 16/03/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/DomainList/Cells/DomainListTypeCell.swift b/phpmon/Domain/DomainList/Cells/DomainListTypeCell.swift index 3dec51b..cc384ab 100644 --- a/phpmon/Domain/DomainList/Cells/DomainListTypeCell.swift +++ b/phpmon/Domain/DomainList/Cells/DomainListTypeCell.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 16/03/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/DomainList/DomainListVC+Actions.swift b/phpmon/Domain/DomainList/DomainListVC+Actions.swift index f12da19..16aa9a9 100644 --- a/phpmon/Domain/DomainList/DomainListVC+Actions.swift +++ b/phpmon/Domain/DomainList/DomainListVC+Actions.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 23/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/DomainList/DomainListVC+ContextMenu.swift b/phpmon/Domain/DomainList/DomainListVC+ContextMenu.swift index 9d4b0c3..cea6db6 100644 --- a/phpmon/Domain/DomainList/DomainListVC+ContextMenu.swift +++ b/phpmon/Domain/DomainList/DomainListVC+ContextMenu.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 10/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/DomainList/DomainListVC.swift b/phpmon/Domain/DomainList/DomainListVC.swift index 43d6632..82a3516 100644 --- a/phpmon/Domain/DomainList/DomainListVC.swift +++ b/phpmon/Domain/DomainList/DomainListVC.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 30/03/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/DomainList/DomainListWindowController.swift b/phpmon/Domain/DomainList/DomainListWindowController.swift index e3717d4..fe846ee 100644 --- a/phpmon/Domain/DomainList/DomainListWindowController.swift +++ b/phpmon/Domain/DomainList/DomainListWindowController.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 03/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/DomainList/PMTableView.swift b/phpmon/Domain/DomainList/PMTableView.swift index 99d214d..7080bd2 100644 --- a/phpmon/Domain/DomainList/PMTableView.swift +++ b/phpmon/Domain/DomainList/PMTableView.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 05/09/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/DomainList/SelectionVC.swift b/phpmon/Domain/DomainList/SelectionVC.swift index 80c2323..f5ea23a 100644 --- a/phpmon/Domain/DomainList/SelectionVC.swift +++ b/phpmon/Domain/DomainList/SelectionVC.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 14/04/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Integrations/Composer/ComposerJson.swift b/phpmon/Domain/Integrations/Composer/ComposerJson.swift index 83ca5bc..deb4b6d 100644 --- a/phpmon/Domain/Integrations/Composer/ComposerJson.swift +++ b/phpmon/Domain/Integrations/Composer/ComposerJson.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 04/01/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift index 754b2e3..7434fc5 100644 --- a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift +++ b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 08/02/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift b/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift index dd66bdc..2dfd435 100644 --- a/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift +++ b/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 26/01/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift index 83fe469..659f870 100644 --- a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift +++ b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 28/11/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Integrations/Nginx/NginxConfigurationFile.swift b/phpmon/Domain/Integrations/Nginx/NginxConfigurationFile.swift index 3ea9eee..fc16072 100644 --- a/phpmon/Domain/Integrations/Nginx/NginxConfigurationFile.swift +++ b/phpmon/Domain/Integrations/Nginx/NginxConfigurationFile.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 15/03/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Integrations/Valet/Domains/FakeValetInteractor.swift b/phpmon/Domain/Integrations/Valet/Domains/FakeValetInteractor.swift index 336da7a..9817111 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/FakeValetInteractor.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/FakeValetInteractor.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 13/12/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift index fb1ae3c..7cc2c26 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 21/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Integrations/Valet/Domains/ValetListable.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetListable.swift index fb63faa..8846b09 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/ValetListable.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/ValetListable.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 12/04/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Integrations/Valet/Proxies/FakeValetProxy.swift b/phpmon/Domain/Integrations/Valet/Proxies/FakeValetProxy.swift index 6591cda..8b1740a 100644 --- a/phpmon/Domain/Integrations/Valet/Proxies/FakeValetProxy.swift +++ b/phpmon/Domain/Integrations/Valet/Proxies/FakeValetProxy.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 16/12/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift b/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift index e9cfbe1..43b909f 100644 --- a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift +++ b/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 30/03/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Integrations/Valet/Scanners/DomainScanner.swift b/phpmon/Domain/Integrations/Valet/Scanners/DomainScanner.swift index a4a16d8..557402c 100644 --- a/phpmon/Domain/Integrations/Valet/Scanners/DomainScanner.swift +++ b/phpmon/Domain/Integrations/Valet/Scanners/DomainScanner.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 02/04/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Integrations/Valet/Scanners/FakeDomainScanner.swift b/phpmon/Domain/Integrations/Valet/Scanners/FakeDomainScanner.swift index 386edc5..b77b818 100644 --- a/phpmon/Domain/Integrations/Valet/Scanners/FakeDomainScanner.swift +++ b/phpmon/Domain/Integrations/Valet/Scanners/FakeDomainScanner.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 02/04/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // class FakeDomainScanner: DomainScanner { diff --git a/phpmon/Domain/Integrations/Valet/Scanners/ValetDomainScanner.swift b/phpmon/Domain/Integrations/Valet/Scanners/ValetDomainScanner.swift index 5cc0e98..c8dcbf5 100644 --- a/phpmon/Domain/Integrations/Valet/Scanners/ValetDomainScanner.swift +++ b/phpmon/Domain/Integrations/Valet/Scanners/ValetDomainScanner.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 02/04/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Integrations/Valet/Scanners/ValetScanners.swift b/phpmon/Domain/Integrations/Valet/Scanners/ValetScanners.swift index 6db3a1f..501ce6c 100644 --- a/phpmon/Domain/Integrations/Valet/Scanners/ValetScanners.swift +++ b/phpmon/Domain/Integrations/Valet/Scanners/ValetScanners.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 01/11/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift index 6cb22a8..0acdf18 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 19/03/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift index 9a0f418..ae0e3e6 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 22/02/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 0b84e5e..61393ef 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 29/11/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Menu/MainMenu+Actions.swift b/phpmon/Domain/Menu/MainMenu+Actions.swift index bac4889..690c06f 100644 --- a/phpmon/Domain/Menu/MainMenu+Actions.swift +++ b/phpmon/Domain/Menu/MainMenu+Actions.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 19/05/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/Menu/MainMenu+Async.swift b/phpmon/Domain/Menu/MainMenu+Async.swift index 013913a..e81e688 100644 --- a/phpmon/Domain/Menu/MainMenu+Async.swift +++ b/phpmon/Domain/Menu/MainMenu+Async.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 06/02/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Menu/MainMenu+FixMyValet.swift b/phpmon/Domain/Menu/MainMenu+FixMyValet.swift index fbc36e9..ea69271 100644 --- a/phpmon/Domain/Menu/MainMenu+FixMyValet.swift +++ b/phpmon/Domain/Menu/MainMenu+FixMyValet.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 20/02/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 2d9dd06..e08035c 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 03/01/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/Menu/MainMenu+Switcher.swift b/phpmon/Domain/Menu/MainMenu+Switcher.swift index b8464b7..c96b75a 100644 --- a/phpmon/Domain/Menu/MainMenu+Switcher.swift +++ b/phpmon/Domain/Menu/MainMenu+Switcher.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 08/02/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index cfd8ae8..bf05c1f 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -2,7 +2,7 @@ // MainMenu.swift // PHP Monitor // -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/Menu/StatusMenu+Items.swift b/phpmon/Domain/Menu/StatusMenu+Items.swift index 315da7f..21c9831 100644 --- a/phpmon/Domain/Menu/StatusMenu+Items.swift +++ b/phpmon/Domain/Menu/StatusMenu+Items.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 18/08/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/Menu/StatusMenu.swift b/phpmon/Domain/Menu/StatusMenu.swift index 2dd9da9..bf072e6 100644 --- a/phpmon/Domain/Menu/StatusMenu.swift +++ b/phpmon/Domain/Menu/StatusMenu.swift @@ -2,7 +2,7 @@ // MainMenuBuilder.swift // PHP Monitor // -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/Notice/BetterAlert.swift b/phpmon/Domain/Notice/BetterAlert.swift index a4b8d2c..33ad04c 100644 --- a/phpmon/Domain/Notice/BetterAlert.swift +++ b/phpmon/Domain/Notice/BetterAlert.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 16/02/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Notice/BetterAlertVC.swift b/phpmon/Domain/Notice/BetterAlertVC.swift index c3d58e1..a1d5251 100644 --- a/phpmon/Domain/Notice/BetterAlertVC.swift +++ b/phpmon/Domain/Notice/BetterAlertVC.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 16/02/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Onboarding/OnboardingWindowController.swift b/phpmon/Domain/Onboarding/OnboardingWindowController.swift index 0f73b9b..5b65200 100644 --- a/phpmon/Domain/Onboarding/OnboardingWindowController.swift +++ b/phpmon/Domain/Onboarding/OnboardingWindowController.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 25/06/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift b/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift index 65dfeb9..010dcbc 100644 --- a/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift +++ b/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 21/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Preferences/CustomPrefs.swift b/phpmon/Domain/Preferences/CustomPrefs.swift index 6ca241e..eaa9c0f 100644 --- a/phpmon/Domain/Preferences/CustomPrefs.swift +++ b/phpmon/Domain/Preferences/CustomPrefs.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 03/01/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Preferences/Keybinds/GlobalKeybindPreference.swift b/phpmon/Domain/Preferences/Keybinds/GlobalKeybindPreference.swift index 7d3dccc..f12b0e2 100644 --- a/phpmon/Domain/Preferences/Keybinds/GlobalKeybindPreference.swift +++ b/phpmon/Domain/Preferences/Keybinds/GlobalKeybindPreference.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 15/04/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Preferences/Keys.swift b/phpmon/Domain/Preferences/Keys.swift index 0216aa3..366a6ff 100644 --- a/phpmon/Domain/Preferences/Keys.swift +++ b/phpmon/Domain/Preferences/Keys.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 25/07/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Preferences/MenuBarIcons.swift b/phpmon/Domain/Preferences/MenuBarIcons.swift index dbe516a..0cb95a1 100644 --- a/phpmon/Domain/Preferences/MenuBarIcons.swift +++ b/phpmon/Domain/Preferences/MenuBarIcons.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 06/02/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Preferences/PreferenceName.swift b/phpmon/Domain/Preferences/PreferenceName.swift index 656d22c..9a94b54 100644 --- a/phpmon/Domain/Preferences/PreferenceName.swift +++ b/phpmon/Domain/Preferences/PreferenceName.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 07/09/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // /** diff --git a/phpmon/Domain/Preferences/Preferences.swift b/phpmon/Domain/Preferences/Preferences.swift index 504c870..6d63ae1 100644 --- a/phpmon/Domain/Preferences/Preferences.swift +++ b/phpmon/Domain/Preferences/Preferences.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 30/03/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Preferences/PreferencesWindowController+Hotkey.swift b/phpmon/Domain/Preferences/PreferencesWindowController+Hotkey.swift index 7ddd785..f5d7338 100644 --- a/phpmon/Domain/Preferences/PreferencesWindowController+Hotkey.swift +++ b/phpmon/Domain/Preferences/PreferencesWindowController+Hotkey.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 25/07/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/Preferences/PreferencesWindowController.swift b/phpmon/Domain/Preferences/PreferencesWindowController.swift index 2a3a90c..20f135d 100644 --- a/phpmon/Domain/Preferences/PreferencesWindowController.swift +++ b/phpmon/Domain/Preferences/PreferencesWindowController.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 02/04/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/Preferences/PrefsVC.swift b/phpmon/Domain/Preferences/PrefsVC.swift index 3e1542f..4bb5ab4 100644 --- a/phpmon/Domain/Preferences/PrefsVC.swift +++ b/phpmon/Domain/Preferences/PrefsVC.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 30/03/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/Preferences/Stats.swift b/phpmon/Domain/Preferences/Stats.swift index 4ceb701..a861d03 100644 --- a/phpmon/Domain/Preferences/Stats.swift +++ b/phpmon/Domain/Preferences/Stats.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 29/01/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Preferences/Views/CheckboxPreferenceView.swift b/phpmon/Domain/Preferences/Views/CheckboxPreferenceView.swift index 6f4ea52..d70c7f2 100644 --- a/phpmon/Domain/Preferences/Views/CheckboxPreferenceView.swift +++ b/phpmon/Domain/Preferences/Views/CheckboxPreferenceView.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 17/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Preferences/Views/HotkeyPreferenceView.swift b/phpmon/Domain/Preferences/Views/HotkeyPreferenceView.swift index 51ab865..1c77d7a 100644 --- a/phpmon/Domain/Preferences/Views/HotkeyPreferenceView.swift +++ b/phpmon/Domain/Preferences/Views/HotkeyPreferenceView.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 17/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Preferences/Views/SelectPreferenceView.swift b/phpmon/Domain/Preferences/Views/SelectPreferenceView.swift index 0f7c508..b8158ea 100644 --- a/phpmon/Domain/Preferences/Views/SelectPreferenceView.swift +++ b/phpmon/Domain/Preferences/Views/SelectPreferenceView.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 06/02/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Presets/Preset.swift b/phpmon/Domain/Presets/Preset.swift index 7bed7e4..2f8cc60 100644 --- a/phpmon/Domain/Presets/Preset.swift +++ b/phpmon/Domain/Presets/Preset.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 31/05/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Presets/PresetHelper.swift b/phpmon/Domain/Presets/PresetHelper.swift index 6ff1242..1693182 100644 --- a/phpmon/Domain/Presets/PresetHelper.swift +++ b/phpmon/Domain/Presets/PresetHelper.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 02/06/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Progress/ProgressVC.swift b/phpmon/Domain/Progress/ProgressVC.swift index 9fc8f2c..74829cd 100644 --- a/phpmon/Domain/Progress/ProgressVC.swift +++ b/phpmon/Domain/Progress/ProgressVC.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 26/07/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Progress/TerminalProgressWindowController.swift b/phpmon/Domain/Progress/TerminalProgressWindowController.swift index 96e232f..80298f8 100644 --- a/phpmon/Domain/Progress/TerminalProgressWindowController.swift +++ b/phpmon/Domain/Progress/TerminalProgressWindowController.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 18/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/SwiftUI/Common/SwiftUIHelper.swift b/phpmon/Domain/SwiftUI/Common/SwiftUIHelper.swift index e553b7e..70f9ae9 100644 --- a/phpmon/Domain/SwiftUI/Common/SwiftUIHelper.swift +++ b/phpmon/Domain/SwiftUI/Common/SwiftUIHelper.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 08/06/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/SwiftUI/Domains/NoDomainResultsView.swift b/phpmon/Domain/SwiftUI/Domains/NoDomainResultsView.swift index 3644fc1..ee591e0 100644 --- a/phpmon/Domain/SwiftUI/Domains/NoDomainResultsView.swift +++ b/phpmon/Domain/SwiftUI/Domains/NoDomainResultsView.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 15/08/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import SwiftUI diff --git a/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift b/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift index e95b03f..fb240ef 100644 --- a/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift +++ b/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 08/06/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import SwiftUI diff --git a/phpmon/Domain/SwiftUI/Menu/HeaderView.swift b/phpmon/Domain/SwiftUI/Menu/HeaderView.swift index b61f08f..4534aab 100644 --- a/phpmon/Domain/SwiftUI/Menu/HeaderView.swift +++ b/phpmon/Domain/SwiftUI/Menu/HeaderView.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 10/06/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import SwiftUI diff --git a/phpmon/Domain/SwiftUI/Menu/SectionHeaderView.swift b/phpmon/Domain/SwiftUI/Menu/SectionHeaderView.swift index e163b2d..e4feba5 100644 --- a/phpmon/Domain/SwiftUI/Menu/SectionHeaderView.swift +++ b/phpmon/Domain/SwiftUI/Menu/SectionHeaderView.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 10/06/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import SwiftUI diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index b9fcd13..4ca1000 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 10/06/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/SwiftUI/Menu/StatsView.swift b/phpmon/Domain/SwiftUI/Menu/StatsView.swift index fdc5301..fd6a00f 100644 --- a/phpmon/Domain/SwiftUI/Menu/StatsView.swift +++ b/phpmon/Domain/SwiftUI/Menu/StatsView.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 09/06/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import SwiftUI diff --git a/phpmon/Domain/SwiftUI/Onboarding/OnboardingView.swift b/phpmon/Domain/SwiftUI/Onboarding/OnboardingView.swift index 36ff786..7cb5fe5 100644 --- a/phpmon/Domain/SwiftUI/Onboarding/OnboardingView.swift +++ b/phpmon/Domain/SwiftUI/Onboarding/OnboardingView.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 08/07/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import SwiftUI diff --git a/phpmon/Domain/SwiftUI/Warning/NoWarningsView.swift b/phpmon/Domain/SwiftUI/Warning/NoWarningsView.swift index 4c22bf0..fd810a4 100644 --- a/phpmon/Domain/SwiftUI/Warning/NoWarningsView.swift +++ b/phpmon/Domain/SwiftUI/Warning/NoWarningsView.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 15/08/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import SwiftUI diff --git a/phpmon/Domain/SwiftUI/Warning/WarningListView.swift b/phpmon/Domain/SwiftUI/Warning/WarningListView.swift index 87a55a4..0c24b8b 100644 --- a/phpmon/Domain/SwiftUI/Warning/WarningListView.swift +++ b/phpmon/Domain/SwiftUI/Warning/WarningListView.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 09/08/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import SwiftUI diff --git a/phpmon/Domain/SwiftUI/Warning/WarningView.swift b/phpmon/Domain/SwiftUI/Warning/WarningView.swift index ec47af2..446e4f5 100644 --- a/phpmon/Domain/SwiftUI/Warning/WarningView.swift +++ b/phpmon/Domain/SwiftUI/Warning/WarningView.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 31/07/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import SwiftUI diff --git a/phpmon/Domain/Warnings/Warning.swift b/phpmon/Domain/Warnings/Warning.swift index e363e4f..8bf3ec2 100644 --- a/phpmon/Domain/Warnings/Warning.swift +++ b/phpmon/Domain/Warnings/Warning.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 09/08/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Warnings/WarningManager.swift b/phpmon/Domain/Warnings/WarningManager.swift index ff2c3d5..d001dab 100644 --- a/phpmon/Domain/Warnings/WarningManager.swift +++ b/phpmon/Domain/Warnings/WarningManager.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 09/08/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Warnings/WarningsWindowController.swift b/phpmon/Domain/Warnings/WarningsWindowController.swift index 4fda741..4cc87e4 100644 --- a/phpmon/Domain/Warnings/WarningsWindowController.swift +++ b/phpmon/Domain/Warnings/WarningsWindowController.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 09/08/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Cocoa diff --git a/phpmon/Domain/Watcher/App+ConfigWatch.swift b/phpmon/Domain/Watcher/App+ConfigWatch.swift index 8423d68..e7637f9 100644 --- a/phpmon/Domain/Watcher/App+ConfigWatch.swift +++ b/phpmon/Domain/Watcher/App+ConfigWatch.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 30/03/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Domain/Watcher/PhpConfigWatcher.swift b/phpmon/Domain/Watcher/PhpConfigWatcher.swift index 9758a78..5634ac6 100644 --- a/phpmon/Domain/Watcher/PhpConfigWatcher.swift +++ b/phpmon/Domain/Watcher/PhpConfigWatcher.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 30/03/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 7e9cb9b..67ccaa0 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -3,7 +3,7 @@ PHP Monitor Created by Nico Verbruggen on 16/05/2020. - Copyright © 2022 Nico Verbruggen. All rights reserved. + Copyright © 2023 Nico Verbruggen. All rights reserved. */ // MENU ITEMS (MI) diff --git a/tests/Shared/TestableConfigurations.swift b/tests/Shared/TestableConfigurations.swift index 6bdfd63..fab4d04 100644 --- a/tests/Shared/TestableConfigurations.swift +++ b/tests/Shared/TestableConfigurations.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 04/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/tests/Shared/Utility.swift b/tests/Shared/Utility.swift index 3caa4ec..55096ad 100644 --- a/tests/Shared/Utility.swift +++ b/tests/Shared/Utility.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 14/02/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import Foundation diff --git a/tests/Shared/XCPMApplication.swift b/tests/Shared/XCPMApplication.swift index e96029a..5a7f443 100644 --- a/tests/Shared/XCPMApplication.swift +++ b/tests/Shared/XCPMApplication.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 16/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/feature/FeatureTestCase.swift b/tests/feature/FeatureTestCase.swift index 3900120..a7282af 100644 --- a/tests/feature/FeatureTestCase.swift +++ b/tests/feature/FeatureTestCase.swift @@ -3,7 +3,7 @@ // Feature Tests // // Created by Nico Verbruggen on 07/11/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/feature/InternalSwitcherTest.swift b/tests/feature/InternalSwitcherTest.swift index 14028ef..9b89eff 100644 --- a/tests/feature/InternalSwitcherTest.swift +++ b/tests/feature/InternalSwitcherTest.swift @@ -3,7 +3,7 @@ // Feature Tests // // Created by Nico Verbruggen on 14/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/ui/DomainsListTest.swift b/tests/ui/DomainsListTest.swift index 292ae63..adbb62d 100644 --- a/tests/ui/DomainsListTest.swift +++ b/tests/ui/DomainsListTest.swift @@ -3,7 +3,7 @@ // UI Tests // // Created by Nico Verbruggen on 14/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/ui/StartupTest.swift b/tests/ui/StartupTest.swift index 090f2c9..369cf26 100644 --- a/tests/ui/StartupTest.swift +++ b/tests/ui/StartupTest.swift @@ -3,7 +3,7 @@ // UI Tests // // Created by Nico Verbruggen on 14/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/ui/UITestCase.swift b/tests/ui/UITestCase.swift index ee2d8cf..e34f9b1 100644 --- a/tests/ui/UITestCase.swift +++ b/tests/ui/UITestCase.swift @@ -3,7 +3,7 @@ // UI Tests // // Created by Nico Verbruggen on 15/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/unit/Commands/CommandTest.swift b/tests/unit/Commands/CommandTest.swift index 1241bf2..ed6fe2e 100644 --- a/tests/unit/Commands/CommandTest.swift +++ b/tests/unit/Commands/CommandTest.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 13/02/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/unit/Parsers/HomebrewPackageTest.swift b/tests/unit/Parsers/HomebrewPackageTest.swift index 2ca09f5..a622070 100644 --- a/tests/unit/Parsers/HomebrewPackageTest.swift +++ b/tests/unit/Parsers/HomebrewPackageTest.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 14/02/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/unit/Parsers/NginxConfigurationTest.swift b/tests/unit/Parsers/NginxConfigurationTest.swift index b09175c..5306072 100644 --- a/tests/unit/Parsers/NginxConfigurationTest.swift +++ b/tests/unit/Parsers/NginxConfigurationTest.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 29/11/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/unit/Parsers/PhpConfigurationTest.swift b/tests/unit/Parsers/PhpConfigurationTest.swift index 95d413c..749c8ac 100644 --- a/tests/unit/Parsers/PhpConfigurationTest.swift +++ b/tests/unit/Parsers/PhpConfigurationTest.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 04/05/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/unit/Parsers/PhpExtensionTest.swift b/tests/unit/Parsers/PhpExtensionTest.swift index 2647559..94f7f04 100644 --- a/tests/unit/Parsers/PhpExtensionTest.swift +++ b/tests/unit/Parsers/PhpExtensionTest.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 13/02/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/unit/Parsers/ValetConfigurationTest.swift b/tests/unit/Parsers/ValetConfigurationTest.swift index 5b57fe9..1c08b72 100644 --- a/tests/unit/Parsers/ValetConfigurationTest.swift +++ b/tests/unit/Parsers/ValetConfigurationTest.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 29/11/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/unit/Testables/Filesystem/RealFileSystemTest.swift b/tests/unit/Testables/Filesystem/RealFileSystemTest.swift index 8e9b9a0..8831dc2 100644 --- a/tests/unit/Testables/Filesystem/RealFileSystemTest.swift +++ b/tests/unit/Testables/Filesystem/RealFileSystemTest.swift @@ -3,7 +3,7 @@ // Unit Tests // // Created by Nico Verbruggen on 02/11/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift b/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift index 698e9ba..1ec44fb 100644 --- a/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift +++ b/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 01/11/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/unit/Testables/Shell/RealShellTest.swift b/tests/unit/Testables/Shell/RealShellTest.swift index 8c57917..685b0b6 100644 --- a/tests/unit/Testables/Shell/RealShellTest.swift +++ b/tests/unit/Testables/Shell/RealShellTest.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 28/09/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/unit/Testables/Shell/TestableShellTest.swift b/tests/unit/Testables/Shell/TestableShellTest.swift index d2a819a..c12afac 100644 --- a/tests/unit/Testables/Shell/TestableShellTest.swift +++ b/tests/unit/Testables/Shell/TestableShellTest.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 20/09/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/unit/Testables/TestableConfigurationTest.swift b/tests/unit/Testables/TestableConfigurationTest.swift index 97ec216..0b0ef00 100644 --- a/tests/unit/Testables/TestableConfigurationTest.swift +++ b/tests/unit/Testables/TestableConfigurationTest.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 16/10/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/unit/Versions/AppUpdaterCheckTest.swift b/tests/unit/Versions/AppUpdaterCheckTest.swift index c70d5a2..463635e 100644 --- a/tests/unit/Versions/AppUpdaterCheckTest.swift +++ b/tests/unit/Versions/AppUpdaterCheckTest.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 10/05/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/unit/Versions/AppVersionTest.swift b/tests/unit/Versions/AppVersionTest.swift index 040f819..c2ac087 100644 --- a/tests/unit/Versions/AppVersionTest.swift +++ b/tests/unit/Versions/AppVersionTest.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 10/05/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/unit/Versions/PhpVersionDetectionTest.swift b/tests/unit/Versions/PhpVersionDetectionTest.swift index fc77d78..b496495 100644 --- a/tests/unit/Versions/PhpVersionDetectionTest.swift +++ b/tests/unit/Versions/PhpVersionDetectionTest.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 01/04/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/unit/Versions/PhpVersionNumberTest.swift b/tests/unit/Versions/PhpVersionNumberTest.swift index a9f3e4b..e2744b0 100644 --- a/tests/unit/Versions/PhpVersionNumberTest.swift +++ b/tests/unit/Versions/PhpVersionNumberTest.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 23/01/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/unit/Versions/ValetVersionExtractorTest.swift b/tests/unit/Versions/ValetVersionExtractorTest.swift index 9e7673e..a36f334 100644 --- a/tests/unit/Versions/ValetVersionExtractorTest.swift +++ b/tests/unit/Versions/ValetVersionExtractorTest.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 29/11/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest diff --git a/tests/unit/Versions/VersionExtractorTest.swift b/tests/unit/Versions/VersionExtractorTest.swift index 6b44657..b0f07d4 100644 --- a/tests/unit/Versions/VersionExtractorTest.swift +++ b/tests/unit/Versions/VersionExtractorTest.swift @@ -3,7 +3,7 @@ // PHP Monitor // // Created by Nico Verbruggen on 16/12/2021. -// Copyright © 2022 Nico Verbruggen. All rights reserved. +// Copyright © 2023 Nico Verbruggen. All rights reserved. // import XCTest From 44bc07c9dcce3f06de3f904893471b78a3bd8fcb Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 20 Jan 2023 16:36:56 +0100 Subject: [PATCH 177/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Parsing=20.valetr?= =?UTF-8?q?c=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Integrations/Valet/Sites/ValetSite.swift | 47 +++++++++++++++---- phpmon/Localizable.strings | 1 + 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift index ae0e3e6..d0742e7 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift @@ -64,6 +64,7 @@ class ValetSite: ValetListable { case require case platform case valetphprc + case valetrc } init( @@ -209,21 +210,49 @@ class ValetSite: ValetListable { Checks the contents of the .valetphprc file and determine the version, if possible. */ private func determineValetPhpFileInfo() { - let path = "\(absolutePath)/.valetphprc" + let files = [ + (".valetphprc", VersionSource.valetphprc), + (".valetrc", VersionSource.valetrc) + ] - do { - if FileSystem.fileExists(path) { - let contents = try String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8) - if let version = VersionExtractor.from(contents) { - self.composerPhp = version - self.composerPhpSource = .valetphprc + for (suffix, source) in files { + do { + let path = "\(absolutePath)/\(suffix)" + if FileSystem.fileExists(path) { + try self.handleValetFile(path, source) } + } catch { + Log.err("Something went wrong parsing the '\(suffix)' file") } - } catch { - Log.err("Something went wrong parsing the .valetphprc file") } } + /** + Parse a Valet file (either .valetphprc or .valetrc). + */ + private func handleValetFile(_ path: String, _ source: VersionSource) throws { + let contents = try String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8) + switch source { + case .valetphprc: + if let version = VersionExtractor.from(contents) { + self.composerPhp = version + self.composerPhpSource = source + } + case .valetrc: + self.parseValetRcFile(contents) + default: + return + } + } + + /** + Specifically extract PHP information from a .valetrc file. + */ + private func parseValetRcFile(_ text: String) { + // TODO: Implement this + fatalError("A .valetrc file was found, needs to be parsed!") + } + // MARK: - File Parsing public static func isolatedVersion(_ filePath: String) -> String? { diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 67ccaa0..c15ad2b 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -385,6 +385,7 @@ problem manually, using your own Terminal app (this just shows you the output)." "alert.composer_php_requirement.type.require" = "This required PHP version was determined by checking the `require` field in the `composer.json` file when the site list was last refreshed."; "alert.composer_php_requirement.type.platform" = "This required PHP version was determined by checking the `platform` field in the `composer.json` file when the site list was last refreshed."; "alert.composer_php_requirement.type.valetphprc" = "This required PHP version was determined by checking the .valetphprc file in your project's directory."; +"alert.composer_php_requirement.type.valetrc" = "This required PHP version was determined by checking the .valetrc file in your project's directory."; "alert.unable_to_determine_is_fine" = "If you have a simple project, there may not be a specified PHP version set as a requirement. In that case, you are free to ignore this warning."; "alert.php_version_ideal" = "The currently active PHP version is ideal for this site."; "alert.php_version_incorrect" = "The currently active PHP version does not match the required constraint set for this site."; From b5d2fef1846e03d999a857ca92c5f9062c93b2d5 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 20 Jan 2023 16:42:50 +0100 Subject: [PATCH 178/181] =?UTF-8?q?=F0=9F=8F=97=20WIP:=20Add=20test=20for?= =?UTF-8?q?=20feature=20to=20be=20implemented?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 12 ++++++++ tests/unit/Parsers/ValetRcTest.swift | 30 +++++++++++++++++++ tests/unit/Test Files/valet/valetrc.rc | 3 ++ tests/unit/Test Files/valet/valetrc_broken.rc | 6 ++++ 4 files changed, 51 insertions(+) create mode 100644 tests/unit/Parsers/ValetRcTest.swift create mode 100644 tests/unit/Test Files/valet/valetrc.rc create mode 100644 tests/unit/Test Files/valet/valetrc_broken.rc diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 99832e6..ffd5963 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -143,6 +143,9 @@ C451AFF72969E40F0078E617 /* HelpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C451AFF52969E40F0078E617 /* HelpButton.swift */; }; C451AFF82969E40F0078E617 /* HelpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C451AFF52969E40F0078E617 /* HelpButton.swift */; }; C451AFF92969E40F0078E617 /* HelpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C451AFF52969E40F0078E617 /* HelpButton.swift */; }; + C4551657297AED18009B8466 /* ValetRcTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4551656297AED18009B8466 /* ValetRcTest.swift */; }; + C4551659297AED7D009B8466 /* valetrc.rc in Resources */ = {isa = PBXBuildFile; fileRef = C4551658297AED7D009B8466 /* valetrc.rc */; }; + C455165B297AEDB5009B8466 /* valetrc_broken.rc in Resources */ = {isa = PBXBuildFile; fileRef = C455165A297AEDB5009B8466 /* valetrc_broken.rc */; }; C4570C3A28FC355300D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C4570C3B28FC355300D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C4570C3C28FC355400D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; @@ -806,6 +809,9 @@ C44F868D2835BD8D005C353A /* phpmon-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "phpmon-config.json"; sourceTree = ""; }; C450C8C528C919EC002A2B4B /* PreferenceName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceName.swift; sourceTree = ""; }; C451AFF52969E40F0078E617 /* HelpButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpButton.swift; sourceTree = ""; }; + C4551656297AED18009B8466 /* ValetRcTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetRcTest.swift; sourceTree = ""; }; + C4551658297AED7D009B8466 /* valetrc.rc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = valetrc.rc; sourceTree = ""; }; + C455165A297AEDB5009B8466 /* valetrc_broken.rc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = valetrc_broken.rc; sourceTree = ""; }; C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-proxy.test"; sourceTree = ""; }; C45B9148295607F400F4EC78 /* Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Service.swift; sourceTree = ""; }; C45B914D295608E300F4EC78 /* ValetServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetServicesManager.swift; sourceTree = ""; }; @@ -1287,6 +1293,8 @@ C459B4C027F6096300E9B4B4 /* valet */ = { isa = PBXGroup; children = ( + C455165A297AEDB5009B8466 /* valetrc_broken.rc */, + C4551658297AED7D009B8466 /* valetrc.rc */, C4AF9F70275445FF00D44ED0 /* valet-config.json */, ); path = valet; @@ -1559,6 +1567,7 @@ C46FA98A2822F08F00D78807 /* PhpConfigurationTest.swift */, C43A8A2325D9D20D00591B77 /* HomebrewPackageTest.swift */, C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */, + C4551656297AED18009B8466 /* ValetRcTest.swift */, ); path = Parsers; sourceTree = ""; @@ -1920,6 +1929,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + C4551659297AED7D009B8466 /* valetrc.rc in Resources */, C4570C3C28FC355400D18420 /* Localizable.strings in Resources */, 54FCFD27276C883F004CE748 /* SelectPreferenceView.xib in Resources */, 54FCFD2E276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */, @@ -1933,6 +1943,7 @@ C44C1992276E44CB0072762D /* ProgressWindow.storyboard in Resources */, C42F26762805FEE200938AC7 /* nginx-secure-proxy.test in Resources */, C4F30B08278E195800755FCE /* brew-services.json in Resources */, + C455165B297AEDB5009B8466 /* valetrc_broken.rc in Resources */, 54A18D40282A566E000A0D81 /* nginx-secure-proxy-custom-tld.test in Resources */, C42CFB1627DFDE7900862737 /* nginx-site.test in Resources */, C459B4BD27F6093700E9B4B4 /* nginx-proxy.test in Resources */, @@ -2628,6 +2639,7 @@ C4A81CA528C67101008DD9D1 /* PMTableView.swift in Sources */, C45E76152854A65300B4FE0C /* ServicesManager.swift in Sources */, C4D36602291132B7006BD146 /* ValetScanners.swift in Sources */, + C4551657297AED18009B8466 /* ValetRcTest.swift in Sources */, C464ADAD275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */, C40C7F1F2772136000DDDCDC /* PhpEnv.swift in Sources */, C464ADB0275A7A6A003FCD53 /* DomainListVC.swift in Sources */, diff --git a/tests/unit/Parsers/ValetRcTest.swift b/tests/unit/Parsers/ValetRcTest.swift new file mode 100644 index 0000000..222b91a --- /dev/null +++ b/tests/unit/Parsers/ValetRcTest.swift @@ -0,0 +1,30 @@ +// +// ValetRcTest.swift +// Unit Tests +// +// Created by Nico Verbruggen on 20/01/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import XCTest + +class ValetRcTest: XCTestCase { + + // MARK: - Test Files + + static var path: URL { + return Bundle(for: Self.self) + .url(forResource: "valetrc", withExtension: "rc")! + } + + // MARK: - Tests + + func test_can_extract_fields_from_valetrc_file() throws { + // TODO: Load the path and get the fields + } + + func test_skip_invalid_fields_valetrc_file() throws { + // TODO: Load the path and throw error + } + +} diff --git a/tests/unit/Test Files/valet/valetrc.rc b/tests/unit/Test Files/valet/valetrc.rc new file mode 100644 index 0000000..8e9c64b --- /dev/null +++ b/tests/unit/Test Files/valet/valetrc.rc @@ -0,0 +1,3 @@ +PHP=php@8.2 +OTHER=thing +PHPMON_WATCH=true \ No newline at end of file diff --git a/tests/unit/Test Files/valet/valetrc_broken.rc b/tests/unit/Test Files/valet/valetrc_broken.rc new file mode 100644 index 0000000..6f0715c --- /dev/null +++ b/tests/unit/Test Files/valet/valetrc_broken.rc @@ -0,0 +1,6 @@ +fdsgdfg +dgdfg + +PHP=fsdfs + +;PHP=8.2 From 66393094b0f49051ad9d44240fc1db6ce6f0b711 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 24 Jan 2023 19:47:28 +0100 Subject: [PATCH 179/181] =?UTF-8?q?=E2=9C=A8=20Correctly=20parse=20.valetr?= =?UTF-8?q?c=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 34 +++++++++---- .../Domain/Integrations/Common/RCFile.swift | 48 +++++++++++++++++++ .../Integrations/Valet/Sites/ValetSite.swift | 28 +++++++---- tests/unit/Parsers/ValetRcTest.swift | 34 ++++++++++--- .../{valetrc_broken.rc => valetrc.broken} | 0 tests/unit/Test Files/valet/valetrc.rc | 3 -- tests/unit/Test Files/valet/valetrc.valid | 6 +++ 7 files changed, 127 insertions(+), 26 deletions(-) create mode 100644 phpmon/Domain/Integrations/Common/RCFile.swift rename tests/unit/Test Files/valet/{valetrc_broken.rc => valetrc.broken} (100%) delete mode 100644 tests/unit/Test Files/valet/valetrc.rc create mode 100644 tests/unit/Test Files/valet/valetrc.valid diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index ffd5963..583f332 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -118,6 +118,10 @@ C44067FB27E25FD70045BD4E /* DomainListTLSCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067FA27E25FD70045BD4E /* DomainListTLSCell.swift */; }; C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */; }; C44264C02850BD2A007400F1 /* VersionPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */; }; + C4463FCC29804BCB007B93D5 /* RCFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4463FCB29804BCB007B93D5 /* RCFile.swift */; }; + C4463FCD29804BCB007B93D5 /* RCFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4463FCB29804BCB007B93D5 /* RCFile.swift */; }; + C4463FCE29804BCB007B93D5 /* RCFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4463FCB29804BCB007B93D5 /* RCFile.swift */; }; + C4463FCF29804BCB007B93D5 /* RCFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4463FCB29804BCB007B93D5 /* RCFile.swift */; }; C449B4F027EE7FB800C47E8A /* DomainListTLSCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067FA27E25FD70045BD4E /* DomainListTLSCell.swift */; }; C449B4F127EE7FC200C47E8A /* DomainListNameCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F427E2582B0045BD4E /* DomainListNameCell.swift */; }; C449B4F227EE7FC400C47E8A /* DomainListPhpCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F627E258410045BD4E /* DomainListPhpCell.swift */; }; @@ -144,8 +148,8 @@ C451AFF82969E40F0078E617 /* HelpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C451AFF52969E40F0078E617 /* HelpButton.swift */; }; C451AFF92969E40F0078E617 /* HelpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C451AFF52969E40F0078E617 /* HelpButton.swift */; }; C4551657297AED18009B8466 /* ValetRcTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4551656297AED18009B8466 /* ValetRcTest.swift */; }; - C4551659297AED7D009B8466 /* valetrc.rc in Resources */ = {isa = PBXBuildFile; fileRef = C4551658297AED7D009B8466 /* valetrc.rc */; }; - C455165B297AEDB5009B8466 /* valetrc_broken.rc in Resources */ = {isa = PBXBuildFile; fileRef = C455165A297AEDB5009B8466 /* valetrc_broken.rc */; }; + C4551659297AED7D009B8466 /* valetrc.valid in Resources */ = {isa = PBXBuildFile; fileRef = C4551658297AED7D009B8466 /* valetrc.valid */; }; + C455165B297AEDB5009B8466 /* valetrc.broken in Resources */ = {isa = PBXBuildFile; fileRef = C455165A297AEDB5009B8466 /* valetrc.broken */; }; C4570C3A28FC355300D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C4570C3B28FC355300D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C4570C3C28FC355400D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; @@ -799,6 +803,7 @@ C44067FA27E25FD70045BD4E /* DomainListTLSCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListTLSCell.swift; sourceTree = ""; }; C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIHelper.swift; sourceTree = ""; }; C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionPopoverView.swift; sourceTree = ""; }; + C4463FCB29804BCB007B93D5 /* RCFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RCFile.swift; sourceTree = ""; }; C44A874728905BB000498BC4 /* ProgressVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressVC.swift; sourceTree = ""; }; C44AD3F62912EF7100997FF4 /* RealFileSystemTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealFileSystemTest.swift; sourceTree = ""; }; C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeIntervalExtension.swift; sourceTree = ""; }; @@ -810,8 +815,8 @@ C450C8C528C919EC002A2B4B /* PreferenceName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceName.swift; sourceTree = ""; }; C451AFF52969E40F0078E617 /* HelpButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpButton.swift; sourceTree = ""; }; C4551656297AED18009B8466 /* ValetRcTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetRcTest.swift; sourceTree = ""; }; - C4551658297AED7D009B8466 /* valetrc.rc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = valetrc.rc; sourceTree = ""; }; - C455165A297AEDB5009B8466 /* valetrc_broken.rc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = valetrc_broken.rc; sourceTree = ""; }; + C4551658297AED7D009B8466 /* valetrc.valid */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = valetrc.valid; sourceTree = ""; }; + C455165A297AEDB5009B8466 /* valetrc.broken */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = valetrc.broken; sourceTree = ""; }; C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-proxy.test"; sourceTree = ""; }; C45B9148295607F400F4EC78 /* Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Service.swift; sourceTree = ""; }; C45B914D295608E300F4EC78 /* ValetServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetServicesManager.swift; sourceTree = ""; }; @@ -1232,6 +1237,14 @@ path = Cells; sourceTree = ""; }; + C4463FD029804C13007B93D5 /* Common */ = { + isa = PBXGroup; + children = ( + C4463FCB29804BCB007B93D5 /* RCFile.swift */, + ); + path = Common; + sourceTree = ""; + }; C44A874628905B8500498BC4 /* Onboarding */ = { isa = PBXGroup; children = ( @@ -1293,8 +1306,8 @@ C459B4C027F6096300E9B4B4 /* valet */ = { isa = PBXGroup; children = ( - C455165A297AEDB5009B8466 /* valetrc_broken.rc */, - C4551658297AED7D009B8466 /* valetrc.rc */, + C455165A297AEDB5009B8466 /* valetrc.broken */, + C4551658297AED7D009B8466 /* valetrc.valid */, C4AF9F70275445FF00D44ED0 /* valet-config.json */, ); path = valet; @@ -1441,6 +1454,7 @@ C4AF9F6B275445D300D44ED0 /* Integrations */ = { isa = PBXGroup; children = ( + C4463FD029804C13007B93D5 /* Common */, C4C0E8DA27F887CC002D32A9 /* Nginx */, C4D89BC42783C98800A02B68 /* Composer */, C4AF9F6C275445D900D44ED0 /* Homebrew */, @@ -1929,7 +1943,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - C4551659297AED7D009B8466 /* valetrc.rc in Resources */, + C4551659297AED7D009B8466 /* valetrc.valid in Resources */, C4570C3C28FC355400D18420 /* Localizable.strings in Resources */, 54FCFD27276C883F004CE748 /* SelectPreferenceView.xib in Resources */, 54FCFD2E276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */, @@ -1943,7 +1957,7 @@ C44C1992276E44CB0072762D /* ProgressWindow.storyboard in Resources */, C42F26762805FEE200938AC7 /* nginx-secure-proxy.test in Resources */, C4F30B08278E195800755FCE /* brew-services.json in Resources */, - C455165B297AEDB5009B8466 /* valetrc_broken.rc in Resources */, + C455165B297AEDB5009B8466 /* valetrc.broken in Resources */, 54A18D40282A566E000A0D81 /* nginx-secure-proxy-custom-tld.test in Resources */, C42CFB1627DFDE7900862737 /* nginx-site.test in Resources */, C459B4BD27F6093700E9B4B4 /* nginx-proxy.test in Resources */, @@ -2020,6 +2034,7 @@ C4E2E85C28FC282B003B070C /* TestableConfiguration.swift in Sources */, C4C0E8DF27F88AEB002D32A9 /* FakeDomainScanner.swift in Sources */, C44B3A4628E5C70100718CB1 /* TimeIntervalExtension.swift in Sources */, + C4463FCC29804BCB007B93D5 /* RCFile.swift in Sources */, C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */, C4E9D2C02878B336008FFDAD /* OnboardingView.swift in Sources */, C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */, @@ -2201,6 +2216,7 @@ C471E86328F9BB650021E251 /* PMTableView.swift in Sources */, C471E86428F9BB650021E251 /* Warning.swift in Sources */, C40175BA2903108900763A68 /* ValetInteractor.swift in Sources */, + C4463FCE29804BCB007B93D5 /* RCFile.swift in Sources */, C45B9150295608E300F4EC78 /* ValetServicesManager.swift in Sources */, C471E86528F9BB650021E251 /* WarningManager.swift in Sources */, C471E86628F9BB650021E251 /* WarningsWindowController.swift in Sources */, @@ -2433,6 +2449,7 @@ C4D3660E29113F20006BD146 /* System.swift in Sources */, C471E80428F9BAD40021E251 /* PhpExtension.swift in Sources */, C471E7F728F9BACB0021E251 /* PhpSwitcher.swift in Sources */, + C4463FCF29804BCB007B93D5 /* RCFile.swift in Sources */, C471E82C28F9BB340021E251 /* ValetListable.swift in Sources */, C471E82828F9BB310021E251 /* HomebrewDiagnostics.swift in Sources */, C471E81E28F9BB260021E251 /* BetterAlert.swift in Sources */, @@ -2614,6 +2631,7 @@ C44AD3F72912EF7100997FF4 /* RealFileSystemTest.swift in Sources */, C4F30B0A278E1A1A00755FCE /* ComposerJson.swift in Sources */, C4C0E8E027F88AEB002D32A9 /* FakeDomainScanner.swift in Sources */, + C4463FCD29804BCB007B93D5 /* RCFile.swift in Sources */, C4AF9F7D275454A900D44ED0 /* ValetVersionExtractorTest.swift in Sources */, C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */, C4B585452770FE3900DA4FBE /* RealCommand.swift in Sources */, diff --git a/phpmon/Domain/Integrations/Common/RCFile.swift b/phpmon/Domain/Integrations/Common/RCFile.swift new file mode 100644 index 0000000..52a34e4 --- /dev/null +++ b/phpmon/Domain/Integrations/Common/RCFile.swift @@ -0,0 +1,48 @@ +// +// RCFile.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 24/01/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import Foundation + +struct RCFile { + let path: String? + let fields: [String: String] + + static func fromPath(_ path: String) -> RCFile? { + do { + let text = try String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8) + return RCFile(path: path, contents: text) + } catch { + return nil + } + } + + init(path: String? = nil, contents: String) { + var fields: [String: String] = [:] + + contents + .split(separator: "\n") + .forEach({ line in + if line.contains("=") { + let content = line.split(separator: "=") + let key = String(content[0]) + .trimmingCharacters(in: .whitespaces) + .replacingOccurrences(of: "\"", with: "") + if key.starts(with: "#") { + return + } + let value = String(content[1]) + .trimmingCharacters(in: .whitespaces) + .replacingOccurrences(of: "\"", with: "") + fields[key] = value + } + }) + + self.path = path + self.fields = fields + } +} diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift index d0742e7..3baa650 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift @@ -207,19 +207,20 @@ class ValetSite: ValetListable { } /** - Checks the contents of the .valetphprc file and determine the version, if possible. + Checks the contents of the .valetphprc file and determine the version. + The first file found takes precendence over all others. */ private func determineValetPhpFileInfo() { let files = [ - (".valetphprc", VersionSource.valetphprc), - (".valetrc", VersionSource.valetrc) + (".valetrc", VersionSource.valetrc), + (".valetphprc", VersionSource.valetphprc) ] for (suffix, source) in files { do { let path = "\(absolutePath)/\(suffix)" if FileSystem.fileExists(path) { - try self.handleValetFile(path, source) + return try self.handleValetFile(path, source) } } catch { Log.err("Something went wrong parsing the '\(suffix)' file") @@ -239,7 +240,7 @@ class ValetSite: ValetListable { self.composerPhpSource = source } case .valetrc: - self.parseValetRcFile(contents) + self.parseValetRcFile(path, contents) default: return } @@ -248,9 +249,20 @@ class ValetSite: ValetListable { /** Specifically extract PHP information from a .valetrc file. */ - private func parseValetRcFile(_ text: String) { - // TODO: Implement this - fatalError("A .valetrc file was found, needs to be parsed!") + private func parseValetRcFile(_ path: String, _ text: String) { + let valetRc = RCFile(path: path, contents: text) + + guard let versionString = valetRc.fields["PHP"] else { + if valetRc.path != nil { + Log.perf("\(self.name)'s .valetrc file at '\(valetRc.path!)' lacks a 'PHP' entry.") + } + return + } + + if let version = VersionExtractor.from(versionString) { + self.composerPhp = version + self.composerPhpSource = .valetrc + } } // MARK: - File Parsing diff --git a/tests/unit/Parsers/ValetRcTest.swift b/tests/unit/Parsers/ValetRcTest.swift index 222b91a..a89c028 100644 --- a/tests/unit/Parsers/ValetRcTest.swift +++ b/tests/unit/Parsers/ValetRcTest.swift @@ -12,19 +12,39 @@ class ValetRcTest: XCTestCase { // MARK: - Test Files - static var path: URL { + static var validPath: URL { return Bundle(for: Self.self) - .url(forResource: "valetrc", withExtension: "rc")! + .url(forResource: "valetrc", withExtension: "valid")! } + static var brokenPath: URL { + return Bundle(for: Self.self) + .url(forResource: "valetrc", withExtension: "broken")! + } + + // MARK: - Tests func test_can_extract_fields_from_valetrc_file() throws { - // TODO: Load the path and get the fields - } + let fakeFile = RCFile.fromPath("/Users/fake/file.rc") + XCTAssertNil(fakeFile) - func test_skip_invalid_fields_valetrc_file() throws { - // TODO: Load the path and throw error - } + // Can parse the file + let validFile = RCFile.fromPath(ValetRcTest.validPath.path) + XCTAssertNotNil(validFile) + let fields = validFile!.fields + + // Correctly parses and trims (and omits double quotes) per line + XCTAssertEqual(fields["PHP"], "php@8.2") + XCTAssertEqual(fields["OTHER"], "thing") + XCTAssertEqual(fields["PHPMON_WATCH"], "true") + XCTAssertEqual(fields["SYNTAX"], "variable") + + // Ignores entries prefixed with # + XCTAssertTrue(!fields.keys.contains("#PHP")) + + // Ignores invalid lines + XCTAssertTrue(!fields.keys.contains("OOF")) + } } diff --git a/tests/unit/Test Files/valet/valetrc_broken.rc b/tests/unit/Test Files/valet/valetrc.broken similarity index 100% rename from tests/unit/Test Files/valet/valetrc_broken.rc rename to tests/unit/Test Files/valet/valetrc.broken diff --git a/tests/unit/Test Files/valet/valetrc.rc b/tests/unit/Test Files/valet/valetrc.rc deleted file mode 100644 index 8e9c64b..0000000 --- a/tests/unit/Test Files/valet/valetrc.rc +++ /dev/null @@ -1,3 +0,0 @@ -PHP=php@8.2 -OTHER=thing -PHPMON_WATCH=true \ No newline at end of file diff --git a/tests/unit/Test Files/valet/valetrc.valid b/tests/unit/Test Files/valet/valetrc.valid new file mode 100644 index 0000000..1ac4bcd --- /dev/null +++ b/tests/unit/Test Files/valet/valetrc.valid @@ -0,0 +1,6 @@ +PHP=php@8.2 +#PHP=php +OTHER=thing +PHPMON_WATCH=true +SYNTAX = "variable" +OOF:NICE From f60ecb877c2b9d88c7d730e393747eccfed0db6c Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 26 Jan 2023 20:29:56 +0100 Subject: [PATCH 180/181] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 18 ++-- .../DomainList/Cells/DomainListPhpCell.swift | 8 +- .../DomainList/Cells/DomainListTypeCell.swift | 2 +- .../Integrations/Composer/ComposerJson.swift | 2 +- .../Valet/Sites/FakeValetSite.swift | 13 ++- .../Valet/Sites/PhpVersionSource.swift | 17 ++++ .../Integrations/Valet/Sites/ValetSite.swift | 96 ++++++++----------- .../SwiftUI/Domains/VersionPopoverView.swift | 10 +- 8 files changed, 89 insertions(+), 77 deletions(-) create mode 100644 phpmon/Domain/Integrations/Valet/Sites/PhpVersionSource.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 583f332..c82ae5c 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -547,6 +547,10 @@ C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */; }; C4B97B7B275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */; }; C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */; }; + C4BB39392981AFC700F8E797 /* PhpVersionSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BB39382981AFC700F8E797 /* PhpVersionSource.swift */; }; + C4BB393A2981AFC700F8E797 /* PhpVersionSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BB39382981AFC700F8E797 /* PhpVersionSource.swift */; }; + C4BB393B2981AFC700F8E797 /* PhpVersionSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BB39382981AFC700F8E797 /* PhpVersionSource.swift */; }; + C4BB393C2981AFC700F8E797 /* PhpVersionSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BB39382981AFC700F8E797 /* PhpVersionSource.swift */; }; C4BF56AB2949381100379603 /* FakeValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BF56AA2949381100379603 /* FakeValetInteractor.swift */; }; C4BF56AC2949381100379603 /* FakeValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BF56AA2949381100379603 /* FakeValetInteractor.swift */; }; C4BF56AD2949381100379603 /* FakeValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BF56AA2949381100379603 /* FakeValetInteractor.swift */; }; @@ -876,6 +880,7 @@ C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+MenuOutlets.swift"; sourceTree = ""; }; C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+ActivationPolicy.swift"; sourceTree = ""; }; C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+GlobalHotkey.swift"; sourceTree = ""; }; + C4BB39382981AFC700F8E797 /* PhpVersionSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpVersionSource.swift; sourceTree = ""; }; C4BF56AA2949381100379603 /* FakeValetInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeValetInteractor.swift; sourceTree = ""; }; C4C0E8DE27F88AEB002D32A9 /* FakeDomainScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeDomainScanner.swift; sourceTree = ""; }; C4C0E8E127F88B13002D32A9 /* ValetDomainScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetDomainScanner.swift; sourceTree = ""; }; @@ -1544,7 +1549,7 @@ children = ( C4E4404527C56F4700D225E1 /* ValetSite.swift */, C41C02A827E61A65009F26CB /* FakeValetSite.swift */, - C4C0E8E427F88B1F002D32A9 /* SiteScanner */, + C4BB39382981AFC700F8E797 /* PhpVersionSource.swift */, ); path = Sites; sourceTree = ""; @@ -1566,13 +1571,6 @@ path = Nginx; sourceTree = ""; }; - C4C0E8E427F88B1F002D32A9 /* SiteScanner */ = { - isa = PBXGroup; - children = ( - ); - path = SiteScanner; - sourceTree = ""; - }; C4C1019727C65A11001FACC2 /* Parsers */ = { isa = PBXGroup; children = ( @@ -2051,6 +2049,7 @@ C44067F727E258410045BD4E /* DomainListPhpCell.swift in Sources */, C4FACE80288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift in Sources */, C415D3B72770F294005EF286 /* Actions.swift in Sources */, + C4BB39392981AFC700F8E797 /* PhpVersionSource.swift in Sources */, C4C3643928AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */, C4AC51FC27E27F47008528CA /* DomainListKindCell.swift in Sources */, C4CDA893288F1A71007CE25F /* Keys.swift in Sources */, @@ -2162,6 +2161,7 @@ C471E83028F9BB650021E251 /* Application.swift in Sources */, C471E83128F9BB650021E251 /* LocalNotification.swift in Sources */, C471E83228F9BB650021E251 /* MenuBarImageGenerator.swift in Sources */, + C4BB393B2981AFC700F8E797 /* PhpVersionSource.swift in Sources */, C471E83328F9BB650021E251 /* PMWindowController.swift in Sources */, C471E83428F9BB650021E251 /* VersionExtractor.swift in Sources */, C471E83528F9BB650021E251 /* ValetProxy.swift in Sources */, @@ -2428,6 +2428,7 @@ C4CE7F9929683B43000102CF /* PhpVersionNumberCollection.swift in Sources */, C471E7FC28F9BACE0021E251 /* HomebrewPackage.swift in Sources */, C471E7CF28F9BA600021E251 /* ActiveShell.swift in Sources */, + C4BB393C2981AFC700F8E797 /* PhpVersionSource.swift in Sources */, C471E7F628F9BAC80021E251 /* PhpHelper.swift in Sources */, C471E7EE28F9BAC30021E251 /* Constants.swift in Sources */, C471E80E28F9BAE80021E251 /* DateExtension.swift in Sources */, @@ -2560,6 +2561,7 @@ C485707B28BF458900539B36 /* VersionPopoverView.swift in Sources */, C4E2E85D28FC282B003B070C /* TestableConfiguration.swift in Sources */, C485706E28BF451C00539B36 /* OnboardingWindowController.swift in Sources */, + C4BB393A2981AFC700F8E797 /* PhpVersionSource.swift in Sources */, C4CB6E66292C362C002E9027 /* Homebrew.swift in Sources */, C43603A1275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */, C4C3643A28AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */, diff --git a/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift b/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift index 6f75e53..9271a29 100644 --- a/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift +++ b/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift @@ -28,7 +28,7 @@ class DomainListPhpCell: NSTableCellView, DomainListCellProtocol { imageViewPhpVersionOK.toolTip = nil - imageViewPhpVersionOK.contentTintColor = site.composerPhpCompatibleWithLinked + imageViewPhpVersionOK.contentTintColor = site.isCompatibleWithPreferredPhpVersion ? NSColor(named: "IconColorGreen") : NSColor(named: "IconColorRed") @@ -37,9 +37,9 @@ class DomainListPhpCell: NSTableCellView, DomainListCellProtocol { imageViewPhpVersionOK.image = NSImage(named: "Isolated") imageViewPhpVersionOK.toolTip = "domain_list.tooltips.isolated".localized(site.servingPhpVersion) } else { - imageViewPhpVersionOK.isHidden = (site.composerPhp == "???" || !site.composerPhpCompatibleWithLinked) + imageViewPhpVersionOK.isHidden = (site.preferredPhpVersion == "???" || !site.isCompatibleWithPreferredPhpVersion) imageViewPhpVersionOK.image = NSImage(named: "Checkmark") - imageViewPhpVersionOK.toolTip = "domain_list.tooltips.checkmark".localized(site.composerPhp) + imageViewPhpVersionOK.toolTip = "domain_list.tooltips.checkmark".localized(site.preferredPhpVersion) } } @@ -58,7 +58,7 @@ class DomainListPhpCell: NSTableCellView, DomainListCellProtocol { return [] } - return PhpEnv.shared.validVersions(for: site.composerPhp).filter({ version in + return PhpEnv.shared.validVersions(for: site.preferredPhpVersion).filter({ version in version.short != PhpEnv.phpInstall.version.short }) } diff --git a/phpmon/Domain/DomainList/Cells/DomainListTypeCell.swift b/phpmon/Domain/DomainList/Cells/DomainListTypeCell.swift index cc384ab..1208b81 100644 --- a/phpmon/Domain/DomainList/Cells/DomainListTypeCell.swift +++ b/phpmon/Domain/DomainList/Cells/DomainListTypeCell.swift @@ -25,7 +25,7 @@ class DomainListTypeCell: NSTableCellView, DomainListCellProtocol { } // PHP version - labelPhpVersion.stringValue = site.composerPhp == "???" ? "Any PHP" : "PHP \(site.composerPhp)" + labelPhpVersion.stringValue = site.preferredPhpVersion == "???" ? "Any PHP" : "PHP \(site.preferredPhpVersion)" } func populateCell(with proxy: ValetProxy) { diff --git a/phpmon/Domain/Integrations/Composer/ComposerJson.swift b/phpmon/Domain/Integrations/Composer/ComposerJson.swift index deb4b6d..1e3047e 100644 --- a/phpmon/Domain/Integrations/Composer/ComposerJson.swift +++ b/phpmon/Domain/Integrations/Composer/ComposerJson.swift @@ -40,7 +40,7 @@ struct ComposerJson: Decodable { Checks what the PHP version constraint is. Returns a tuple (constraint, location of constraint). */ - public func getPhpVersion() -> (String, ValetSite.VersionSource) { + public func getPhpVersion() -> (String, PhpVersionSource) { // Check if in platform if configuration?.platform?.php != nil { return (configuration!.platform!.php!, .platform) diff --git a/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift index 0acdf18..b866f8c 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift @@ -19,10 +19,17 @@ class FakeValetSite: ValetSite { constraint: String = "^8.1", isolated: String? = nil ) { - self.init(name: name, tld: tld, absolutePath: path, aliasPath: nil, makeDeterminations: false) + self.init( + name: name, + tld: tld, + absolutePath: path, + aliasPath: nil, + makeDeterminations: false + ) + self.secured = secure - self.composerPhp = constraint - self.composerPhpSource = constraint != "" ? .require : .unknown + self.preferredPhpVersion = constraint + self.preferredPhpVersionSource = constraint != "" ? .require : .unknown self.driver = driver self.driverDeterminedByComposer = true diff --git a/phpmon/Domain/Integrations/Valet/Sites/PhpVersionSource.swift b/phpmon/Domain/Integrations/Valet/Sites/PhpVersionSource.swift new file mode 100644 index 0000000..de4f949 --- /dev/null +++ b/phpmon/Domain/Integrations/Valet/Sites/PhpVersionSource.swift @@ -0,0 +1,17 @@ +// +// VersionSource.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 25/01/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import Foundation + +enum PhpVersionSource: String { + case unknown + case require + case platform + case valetphprc + case valetrc +} diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift index 3baa650..baeff52 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift @@ -44,14 +44,15 @@ class ValetSite: ValetListable { /// A list of notable Composer dependencies. var notableComposerDependencies: [String: String] = [:] - /// The PHP version as discovered in `composer.json` or in .valetphprc. - var composerPhp: String = "???" + /// The PHP version as discovered in `composer.json` or in .valetphprc/.valetrc. + /// This is the preferred version needed to correctly run the domain or site. + var preferredPhpVersion: String = "???" /// Check whether the PHP version is valid for the currently linked version. - var composerPhpCompatibleWithLinked: Bool = false + var isCompatibleWithPreferredPhpVersion: Bool = false /// How the PHP version was determined. - var composerPhpSource: VersionSource = .unknown + var preferredPhpVersionSource: PhpVersionSource = .unknown /// Which version of PHP is actually used to serve this site. var servingPhpVersion: String { @@ -59,14 +60,6 @@ class ValetSite: ValetListable { ?? PhpEnv.phpInstall.version.short } - enum VersionSource: String { - case unknown - case require - case platform - case valetphprc - case valetrc - } - init( name: String, tld: String, @@ -139,22 +132,6 @@ class ValetSite: ValetListable { self.evaluateCompatibility() } - public func evaluateCompatibility() { - 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 - let origin = self.isolatedPhpVersion?.versionNumber.short ?? PhpEnv.phpInstall.version.long - return !PhpVersionNumberCollection.make(from: [origin]) - .matching(constraint: string.trimmingCharacters(in: .whitespacesAndNewlines)) - .isEmpty - }.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. @@ -195,10 +172,14 @@ class ValetSite: ValetListable { if FileSystem.fileExists(path) { let decoded = try JSONDecoder().decode( ComposerJson.self, - from: String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8).data(using: .utf8)! + from: String( + contentsOf: URL(fileURLWithPath: path), + encoding: .utf8 + ).data(using: .utf8)! ) - (self.composerPhp, self.composerPhpSource) = decoded.getPhpVersion() + (self.preferredPhpVersion, + self.preferredPhpVersionSource) = decoded.getPhpVersion() self.notableComposerDependencies = decoded.getNotableDependencies() } } catch { @@ -212,8 +193,8 @@ class ValetSite: ValetListable { */ private func determineValetPhpFileInfo() { let files = [ - (".valetrc", VersionSource.valetrc), - (".valetphprc", VersionSource.valetphprc) + (".valetrc", PhpVersionSource.valetrc), + (".valetphprc", PhpVersionSource.valetphprc) ] for (suffix, source) in files { @@ -231,40 +212,45 @@ class ValetSite: ValetListable { /** Parse a Valet file (either .valetphprc or .valetrc). */ - private func handleValetFile(_ path: String, _ source: VersionSource) throws { - let contents = try String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8) + private func handleValetFile(_ path: String, _ source: PhpVersionSource) throws { + var versionString = "" + switch source { case .valetphprc: - if let version = VersionExtractor.from(contents) { - self.composerPhp = version - self.composerPhpSource = source - } + versionString = try String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8) case .valetrc: - self.parseValetRcFile(path, contents) + guard let valetRc = RCFile.fromPath(path) else { return } + guard let phpField = valetRc.fields["PHP"] else { return } + versionString = phpField default: return } - } - - /** - Specifically extract PHP information from a .valetrc file. - */ - private func parseValetRcFile(_ path: String, _ text: String) { - let valetRc = RCFile(path: path, contents: text) - - guard let versionString = valetRc.fields["PHP"] else { - if valetRc.path != nil { - Log.perf("\(self.name)'s .valetrc file at '\(valetRc.path!)' lacks a 'PHP' entry.") - } - return - } if let version = VersionExtractor.from(versionString) { - self.composerPhp = version - self.composerPhpSource = .valetrc + self.preferredPhpVersion = version + self.preferredPhpVersionSource = source } } + public func evaluateCompatibility() { + if self.preferredPhpVersion == "???" { + return + } + + // Split the composer list (on "|") to evaluate multiple constraints + // For example, for Laravel 8 projects the value is "^7.3|^8.0" + self.isCompatibleWithPreferredPhpVersion = self.preferredPhpVersion.split(separator: "|").map { string in + let origin = self.isolatedPhpVersion?.versionNumber.short + ?? PhpEnv.phpInstall.version.long + + let normalizedPhpVersion = string.trimmingCharacters(in: .whitespacesAndNewlines) + + return !PhpVersionNumberCollection.make(from: [origin]) + .matching(constraint: normalizedPhpVersion) + .isEmpty + }.contains(true) + } + // MARK: - File Parsing public static func isolatedVersion(_ filePath: String) -> String? { diff --git a/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift b/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift index fb240ef..d478bed 100644 --- a/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift +++ b/phpmon/Domain/SwiftUI/Domains/VersionPopoverView.swift @@ -42,14 +42,14 @@ struct VersionPopoverView: View { }.padding(EdgeInsets(top: 10, leading: 0, bottom: 0, trailing: 0)) } } else { - if site.composerPhpSource == .unknown { + if site.preferredPhpVersionSource == .unknown { // We don't know which PHP version is required DisclaimerView( iconName: "questionmark.circle.fill", message: "alert.unable_to_determine_is_fine".localized ) } else { - if site.composerPhpCompatibleWithLinked { + if site.isCompatibleWithPreferredPhpVersion { DisclaimerView( iconName: "checkmark.circle.fill", message: "alert.php_version_ideal".localized, @@ -73,7 +73,7 @@ struct VersionPopoverView: View { } func getTitleText() -> String { - if site.composerPhpSource == .unknown { + if site.preferredPhpVersionSource == .unknown { return "alert.composer_php_requirement.unable_to_determine".localized } @@ -87,7 +87,7 @@ struct VersionPopoverView: View { return "alert.composer_php_requirement.title".localized( "\(site.name).\(suffix)", - site.composerPhp + site.preferredPhpVersion ) } @@ -102,7 +102,7 @@ struct VersionPopoverView: View { information += "\n\n" } - information += "alert.composer_php_requirement.type.\(site.composerPhpSource.rawValue)" + information += "alert.composer_php_requirement.type.\(site.preferredPhpVersionSource.rawValue)" .localized return information From e623207844644bf3ee362066453638fe499f1489 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 26 Jan 2023 20:32:39 +0100 Subject: [PATCH 181/181] =?UTF-8?q?=F0=9F=94=A7=20New=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index c82ae5c..14ff847 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2831,7 +2831,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1027; + CURRENT_PROJECT_VERSION = 1030; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2860,7 +2860,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1027; + CURRENT_PROJECT_VERSION = 1030; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -3088,7 +3088,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1027; + CURRENT_PROJECT_VERSION = 1030; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3198,7 +3198,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1027; + CURRENT_PROJECT_VERSION = 1030; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES;

Do you enjoy using the app? Leave a star on GitHub!