├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.yaml
│ └── feature_request.yaml
├── contributing.md
└── pull_request_template.md
├── .gitignore
├── .swiftlint.yml
├── DEVELOPER.md
├── LICENSE
├── PHP Monitor.xcodeproj
├── project.pbxproj
└── xcshareddata
│ └── xcschemes
│ ├── PHP Monitor DEV.xcscheme
│ ├── PHP Monitor EAP.xcscheme
│ ├── PHP Monitor Self-Updater.xcscheme
│ ├── PHP Monitor.xcscheme
│ └── Unit Tests.xcscheme
├── README.md
├── SECURITY.md
├── assets
├── affinity
│ ├── icon-dev.afdesign
│ ├── icon-eap.afdesign
│ ├── icon-updater.afdesign
│ ├── icon.afdesign
│ └── icons.afdesign
├── icon.svg
├── menubar.svg
└── source.svg
├── docs
├── build.png
├── logo.png
├── notification.png
└── screenshot.jpg
├── integrations
└── phpmon.alfredworkflow
├── phpmon-updater
├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── icon_128x128.png
│ │ ├── icon_128x128@2x.png
│ │ ├── icon_16x16.png
│ │ ├── icon_16x16@2x.png
│ │ ├── icon_256x256.png
│ │ ├── icon_256x256@2x.png
│ │ ├── icon_32x32.png
│ │ ├── icon_32x32@2x.png
│ │ ├── icon_512x512.png
│ │ └── icon_512x512@2x.png
│ └── Contents.json
├── main.swift
└── phpmon-updater.entitlements
├── phpmon
├── Assets.xcassets
│ ├── AppColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── icon_128x128.png
│ │ ├── icon_128x128@2x.png
│ │ ├── icon_16x16.png
│ │ ├── icon_16x16@2x.png
│ │ ├── icon_256x256.png
│ │ ├── icon_256x256@2x.png
│ │ ├── icon_32x32.png
│ │ ├── icon_32x32@2x.png
│ │ ├── icon_512x512.png
│ │ └── icon_512x512@2x.png
│ ├── AppIconDev.appiconset
│ │ ├── Contents.json
│ │ ├── icon_128x128.png
│ │ ├── icon_128x128@2x.png
│ │ ├── icon_16x16.png
│ │ ├── icon_16x16@2x.png
│ │ ├── icon_256x256.png
│ │ ├── icon_256x256@2x.png
│ │ ├── icon_32x32.png
│ │ ├── icon_32x32@2x.png
│ │ ├── icon_512x512.png
│ │ └── icon_512x512@2x.png
│ ├── AppIconEAP.appiconset
│ │ ├── Contents.json
│ │ ├── icon_128x128.png
│ │ ├── icon_128x128@2x.png
│ │ ├── icon_16x16.png
│ │ ├── icon_16x16@2x.png
│ │ ├── icon_256x256.png
│ │ ├── icon_256x256@2x.png
│ │ ├── icon_32x32.png
│ │ ├── icon_32x32@2x.png
│ │ ├── icon_512x512.png
│ │ └── icon_512x512@2x.png
│ ├── AppSecondary.colorset
│ │ └── Contents.json
│ ├── Checkmark.imageset
│ │ ├── Contents.json
│ │ └── check.svg
│ ├── Contents.json
│ ├── IconColorGreen.colorset
│ │ └── Contents.json
│ ├── IconColorNormal.colorset
│ │ └── Contents.json
│ ├── IconColorRed.colorset
│ │ └── Contents.json
│ ├── IconDefault.imageset
│ │ ├── Contents.json
│ │ ├── Default.png
│ │ └── Default@2x.png
│ ├── IconLinked.imageset
│ │ ├── Contents.json
│ │ ├── Linked.png
│ │ └── Linked@2x.png
│ ├── IconParked.imageset
│ │ ├── Contents.json
│ │ ├── Parked.png
│ │ └── Parked@2x.png
│ ├── IconProxy.imageset
│ │ ├── Contents.json
│ │ ├── Proxy.png
│ │ └── Proxy@2x.png
│ ├── Isolated.imageset
│ │ ├── Contents.json
│ │ ├── Isolated.png
│ │ └── Isolated@2x.png
│ ├── Lock.imageset
│ │ ├── Contents.json
│ │ └── Locked.svg
│ ├── LockUnlocked.imageset
│ │ ├── Contents.json
│ │ └── Unlocked.svg
│ ├── Menu Bar Icons
│ │ ├── Contents.json
│ │ ├── MenuBar_elephant.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Menu Bar Elephant.png
│ │ │ └── Menu Bar Elephant@2x.png
│ │ └── MenuBar_php.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Menu Bar.png
│ │ │ └── Menu Bar@2x.png
│ ├── SpinnerBackground.colorset
│ │ └── Contents.json
│ ├── StatusBarIcon.imageset
│ │ ├── Contents.json
│ │ ├── phpmon.png
│ │ └── phpmon@2x.png
│ ├── StatusBarIconStatic.imageset
│ │ ├── Contents.json
│ │ ├── phpmon.png
│ │ └── phpmon@2x.png
│ ├── StatusColorBlue.colorset
│ │ └── Contents.json
│ ├── StatusColorGreen.colorset
│ │ └── Contents.json
│ ├── StatusColorRed.colorset
│ │ └── Contents.json
│ ├── StatusColorYellow.colorset
│ │ └── Contents.json
│ ├── StatusColorYellowTranslucent.colorset
│ │ └── Contents.json
│ └── php.imageset
│ │ ├── Contents.json
│ │ └── php.svg
├── Common
│ ├── Command
│ │ ├── ActiveCommand.swift
│ │ ├── CommandProtocol.swift
│ │ └── RealCommand.swift
│ ├── Core
│ │ ├── Actions.swift
│ │ ├── Constants.swift
│ │ ├── Events.swift
│ │ ├── Helpers.swift
│ │ ├── Homebrew.swift
│ │ ├── Logger.swift
│ │ ├── Paths.swift
│ │ └── Process.swift
│ ├── Errors
│ │ ├── AlertableError.swift
│ │ └── Errors.swift
│ ├── Extensions
│ │ ├── ArrayExtension.swift
│ │ ├── DataExtension.swift
│ │ ├── DateExtension.swift
│ │ ├── DictionaryExtension.swift
│ │ ├── NSMenuExtension.swift
│ │ ├── NSMenuItemExtension.swift
│ │ ├── NSWindowExtension.swift
│ │ ├── NVAlertExtension.swift
│ │ ├── StringExtension.swift
│ │ ├── TimeIntervalExtension.swift
│ │ └── XibLoadable.swift
│ ├── Filesystem
│ │ ├── ActiveFileSystem.swift
│ │ ├── FileSystemProtocol.swift
│ │ └── RealFileSystem.swift
│ ├── Helpers
│ │ ├── Alert.swift
│ │ ├── Application.swift
│ │ ├── LocalNotification.swift
│ │ ├── LoginItemManager.swift
│ │ ├── Measurements.swift
│ │ ├── MenuBarImageGenerator.swift
│ │ ├── PMWindowController.swift
│ │ ├── System.swift
│ │ └── VersionExtractor.swift
│ ├── PHP
│ │ ├── ActivePhpInstallation.swift
│ │ ├── Extensions
│ │ │ └── Xdebug.swift
│ │ ├── Homebrew
│ │ │ ├── HomebrewDecodable.swift
│ │ │ └── HomebrewService.swift
│ │ ├── PHP Version
│ │ │ ├── PhpEnvironments.swift
│ │ │ ├── PhpHelper.swift
│ │ │ ├── PhpVersionNumberCollection.swift
│ │ │ └── VersionNumber.swift
│ │ ├── PhpConfigurationFile.swift
│ │ ├── PhpExtension.swift
│ │ ├── PhpInstallation.swift
│ │ └── Switcher
│ │ │ ├── InternalSwitcher+Valet.swift
│ │ │ ├── InternalSwitcher.swift
│ │ │ └── PhpSwitcher.swift
│ ├── Protocols
│ │ └── CreatedFromFile.swift
│ ├── Shell
│ │ ├── ActiveShell.swift
│ │ ├── RealShell.swift
│ │ └── ShellProtocol.swift
│ ├── State
│ │ └── BusyStatus.swift
│ └── Testables
│ │ ├── TestableCommand.swift
│ │ ├── TestableConfiguration.swift
│ │ ├── TestableFileSystem.swift
│ │ └── TestableShell.swift
├── Credits.html
├── Domain
│ ├── App
│ │ ├── App+ActivationPolicy.swift
│ │ ├── App+GlobalHotkey.swift
│ │ ├── App.swift
│ │ ├── AppDelegate+InterApp.swift
│ │ ├── AppDelegate+MenuOutlets.swift
│ │ ├── AppDelegate+Notifications.swift
│ │ ├── AppDelegate.swift
│ │ ├── AppUpdater.swift
│ │ ├── AppVersion.swift
│ │ ├── Base.lproj
│ │ │ └── Main.storyboard
│ │ ├── EnvironmentCheck.swift
│ │ ├── InterAppHandler.swift
│ │ ├── Services
│ │ │ ├── FakeServicesManager.swift
│ │ │ ├── Service.swift
│ │ │ ├── ServicesManager.swift
│ │ │ └── ValetServicesManager.swift
│ │ └── Startup.swift
│ ├── Integrations
│ │ ├── Common
│ │ │ └── RCFile.swift
│ │ ├── Composer
│ │ │ ├── ComposerJson.swift
│ │ │ ├── ComposerWindow.swift
│ │ │ └── ProjectTypeDetection.swift
│ │ ├── Homebrew
│ │ │ ├── Behaviors
│ │ │ │ └── BrewPermissionFixer.swift
│ │ │ ├── Brew.swift
│ │ │ ├── BrewDiagnostics.swift
│ │ │ ├── BrewPhpExtension.swift
│ │ │ ├── BrewPhpFormula.swift
│ │ │ ├── BrewPhpFormulaeHandler.swift
│ │ │ ├── BrewTapFormulae.swift
│ │ │ ├── CaskFile.swift
│ │ │ ├── Commands
│ │ │ │ ├── BrewCommand.swift
│ │ │ │ ├── PHP Extensions
│ │ │ │ │ ├── InstallPhpExtensionCommand.swift
│ │ │ │ │ └── RemovePhpExtensionCommand.swift
│ │ │ │ └── PHP Versions
│ │ │ │ │ ├── ModifyPhpVersionCommand.swift
│ │ │ │ │ └── RemovePhpVersionCommand.swift
│ │ │ └── Fake
│ │ │ │ └── FakeCommand.swift
│ │ ├── Nginx
│ │ │ └── NginxConfigurationFile.swift
│ │ └── Valet
│ │ │ ├── Domains
│ │ │ ├── FakeValetInteractor.swift
│ │ │ ├── ValetInteractor.swift
│ │ │ └── ValetListable.swift
│ │ │ ├── Proxies
│ │ │ ├── FakeValetProxy.swift
│ │ │ └── ValetProxy.swift
│ │ │ ├── Scanners
│ │ │ ├── DomainScanner.swift
│ │ │ ├── FakeDomainScanner.swift
│ │ │ ├── ValetDomainScanner.swift
│ │ │ └── ValetScanners.swift
│ │ │ ├── Sites
│ │ │ ├── FakeValetSite.swift
│ │ │ ├── PhpVersionSource.swift
│ │ │ └── ValetSite.swift
│ │ │ ├── Valet+Alerts.swift
│ │ │ └── Valet.swift
│ ├── Menu
│ │ ├── AppMenu.swift
│ │ ├── MainMenu+Actions.swift
│ │ ├── MainMenu+Async.swift
│ │ ├── MainMenu+FixMyValet.swift
│ │ ├── MainMenu+Startup.swift
│ │ ├── MainMenu+Switcher.swift
│ │ ├── MainMenu.swift
│ │ ├── StatusMenu+Items.swift
│ │ └── StatusMenu.swift
│ ├── PHP
│ │ └── PhpGuard.swift
│ ├── Preferences
│ │ ├── CustomPrefs.swift
│ │ ├── Keybinds
│ │ │ └── GlobalKeybindPreference.swift
│ │ ├── Keys.swift
│ │ ├── MenuBarIcons.swift
│ │ ├── PreferenceName.swift
│ │ ├── Preferences.swift
│ │ ├── PreferencesTabs.swift
│ │ ├── PreferencesVC.swift
│ │ ├── PreferencesWindowController+Hotkey.swift
│ │ ├── PreferencesWindowController.swift
│ │ ├── Stats.swift
│ │ └── Views
│ │ │ ├── CheckboxPreferenceView.swift
│ │ │ ├── CheckboxPreferenceView.xib
│ │ │ ├── HotkeyPreferenceView.swift
│ │ │ ├── HotkeyPreferenceView.xib
│ │ │ ├── SelectPreferenceView.swift
│ │ │ └── SelectPreferenceView.xib
│ ├── Presets
│ │ ├── Preset.swift
│ │ └── PresetHelper.swift
│ ├── SwiftUI
│ │ ├── Common
│ │ │ ├── BlockingOverlayView.swift
│ │ │ ├── CustomButtonStyles.swift
│ │ │ ├── HelpButton.swift
│ │ │ ├── SwiftUIHelper.swift
│ │ │ └── UnavailableContentView.swift
│ │ ├── Domains
│ │ │ └── VersionPopoverView.swift
│ │ └── Menu
│ │ │ ├── HeaderView.swift
│ │ │ ├── SectionHeaderView.swift
│ │ │ ├── ServicesView.swift
│ │ │ └── StatsView.swift
│ ├── Terminal Alert
│ │ ├── ProgressVC.swift
│ │ ├── ProgressWindow.storyboard
│ │ └── TerminalProgressWindowController.swift
│ ├── Terminal
│ │ └── Paths.swift
│ └── Watcher
│ │ ├── App+BrewWatch.swift
│ │ ├── App+ConfigWatch.swift
│ │ ├── ConfigFSNotifier.swift
│ │ ├── ConfigWatchManager.swift
│ │ └── FSNotifier.swift
├── IAP
│ ├── InternetAccessPolicy.plist
│ └── InternetAccessPolicy.strings
├── Info.plist
├── Modules
│ ├── Domain List
│ │ ├── Favorites.swift
│ │ └── UI
│ │ │ ├── AddProxyVC.swift
│ │ │ ├── AddSiteVC.swift
│ │ │ ├── Cells
│ │ │ ├── DomainListCellProtocol.swift
│ │ │ ├── DomainListKindCell.swift
│ │ │ ├── DomainListNameCell.swift
│ │ │ ├── DomainListPhpCell.swift
│ │ │ ├── DomainListTLSCell.swift
│ │ │ └── DomainListTypeCell.swift
│ │ │ ├── DomainListVC+Actions.swift
│ │ │ ├── DomainListVC+ContextMenu.swift
│ │ │ ├── DomainListVC.swift
│ │ │ ├── DomainListWindowController.swift
│ │ │ ├── SelectionVC.swift
│ │ │ └── Subclass
│ │ │ └── PMTableView.swift
│ ├── Onboarding
│ │ ├── OnboardingView.swift
│ │ └── OnboardingWindowController.swift
│ ├── PHP Config Editor
│ │ ├── Data
│ │ │ ├── BytePhpPreference.swift
│ │ │ └── PhpPreference.swift
│ │ └── UI
│ │ │ ├── ByteLimitView.swift
│ │ │ ├── ConfigManagerView.swift
│ │ │ └── ConfigManagerWindowController.swift
│ ├── PHP Doctor
│ │ ├── Data
│ │ │ ├── PhpConfigChecker.swift
│ │ │ ├── Warning.swift
│ │ │ └── WarningManager.swift
│ │ └── UI
│ │ │ ├── NoWarningsView.swift
│ │ │ ├── PhpDoctorView.swift
│ │ │ ├── PhpDoctorWindowController.swift
│ │ │ └── WarningView.swift
│ ├── PHP Extension Manager
│ │ ├── Data
│ │ │ └── BrewExtensionsObservable.swift
│ │ └── UI
│ │ │ ├── PhpExtensionManagerView+Actions.swift
│ │ │ ├── PhpExtensionManagerView.swift
│ │ │ └── PhpExtensionManagerWindowController.swift
│ └── PHP Version Manager
│ │ ├── Data
│ │ ├── BrewFormula+UI.swift
│ │ ├── BrewFormulaeObservable.swift
│ │ └── Fake
│ │ │ └── FakeBrewFormulaeHandler.swift
│ │ └── UI
│ │ ├── PhpVersionManagerView+Actions.swift
│ │ ├── PhpVersionManagerView.swift
│ │ └── PhpVersionManagerWindowController.swift
├── Vendor
│ └── HotKey
│ │ ├── HotKey.swift
│ │ ├── HotKeysController.swift
│ │ ├── Key.swift
│ │ ├── KeyCombo.swift
│ │ ├── LICENSE
│ │ └── ModifierFlagsExtension.swift
├── de.lproj
│ └── Localizable.strings
├── en.lproj
│ └── Localizable.strings
├── es.lproj
│ └── Localizable.strings
├── fr.lproj
│ └── Localizable.strings
├── id.lproj
│ └── Localizable.strings
├── nl.lproj
│ └── Localizable.strings
├── phpmon.entitlements
├── pt-PT.lproj
│ └── Localizable.strings
├── vi.lproj
│ └── Localizable.strings
└── zh-Hans.lproj
│ └── Localizable.strings
└── tests
├── PHP Monitor.xctestplan
├── Shared
├── TestableConfigurations.swift
├── Utility.swift
└── XCPMApplication.swift
├── feature
├── FeatureTestCase.swift
└── InternalSwitcherTest.swift
├── ui
├── DomainsListTest.swift
├── MainMenuTest.swift
├── StartupTest.swift
├── UITestCase.swift
└── UpdateCheckTest.swift
└── unit
├── Commands
└── CommandTest.swift
├── Parsers
├── CaskFileParserTest.swift
├── Config
│ ├── BytePhpPreferenceTest.swift
│ └── PhpConfigurationFileTest.swift
├── ExtensionEnumeratorTest.swift
├── HomebrewPackageTest.swift
├── HomebrewUpgradableTest.swift
├── NginxConfigurationTest.swift
├── PhpExtensionTest.swift
├── ValetConfigurationTest.swift
└── ValetRcTest.swift
├── Test Files
├── brew
│ ├── brew-formula.json
│ ├── brew-outdated.json
│ ├── brew-services-normal.json
│ ├── brew-services-sudo.json
│ ├── brew-services.json
│ └── phpmon-dev.rb
├── nginx
│ ├── nginx-proxy.test
│ ├── nginx-secure-proxy-custom-tld.test
│ ├── nginx-secure-proxy.test
│ ├── nginx-site-isolated.test
│ └── nginx-site.test
├── php
│ └── php.ini
├── phpmon
│ └── phpmon-config.json
└── valet
│ ├── valet-config.json
│ ├── valetrc.broken
│ └── valetrc.valid
├── Testables
├── Filesystem
│ ├── RealFileSystemTest.swift
│ └── TestableFileSystemTest.swift
├── Shell
│ ├── RealShellTest.swift
│ └── TestableShellTest.swift
└── TestableConfigurationTest.swift
└── Versions
├── AppVersionTest.swift
├── PhpVersionDetectionTest.swift
├── PhpVersionNumberTest.swift
├── ValetVersionExtractorTest.swift
└── VersionExtractorTest.swift
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: nicoverbruggen
2 | custom: ['https://nicoverbruggen.be/sponsor']
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yaml:
--------------------------------------------------------------------------------
1 | name: 🐞 Bug report
2 | description: Something going wrong? File a bug report!
3 | title: "[Bug]
"
4 | labels: [bug]
5 | assignees: nicoverbruggen
6 | body:
7 | - type: checkboxes
8 | attributes:
9 | label: Is there an existing issue for this?
10 | description: Please search to see if an issue already exists for the bug you encountered.
11 | options:
12 | - label: I have searched the existing issues
13 | required: true
14 | - type: textarea
15 | attributes:
16 | label: Current Behavior
17 | description: A concise description of what you're experiencing.
18 | validations:
19 | required: false
20 | - type: textarea
21 | attributes:
22 | label: Expected Behavior
23 | description: A concise description of what you expected to happen.
24 | validations:
25 | required: false
26 | - type: textarea
27 | attributes:
28 | label: Steps To Reproduce
29 | description: Steps to reproduce the behavior.
30 | placeholder: |
31 | 1. Open this menu...
32 | 2. Click here...
33 | 3. Scroll to...
34 | 4. See error...
35 | validations:
36 | required: false
37 | - type: textarea
38 | attributes:
39 | label: Environment
40 | description: |
41 | examples:
42 | - **macOS**: (e.g. Ventura 13.3)
43 | - **Valet**: (e.g. 4.0)
44 | - **PHP Monitor**: (e.g. 5.8)
45 | value: |
46 | - macOS:
47 | - Valet:
48 | - PHP Monitor:
49 | render: markdown
50 | validations:
51 | required: false
52 | - type: textarea
53 | attributes:
54 | label: Do you have a log file (or a screenshot) or any additional information?
55 | description: |
56 | You can start extra verbose logging by running: `touch ~/.config/phpmon/verbose` and restarting PHP Monitor.
57 |
58 | You can find the latest log in: `~/.config/phpmon/last_session.log`. Please attach it here!
59 |
60 | (You can attach images or log files by clicking this area to highlight it and then dragging files in.)
61 | validations:
62 | required: false
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yaml:
--------------------------------------------------------------------------------
1 | name: 😎 Feature request
2 | description: Do you have a great idea for an enhancement that could improve PHP Monitor?
3 | title: "[Feature] "
4 | labels: [enhancement]
5 | assignees: nicoverbruggen
6 | body:
7 | - type: checkboxes
8 | attributes:
9 | label: Is there an existing issue for this?
10 | description: Please search to see if an issue already exists for the bug you encountered. Please make sure you've checked the discussions tab as well. Enhancement requests that are not immediately approved will be moved to a discussion instead, so you will find some there.
11 | options:
12 | - label: I have searched the existing issues and discussions
13 | required: true
14 | - type: textarea
15 | attributes:
16 | label: Is this feature request related to a problem?
17 | description: "A clear and concise description of what the problem is. For example: 'I am always frustrated when...'"
18 | validations:
19 | required: true
20 | - type: textarea
21 | attributes:
22 | label: Describe the solution you'd like to see
23 | description: What would be a user-friendly way of resolving this particular issue?
24 | validations:
25 | required: true
26 | - type: textarea
27 | attributes:
28 | label: Additional information or context
29 | description: Add any other context or screenshots about the feature request here.
30 | validations:
31 | required: false
--------------------------------------------------------------------------------
/.github/contributing.md:
--------------------------------------------------------------------------------
1 | # Contribution Guidelines
2 |
3 | Thank you for your interest in contributing to PHP Monitor.
4 |
5 | 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.
6 |
7 | **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.**
8 |
9 | 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.
10 |
11 | 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.
12 |
13 | Plus, at this point, the majority of PHP Monitor's main functionality is also done.
14 |
15 | 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:
16 |
17 | **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.**
18 |
19 | To repeat, I am **not opposed** to small contributions and fixes, if they are **meaningful or insightful**.
20 |
21 | 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.)
22 |
23 | Thank you for respecting this!
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | Hello there! Thank you for considering a pull request for PHP Monitor.
2 |
3 | Please read the text below first before you submit your PR.
4 |
5 | ## Do not PR unless...
6 |
7 | In order to make development and maintenance of PHP Monitor easier, I ask that you _avoid_ making a pull request in the following situations:
8 |
9 | * No issue has been associated with the changes you‘d like to merge
10 | * You have not announced you will be addressing a particular issue
11 | * The PR is a low effort change: e.g. commits that only fix typos or phrasing may not be accepted
12 |
13 | (If you believe the phrasing of particular text in the app is unclear or incorrect, please open an issue first.)
14 |
15 | In short: It is usually best to *get in touch first* if you are making substantial changes.
16 |
17 | ## About destination branches
18 |
19 | 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.**
20 |
21 | Usually, the best target is the stable `dev/x.x` branch that corresponds with the latest major version that is released.
22 |
23 | There may be a newer branch available, which is an appropriate place for bigger changes, but please keep in mind that it is usually best to announce you‘ll be working on such a change before you spend the time, since as the lead contributor I might not even want said change in the app. Thank you.
24 |
25 | ## Your changes
26 |
27 | (feel free to remove the disclaimer above)
28 |
29 | * Affected parts of the app: shared code / UI code / CLI (remove what does not apply)
30 | * Estimated impact on performance: none / low / high (remove what does not apply)
31 | * Made a new build with Xcode and tested this: yes / no (remove what does not apply)
32 | * Tested on macOS version + architecture: (e.g. "Monterey on M1" or "Big Sur on Intel")
33 | * References issue(s): (please reference the issue here, using # and the number of the issue)
34 |
35 | (please describe what you have changed here)
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | PHP Monitor.xcodeproj/project.xcworkspace
2 | PHP Monitor.xcodeproj/xcuserdata
3 |
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | - todo
3 | - identifier_name
4 | - force_try
5 | - force_cast
6 |
7 | opt_in_rules:
8 | - empty_count
9 |
10 | included:
11 | - phpmon
12 | - phpmon-tests
13 |
14 | excluded:
15 | - phpmon/Vendor
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2023 Nico Verbruggen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/PHP Monitor.xcodeproj/xcshareddata/xcschemes/Unit Tests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
17 |
23 |
24 |
25 |
26 |
27 |
37 |
38 |
44 |
45 |
47 |
48 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/assets/affinity/icon-dev.afdesign:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/assets/affinity/icon-dev.afdesign
--------------------------------------------------------------------------------
/assets/affinity/icon-eap.afdesign:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/assets/affinity/icon-eap.afdesign
--------------------------------------------------------------------------------
/assets/affinity/icon-updater.afdesign:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/assets/affinity/icon-updater.afdesign
--------------------------------------------------------------------------------
/assets/affinity/icon.afdesign:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/assets/affinity/icon.afdesign
--------------------------------------------------------------------------------
/assets/affinity/icons.afdesign:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/assets/affinity/icons.afdesign
--------------------------------------------------------------------------------
/assets/menubar.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/build.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/docs/build.png
--------------------------------------------------------------------------------
/docs/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/docs/logo.png
--------------------------------------------------------------------------------
/docs/notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/docs/notification.png
--------------------------------------------------------------------------------
/docs/screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/docs/screenshot.jpg
--------------------------------------------------------------------------------
/integrations/phpmon.alfredworkflow:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/integrations/phpmon.alfredworkflow
--------------------------------------------------------------------------------
/phpmon-updater/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/phpmon-updater/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_16x16.png",
5 | "idiom" : "mac",
6 | "scale" : "1x",
7 | "size" : "16x16"
8 | },
9 | {
10 | "filename" : "icon_16x16@2x.png",
11 | "idiom" : "mac",
12 | "scale" : "2x",
13 | "size" : "16x16"
14 | },
15 | {
16 | "filename" : "icon_32x32.png",
17 | "idiom" : "mac",
18 | "scale" : "1x",
19 | "size" : "32x32"
20 | },
21 | {
22 | "filename" : "icon_32x32@2x.png",
23 | "idiom" : "mac",
24 | "scale" : "2x",
25 | "size" : "32x32"
26 | },
27 | {
28 | "filename" : "icon_128x128.png",
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "filename" : "icon_128x128@2x.png",
35 | "idiom" : "mac",
36 | "scale" : "2x",
37 | "size" : "128x128"
38 | },
39 | {
40 | "filename" : "icon_256x256.png",
41 | "idiom" : "mac",
42 | "scale" : "1x",
43 | "size" : "256x256"
44 | },
45 | {
46 | "filename" : "icon_256x256@2x.png",
47 | "idiom" : "mac",
48 | "scale" : "2x",
49 | "size" : "256x256"
50 | },
51 | {
52 | "filename" : "icon_512x512.png",
53 | "idiom" : "mac",
54 | "scale" : "1x",
55 | "size" : "512x512"
56 | },
57 | {
58 | "filename" : "icon_512x512@2x.png",
59 | "idiom" : "mac",
60 | "scale" : "2x",
61 | "size" : "512x512"
62 | }
63 | ],
64 | "info" : {
65 | "author" : "xcode",
66 | "version" : 1
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_128x128.png
--------------------------------------------------------------------------------
/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png
--------------------------------------------------------------------------------
/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_16x16.png
--------------------------------------------------------------------------------
/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png
--------------------------------------------------------------------------------
/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_256x256.png
--------------------------------------------------------------------------------
/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png
--------------------------------------------------------------------------------
/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_32x32.png
--------------------------------------------------------------------------------
/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png
--------------------------------------------------------------------------------
/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_512x512.png
--------------------------------------------------------------------------------
/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon-updater/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png
--------------------------------------------------------------------------------
/phpmon-updater/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/phpmon-updater/main.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // PHP Monitor Self-Updater
4 | //
5 | // Created by Nico Verbruggen on 01/02/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import NVAppUpdater
11 |
12 | let delegate = SelfUpdater(
13 | appName: "PHP Monitor",
14 | bundleIdentifiers: [
15 | "com.nicoverbruggen.phpmon.eap",
16 | "com.nicoverbruggen.phpmon.dev",
17 | "com.nicoverbruggen.phpmon"
18 | ],
19 | selfUpdaterPath: "~/.config/phpmon/updater"
20 | )
21 |
22 | NSApplication.shared.delegate = delegate
23 | _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
24 |
--------------------------------------------------------------------------------
/phpmon-updater/phpmon-updater.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.988",
9 | "green" : "0.580",
10 | "red" : "0.278"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.988",
27 | "green" : "0.723",
28 | "red" : "0.277"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_16x16.png",
5 | "idiom" : "mac",
6 | "scale" : "1x",
7 | "size" : "16x16"
8 | },
9 | {
10 | "filename" : "icon_16x16@2x.png",
11 | "idiom" : "mac",
12 | "scale" : "2x",
13 | "size" : "16x16"
14 | },
15 | {
16 | "filename" : "icon_32x32.png",
17 | "idiom" : "mac",
18 | "scale" : "1x",
19 | "size" : "32x32"
20 | },
21 | {
22 | "filename" : "icon_32x32@2x.png",
23 | "idiom" : "mac",
24 | "scale" : "2x",
25 | "size" : "32x32"
26 | },
27 | {
28 | "filename" : "icon_128x128.png",
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "filename" : "icon_128x128@2x.png",
35 | "idiom" : "mac",
36 | "scale" : "2x",
37 | "size" : "128x128"
38 | },
39 | {
40 | "filename" : "icon_256x256.png",
41 | "idiom" : "mac",
42 | "scale" : "1x",
43 | "size" : "256x256"
44 | },
45 | {
46 | "filename" : "icon_256x256@2x.png",
47 | "idiom" : "mac",
48 | "scale" : "2x",
49 | "size" : "256x256"
50 | },
51 | {
52 | "filename" : "icon_512x512.png",
53 | "idiom" : "mac",
54 | "scale" : "1x",
55 | "size" : "512x512"
56 | },
57 | {
58 | "filename" : "icon_512x512@2x.png",
59 | "idiom" : "mac",
60 | "scale" : "2x",
61 | "size" : "512x512"
62 | }
63 | ],
64 | "info" : {
65 | "author" : "xcode",
66 | "version" : 1
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIcon.appiconset/icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIcon.appiconset/icon_128x128.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIcon.appiconset/icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIcon.appiconset/icon_16x16.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIcon.appiconset/icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIcon.appiconset/icon_256x256.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIcon.appiconset/icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIcon.appiconset/icon_32x32.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIcon.appiconset/icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIcon.appiconset/icon_512x512.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconDev.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_16x16.png",
5 | "idiom" : "mac",
6 | "scale" : "1x",
7 | "size" : "16x16"
8 | },
9 | {
10 | "filename" : "icon_16x16@2x.png",
11 | "idiom" : "mac",
12 | "scale" : "2x",
13 | "size" : "16x16"
14 | },
15 | {
16 | "filename" : "icon_32x32.png",
17 | "idiom" : "mac",
18 | "scale" : "1x",
19 | "size" : "32x32"
20 | },
21 | {
22 | "filename" : "icon_32x32@2x.png",
23 | "idiom" : "mac",
24 | "scale" : "2x",
25 | "size" : "32x32"
26 | },
27 | {
28 | "filename" : "icon_128x128.png",
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "filename" : "icon_128x128@2x.png",
35 | "idiom" : "mac",
36 | "scale" : "2x",
37 | "size" : "128x128"
38 | },
39 | {
40 | "filename" : "icon_256x256.png",
41 | "idiom" : "mac",
42 | "scale" : "1x",
43 | "size" : "256x256"
44 | },
45 | {
46 | "filename" : "icon_256x256@2x.png",
47 | "idiom" : "mac",
48 | "scale" : "2x",
49 | "size" : "256x256"
50 | },
51 | {
52 | "filename" : "icon_512x512.png",
53 | "idiom" : "mac",
54 | "scale" : "1x",
55 | "size" : "512x512"
56 | },
57 | {
58 | "filename" : "icon_512x512@2x.png",
59 | "idiom" : "mac",
60 | "scale" : "2x",
61 | "size" : "512x512"
62 | }
63 | ],
64 | "info" : {
65 | "author" : "xcode",
66 | "version" : 1
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_128x128.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_128x128@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_16x16.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_16x16@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_256x256.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_256x256@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_32x32.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_32x32@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_512x512.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconDev.appiconset/icon_512x512@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconEAP.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_16x16.png",
5 | "idiom" : "mac",
6 | "scale" : "1x",
7 | "size" : "16x16"
8 | },
9 | {
10 | "filename" : "icon_16x16@2x.png",
11 | "idiom" : "mac",
12 | "scale" : "2x",
13 | "size" : "16x16"
14 | },
15 | {
16 | "filename" : "icon_32x32.png",
17 | "idiom" : "mac",
18 | "scale" : "1x",
19 | "size" : "32x32"
20 | },
21 | {
22 | "filename" : "icon_32x32@2x.png",
23 | "idiom" : "mac",
24 | "scale" : "2x",
25 | "size" : "32x32"
26 | },
27 | {
28 | "filename" : "icon_128x128.png",
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "filename" : "icon_128x128@2x.png",
35 | "idiom" : "mac",
36 | "scale" : "2x",
37 | "size" : "128x128"
38 | },
39 | {
40 | "filename" : "icon_256x256.png",
41 | "idiom" : "mac",
42 | "scale" : "1x",
43 | "size" : "256x256"
44 | },
45 | {
46 | "filename" : "icon_256x256@2x.png",
47 | "idiom" : "mac",
48 | "scale" : "2x",
49 | "size" : "256x256"
50 | },
51 | {
52 | "filename" : "icon_512x512.png",
53 | "idiom" : "mac",
54 | "scale" : "1x",
55 | "size" : "512x512"
56 | },
57 | {
58 | "filename" : "icon_512x512@2x.png",
59 | "idiom" : "mac",
60 | "scale" : "2x",
61 | "size" : "512x512"
62 | }
63 | ],
64 | "info" : {
65 | "author" : "xcode",
66 | "version" : 1
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_128x128.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_128x128@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_16x16.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_16x16@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_256x256.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_256x256@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_32x32.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_32x32@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_512x512.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/AppIconEAP.appiconset/icon_512x512@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/AppSecondary.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.250",
9 | "green" : "0.250",
10 | "red" : "0.250"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.750",
27 | "green" : "0.750",
28 | "red" : "0.750"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/Checkmark.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "check.svg",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "template"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/Checkmark.imageset/check.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/IconColorGreen.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.501",
9 | "green" : "0.697",
10 | "red" : "0.247"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.501",
27 | "green" : "0.765",
28 | "red" : "0.247"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/IconColorNormal.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.000",
9 | "green" : "0.000",
10 | "red" : "0.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "1.000",
27 | "green" : "1.000",
28 | "red" : "1.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/IconColorRed.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.180",
9 | "green" : "0.000",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.426",
27 | "green" : "0.363",
28 | "red" : "1.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/IconDefault.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Default.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Default@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "template"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/IconDefault.imageset/Default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/IconDefault.imageset/Default.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/IconDefault.imageset/Default@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/IconDefault.imageset/Default@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/IconLinked.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Linked.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Linked@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "template"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/IconLinked.imageset/Linked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/IconLinked.imageset/Linked.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/IconLinked.imageset/Linked@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/IconLinked.imageset/Linked@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/IconParked.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Parked.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Parked@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "template"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/IconParked.imageset/Parked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/IconParked.imageset/Parked.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/IconParked.imageset/Parked@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/IconParked.imageset/Parked@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/IconProxy.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Proxy.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Proxy@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "template"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/IconProxy.imageset/Proxy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/IconProxy.imageset/Proxy.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/IconProxy.imageset/Proxy@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/IconProxy.imageset/Proxy@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/Isolated.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Isolated.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Isolated@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "template"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/Isolated.imageset/Isolated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/Isolated.imageset/Isolated.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/Isolated.imageset/Isolated@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/Isolated.imageset/Isolated@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/Lock.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "Locked.svg",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "template"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/Lock.imageset/Locked.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/LockUnlocked.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "Unlocked.svg",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "template"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/LockUnlocked.imageset/Unlocked.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/Menu Bar Icons/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/Menu Bar Icons/MenuBar_elephant.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Menu Bar Elephant.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Menu Bar Elephant@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/Menu Bar Icons/MenuBar_elephant.imageset/Menu Bar Elephant.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/Menu Bar Icons/MenuBar_elephant.imageset/Menu Bar Elephant.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/Menu Bar Icons/MenuBar_elephant.imageset/Menu Bar Elephant@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/Menu Bar Icons/MenuBar_elephant.imageset/Menu Bar Elephant@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/Menu Bar Icons/MenuBar_php.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Menu Bar.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Menu Bar@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/Menu Bar Icons/MenuBar_php.imageset/Menu Bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/Menu Bar Icons/MenuBar_php.imageset/Menu Bar.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/Menu Bar Icons/MenuBar_php.imageset/Menu Bar@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/Menu Bar Icons/MenuBar_php.imageset/Menu Bar@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/SpinnerBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "1.000",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.172",
27 | "green" : "0.182",
28 | "red" : "0.182"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/StatusBarIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "phpmon.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "phpmon@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/StatusBarIcon.imageset/phpmon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/StatusBarIcon.imageset/phpmon.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/StatusBarIcon.imageset/phpmon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/StatusBarIcon.imageset/phpmon@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/StatusBarIconStatic.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "phpmon.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "phpmon@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/StatusBarIconStatic.imageset/phpmon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/StatusBarIconStatic.imageset/phpmon.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/StatusBarIconStatic.imageset/phpmon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoverbruggen/phpmon/4827c4a44b9f7dc1a9f561ea2537713976c3d68c/phpmon/Assets.xcassets/StatusBarIconStatic.imageset/phpmon@2x.png
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/StatusColorBlue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.988",
9 | "green" : "0.580",
10 | "red" : "0.278"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.988",
27 | "green" : "0.444",
28 | "red" : "0.277"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/StatusColorGreen.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.501",
9 | "green" : "0.697",
10 | "red" : "0.247"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.501",
27 | "green" : "0.765",
28 | "red" : "0.247"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/StatusColorRed.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.180",
9 | "green" : "0.000",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.426",
27 | "green" : "0.363",
28 | "red" : "1.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/StatusColorYellow.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.180",
9 | "green" : "0.841",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.426",
27 | "green" : "0.809",
28 | "red" : "1.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/StatusColorYellowTranslucent.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "0.300",
8 | "blue" : "0.180",
9 | "green" : "0.841",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "0.300",
26 | "blue" : "0.426",
27 | "green" : "0.809",
28 | "red" : "1.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/php.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "php.svg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "preserves-vector-representation" : true,
23 | "template-rendering-intent" : "template"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/phpmon/Assets.xcassets/php.imageset/php.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/phpmon/Common/Command/ActiveCommand.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ActiveCommand.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 12/10/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | var Command: CommandProtocol {
12 | return ActiveCommand.shared
13 | }
14 |
15 | class ActiveCommand {
16 | static var shared: CommandProtocol = RealCommand()
17 |
18 | public static func useTestable(_ output: [String: String]) {
19 | Self.shared = TestableCommand(commands: output)
20 | }
21 |
22 | public static func useSystem() {
23 | Self.shared = RealCommand()
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/phpmon/Common/Command/CommandProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommandProtocol.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 12/10/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol CommandProtocol {
12 |
13 | /**
14 | Immediately executes a command.
15 |
16 | - Parameter path: The path of the command or program to invoke.
17 | - Parameter arguments: A list of arguments that are passed on.
18 | - Parameter trimNewlines: Removes empty new line output.
19 | - Parameter withStandardError: Outputs standard error output to the same string output as well.
20 | */
21 | func execute(
22 | path: String,
23 | arguments: [String],
24 | trimNewlines: Bool,
25 | withStandardError: Bool
26 | ) -> String
27 |
28 | /**
29 | Immediately executes a command.
30 |
31 | - Parameter path: The path of the command or program to invoke.
32 | - Parameter arguments: A list of arguments that are passed on.
33 | - Parameter trimNewlines: Removes empty new line output.
34 | */
35 | func execute(
36 | path: String,
37 | arguments: [String],
38 | trimNewlines: Bool
39 | ) -> String
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/phpmon/Common/Command/RealCommand.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Command.swift
3 | // PHP Monitor
4 | //
5 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
6 | //
7 |
8 | import Cocoa
9 |
10 | public class RealCommand: CommandProtocol {
11 |
12 | public func execute(
13 | path: String,
14 | arguments: [String],
15 | trimNewlines: Bool,
16 | withStandardError: Bool
17 | ) -> String {
18 | let task = Process()
19 | task.launchPath = path
20 | task.arguments = arguments
21 |
22 | let pipe = Pipe()
23 | task.standardOutput = pipe
24 |
25 | if withStandardError {
26 | task.standardError = pipe
27 | }
28 |
29 | task.launch()
30 |
31 | let data = pipe.fileHandleForReading.readDataToEndOfFile()
32 | let output: String = String.init(data: data, encoding: String.Encoding.utf8)!
33 |
34 | if trimNewlines {
35 | return output.components(separatedBy: .newlines)
36 | .filter({ !$0.isEmpty })
37 | .joined(separator: "\n")
38 | }
39 |
40 | return output
41 | }
42 |
43 | public func execute(
44 | path: String,
45 | arguments: [String],
46 | trimNewlines: Bool = false
47 | ) -> String {
48 | self.execute(
49 | path: path,
50 | arguments: arguments,
51 | trimNewlines: trimNewlines,
52 | withStandardError: false
53 | )
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/phpmon/Common/Core/Events.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Events.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 23/01/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class Events {
12 |
13 | static let ServicesUpdated = Notification.Name("ServicesUpdated")
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/phpmon/Common/Core/Helpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Helpers.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 24/12/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | // MARK: Common Shell Commands
10 |
11 | /**
12 | Runs a `brew` command. Can run as superuser.
13 | */
14 | func brew(_ command: String, sudo: Bool = false) async {
15 | await Shell.quiet("\(sudo ? "sudo " : "")" + "\(Paths.brew) \(command)")
16 | }
17 |
18 | /**
19 | Runs `sed` in order to replace all occurrences of a string in a specific file with another.
20 | */
21 | func sed(file: String, original: String, replacement: String) async {
22 | // Escape slashes (or `sed` won't work)
23 | let e_original = original.replacingOccurrences(of: "/", with: "\\/")
24 | let e_replacement = replacement.replacingOccurrences(of: "/", with: "\\/")
25 |
26 | // Check if gsed exists; it is able to follow symlinks,
27 | // which we want to do to toggle the extension
28 | if FileSystem.fileExists("\(Paths.binPath)/gsed") {
29 | await Shell.quiet("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)")
30 | } else {
31 | await Shell.quiet("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)")
32 | }
33 | }
34 |
35 | /**
36 | Uses `grep` to determine whether a particular query string can be found in a particular file.
37 | */
38 | func grepContains(file: String, query: String) async -> Bool {
39 | return await Shell.pipe("""
40 | grep -q '\(query)' \(file); [ $? -eq 0 ] && echo "YES" || echo "NO"
41 | """).out
42 | .trimmingCharacters(in: .whitespacesAndNewlines)
43 | .contains("YES")
44 | }
45 |
46 | /**
47 | Attempts to introduce sleep for a particular duration. Use with caution.
48 | */
49 | func delay(seconds: Double) async {
50 | try! await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
51 | }
52 |
--------------------------------------------------------------------------------
/phpmon/Common/Core/Homebrew.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Homebrew.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 21/11/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct HomebrewFormulae {
12 | static var php: HomebrewFormula {
13 | if PhpEnvironments.shared.homebrewPackage == nil {
14 | return HomebrewFormula("php", elevated: true)
15 | }
16 |
17 | guard let install = PhpEnvironments.phpInstall else {
18 | return HomebrewFormula("php", elevated: true)
19 | }
20 |
21 | return HomebrewFormula(install.formula, elevated: true)
22 | }
23 |
24 | static var nginx: HomebrewFormula {
25 | return BrewDiagnostics.usesNginxFullFormula
26 | ? HomebrewFormula("nginx-full", elevated: true)
27 | : HomebrewFormula("nginx", elevated: true)
28 | }
29 |
30 | static var dnsmasq: HomebrewFormula {
31 | return HomebrewFormula("dnsmasq", elevated: true)
32 | }
33 | }
34 |
35 | class HomebrewFormula: Equatable, Hashable, CustomStringConvertible {
36 | let name: String
37 | let elevated: Bool
38 |
39 | var description: String {
40 | return name
41 | }
42 |
43 | init(_ name: String, elevated: Bool = true) {
44 | self.name = name
45 | self.elevated = elevated
46 | }
47 |
48 | static func == (lhs: HomebrewFormula, rhs: HomebrewFormula) -> Bool {
49 | return lhs.elevated == rhs.elevated && lhs.name == rhs.name
50 | }
51 |
52 | public func hash(into hasher: inout Hasher) {
53 | hasher.combine(name)
54 | hasher.combine(elevated)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/phpmon/Common/Errors/AlertableError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Errors.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 06/02/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol AlertableError {
12 | func getErrorMessageKey() -> String
13 | }
14 |
--------------------------------------------------------------------------------
/phpmon/Common/Errors/Errors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VersionParseError.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 08/02/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // MARK: - Alertable Errors
12 | // These errors must be resolved by the user.
13 |
14 | struct HomebrewPermissionError: Error, AlertableError {
15 | enum Kind: String {
16 | case applescriptNilError = "homebrew_permissions.applescript_returned_nil"
17 | }
18 |
19 | let kind: Kind
20 |
21 | func getErrorMessageKey() -> String {
22 | return "alert.errors.\(self.kind.rawValue)"
23 | }
24 | }
25 |
26 | // MARK: - Errors that do not have an associated alert message
27 | // The errors must be resolved by the developer.
28 |
29 | struct VersionParseError: Error {}
30 |
--------------------------------------------------------------------------------
/phpmon/Common/Extensions/ArrayExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArrayExtension.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 11/06/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Array {
12 | /**
13 | Sourced from Stack Overflow
14 | https://stackoverflow.com/a/33540708
15 | */
16 | func chunked(by distance: Int) -> [[Element]] {
17 | let indicesSequence = stride(from: startIndex, to: endIndex, by: distance)
18 | let array: [[Element]] = indicesSequence.map {
19 | let newIndex = $0.advanced(by: distance) > endIndex ? endIndex : $0.advanced(by: distance)
20 | return Array(self[$0 ..< newIndex])
21 | }
22 | return array
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/phpmon/Common/Extensions/DataExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DataExtension.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 16/10/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Data {
12 | var prettyPrintedJSONString: NSString? {
13 | guard let object = try? JSONSerialization.jsonObject(with: self, options: []),
14 | let data = try? JSONSerialization.data(withJSONObject: object, options: [.prettyPrinted]),
15 | let prettyPrintedString = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else {
16 | return nil
17 | }
18 |
19 | return prettyPrintedString
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/phpmon/Common/Extensions/DateExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Date.swift
3 | // PHP Monitor
4 | //
5 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
6 | //
7 |
8 | import Cocoa
9 |
10 | extension Date {
11 |
12 | func toString() -> String {
13 | let dateFormatter = DateFormatter()
14 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
15 | return dateFormatter.string(from: self)
16 | }
17 |
18 | static func fromString(_ string: String) -> Date? {
19 | let dateFormatter = DateFormatter()
20 | dateFormatter.dateFormat = "yyyy-MM-dd"
21 | return dateFormatter.date(from: string)
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/phpmon/Common/Extensions/DictionaryExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DictionaryExtension.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 01/11/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Dictionary {
12 | mutating func renameKey(fromKey: Key, toKey: Key) {
13 | if let entry = removeValue(forKey: fromKey) {
14 | self[toKey] = entry
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/phpmon/Common/Extensions/NSMenuExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSMenuExtension.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 14/04/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | extension NSMenu {
12 | convenience init(items: [NSMenuItem], target: NSObject? = nil) {
13 | self.init()
14 | self.addItems(items, target: target)
15 | }
16 |
17 | public func addItems(_ items: [NSMenuItem], target: NSObject? = nil) {
18 | for item in items {
19 | self.addItem(item)
20 | if target != nil {
21 | item.target = target
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/phpmon/Common/Extensions/NSWindowExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSWindowExtension.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 17/02/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
12 | extension NSWindow {
13 |
14 | /**
15 | Centers a window. Taken from: https://stackoverflow.com/a/66140320
16 | */
17 | public func setCenterPosition(offsetY: CGFloat = 0) {
18 | if let screenSize = screen?.visibleFrame.size {
19 | self.setFrameOrigin(
20 | NSPoint(
21 | x: (screenSize.width - frame.size.width) / 2,
22 | y: (screenSize.height - frame.size.height) / 2 + offsetY
23 | )
24 | )
25 | }
26 | }
27 |
28 | /**
29 | Shakes a window. Inspired by: http://blog.ericd.net/2016/09/30/shaking-a-macos-window/
30 | */
31 | func shake() {
32 | let numberOfShakes = 3, durationOfShake = 0.2, vigourOfShake: CGFloat = 0.03
33 |
34 | let frame: CGRect = self.frame
35 | let shakeAnimation: CAKeyframeAnimation = CAKeyframeAnimation()
36 |
37 | let shakePath = CGMutablePath()
38 | shakePath.move( to: CGPoint(x: frame.minX, y: frame.minY))
39 |
40 | for _ in 0...numberOfShakes-1 {
41 | shakePath.addLine(to: CGPoint(x: frame.minX - frame.size.width * vigourOfShake, y: frame.minY))
42 | shakePath.addLine(to: CGPoint(x: frame.minX + frame.size.width * vigourOfShake, y: frame.minY))
43 | }
44 |
45 | shakePath.closeSubpath()
46 | shakeAnimation.path = shakePath
47 | shakeAnimation.duration = durationOfShake
48 |
49 | self.animations = ["frameOrigin": shakeAnimation]
50 | self.animator().setFrameOrigin(self.frame.origin)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/phpmon/Common/Extensions/NVAlertExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NVAlertExtension.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 16/07/2024.
6 | // Copyright © 2024 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import NVAlert
11 |
12 | extension NVAlert {
13 | /**
14 | Shows the modal for a particular error.
15 | */
16 | @MainActor public static func show(for error: Error & AlertableError) {
17 | let key = error.getErrorMessageKey()
18 | return NVAlert().withInformation(
19 | title: "\(key).title".localized,
20 | subtitle: "\(key).description".localized
21 | ).withPrimary(text: "generic.ok".localized).show()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/phpmon/Common/Extensions/TimeIntervalExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TimeExtension.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 29/09/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension TimeInterval {
12 | public static func minutes(_ amount: Int) -> TimeInterval {
13 | return Double(amount * 60)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/phpmon/Common/Extensions/XibLoadable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NibLoadable.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 04/02/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
12 | // Adapted from: https://stackoverflow.com/a/46268778
13 |
14 | protocol XibLoadable {
15 |
16 | static var xibName: String? { get }
17 | static func createFromXib(in bundle: Bundle) -> Self?
18 |
19 | }
20 |
21 | extension XibLoadable where Self: NSView {
22 |
23 | static var xibName: String? {
24 | return String(describing: Self.self)
25 | }
26 |
27 | static func createFromXib(in bundle: Bundle = Bundle.main) -> Self? {
28 | guard let xibName = xibName else { return nil }
29 | var topLevelArray: NSArray?
30 | bundle.loadNibNamed(NSNib.Name(xibName), owner: self, topLevelObjects: &topLevelArray)
31 | guard let results = topLevelArray else { return nil }
32 | let views = [Any](results).filter { $0 is Self }
33 | return views.last as? Self
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/phpmon/Common/Filesystem/ActiveFileSystem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FS.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 08/10/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | var FileSystem: FileSystemProtocol {
12 | return ActiveFileSystem.shared
13 | }
14 |
15 | class ActiveFileSystem {
16 | static var shared: FileSystemProtocol = RealFileSystem()
17 |
18 | /** Note: Intermediate directories are not automatically inferred and have to be manually declared. */
19 | public static func useTestable(_ files: [String: FakeFile]) {
20 | Self.shared = TestableFileSystem(files: files)
21 | }
22 |
23 | public static func useSystem() {
24 | Self.shared = RealFileSystem()
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/phpmon/Common/Filesystem/FileSystemProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileSystemProtocol.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 08/10/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol FileSystemProtocol {
12 |
13 | // MARK: - Basics
14 |
15 | func createDirectory(_ path: String, withIntermediateDirectories: Bool) throws
16 |
17 | func writeAtomicallyToFile(_ path: String, content: String) throws
18 |
19 | func getStringFromFile(_ path: String) throws -> String
20 |
21 | func getShallowContentsOfDirectory(_ path: String) throws -> [String]
22 |
23 | func getDestinationOfSymlink(_ path: String) throws -> String
24 |
25 | // MARK: - Move & Delete Files
26 |
27 | func move(from path: String, to newPath: String) throws
28 |
29 | func remove(_ path: String) throws
30 |
31 | // MARK: — Attributes
32 |
33 | func makeExecutable(_ path: String) throws
34 |
35 | // MARK: - Checks
36 |
37 | func isExecutableFile(_ path: String) -> Bool
38 |
39 | func isWriteableFile(_ path: String) -> Bool
40 |
41 | func anyExists(_ path: String) -> Bool
42 |
43 | func fileExists(_ path: String) -> Bool
44 |
45 | func directoryExists(_ path: String) -> Bool
46 |
47 | func isSymlink(_ path: String) -> Bool
48 |
49 | func isDirectory(_ path: String) -> Bool
50 | }
51 |
--------------------------------------------------------------------------------
/phpmon/Common/Helpers/Alert.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Alert.swift
3 | // PHP Monitor
4 | //
5 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
6 | //
7 |
8 | import Cocoa
9 |
10 | class Alert {
11 |
12 | public static func confirm(
13 | onWindow window: NSWindow,
14 | messageText: String,
15 | informativeText: String,
16 | buttonTitle: String = "generic.ok".localized,
17 | buttonIsDestructive: Bool = false,
18 | secondButtonTitle: String = "generic.cancel".localized,
19 | style: NSAlert.Style = .warning,
20 | onFirstButtonPressed: @escaping (() -> Void)
21 | ) {
22 | if !Thread.isMainThread {
23 | fatalError("You should always present alerts on the main thread!")
24 | }
25 |
26 | let alert = NSAlert.init()
27 | alert.alertStyle = style
28 | alert.messageText = messageText
29 | alert.informativeText = informativeText
30 | alert.addButton(withTitle: buttonTitle)
31 | alert.buttons.first?.hasDestructiveAction = buttonIsDestructive
32 | if !secondButtonTitle.isEmpty {
33 | alert.addButton(withTitle: secondButtonTitle)
34 | }
35 | alert.beginSheetModal(for: window) { response in
36 | if response == .alertFirstButtonReturn {
37 | onFirstButtonPressed()
38 | }
39 | }
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/phpmon/Common/Helpers/LocalNotification.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocalNotification.swift
3 | // PHP Monitor
4 | //
5 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
6 | //
7 |
8 | import Foundation
9 | import UserNotifications
10 |
11 | class LocalNotification {
12 |
13 | @MainActor public static func send(title: String, subtitle: String, preference: PreferenceName?) {
14 | if preference != nil && !Preferences.isEnabled(preference!) {
15 | return
16 | }
17 |
18 | let content = UNMutableNotificationContent()
19 | content.title = title
20 | content.body = subtitle
21 |
22 | let uuidString = UUID().uuidString
23 | let request = UNNotificationRequest(
24 | identifier: uuidString,
25 | content: content,
26 | trigger: nil
27 | )
28 |
29 | let notificationCenter = UNUserNotificationCenter.current()
30 | notificationCenter.add(request) { (error) in
31 | if error != nil {
32 | Log.err(error!)
33 | }
34 | }
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/phpmon/Common/Helpers/LoginItemManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginItemManager.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 15/02/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import AppKit
10 | import ServiceManagement
11 |
12 | @available(macOS 13.0, *)
13 | class LoginItemManager {
14 | func loginItemIsEnabled() -> Bool {
15 | return SMAppService.mainApp.status == .enabled
16 | }
17 |
18 | func disableLoginItem() {
19 | try? SMAppService.mainApp.unregister()
20 | }
21 |
22 | func enableLoginItem() {
23 | try? SMAppService.mainApp.register()
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/phpmon/Common/Helpers/Measurements.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Measurements.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 02/03/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct Measurement {
12 | let started = Date()
13 |
14 | var milliseconds: Double {
15 | return round(Date().timeIntervalSince(started) * 1000 * 1000) / 1000
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/phpmon/Common/Helpers/PMWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PMWindowController.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 05/12/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | /**
12 | This window class keeps track of which windows are currently visible, and reports this info back to the App class.
13 | For more information, check the `windows` property on `App`.
14 |
15 | - Note: This class does make a simple assumption: each window controller corresponds to a single view.
16 | */
17 | class PMWindowController: NSWindowController, NSWindowDelegate {
18 |
19 | public var windowName: String {
20 | fatalError("Please specify a window name")
21 | }
22 |
23 | override func showWindow(_ sender: Any?) {
24 | super.showWindow(sender)
25 | App.shared.register(window: windowName)
26 | }
27 |
28 | func windowWillClose(_ notification: Notification) {
29 | App.shared.remove(window: windowName)
30 | }
31 |
32 | func windowDidResize(_ notification: Notification) {}
33 |
34 | deinit {
35 | Log.perf("deinit: \(String(describing: self)).\(#function)")
36 | }
37 |
38 | }
39 |
40 | extension NSWindowController {
41 |
42 | public func positionWindowInTopRightCorner(offsetY: CGFloat = 0, offsetX: CGFloat = 0) {
43 | guard let frame = NSScreen.main?.frame else { return }
44 | guard let window = self.window else { return }
45 |
46 | window.setFrame(NSRect(
47 | x: frame.size.width - window.frame.size.width - 20 + offsetX,
48 | y: frame.size.height - window.frame.size.height - 40 + offsetY,
49 | width: window.frame.width,
50 | height: window.frame.height
51 | ), display: true)
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/phpmon/Common/Helpers/System.swift:
--------------------------------------------------------------------------------
1 | //
2 | // System.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 01/11/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | Run a simple blocking Shell command on the user's own system.
13 | */
14 | public func system(_ command: String) -> String {
15 | let task = Process()
16 | task.launchPath = "/bin/sh"
17 | task.arguments = ["-c", command]
18 |
19 | let pipe = Pipe()
20 | task.standardOutput = pipe
21 | task.launch()
22 |
23 | let data = pipe.fileHandleForReading.readDataToEndOfFile()
24 | let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String
25 |
26 | return output
27 | }
28 |
29 | /**
30 | Same as the `system` command, but does not return the output.
31 | */
32 | public func system_quiet(_ command: String) {
33 | let task = Process()
34 | task.launchPath = "/bin/sh"
35 | task.arguments = ["-c", command]
36 |
37 | let pipe = Pipe()
38 | task.standardOutput = pipe
39 | task.launch()
40 |
41 | _ = pipe.fileHandleForReading.readDataToEndOfFile()
42 | return
43 | }
44 |
45 | /**
46 | Retrieves the username for the currently signed in user via `/usr/bin/id`.
47 | This cannot fail or the application will crash.
48 | */
49 | public func identity() -> String {
50 | let task = Process()
51 | task.launchPath = "/usr/bin/id"
52 | task.arguments = ["-un"]
53 |
54 | let pipe = Pipe()
55 | task.standardOutput = pipe
56 | task.launch()
57 |
58 | guard let output = String(
59 | data: pipe.fileHandleForReading.readDataToEndOfFile(),
60 | encoding: String.Encoding.utf8
61 | ) else {
62 | fatalError("Could not retrieve username via `id -un`!")
63 | }
64 |
65 | return output.trimmingCharacters(in: .whitespacesAndNewlines)
66 | }
67 |
68 | /**
69 | Retrieves the user's preferred shell.
70 | */
71 | public func preferred_shell() -> String {
72 | return system("dscl . -read ~/ UserShell | sed 's/UserShell: //'")
73 | .trimmingCharacters(in: .whitespacesAndNewlines)
74 | }
75 |
--------------------------------------------------------------------------------
/phpmon/Common/Helpers/VersionExtractor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VersionExtractor.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 16/12/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class VersionExtractor {
12 |
13 | /**
14 | This attempts to extract the version number from any given string.
15 | */
16 | public static func from(_ string: String) -> String? {
17 | do {
18 | let regex = try NSRegularExpression(
19 | pattern: #"(?(\d+)(.)(\d+)((.)(\d+))?)"#,
20 | options: []
21 | )
22 |
23 | let match = regex.matches(
24 | in: string,
25 | options: [],
26 | range: NSRange(location: 0, length: string.count)
27 | ).first
28 |
29 | guard let match = match else {
30 | return nil
31 | }
32 |
33 | let range = Range(
34 | match.range(withName: "version"),
35 | in: string
36 | )!
37 |
38 | return String(string[range])
39 | } catch {
40 | return nil
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/phpmon/Common/PHP/Extensions/Xdebug.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Xdebug.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 01/05/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
12 | class Xdebug {
13 |
14 | public static var enabled: Bool {
15 | return PhpEnvironments.shared.getConfigFile(forKey: "xdebug.mode") != nil
16 | }
17 |
18 | public static var activeModes: [String] {
19 | guard let file = PhpEnvironments.shared.getConfigFile(forKey: "xdebug.mode") else {
20 | return []
21 | }
22 |
23 | guard let value = file.get(for: "xdebug.mode") else {
24 | return []
25 | }
26 |
27 | return value.components(separatedBy: ",").filter { self.modes.contains($0) }
28 | }
29 |
30 | public static func asMenuItems() -> [NSMenuItem] {
31 | var items: [NSMenuItem] = []
32 |
33 | let activeModes = Self.activeModes
34 |
35 | for mode in Self.modes {
36 | let item = XdebugMenuItem(
37 | title: mode,
38 | action: #selector(MainMenu.toggleXdebugMode(sender:)),
39 | keyEquivalent: ""
40 | )
41 |
42 | item.state = activeModes.contains(mode) ? .on : .off
43 | item.mode = mode
44 | items.append(item)
45 | }
46 |
47 | return items
48 | }
49 |
50 | public static var modes: [String] {
51 | return [
52 | "develop",
53 | "coverage",
54 | "debug",
55 | "gcstats",
56 | "profile",
57 | "trace"
58 | ]
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/phpmon/Common/PHP/Homebrew/HomebrewDecodable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomebrewDecodable.swift
3 | // PHP Monitor
4 | //
5 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | struct HomebrewPackage: Decodable {
11 | let full_name: String
12 | let aliases: [String]
13 | let installed: [HomebrewInstalled]
14 | let linked_keg: String?
15 |
16 | public var version: String {
17 | return aliases.first!
18 | .replacingOccurrences(of: "php@", with: "")
19 | }
20 | }
21 |
22 | struct HomebrewInstalled: Decodable {
23 | let version: String
24 | let built_as_bottle: Bool
25 | let installed_as_dependency: Bool
26 | let installed_on_request: Bool
27 | }
28 |
29 | struct OutdatedFormulae: Decodable {
30 | let formulae: [OutdatedFormula]
31 | }
32 |
33 | struct OutdatedFormula: Decodable {
34 | let name: String
35 | let installed_versions: [String]
36 | let current_version: String
37 | let pinned: Bool
38 | let pinned_version: String?
39 | }
40 |
--------------------------------------------------------------------------------
/phpmon/Common/PHP/Homebrew/HomebrewService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomebrewService.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 11/01/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | final class HomebrewService: Sendable, Decodable {
12 | let name: String
13 | let service_name: String
14 | let running: Bool
15 | let loaded: Bool
16 | let pid: Int?
17 | let user: String?
18 | let status: String?
19 | let log_path: String?
20 | let error_log_path: String?
21 |
22 | init(
23 | name: String,
24 | service_name: String,
25 | running: Bool,
26 | loaded: Bool,
27 | pid: Int? = nil,
28 | user: String? = nil,
29 | status: String? = nil,
30 | log_path: String? = nil,
31 | error_log_path: String? = nil
32 | ) {
33 | self.name = name
34 | self.service_name = service_name
35 | self.running = running
36 | self.loaded = loaded
37 | self.pid = pid
38 | self.user = user
39 | self.status = status
40 | self.log_path = log_path
41 | self.error_log_path = error_log_path
42 | }
43 |
44 | /**
45 | Dummy data for preview purposes.
46 | */
47 | public static func dummy(named service: String, enabled: Bool, status: String? = nil) -> HomebrewService {
48 | return HomebrewService(
49 | name: service,
50 | service_name: service,
51 | running: enabled,
52 | loaded: enabled,
53 | pid: nil,
54 | user: nil,
55 | status: status,
56 | log_path: nil,
57 | error_log_path: nil
58 | )
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/phpmon/Common/PHP/Switcher/PhpSwitcher.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhpVersionSwitchContract.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 24/12/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol PhpSwitcherDelegate: AnyObject {
12 |
13 | func switcherDidStartSwitching(to version: String)
14 |
15 | func switcherDidCompleteSwitch(to version: String)
16 |
17 | }
18 |
19 | protocol PhpSwitcher {
20 |
21 | func performSwitch(to version: String) async
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/phpmon/Common/Protocols/CreatedFromFile.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CreatedFromFile.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 15/05/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol CreatedFromFile {
12 |
13 | static func from(filePath: String) -> Self?
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/phpmon/Common/Shell/ActiveShell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Shell.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 20/09/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | var Shell: ShellProtocol {
12 | return ActiveShell.shared
13 | }
14 |
15 | class ActiveShell {
16 | static var shared: ShellProtocol = RealShell()
17 |
18 | public static func useTestable(_ expectations: [String: BatchFakeShellOutput]) {
19 | Self.shared = TestableShell(expectations: expectations)
20 | }
21 |
22 | public static func useSystem() {
23 | Self.shared = RealShell()
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/phpmon/Common/State/BusyStatus.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BusyStatus.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 02/05/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class BusyStatus: ObservableObject {
12 | @Published var busy: Bool
13 | @Published var title: String
14 | @Published var description: String
15 |
16 | init(busy: Bool, title: String, description: String) {
17 | self.busy = busy
18 | self.title = title
19 | self.description = description
20 | }
21 |
22 | public static func notBusy() -> BusyStatus {
23 | return BusyStatus(busy: false, title: "", description: "")
24 | }
25 |
26 | public static func busy() -> BusyStatus {
27 | return BusyStatus(busy: false, title: "", description: "")
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/phpmon/Common/Testables/TestableCommand.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestableCommand.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 12/10/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class TestableCommand: CommandProtocol {
12 | init(commands: [String: String]) {
13 | self.commands = commands
14 | }
15 |
16 | var commands: [String: String]
17 |
18 | func execute(path: String, arguments: [String]) -> String {
19 | self.execute(path: path, arguments: arguments, trimNewlines: false)
20 | }
21 |
22 | public func execute(path: String, arguments: [String], trimNewlines: Bool, withStandardError: Bool) -> String {
23 | self.execute(path: path, arguments: arguments, trimNewlines: trimNewlines)
24 | }
25 |
26 | public func execute(path: String, arguments: [String], trimNewlines: Bool) -> String {
27 | let concatenatedCommand = "\(path) \(arguments.joined(separator: " "))"
28 | assert(commands.keys.contains(concatenatedCommand), "Command `\(concatenatedCommand)` not found")
29 | return self.commands[concatenatedCommand]!
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/phpmon/Domain/App/App+ActivationPolicy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // App+ActivationPolicy.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 05/12/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import Foundation
11 |
12 | extension App {
13 |
14 | // MARK: - Application State
15 |
16 | /**
17 | Registers a window as currently open.
18 | */
19 | public func register(window name: String) {
20 | if !openWindows.contains(name) {
21 | openWindows.append(name)
22 | }
23 | updateActivationPolicy()
24 | }
25 |
26 | /**
27 | Removes a window, assuming it was closed.
28 | */
29 | public func remove(window name: String) {
30 | openWindows.removeAll { window in
31 | window == name
32 | }
33 | updateActivationPolicy()
34 | }
35 |
36 | /**
37 | If there are any open windows, the app will be a regular app.
38 | If there are no windows open, the app will be an accessory (toolbar) app.
39 | */
40 | public func updateActivationPolicy() {
41 | NSApp.setActivationPolicy(!openWindows.isEmpty ? .regular : .accessory)
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/phpmon/Domain/App/App+GlobalHotkey.swift:
--------------------------------------------------------------------------------
1 | //
2 | // App+GlobalHotkey.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 05/12/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | extension App {
12 |
13 | // MARK: - Methods
14 |
15 | /**
16 | On startup, the preferences should be loaded from the .plist,
17 | and we'll enable the shortcut if it is set.
18 | */
19 | func loadGlobalHotkey() {
20 | // Make sure we can retrieve the hotkey from preferences
21 | guard let hotkey = Preferences.preferences[.globalHotkey] as? String else {
22 | Log.info("No global hotkey was saved in preferences. None set.")
23 | return
24 | }
25 |
26 | // Make sure we can parse the JSON into the desired format
27 | guard let keybindPref = GlobalKeybindPreference.fromJson(hotkey) else {
28 | Log.err("No global hotkey loaded, could not be parsed!")
29 | shortcutHotkey = nil
30 | return
31 | }
32 |
33 | shortcutHotkey = HotKey(keyCombo: KeyCombo(
34 | carbonKeyCode: keybindPref.keyCode,
35 | carbonModifiers: keybindPref.carbonFlags
36 | ))
37 | }
38 |
39 | /**
40 | Sets up the action that needs to occur when the shortcut key is pressed
41 | (opens the menu).
42 | */
43 | func setupGlobalHotkeyListener() {
44 | guard let hotkey = shortcutHotkey else {
45 | return
46 | }
47 |
48 | hotkey.keyDownHandler = {
49 | Task { @MainActor in
50 | MainMenu.shared.statusItem.button?.performClick(nil)
51 | NSApplication.shared.activate(ignoringOtherApps: true)
52 | }
53 | }
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/phpmon/Domain/App/AppDelegate+InterApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate+InterApp.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 20/12/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import Foundation
11 |
12 | extension AppDelegate {
13 |
14 | /**
15 | This is an entry point for future development for integrating with the PHP Monitor
16 | application URL. You can use the `phpmon://` protocol to communicate with the app.
17 |
18 | At this time you can trigger the site list using Alfred (or some other application)
19 | by opening the following URL: `phpmon://list`.
20 |
21 | Please note that PHP Monitor needs to be running in the background for this to work.
22 | */
23 | @MainActor func application(_ application: NSApplication, open urls: [URL]) {
24 | if !Preferences.isEnabled(.allowProtocolForIntegrations) {
25 | Log.info("Acting on commands via phpmon:// has been disabled.")
26 | return
27 | }
28 |
29 | guard let url = urls.first else { return }
30 |
31 | self.interpretCommand(
32 | url.absoluteString.replacingOccurrences(of: "phpmon://", with: ""),
33 | commands: InterApp.getCommands()
34 | )
35 | }
36 |
37 | private func interpretCommand(_ command: String, commands: [InterApp.Action]) {
38 | commands.forEach { action in
39 | if command.starts(with: action.command) {
40 | let lastElement = String(command.split(separator: "/").last!)
41 | action.action(lastElement)
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/phpmon/Domain/App/AppDelegate+MenuOutlets.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate+MenuOutlets.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 05/12/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import AppKit
11 |
12 | /**
13 | Any outlets connected to the app's main menu (not the menu that shows when the icon in
14 | the menu bar is clicked, but the regular app's main menu) are configured here.
15 |
16 | Default interactions like copy/paste, select all, close window etc. are wired up by
17 | default in the storyboard and do not need to be manually added.
18 |
19 | Extra functionality (like the menu item to reload the list of sites) does, however.
20 |
21 | - Note: This menu is only displayed when the app is NOT running in accessory mode.
22 | For more information about this, please see the ActivationPolicy-related extension.
23 | */
24 | extension AppDelegate {
25 |
26 | // MARK: - Menu Interactions
27 |
28 | @IBAction func addSiteLinkPressed(_ sender: Any) {
29 | DomainListVC.show()
30 |
31 | guard let windowController = App.shared.domainListWindowController else { return }
32 | windowController.pressedAddLink(nil)
33 | }
34 |
35 | @IBAction func reloadDomainListPressed(_ sender: Any) {
36 | Task { // Reload domains
37 | let vc = App.shared.domainListWindowController?
38 | .window?.contentViewController as? DomainListVC
39 |
40 | if vc != nil {
41 | // If the view exists, directly reload the list of sites.
42 | await vc!.reloadDomains()
43 | } else {
44 | // If the view does not exist, reload the cached data that was populated when the app launched.
45 | await Valet.shared.reloadSites()
46 | }
47 | }
48 | }
49 |
50 | @IBAction func focusSearchField(_ sender: Any) {
51 | DomainListVC.show()
52 |
53 | guard let windowController = App.shared.domainListWindowController else { return }
54 | windowController.searchToolbarItem.searchField.becomeFirstResponder()
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/phpmon/Domain/App/AppDelegate+Notifications.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate+Notifications.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 06/12/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UserNotifications
11 |
12 | extension AppDelegate {
13 |
14 | // MARK: - Notifications
15 |
16 | /**
17 | Sets up notifications. That does mean we need to ask for permission first.
18 | If we cannot get permission, we should log this.
19 | */
20 | public func setupNotifications() {
21 | let notificationCenter = UNUserNotificationCenter.current()
22 | notificationCenter.delegate = self
23 | notificationCenter.requestAuthorization(options: [.alert], completionHandler: { granted, error in
24 | if !granted {
25 | Log.warn("PHP Monitor does not have permission to show notifications.")
26 | }
27 | if let error = error {
28 | Log.err("PHP Monitor encounted an error determining notification permissions:")
29 | Log.err(error)
30 | }
31 | })
32 | }
33 |
34 | /**
35 | Ensure that the application displays notifications even when the app is active.
36 | */
37 | func userNotificationCenter(
38 | _ center: UNUserNotificationCenter,
39 | willPresent notification: UNNotification,
40 | withCompletionHandler completionHandler:
41 | @escaping (UNNotificationPresentationOptions) -> Void
42 | ) {
43 | completionHandler([.banner])
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/phpmon/Domain/App/EnvironmentCheck.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EnvironmentCheck.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 10/08/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | The `EnvironmentCheck` is used to defer the execution of all of these commands until necessary.
13 | Checks that require an app restart will always lead to an alert and app termination shortly after.
14 | */
15 | struct EnvironmentCheck {
16 | let command: () async -> Bool
17 | let name: String
18 | let titleText: String
19 | let subtitleText: String
20 | let descriptionText: String
21 | let buttonText: String
22 | let requiresAppRestart: Bool
23 |
24 | init(
25 | command: @escaping () async -> Bool,
26 | name: String,
27 | titleText: String,
28 | subtitleText: String,
29 | descriptionText: String = "",
30 | buttonText: String = "OK",
31 | requiresAppRestart: Bool = false
32 | ) {
33 | self.command = command
34 | self.name = name
35 | self.titleText = titleText
36 | self.subtitleText = subtitleText
37 | self.descriptionText = descriptionText
38 | self.buttonText = buttonText
39 | self.requiresAppRestart = requiresAppRestart
40 | }
41 |
42 | public func succeeds() async -> Bool {
43 | return await !self.command()
44 | }
45 | }
46 |
47 | struct EnvironmentCheckGroup {
48 | let name: String
49 | let condition: () -> Bool
50 | let checks: [EnvironmentCheck]
51 | }
52 |
--------------------------------------------------------------------------------
/phpmon/Domain/App/Services/Service.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ServiceWrapper.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 23/12/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /** Service linked to a Homebrew formula and whether it is currently (in)active or missing. */
12 | public struct Service: Hashable {
13 | var formula: HomebrewFormula
14 | var status: Status = .missing
15 |
16 | public var name: String {
17 | return formula.name
18 | }
19 |
20 | init(formula: HomebrewFormula, service: HomebrewService? = nil) {
21 | self.formula = formula
22 |
23 | guard let service else { return }
24 |
25 | self.status = service.running ? .active : .inactive
26 |
27 | if service.status == "error" {
28 | self.status = .error
29 | }
30 | }
31 |
32 | // MARK: - Protocols
33 |
34 | public static func == (lhs: Service, rhs: Service) -> Bool {
35 | return lhs.hashValue == rhs.hashValue
36 | }
37 |
38 | public func hash(into hasher: inout Hasher) {
39 | hasher.combine(formula)
40 | hasher.combine(status)
41 | }
42 |
43 | // MARK: - Status
44 |
45 | public enum Status: String {
46 | case active
47 | case inactive
48 | case error
49 | case missing
50 |
51 | var asBool: Bool {
52 | return self == .active
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/phpmon/Domain/Integrations/Common/RCFile.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RCFile.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 24/01/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct RCFile {
12 | let path: String?
13 | let fields: [String: String]
14 |
15 | static func fromPath(_ path: String) -> RCFile? {
16 | do {
17 | let text = try String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8)
18 | return RCFile(path: path, contents: text)
19 | } catch {
20 | return nil
21 | }
22 | }
23 |
24 | init(path: String? = nil, contents: String) {
25 | var fields: [String: String] = [:]
26 |
27 | contents
28 | .split(separator: "\n")
29 | .forEach({ line in
30 | if line.contains("=") {
31 | let content = line.split(separator: "=")
32 | let key = String(content[0])
33 | .trimmingCharacters(in: .whitespaces)
34 | .replacingOccurrences(of: "\"", with: "")
35 | if key.starts(with: "#") {
36 | return
37 | }
38 | let value = String(content[1])
39 | .trimmingCharacters(in: .whitespaces)
40 | .replacingOccurrences(of: "\"", with: "")
41 | fields[key] = value
42 | }
43 | })
44 |
45 | self.path = path
46 | self.fields = fields
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/phpmon/Domain/Integrations/Homebrew/Brew.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Homebrew.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 17/03/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class Brew {
12 | static let shared = Brew()
13 |
14 | /// Formulae that can be observed.
15 | var formulae = BrewFormulaeObservable()
16 |
17 | /// The version of Homebrew that was detected.
18 | var version: VersionNumber?
19 |
20 | /// Determine which version of Homebrew is installed.
21 | public func determineVersion() async {
22 | let output = await Shell.pipe("\(Paths.brew) --version")
23 | self.version = try? VersionNumber.parse(output.out)
24 |
25 | if let version = version {
26 | Log.info("The user has Homebrew \(version.text) installed.")
27 |
28 | if version.major < 4 {
29 | Log.warn("Managing PHP versions is only officially supported with Homebrew 4 or newer!")
30 | }
31 | } else {
32 | Log.warn("The Homebrew version could not be determined.")
33 | }
34 | }
35 |
36 | /// Each formula for each PHP version that can be installed.
37 | public static let phpVersionFormulae = [
38 | "8.5": "shivammathur/php/php@8.5",
39 | "8.4": "shivammathur/php/php@8.4",
40 | "8.3": "shivammathur/php/php@8.3",
41 | "8.2": "shivammathur/php/php@8.2",
42 | "8.1": "shivammathur/php/php@8.1",
43 | "8.0": "shivammathur/php/php@8.0",
44 | "7.4": "shivammathur/php/php@7.4",
45 | "7.3": "shivammathur/php/php@7.3",
46 | "7.2": "shivammathur/php/php@7.2",
47 | "7.1": "shivammathur/php/php@7.1",
48 | "7.0": "shivammathur/php/php@7.0",
49 | "5.6": "shivammathur/php/php@5.6"
50 | ]
51 | }
52 |
--------------------------------------------------------------------------------
/phpmon/Domain/Integrations/Homebrew/BrewTapFormulae.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BrewTapFormulae.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 01/11/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class BrewTapFormulae {
12 | public static func from(tap: String) -> [String: [BrewPhpExtension]] {
13 | let directory = "\(Paths.tapPath)/\(tap)/Formula"
14 |
15 | let files = try? FileSystem.getShallowContentsOfDirectory(directory)
16 |
17 | var availableExtensions = [String: [BrewPhpExtension]]()
18 |
19 | guard let files = files else {
20 | return availableExtensions
21 | }
22 |
23 | let regex = try! NSRegularExpression(pattern: "(\\w+)@(\\d+\\.\\d+)\\.rb")
24 |
25 | for file in files {
26 | let matches = regex.matches(in: file, range: NSRange(file.startIndex..., in: file))
27 | if let match = matches.first {
28 | if let phpExtensionRange = Range(match.range(at: 1), in: file),
29 | let versionRange = Range(match.range(at: 2), in: file) {
30 | // Determine what the extension's name is
31 | let phpExtensionName = String(file[phpExtensionRange])
32 |
33 | // Determine what PHP version this is for
34 | let phpVersion = String(file[versionRange])
35 |
36 | // Create a new BrewPhpExtension object (determines if installed)
37 | let phpExtension = BrewPhpExtension(
38 | path: "\(Paths.tapPath)/\(tap)/Formula/\(file)",
39 | name: phpExtensionName,
40 | phpVersion: phpVersion
41 | )
42 |
43 | // Append the extension to the list
44 | var extensions = availableExtensions[phpVersion, default: []]
45 | extensions.append(phpExtension)
46 | availableExtensions[phpVersion] = extensions.sorted()
47 | }
48 | }
49 | }
50 |
51 | return availableExtensions
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/phpmon/Domain/Integrations/Homebrew/Fake/FakeCommand.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FakeCommand.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 21/03/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class FakeCommand: BrewCommand {
12 | func getCommandTitle() -> String {
13 | return "Hello"
14 | }
15 |
16 | let version: String
17 |
18 | init(version: String) {
19 | self.version = version
20 | }
21 |
22 | func execute(onProgress: @escaping (BrewCommandProgress) -> Void) async throws {
23 | onProgress(.create(value: 0.2, title: "Hello", description: "Doing the work"))
24 | await delay(seconds: 2)
25 | onProgress(.create(value: 0.5, title: "Hello", description: "Doing some more work"))
26 | await delay(seconds: 1)
27 | onProgress(.create(value: 1, title: "Hello", description: "Job's done"))
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/phpmon/Domain/Integrations/Valet/Domains/ValetListable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ValetListable.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 12/04/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol ValetListable {
12 |
13 | func getListableName() -> String
14 |
15 | func getListableSecured() -> Bool
16 |
17 | func getListableAbsolutePath() -> String
18 |
19 | func getListablePhpVersion() -> String
20 |
21 | func getListableKind() -> String
22 |
23 | func getListableType() -> String
24 |
25 | func getListableUrl() -> URL?
26 |
27 | func getListableFavorited() -> Bool
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/phpmon/Domain/Integrations/Valet/Proxies/FakeValetProxy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FakeValetProxy.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 16/12/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class FakeValetProxy: ValetProxy {
12 | override func determineSecured() {
13 | return
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/phpmon/Domain/Integrations/Valet/Scanners/DomainScanner.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DomainScanner.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 02/04/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol DomainScanner {
12 |
13 | // MARK: - Sites
14 |
15 | func resolveSiteCount(paths: [String]) -> Int
16 |
17 | func resolveSitesFrom(paths: [String]) -> [ValetSite]
18 |
19 | func resolveSite(path: String) -> ValetSite?
20 |
21 | // MARK: - Proxies
22 |
23 | func resolveProxies(directoryPath: String) -> [ValetProxy]
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/phpmon/Domain/Integrations/Valet/Scanners/FakeDomainScanner.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FakeDomainScanner.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 02/04/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | class FakeDomainScanner: DomainScanner {
10 |
11 | var sites: [ValetSite] = [
12 | FakeValetSite(fakeWithName: "laravel", tld: "test", secure: true,
13 | path: "~/Code/laravel/framework", linked: true),
14 |
15 | FakeValetSite(fakeWithName: "tailwind", tld: "test", secure: true,
16 | path: "~/Code/tailwind/site", linked: true, constraint: "8.0"),
17 |
18 | FakeValetSite(fakeWithName: "forge", tld: "test", secure: true,
19 | path: "~/Code/laravel/forge", linked: true),
20 |
21 | FakeValetSite(fakeWithName: "concord", tld: "test", secure: false,
22 | path: "~/Code/concord", linked: true, driver: "Laravel (^8.0)", constraint: "^7.4", isolated: "7.4"),
23 |
24 | FakeValetSite(fakeWithName: "drupal", tld: "test", secure: false,
25 | path: "~/Sites/drupal", linked: false, driver: "Drupal", constraint: "^7.4", isolated: "7.4"),
26 |
27 | FakeValetSite(fakeWithName: "wordpress", tld: "test", secure: false,
28 | path: "~/Sites/wordpress", linked: false, driver: "WordPress", constraint: "^7.4", isolated: "7.4")
29 | ]
30 |
31 | var proxies: [ValetProxy] = [
32 | FakeValetProxy(domain: "mailgun", target: "http://127.0.0.1:9999", secure: true, tld: "test")
33 | ]
34 |
35 | // MARK: - Sites
36 |
37 | func resolveSiteCount(paths: [String]) -> Int {
38 | return sites.count
39 | }
40 |
41 | func resolveSitesFrom(paths: [String]) -> [ValetSite] {
42 | return sites
43 | }
44 |
45 | func resolveSite(path: String) -> ValetSite? {
46 | return nil
47 | }
48 |
49 | // MARK: - Proxies
50 |
51 | func resolveProxies(directoryPath: String) -> [ValetProxy] {
52 | return proxies
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/phpmon/Domain/Integrations/Valet/Scanners/ValetScanners.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Valet+Scanners.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 01/11/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class ValetScanner {
12 |
13 | static var active: DomainScanner = ValetDomainScanner()
14 |
15 | public static func useFake() {
16 | ValetScanner.active = FakeDomainScanner()
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/phpmon/Domain/Integrations/Valet/Sites/FakeValetSite.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ValetSite+Fake.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 19/03/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class FakeValetSite: ValetSite {
12 | convenience init(
13 | fakeWithName name: String,
14 | tld: String,
15 | secure: Bool,
16 | path: String,
17 | linked: Bool,
18 | driver: String = "Laravel (^9.0)",
19 | constraint: String = "^8.1",
20 | isolated: String? = nil
21 | ) {
22 | self.init(
23 | name: name,
24 | tld: tld,
25 | absolutePath: path,
26 | aliasPath: nil,
27 | makeDeterminations: false
28 | )
29 |
30 | self.secured = secure
31 | self.preferredPhpVersion = constraint
32 | self.preferredPhpVersionSource = constraint != "" ? .require : .unknown
33 |
34 | self.driver = driver
35 | self.driverDeterminedByComposer = true
36 |
37 | if linked {
38 | self.aliasPath = self.absolutePath
39 | }
40 |
41 | if let isolated = isolated {
42 | self.isolatedPhpVersion = PhpInstallation(isolated)
43 | }
44 |
45 | if PhpEnvironments.shared.currentInstall != nil {
46 | self.evaluateCompatibility()
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/phpmon/Domain/Integrations/Valet/Sites/PhpVersionSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VersionSource.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 25/01/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum PhpVersionSource: String {
12 | case unknown
13 | case require
14 | case platform
15 | case valetphprc
16 | case valetrc
17 | }
18 |
--------------------------------------------------------------------------------
/phpmon/Domain/Menu/AppMenu.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppMenu.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 27/06/2024.
6 | // Copyright © 2024 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class AppMenu {
12 |
13 | // MARK: - Main Menu
14 |
15 | static var appMenu: NSMenu? {
16 | return NSApplication.shared.mainMenu?.items[0].submenu
17 | }
18 |
19 | static var sitesMenu: NSMenu? {
20 | return NSApplication.shared.mainMenu?.items[1].submenu
21 | }
22 |
23 | static var editMenu: NSMenu? {
24 | return NSApplication.shared.mainMenu?.items[2].submenu
25 | }
26 |
27 | static var windowMenu: NSMenu? {
28 | return NSApplication.shared.mainMenu?.items[3].submenu
29 | }
30 |
31 | static var helpMenu: NSMenu? {
32 | return NSApplication.shared.mainMenu?.items[4].submenu
33 | }
34 |
35 | // MARK: - Submenu
36 |
37 | static var actionsMenu: NSMenuItem? {
38 | return sitesMenu?.items.last
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/phpmon/Domain/Preferences/Keybinds/GlobalKeybindPreference.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GlobalKeybindPreference.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 15/04/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct GlobalKeybindPreference: Codable, CustomStringConvertible {
12 |
13 | // MARK: - Internal variables
14 |
15 | let function: Bool
16 | let control: Bool
17 | let command: Bool
18 | let shift: Bool
19 | let option: Bool
20 | let capsLock: Bool
21 | let carbonFlags: UInt32
22 | let characters: String?
23 | let keyCode: UInt32
24 |
25 | // MARK: - How the keybind is display in Preferences
26 |
27 | var description: String {
28 | var stringBuilder = ""
29 | if self.function {
30 | stringBuilder += "Fn"
31 | }
32 | if self.control {
33 | stringBuilder += "⌃"
34 | }
35 | if self.option {
36 | stringBuilder += "⌥"
37 | }
38 | if self.command {
39 | stringBuilder += "⌘"
40 | }
41 | if self.shift {
42 | stringBuilder += "⇧"
43 | }
44 | if self.capsLock {
45 | stringBuilder += "⇪"
46 | }
47 | if let characters = self.characters {
48 | stringBuilder += characters.uppercased()
49 | }
50 | return "\(stringBuilder)"
51 | }
52 |
53 | // MARK: - Persisting data to UserDefaults (as JSON)
54 |
55 | public func toJson() -> String {
56 | let jsonData = try! JSONEncoder().encode(self)
57 | return String(data: jsonData, encoding: .utf8)!
58 | }
59 |
60 | public static func fromJson(_ string: String?) -> GlobalKeybindPreference? {
61 | if string == nil {
62 | return nil
63 | }
64 |
65 | if let jsonData = string!.data(using: .utf8) {
66 | let decoder = JSONDecoder()
67 | do {
68 | return try decoder.decode(GlobalKeybindPreference.self, from: jsonData)
69 | } catch {
70 | return nil
71 | }
72 | }
73 | return nil
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/phpmon/Domain/Preferences/Keys.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Keys.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 25/07/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct Keys {
12 | static let Escape = 53
13 | static let Space = 49
14 | }
15 |
--------------------------------------------------------------------------------
/phpmon/Domain/Preferences/MenuBarIcons.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MenuBarIcons.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 06/02/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
12 | enum MenuBarIcon: String, CaseIterable {
13 | case iconPhp = "php"
14 | case iconElephant = "elephant"
15 | case noIcon = "none"
16 | }
17 |
--------------------------------------------------------------------------------
/phpmon/Domain/Preferences/PreferencesWindowController+Hotkey.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PreferencesWindowController+Hotkey.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 25/07/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | extension PreferencesWindowController {
12 |
13 | // MARK: - Key Interaction
14 |
15 | override func keyDown(with event: NSEvent) {
16 | super.keyDown(with: event)
17 |
18 | guard let tabVC = self.contentViewController as? NSTabViewController else {
19 | return
20 | }
21 |
22 | guard let vc = tabVC.tabViewItems[tabVC.selectedTabViewItemIndex].viewController as? GenericPreferenceVC else {
23 | return
24 | }
25 |
26 | if vc.listeningForHotkeyView == nil {
27 | return
28 | }
29 |
30 | if event.keyCode == Keys.Escape || event.keyCode == Keys.Space {
31 | Log.info("A blacklisted key was pressed, canceling listen!")
32 | vc.listeningForHotkeyView!.unregister(nil)
33 | } else {
34 | vc.listeningForHotkeyView!.updateShortcut(event)
35 | }
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/phpmon/Domain/Presets/PresetHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PresetHelper.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 02/06/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class PresetHelper {
12 |
13 | static var rollbackPreset: Preset?
14 |
15 | // MARK: - Reloading Configuration
16 |
17 | public static func loadRollbackPresetFromFile() {
18 | guard let revert = try? String(
19 | contentsOfFile: "\(Paths.homePath)/.config/phpmon/preset_revert.json",
20 | encoding: .utf8
21 | ) else {
22 | PresetHelper.rollbackPreset = nil
23 | return
24 | }
25 |
26 | guard let preset = try? JSONDecoder().decode(
27 | Preset.self,
28 | from: revert.data(using: .utf8)!
29 | ) else {
30 | PresetHelper.rollbackPreset = nil
31 | return
32 | }
33 |
34 | PresetHelper.rollbackPreset = preset
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/phpmon/Domain/SwiftUI/Common/BlockingOverlayView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BlockingOverlayView.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 19/03/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | struct BlockingOverlayView: View {
13 | var isBlocking: Bool
14 | var titleText: String
15 | var detailText: String
16 | var content: () -> Content
17 |
18 | init(
19 | busy: Bool,
20 | title: String,
21 | text: String,
22 | @ViewBuilder content: @escaping () -> Content
23 | ) {
24 | self.isBlocking = busy
25 | self.titleText = title
26 | self.detailText = text
27 | self.content = content
28 | }
29 |
30 | var body: some View {
31 | ZStack(alignment: .center) {
32 | content().opacity(isBlocking ? 0 : 1)
33 | if isBlocking {
34 | VStack {
35 | ActivityIndicator()
36 | Text(titleText)
37 | .font(.system(size: 14))
38 | .bold()
39 | .foregroundColor(.primary)
40 | .padding(.top, 8)
41 | .multilineTextAlignment(.center)
42 | Text(detailText)
43 | .font(.system(size: 11))
44 | .foregroundColor(.primary)
45 | .padding(.top, -4)
46 | .multilineTextAlignment(.center)
47 | }.padding(60)
48 | }
49 | }
50 | .background(Color.spinnerBackground)
51 | .disabled(isBlocking)
52 | }
53 | }
54 |
55 | struct ActivityIndicator: NSViewRepresentable {
56 | func makeNSView(context: Context) -> NSProgressIndicator {
57 | let nsView = NSProgressIndicator()
58 | nsView.style = .spinning
59 | nsView.startAnimation(nil)
60 | return nsView
61 | }
62 |
63 | func updateNSView(_ nsView: NSProgressIndicator, context: Context) {
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/phpmon/Domain/SwiftUI/Common/CustomButtonStyles.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomButtonStyles.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 15/03/2024.
6 | // Copyright © 2024 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public struct CustomButtonStyle: ButtonStyle {
12 | @Environment(\.isEnabled) var isEnabled
13 |
14 | public func makeBody(configuration: Self.Configuration) -> some View {
15 | configuration.label
16 | .padding(.vertical, 4)
17 | .padding(.horizontal, 8)
18 | .foregroundStyle(.white)
19 | .background(.statusColorBlue, in: .rect(cornerRadius: 8, style: .continuous))
20 | .opacity({
21 | if configuration.isPressed {
22 | return 0.4
23 | }
24 |
25 | if !isEnabled {
26 | return 0.2
27 | }
28 |
29 | return 1.0
30 | }())
31 | }
32 | }
33 |
34 | extension ButtonStyle where Self == CustomButtonStyle {
35 | static var custom: CustomButtonStyle { .init() }
36 | }
37 |
--------------------------------------------------------------------------------
/phpmon/Domain/SwiftUI/Common/HelpButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HelpButton.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 07/01/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | struct HelpButton: View {
13 | @State var frameSize: CGFloat = 14
14 | @State var textSize: CGFloat = 12
15 | @State var shadowOpacity: CGFloat = 0.3
16 | @State var shadowRadius: CGFloat = 1
17 |
18 | var action: () -> Void
19 |
20 | var body: some View {
21 | Button(action: action, label: {
22 | ZStack {
23 | Circle()
24 | .strokeBorder(Color(NSColor.separatorColor), lineWidth: 0.5)
25 | .background(Circle().foregroundColor(Color(NSColor.controlColor)).opacity(0.7))
26 | .shadow(color: Color(NSColor.separatorColor)
27 | .opacity(shadowOpacity), radius: shadowRadius)
28 | .frame(width: frameSize, height: frameSize)
29 | Text("?").font(.system(size: textSize, weight: .medium))
30 | .foregroundColor(Color(NSColor.labelColor))
31 | }
32 | })
33 | .buttonStyle(BorderlessButtonStyle())
34 | .focusable(false)
35 | }
36 | }
37 |
38 | #Preview("Light Mode") {
39 | HelpButton(action: {})
40 | .padding(100)
41 | }
42 |
43 | #Preview("Dark Mode") {
44 | HelpButton(action: {})
45 | .padding(100)
46 | .preferredColorScheme(.dark)
47 | }
48 |
--------------------------------------------------------------------------------
/phpmon/Domain/SwiftUI/Common/SwiftUIHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftUIHelper.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 08/06/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | var isRunningTests: Bool {
13 | let environment = ProcessInfo.processInfo.environment
14 | return environment["TEST_MODE"] != nil
15 | || environment["XCTestConfigurationFilePath"] != nil
16 | }
17 |
18 | var isRunningSwiftUIPreview: Bool {
19 | #if DEBUG
20 | // If running SwiftUI *and* when debugging
21 | return ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] != nil
22 | #else
23 | // Release builds should always return false here
24 | return false
25 | #endif
26 | }
27 |
28 | extension Color {
29 | public static var appPrimary: Color = Color("AppColor")
30 |
31 | // This next one is generated automatically via asset catalogs now
32 | // public static var appSecondary: Color = Color("AppSecondary")
33 |
34 | public static var debug: Color = {
35 | if ProcessInfo.processInfo.environment["PAINT_PHPMON_SWIFTUI_VIEWS"] != nil {
36 | return Color.yellow
37 | }
38 | return Color.clear
39 | }()
40 | }
41 |
--------------------------------------------------------------------------------
/phpmon/Domain/SwiftUI/Common/UnavailableContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NoDomainsView.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 19/03/2024.
6 | // Copyright © 2024 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct UnavailableContentView: View {
12 | var title: String
13 | var description: String
14 | var icon: String
15 | var button: String?
16 | var action: (() -> Void)?
17 |
18 | init(
19 | title: String,
20 | description: String,
21 | icon: String,
22 | button: String? = nil,
23 | action: (() -> Void)? = nil
24 | ) {
25 | self.title = title
26 | self.description = description
27 | self.icon = icon
28 | self.button = button
29 | self.action = action
30 | }
31 |
32 | var body: some View {
33 | Group {
34 | VStack(spacing: 15) {
35 | Image(systemName: self.icon)
36 | .resizable()
37 | .frame(width: 48, height: 48)
38 | .foregroundColor(Color.appPrimary)
39 | .padding(.bottom, 10)
40 | Text(self.title)
41 | .font(.system(size: 18, weight: .bold))
42 |
43 | Text(self.description)
44 | .foregroundStyle(Color.secondary)
45 | .multilineTextAlignment(.center)
46 |
47 | if self.button != nil {
48 | Button(self.button!) {
49 | self.action!()
50 | }.buttonStyle(.custom)
51 | }
52 | }
53 | }
54 | .padding(30)
55 | .frame(maxWidth: 400)
56 | }
57 | }
58 |
59 | #Preview {
60 | UnavailableContentView(
61 | title: "domain_list.domains_empty.title".localized,
62 | description: "domain_list.domains_empty.desc".localized,
63 | icon: "globe",
64 | button: "domain_list.domains_empty.button".localized,
65 | action: {}
66 | )
67 | }
68 |
--------------------------------------------------------------------------------
/phpmon/Domain/SwiftUI/Menu/HeaderView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MiniHeaderView.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 10/06/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct HeaderView: View {
12 | @State var text: String
13 |
14 | var body: some View {
15 | Text(text.uppercased())
16 | .font(.system(size: 12))
17 | .fontWeight(.bold)
18 | .foregroundColor(.appSecondary)
19 | .multilineTextAlignment(.leading)
20 | .padding(.leading, 14.0)
21 | .frame(maxWidth: .infinity, alignment: .leading)
22 | .background(Color.debug)
23 | }
24 |
25 | // MARK: - NSMenuItem
26 |
27 | static func asMenuItem(
28 | text: String,
29 | minimumWidth: CGFloat? = nil
30 | ) -> NSMenuItem {
31 | let view = NSHostingView(rootView: Self(text: text))
32 | view.autoresizingMask = [.width, .height]
33 |
34 | var finalWidth = view.frame.width
35 | if minimumWidth != nil && minimumWidth! > finalWidth {
36 | finalWidth = minimumWidth!
37 | }
38 |
39 | view.setFrameSize(CGSize(width: finalWidth, height: 24))
40 |
41 | let item = NSMenuItem()
42 | item.view = view
43 |
44 | return item
45 | }
46 | }
47 |
48 | #Preview {
49 | HeaderView(text: "Hello world")
50 | .frame(width: 330.0)
51 | }
52 |
--------------------------------------------------------------------------------
/phpmon/Domain/SwiftUI/Menu/SectionHeaderView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MiniHeaderView.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 10/06/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct SectionHeaderView: View {
12 |
13 | @State var text: String
14 |
15 | var body: some View {
16 | Text(text)
17 | .font(.system(size: 11))
18 | .fontWeight(.medium)
19 | .foregroundColor(.appSecondary)
20 | .background(Color.debug)
21 | .minimumScaleFactor(0.8)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/phpmon/Domain/Terminal Alert/ProgressVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProgressVC.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 26/07/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import AppKit
11 |
12 | class ProgressViewController: NSViewController {
13 |
14 | @IBOutlet weak var labelTitle: NSTextField!
15 | @IBOutlet weak var labelDescription: NSTextField!
16 |
17 | @IBOutlet var textView: NSTextView!
18 | @IBOutlet weak var imageViewType: NSImageView!
19 |
20 | deinit {
21 | Log.perf("deinit: \(String(describing: self)).\(#function)")
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/phpmon/Domain/Terminal Alert/TerminalProgressWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TerminalProgressWindowController.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 18/12/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import AppKit
11 |
12 | class TerminalProgressWindowController: NSWindowController, NSWindowDelegate {
13 |
14 | static func display(title: String, description: String) -> TerminalProgressWindowController {
15 | let storyboard = NSStoryboard(name: "ProgressWindow", bundle: nil)
16 |
17 | let windowController = storyboard.instantiateController(
18 | withIdentifier: "progressWindow"
19 | ) as! TerminalProgressWindowController
20 |
21 | windowController.showWindow(windowController)
22 | windowController.window?.makeKeyAndOrderFront(nil)
23 | windowController.positionWindowInTopRightCorner()
24 |
25 | windowController.progressView?.labelTitle.stringValue = title
26 | windowController.progressView?.labelDescription.stringValue = description
27 |
28 | NSApp.activate(ignoringOtherApps: true)
29 |
30 | return windowController
31 | }
32 |
33 | var progressView: ProgressViewController? {
34 | return self.contentViewController as? ProgressViewController
35 | }
36 |
37 | public func addToConsole(_ string: String) {
38 | Task { @MainActor in
39 | guard let textView = self.progressView?.textView else {
40 | return
41 | }
42 |
43 | textView.string += string
44 | textView.scrollToEndOfDocument(nil)
45 | }
46 | }
47 |
48 | public func setType(info: Bool = true) {
49 | guard let imageView = self.progressView?.imageViewType else {
50 | return
51 | }
52 |
53 | imageView.image = NSImage(named: info ? "NSInfo" : "NSCaution")
54 | }
55 |
56 | func windowWillClose(_ notification: Notification) {
57 | self.contentViewController = nil
58 | }
59 |
60 | deinit {
61 | Log.perf("deinit: \(String(describing: self)).\(#function)")
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/phpmon/Domain/Terminal/Paths.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Paths.swift
3 | // PHP Monitor
4 | //
5 | // Copyright © 2021 Nico Verbruggen. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | enum HomebrewDir: String {
11 | case opt = "/opt/homebrew"
12 | case usr = "/usr/local"
13 | }
14 |
15 | class Paths {
16 |
17 | static let shared = Paths()
18 | var baseDir: HomebrewDir
19 | var userName = String(Shell.pipe("whoami").split(separator: "\n")[0])
20 |
21 | init() {
22 | let optBrewFound = Shell.fileExists("\(HomebrewDir.opt.rawValue)/bin/brew")
23 | let usrBrewFound = Shell.fileExists("\(HomebrewDir.usr.rawValue)/bin/brew")
24 |
25 | if optBrewFound {
26 | // This is usually the case with Homebrew installed on Apple Silicon
27 | baseDir = .opt
28 | } else if usrBrewFound {
29 | // This is usually the case with Homebrew installed on Intel (or Rosetta 2)
30 | baseDir = .usr
31 | } else {
32 | // Falling back to default "legacy" Homebrew location (for Intel)
33 | print("Seems like we couldn't determine the Homebrew directory.")
34 | print("This usually means we're in trouble... (no Homebrew?)")
35 | baseDir = .usr
36 | }
37 | }
38 |
39 | // - MARK: Binaries
40 |
41 | public static var valet: String {
42 | return "\(binPath)/valet"
43 | }
44 |
45 | public static var brew: String {
46 | return "\(binPath)/brew"
47 | }
48 |
49 | public static var php: String {
50 | return "\(binPath)/php"
51 | }
52 |
53 | public static var phpConfig: String {
54 | return "\(binPath)/php-config"
55 | }
56 |
57 | // - MARK: Paths
58 |
59 | public static var whoami: String {
60 | return shared.userName
61 | }
62 |
63 | public static var binPath: String {
64 | return "\(shared.baseDir.rawValue)/bin"
65 | }
66 |
67 | public static var optPath: String {
68 | return "\(shared.baseDir.rawValue)/opt"
69 | }
70 |
71 | public static var etcPath: String {
72 | return "\(shared.baseDir.rawValue)/etc"
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/phpmon/Domain/Watcher/App+BrewWatch.swift:
--------------------------------------------------------------------------------
1 | //
2 | // App+BrewWatch.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 03/03/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension App {
12 |
13 | public func prepareHomebrewWatchers() {
14 | let notifier = FSNotifier(
15 | for: URL(fileURLWithPath: Paths.binPath),
16 | eventMask: .all,
17 | onChange: { Task { await self.onHomebrewPhpModification() } }
18 | )
19 |
20 | App.shared.watchers["homebrewBinaries"] = notifier
21 | }
22 |
23 | public func destroyHomebrewWatchers() {
24 | // Removing requires termination and then removing reference
25 | self.watchers["homebrewBinaries"]?.terminate()
26 | self.watchers["homebrewBinaries"] = nil
27 | }
28 |
29 | public func onHomebrewPhpModification() async {
30 | // let previous = PhpEnvironments.shared.currentInstall?.version.text
31 | Log.info("Something changed in the Homebrew binary directory...")
32 | await PhpEnvironments.detectPhpVersions()
33 | await MainMenu.shared.refreshActiveInstallation()
34 |
35 | //
36 | // TODO: PHP Guard 2.0
37 | // Check if the new and previous version of PHP are different
38 | // if so, we can show a notification if needed or alert the user
39 | //
40 | // let new = PhpEnvironments.shared.currentInstall?.version.text
41 | //
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/phpmon/Domain/Watcher/FSNotifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FSNotifier.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 13/01/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class FSNotifier {
12 |
13 | public static var shared: FSNotifier! = nil
14 |
15 | let queue = DispatchQueue(label: "FSWatch2Queue", attributes: .concurrent)
16 | var lastUpdate: TimeInterval?
17 |
18 | private var fileDescriptor: CInt = -1
19 | private var dispatchSource: DispatchSourceFileSystemObject?
20 |
21 | internal let url: URL
22 |
23 | init(for url: URL, eventMask: DispatchSource.FileSystemEvent, onChange: @escaping () -> Void) {
24 | self.url = url
25 |
26 | fileDescriptor = open(url.path, O_EVTONLY)
27 |
28 | dispatchSource = DispatchSource.makeFileSystemObjectSource(
29 | fileDescriptor: fileDescriptor,
30 | eventMask: eventMask,
31 | queue: self.queue
32 | )
33 |
34 | dispatchSource?.setEventHandler(handler: {
35 | let distance = self.lastUpdate?.distance(to: Date().timeIntervalSince1970)
36 |
37 | if distance == nil || distance != nil && distance! > 1.00 {
38 | // FS event fired, checking in 1s, no duplicate FS events will be acted upon
39 | self.lastUpdate = Date().timeIntervalSince1970
40 |
41 | Task {
42 | await delay(seconds: 1)
43 | onChange()
44 | }
45 | }
46 | })
47 |
48 | dispatchSource?.setCancelHandler(handler: { [weak self] in
49 | guard let self = self else { return }
50 |
51 | close(self.fileDescriptor)
52 | self.fileDescriptor = -1
53 | self.dispatchSource = nil
54 | })
55 |
56 | dispatchSource?.resume()
57 | }
58 |
59 | func terminate() {
60 | dispatchSource?.cancel()
61 | }
62 |
63 | deinit {
64 | Log.perf("FSNotifier for \(self.url) will be deinitialized.")
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/phpmon/IAP/InternetAccessPolicy.strings:
--------------------------------------------------------------------------------
1 | // Top-level, general application description:
2 | "ApplicationDescription" = "PHP Monitor is a tool that shows the active PHP version in your menu bar and gives you easy access to certain PHP service actions and config files.";
3 |
--------------------------------------------------------------------------------
/phpmon/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleURLTypes
22 |
23 |
24 | CFBundleTypeRole
25 | Viewer
26 | CFBundleURLName
27 | com.nicoverbruggen.phpmon
28 | CFBundleURLSchemes
29 |
30 | phpmon
31 |
32 |
33 |
34 | CFBundleVersion
35 | $(CURRENT_PROJECT_VERSION)
36 | LSApplicationCategoryType
37 | public.app-category.utilities
38 | LSMinimumSystemVersion
39 | $(MACOSX_DEPLOYMENT_TARGET)
40 | LSUIElement
41 |
42 | NSHumanReadableCopyright
43 | Copyright © 2019-2025 Nico Verbruggen. All rights reserved.
44 | NSMainStoryboardFile
45 | Main
46 | NSPrincipalClass
47 | NSApplication
48 |
49 |
50 |
--------------------------------------------------------------------------------
/phpmon/Modules/Domain List/Favorites.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Favorites.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 25/08/2024.
6 | // Copyright © 2024 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class Favorites {
12 | static var shared: Favorites = Favorites()
13 |
14 | var items: [String]
15 |
16 | init() {
17 | if let items = UserDefaults.standard.array(forKey: "user_favorites") as? [String] {
18 | self.items = items
19 | } else {
20 | self.items = []
21 | }
22 | }
23 |
24 | public func contains(domain: String) -> Bool {
25 | return self.items.contains(domain)
26 | }
27 |
28 | public func toggle(domain: String) {
29 | if let index = items.firstIndex(of: domain) {
30 | items.remove(at: index)
31 | } else {
32 | items.append(domain)
33 | }
34 |
35 | UserDefaults.standard.setValue(items, forKey: "user_favorites")
36 | UserDefaults.standard.synchronize()
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/phpmon/Modules/Domain List/UI/Cells/DomainListCellProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DomainListCellProtocol.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 03/12/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import AppKit
11 |
12 | protocol DomainListCellProtocol {
13 | static func getCellIdentifier(for domain: ValetListable) -> String
14 | func populateCell(with site: ValetSite)
15 | func populateCell(with proxy: ValetProxy)
16 | }
17 |
--------------------------------------------------------------------------------
/phpmon/Modules/Domain List/UI/Cells/DomainListKindCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DomainListTypeCell.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 16/03/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import AppKit
11 |
12 | class DomainListKindCell: NSTableCellView, DomainListCellProtocol {
13 | @IBOutlet weak var imageViewType: NSImageView!
14 |
15 | static func getCellIdentifier(for domain: ValetListable) -> String {
16 | return "domainListKindCell"
17 | }
18 |
19 | func populateCell(with site: ValetSite) {
20 | // If the `aliasPath` is nil, we're dealing with a parked site (otherwise: linked).
21 | imageViewType.image = site.aliasPath == nil
22 | ? NSImage.iconParked
23 | : NSImage.iconLinked
24 |
25 | // Unless, of course, this is a default site
26 | if site.absolutePath == Valet.shared.config.defaultSite {
27 | imageViewType.image = NSImage.iconDefault
28 | }
29 |
30 | imageViewType.contentTintColor = NSColor.tertiaryLabelColor
31 | }
32 |
33 | func populateCell(with proxy: ValetProxy) {
34 | imageViewType.image = NSImage.iconProxy
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/phpmon/Modules/Domain List/UI/Cells/DomainListNameCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DomainListNameCell.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 16/03/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import AppKit
11 |
12 | class DomainListNameCell: NSTableCellView, DomainListCellProtocol {
13 | @IBOutlet weak var labelSiteName: NSTextField!
14 | @IBOutlet weak var labelPathName: NSTextField!
15 |
16 | static func getCellIdentifier(for domain: ValetListable) -> String {
17 | return domain.getListableFavorited() ? "domainListNameCellFavorited" : "domainListNameCell"
18 | }
19 |
20 | func populateCell(with site: ValetSite) {
21 | labelSiteName.stringValue = "\(site.name).\(site.tld)"
22 | labelPathName.stringValue = site.absolutePathRelative
23 | }
24 |
25 | func populateCell(with proxy: ValetProxy) {
26 | labelSiteName.stringValue = "\(proxy.domain).\(proxy.tld)"
27 | labelPathName.stringValue = proxy.target
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/phpmon/Modules/Domain List/UI/Cells/DomainListTLSCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DomainListNameCell.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 16/03/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import AppKit
11 |
12 | class DomainListTLSCell: NSTableCellView, DomainListCellProtocol {
13 | @IBOutlet weak var imageViewLock: NSImageView!
14 |
15 | static func getCellIdentifier(for domain: ValetListable) -> String {
16 | return "domainListTLSCell"
17 | }
18 |
19 | func populateCell(with site: ValetSite) {
20 | imageViewLock.contentTintColor = site.secured
21 | ? NSColor(named: "IconColorGreen")
22 | : NSColor(named: "IconColorRed")
23 | }
24 |
25 | func populateCell(with proxy: ValetProxy) {
26 | imageViewLock.contentTintColor = proxy.secured
27 | ? NSColor(named: "IconColorGreen")
28 | : NSColor(named: "IconColorRed")
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/phpmon/Modules/Domain List/UI/Cells/DomainListTypeCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DomainListTypeCell.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 16/03/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import AppKit
11 |
12 | class DomainListTypeCell: NSTableCellView, DomainListCellProtocol {
13 | @IBOutlet weak var labelDriver: NSTextField!
14 | @IBOutlet weak var labelPhpVersion: NSTextField!
15 |
16 | static func getCellIdentifier(for domain: ValetListable) -> String {
17 | return "domainListTypeCell"
18 | }
19 |
20 | func populateCell(with site: ValetSite) {
21 | labelDriver.stringValue = site.driver ?? "driver.not_detected".localized
22 |
23 | // Determine the Laravel version
24 | if site.driver == "Laravel" && site.notableComposerDependencies.keys.contains("laravel/framework") {
25 | let constraint = site.notableComposerDependencies["laravel/framework"]!
26 | labelDriver.stringValue = "Laravel (\(constraint))"
27 | }
28 |
29 | // PHP version
30 | labelPhpVersion.stringValue = site.preferredPhpVersion == "???" ? "PHP" : "PHP \(site.preferredPhpVersion)"
31 | }
32 |
33 | func populateCell(with proxy: ValetProxy) {
34 | labelDriver.stringValue = "Proxy"
35 | labelPhpVersion.stringValue = "Active"
36 | return
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/phpmon/Modules/Domain List/UI/SelectionVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SelectionVC.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 14/04/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
12 | class SelectionVC: NSViewController {
13 |
14 | weak var domainListWC: DomainListWindowController?
15 |
16 | @IBOutlet weak var textFieldTitle: NSTextField!
17 | @IBOutlet weak var textFieldDescription: NSTextField!
18 | @IBOutlet weak var buttonCreateLink: NSButton!
19 | @IBOutlet weak var buttonCreateProxy: NSButton!
20 | @IBOutlet weak var buttonCancel: NSButton!
21 |
22 | override func viewDidLoad() {
23 | super.viewDidLoad()
24 | loadStaticLocalisedStrings()
25 | }
26 |
27 | override func viewDidAppear() {
28 | view.window?.makeFirstResponder(buttonCreateLink)
29 | }
30 |
31 | private func dismissView(outcome: NSApplication.ModalResponse) {
32 | guard let window = self.view.window, let parent = window.sheetParent else { return }
33 | parent.endSheet(window, returnCode: outcome)
34 | }
35 |
36 | // MARK: - Localisation
37 |
38 | func loadStaticLocalisedStrings() {
39 | textFieldTitle.stringValue = "selection.title".localized
40 | textFieldDescription.stringValue = "selection.description".localized
41 | buttonCancel.title = "selection.cancel".localized
42 | buttonCreateLink.title = "selection.create_link".localized
43 | buttonCreateProxy.title = "selection.create_proxy".localized
44 | }
45 |
46 | // MARK: - Outlet Interactions
47 |
48 | @IBAction func pressedCreateLink(_ sender: Any) {
49 | self.dismissView(outcome: .continue)
50 | domainListWC?.startCreateLinkFlow()
51 | }
52 |
53 | @IBAction func pressedCreateProxy(_ sender: Any) {
54 | self.dismissView(outcome: .continue)
55 | domainListWC?.startCreateProxyFlow()
56 | }
57 |
58 | @IBAction func pressedCancel(_ sender: Any) {
59 | self.dismissView(outcome: .cancel)
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/phpmon/Modules/Domain List/UI/Subclass/PMTableView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PMTableView.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 05/09/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | /**
12 | This subclassed version of NSTableView selects a row upon right-clicking,
13 | thus making the domain list behave more like you'd expect.
14 | */
15 | public class PMTableView: NSTableView {
16 |
17 | override open func menu(for event: NSEvent) -> NSMenu? {
18 | let row = self.row(at: self.convert(event.locationInWindow, from: nil))
19 |
20 | if row >= 0 {
21 | self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
22 | }
23 |
24 | return super.menu(for: event)
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/phpmon/Modules/Onboarding/OnboardingWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OnboardingWindowController.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 25/06/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import SwiftUI
11 |
12 | class OnboardingWindowController: PMWindowController {
13 |
14 | // MARK: - Window Identifier
15 |
16 | override var windowName: String {
17 | return "Onboarding"
18 | }
19 |
20 | public static func create(delegate: NSWindowDelegate?) {
21 | let windowController = Self()
22 | windowController.window = NSWindow()
23 |
24 | guard let window = windowController.window else { return }
25 | window.title = ""
26 | window.styleMask = [.titled, .closable, .miniaturizable]
27 | window.titlebarAppearsTransparent = true
28 | window.delegate = delegate ?? windowController
29 | window.contentView = NSHostingView(rootView: OnboardingView())
30 | window.setContentSize(window.contentView!.fittingSize)
31 |
32 | App.shared.onboardingWindowController = windowController
33 | }
34 |
35 | public static func show(delegate: NSWindowDelegate? = nil) {
36 | if App.shared.onboardingWindowController == nil {
37 | Self.create(delegate: delegate)
38 | }
39 |
40 | App.shared.onboardingWindowController?.showWindow(self)
41 | App.shared.onboardingWindowController?.window?.setCenterPosition(offsetY: 70)
42 |
43 | NSApp.activate(ignoringOtherApps: true)
44 | }
45 |
46 | override func close() {
47 | super.close()
48 |
49 | // Search for updates after closing the window
50 | if Stats.successfulLaunchCount == 1 {
51 | Task { await AppUpdater().checkForUpdates(userInitiated: false) }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/phpmon/Modules/PHP Config Editor/Data/PhpPreference.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhpPreference.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 04/09/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | class PhpPreference {
13 | let key: String
14 |
15 | init(key: String) {
16 | self.key = key
17 | }
18 |
19 | internal static func persistToIniFile(key: String, value: String) throws {
20 | if let file = PhpEnvironments.shared.getConfigFile(forKey: key) {
21 | return try file.replace(key: key, value: value)
22 | }
23 |
24 | throw PhpConfigurationFile.ReplacementErrors.missingFile
25 | }
26 | }
27 |
28 | class BoolPhpPreference: PhpPreference {
29 | @State var value: Bool = true
30 | }
31 |
32 | class StringPhpPreference: PhpPreference {
33 | @State var value: String = ""
34 | }
35 |
--------------------------------------------------------------------------------
/phpmon/Modules/PHP Config Editor/UI/ConfigManagerWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfigManagerWindowController.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 12/09/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import SwiftUI
11 |
12 | class PhpConfigManagerWindowController: PMWindowController {
13 |
14 | // MARK: - Window Identifier
15 |
16 | override var windowName: String {
17 | return "ConfigManager"
18 | }
19 |
20 | public static func create(delegate: NSWindowDelegate?) {
21 | let windowController = Self()
22 | windowController.window = NSWindow()
23 |
24 | guard let window = windowController.window else { return }
25 | window.title = ""
26 | window.styleMask = [.titled, .closable, .miniaturizable]
27 | window.titlebarAppearsTransparent = true
28 | window.delegate = delegate ?? windowController
29 | window.contentView = NSHostingView(rootView: ConfigManagerView())
30 | window.setContentSize(NSSize(width: 600, height: 480))
31 |
32 | App.shared.phpConfigManagerWindowController = windowController
33 | }
34 |
35 | public static func show(delegate: NSWindowDelegate? = nil) {
36 | if App.shared.phpConfigManagerWindowController == nil {
37 | Self.create(delegate: delegate)
38 | }
39 |
40 | App.shared.phpConfigManagerWindowController?.showWindow(self)
41 | App.shared.phpConfigManagerWindowController?.positionWindowInTopRightCorner()
42 |
43 | NSApp.activate(ignoringOtherApps: true)
44 | App.shared.phpConfigManagerWindowController?.window?.orderFrontRegardless()
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/phpmon/Modules/PHP Doctor/Data/PhpConfigChecker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhpConfigChecker.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 24/02/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct FileExistenceCheck {
12 | let condition: (() -> Bool)?
13 | let path: String
14 | }
15 |
16 | class PhpConfigChecker {
17 | public static var shared = PhpConfigChecker()
18 |
19 | var missing: [String] = []
20 |
21 | public func check() {
22 | missing = []
23 |
24 | let shouldExist: [FileExistenceCheck] = [
25 | FileExistenceCheck(condition: nil, path: "php.ini"),
26 | FileExistenceCheck(condition: nil, path: "php-fpm.conf"),
27 | FileExistenceCheck(condition: { Valet.installed }, path: "php-fpm.d/valet-fpm.conf")
28 | ]
29 |
30 | for version in PhpEnvironments.shared.availablePhpVersions {
31 | for file in shouldExist {
32 | // Early exit in case our condition is not met
33 | if file.condition != nil && file.condition!() == false {
34 | continue
35 | }
36 |
37 | // Do the check
38 | let fullFilePath = Paths.etcPath.appending("/php/\(version)/\(file.path)")
39 | if !FileSystem.fileExists(fullFilePath) {
40 | missing.append(fullFilePath)
41 | }
42 | }
43 | }
44 |
45 | if !missing.isEmpty {
46 | Log.warn("The following config file(s) were missing: \(missing)")
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/phpmon/Modules/PHP Doctor/Data/Warning.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Warning.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 09/08/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct Warning: Identifiable, Hashable {
12 | var id = UUID()
13 | let command: () async -> Bool
14 | let name: String
15 | let title: String
16 | let paragraphs: () -> [String]
17 | let url: String?
18 |
19 | /**
20 | - Parameters:
21 | - command: The command that, if it returns true, means that a warning applies
22 | - name: The internal name or description for this warning
23 | - title: The title displayed for the user
24 | - paragraphs: The main body of text displayed for the user
25 | - url: The URL that one can navigate to for more information (if applicable)
26 | */
27 | init(
28 | command: @escaping () async -> Bool,
29 | name: String,
30 | title: String,
31 | paragraphs: @escaping () -> [String],
32 | url: String?
33 | ) {
34 | self.command = command
35 | self.name = name
36 | self.title = title
37 | self.paragraphs = paragraphs
38 | self.url = url
39 | }
40 |
41 | public func applies() async -> Bool {
42 | return await self.command()
43 | }
44 |
45 | public static func == (lhs: Warning, rhs: Warning) -> Bool {
46 | return lhs.hashValue == rhs.hashValue
47 | }
48 |
49 | public func hash(into hasher: inout Hasher) {
50 | hasher.combine(self.id)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/phpmon/Modules/PHP Doctor/UI/NoWarningsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NoWarningsView.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 15/08/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct NoWarningsView: View {
12 | var body: some View {
13 | VStack(alignment: .center, spacing: 15) {
14 | Image(systemName: "checkmark.circle.fill")
15 | .resizable()
16 | .renderingMode(.template)
17 | .foregroundColor(Color.green)
18 | .frame(width: 24, height: 24)
19 | VStack(alignment: .center) {
20 | Text("warnings.none".localizedForSwiftUI)
21 | }
22 | }
23 | .frame(minWidth: 0, maxWidth: .infinity)
24 | .padding(25)
25 | }
26 | }
27 |
28 | #Preview {
29 | NoWarningsView().padding()
30 | }
31 |
--------------------------------------------------------------------------------
/phpmon/Modules/PHP Doctor/UI/PhpDoctorWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhpDoctorWindowController.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 09/08/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import SwiftUI
11 |
12 | class PhpDoctorWindowController: PMWindowController {
13 |
14 | // MARK: - Window Identifier
15 |
16 | override var windowName: String {
17 | return "Warnings"
18 | }
19 |
20 | public static func create(delegate: NSWindowDelegate?) {
21 | let windowController = Self()
22 | windowController.window = NSWindow()
23 |
24 | guard let window = windowController.window else { return }
25 | window.title = ""
26 | window.styleMask = [.titled, .closable, .miniaturizable]
27 | window.titlebarAppearsTransparent = true
28 | window.delegate = delegate ?? windowController
29 | window.contentView = NSHostingView(rootView: PhpDoctorView())
30 | window.setContentSize(window.contentView!.fittingSize)
31 |
32 | App.shared.phpDoctorWindowController = windowController
33 | }
34 |
35 | public static func show(delegate: NSWindowDelegate? = nil) {
36 | if App.shared.phpDoctorWindowController == nil {
37 | Self.create(delegate: delegate)
38 | }
39 |
40 | App.shared.phpDoctorWindowController?.showWindow(self)
41 | App.shared.phpDoctorWindowController?.window?.setCenterPosition(offsetY: 70)
42 |
43 | NSApp.activate(ignoringOtherApps: true)
44 | App.shared.phpDoctorWindowController?.window?.orderFrontRegardless()
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/phpmon/Modules/PHP Extension Manager/Data/BrewExtensionsObservable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BrewExtensionsObservable.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 21/11/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class BrewExtensionsObservable: ObservableObject {
12 | @Published var phpVersion: String {
13 | didSet {
14 | self.loadExtensionData(for: phpVersion)
15 | }
16 | }
17 |
18 | @Published var extensions: [BrewPhpExtension] = []
19 |
20 | init(phpVersion: String) {
21 | self.phpVersion = phpVersion
22 | self.loadExtensionData(for: phpVersion)
23 | }
24 |
25 | public func loadExtensionData(for version: String) {
26 | let tapFormulae = BrewTapFormulae.from(tap: "shivammathur/homebrew-extensions")
27 |
28 | if let filteredTapFormulae = tapFormulae[version] {
29 | self.extensions = filteredTapFormulae
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/phpmon/Modules/PHP Extension Manager/UI/PhpExtensionManagerWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhpExtensionManagerWindowController.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 13/11/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 | import SwiftUI
12 |
13 | class PhpExtensionManagerWindowController: PMWindowController {
14 |
15 | // MARK: - Window Identifier
16 |
17 | var view: PhpExtensionManagerView!
18 |
19 | override var windowName: String {
20 | return "PhpExtensionManager"
21 | }
22 |
23 | public static func create(delegate: NSWindowDelegate?) {
24 | let windowController = Self()
25 |
26 | windowController.window = NSWindow()
27 | windowController.view = PhpExtensionManagerView()
28 |
29 | guard let window = windowController.window else { return }
30 | window.title = "phpextman.window.title".localized
31 | window.styleMask = [.titled, .closable, .miniaturizable, .resizable]
32 | window.titlebarAppearsTransparent = false
33 | window.delegate = delegate ?? windowController
34 | window.contentView = NSHostingView(rootView: windowController.view)
35 | window.setContentSize(NSSize(width: 600, height: 800))
36 |
37 | App.shared.phpExtensionManagerWindowController = windowController
38 | }
39 |
40 | public static func show(delegate: NSWindowDelegate? = nil) {
41 | if App.shared.phpExtensionManagerWindowController == nil {
42 | Self.create(delegate: delegate)
43 | }
44 |
45 | App.shared.phpExtensionManagerWindowController?.showWindow(self)
46 | App.shared.phpExtensionManagerWindowController?.positionWindowInTopRightCorner()
47 |
48 | NSApp.activate(ignoringOtherApps: true)
49 |
50 | App.shared.phpExtensionManagerWindowController?.window?.orderFrontRegardless()
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/phpmon/Modules/PHP Version Manager/Data/BrewFormula+UI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BrewPhpFormula+UI.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 02/05/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | extension BrewPhpFormula {
13 | var icon: String {
14 | if self.hasUpgrade {
15 | return "arrow.up.square.fill"
16 | } else if self.isInstalled {
17 | return "checkmark.square.fill"
18 | }
19 | return "square.dashed"
20 | }
21 |
22 | var iconColor: Color {
23 | if self.hasUpgrade {
24 | return Color("StatusColorBlue")
25 | } else if self.isInstalled {
26 | return Color("StatusColorGreen")
27 | }
28 | return Color.gray.opacity(0.3)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/phpmon/Modules/PHP Version Manager/Data/BrewFormulaeObservable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BrewPhpFormula.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 13/11/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class BrewFormulaeObservable: ObservableObject {
12 | @Published var phpVersions: [BrewPhpFormula] = []
13 |
14 | var upgradeable: [BrewPhpFormula] {
15 | return phpVersions.filter { formula in
16 | formula.hasUpgrade
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/phpmon/Modules/PHP Version Manager/UI/PhpVersionManagerWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhpVersionManagerWindowController.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 19/03/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 | import SwiftUI
12 |
13 | class PhpVersionManagerWindowController: PMWindowController {
14 |
15 | // MARK: - Window Identifier
16 |
17 | var view: PhpVersionManagerView!
18 |
19 | override var windowName: String {
20 | return "PhpVersionManager"
21 | }
22 |
23 | public static func create(delegate: NSWindowDelegate?) {
24 | let windowController = Self()
25 | windowController.window = NSWindow()
26 | windowController.view = PhpVersionManagerView(
27 | formulae: Brew.shared.formulae,
28 | handler: BrewPhpFormulaeHandler()
29 | )
30 |
31 | guard let window = windowController.window else { return }
32 | window.title = ""
33 | window.styleMask = [.titled, .closable, .miniaturizable, .resizable]
34 | window.titlebarAppearsTransparent = true
35 | window.delegate = delegate ?? windowController
36 | window.contentView = NSHostingView(rootView: windowController.view)
37 | window.setContentSize(NSSize(width: 600, height: 800))
38 |
39 | App.shared.phpVersionManagerWindowController = windowController
40 | }
41 |
42 | public static func show(delegate: NSWindowDelegate? = nil) {
43 | if App.shared.phpVersionManagerWindowController == nil {
44 | Self.create(delegate: delegate)
45 | }
46 |
47 | App.shared.phpVersionManagerWindowController?.showWindow(self)
48 | App.shared.phpVersionManagerWindowController?.positionWindowInTopRightCorner()
49 |
50 | NSApp.activate(ignoringOtherApps: true)
51 |
52 | App.shared.phpVersionManagerWindowController?.window?.orderFrontRegardless()
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/phpmon/Vendor/HotKey/HotKey.swift:
--------------------------------------------------------------------------------
1 | import AppKit
2 | import Carbon
3 |
4 | public final class HotKey {
5 |
6 | // MARK: - Types
7 |
8 | public typealias Handler = () -> Void
9 |
10 | // MARK: - Properties
11 |
12 | let identifier = UUID()
13 |
14 | public let keyCombo: KeyCombo
15 | public var keyDownHandler: Handler?
16 | public var keyUpHandler: Handler?
17 | public var isPaused = false {
18 | didSet {
19 | if isPaused {
20 | HotKeysController.unregister(self)
21 | } else {
22 | HotKeysController.register(self)
23 | }
24 | }
25 | }
26 |
27 | // MARK: - Initializers
28 |
29 | public init(keyCombo: KeyCombo, keyDownHandler: Handler? = nil, keyUpHandler: Handler? = nil) {
30 | self.keyCombo = keyCombo
31 | self.keyDownHandler = keyDownHandler
32 | self.keyUpHandler = keyUpHandler
33 |
34 | HotKeysController.register(self)
35 | }
36 |
37 | public convenience init(carbonKeyCode: UInt32, carbonModifiers: UInt32, keyDownHandler: Handler? = nil, keyUpHandler: Handler? = nil) {
38 | let keyCombo = KeyCombo(carbonKeyCode: carbonKeyCode, carbonModifiers: carbonModifiers)
39 | self.init(keyCombo: keyCombo, keyDownHandler: keyDownHandler, keyUpHandler: keyUpHandler)
40 | }
41 |
42 | public convenience init(key: Key, modifiers: NSEvent.ModifierFlags, keyDownHandler: Handler? = nil, keyUpHandler: Handler? = nil) {
43 | let keyCombo = KeyCombo(key: key, modifiers: modifiers)
44 | self.init(keyCombo: keyCombo, keyDownHandler: keyDownHandler, keyUpHandler: keyUpHandler)
45 | }
46 |
47 | deinit {
48 | HotKeysController.unregister(self)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/phpmon/Vendor/HotKey/KeyCombo.swift:
--------------------------------------------------------------------------------
1 | import AppKit
2 |
3 | public struct KeyCombo: Equatable {
4 |
5 | // MARK: - Properties
6 |
7 | public var carbonKeyCode: UInt32
8 | public var carbonModifiers: UInt32
9 |
10 | public var key: Key? {
11 | get {
12 | return Key(carbonKeyCode: carbonKeyCode)
13 | }
14 |
15 | set {
16 | carbonKeyCode = newValue?.carbonKeyCode ?? 0
17 | }
18 | }
19 |
20 | public var modifiers: NSEvent.ModifierFlags {
21 | get {
22 | return NSEvent.ModifierFlags(carbonFlags: carbonModifiers)
23 | }
24 |
25 | set {
26 | carbonModifiers = newValue.carbonFlags
27 | }
28 | }
29 |
30 | public var isValid: Bool {
31 | return carbonKeyCode >= 0
32 | }
33 |
34 | // MARK: - Initializers
35 |
36 | public init(carbonKeyCode: UInt32, carbonModifiers: UInt32 = 0) {
37 | self.carbonKeyCode = carbonKeyCode
38 | self.carbonModifiers = carbonModifiers
39 | }
40 |
41 | public init(key: Key, modifiers: NSEvent.ModifierFlags = []) {
42 | self.carbonKeyCode = key.carbonKeyCode
43 | self.carbonModifiers = modifiers.carbonFlags
44 | }
45 |
46 | // MARK: - Converting Keys
47 |
48 | public static func carbonKeyCodeToString(_ carbonKeyCode: UInt32) -> String? {
49 | return nil
50 | }
51 | }
52 |
53 | extension KeyCombo {
54 | public var dictionary: [String: Any] {
55 | return [
56 | "keyCode": Int(carbonKeyCode),
57 | "modifiers": Int(carbonModifiers)
58 | ]
59 | }
60 |
61 | public init?(dictionary: [String: Any]) {
62 | guard let keyCode = dictionary["keyCode"] as? Int,
63 | let modifiers = dictionary["modifiers"] as? Int
64 | else {
65 | return nil
66 | }
67 |
68 | self.init(carbonKeyCode: UInt32(keyCode), carbonModifiers: UInt32(modifiers))
69 | }
70 | }
71 |
72 | extension KeyCombo: CustomStringConvertible {
73 | public var description: String {
74 | var output = modifiers.description
75 |
76 | if let keyDescription = key?.description {
77 | output += keyDescription
78 | }
79 |
80 | return output
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/phpmon/Vendor/HotKey/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017–2019 Sam Soffes, http://soff.es
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/phpmon/Vendor/HotKey/ModifierFlagsExtension.swift:
--------------------------------------------------------------------------------
1 | import AppKit
2 | import Carbon
3 |
4 | extension NSEvent.ModifierFlags {
5 | public var carbonFlags: UInt32 {
6 | var carbonFlags: UInt32 = 0
7 |
8 | if contains(.command) {
9 | carbonFlags |= UInt32(cmdKey)
10 | }
11 |
12 | if contains(.option) {
13 | carbonFlags |= UInt32(optionKey)
14 | }
15 |
16 | if contains(.control) {
17 | carbonFlags |= UInt32(controlKey)
18 | }
19 |
20 | if contains(.shift) {
21 | carbonFlags |= UInt32(shiftKey)
22 | }
23 |
24 | return carbonFlags
25 | }
26 |
27 | public init(carbonFlags: UInt32) {
28 | self.init()
29 |
30 | if carbonFlags & UInt32(cmdKey) == UInt32(cmdKey) {
31 | insert(.command)
32 | }
33 |
34 | if carbonFlags & UInt32(optionKey) == UInt32(optionKey) {
35 | insert(.option)
36 | }
37 |
38 | if carbonFlags & UInt32(controlKey) == UInt32(controlKey) {
39 | insert(.control)
40 | }
41 |
42 | if carbonFlags & UInt32(shiftKey) == UInt32(shiftKey) {
43 | insert(.shift)
44 | }
45 | }
46 | }
47 |
48 | extension NSEvent.ModifierFlags: @retroactive CustomStringConvertible {
49 | public var description: String {
50 | var output = ""
51 |
52 | if contains(.control) {
53 | output += Key.control.description
54 | }
55 |
56 | if contains(.option) {
57 | output += Key.option.description
58 | }
59 |
60 | if contains(.shift) {
61 | output += Key.shift.description
62 | }
63 |
64 | if contains(.command) {
65 | output += Key.command.description
66 | }
67 |
68 | return output
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/phpmon/phpmon.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/PHP Monitor.xctestplan:
--------------------------------------------------------------------------------
1 | {
2 | "configurations" : [
3 | {
4 | "id" : "98F42C11-E6D2-4AD9-A5CA-40EFE44F384A",
5 | "name" : "Configuration 1",
6 | "options" : {
7 |
8 | }
9 | }
10 | ],
11 | "defaultOptions" : {
12 | "codeCoverage" : false,
13 | "commandLineArgumentEntries" : [
14 | {
15 | "argument" : "--v",
16 | "enabled" : false
17 | }
18 | ],
19 | "environmentVariableEntries" : [
20 | {
21 | "enabled" : false,
22 | "key" : "EXTREME_DOCTOR_MODE",
23 | "value" : ""
24 | },
25 | {
26 | "enabled" : false,
27 | "key" : "SLOW_SHELL_MODE",
28 | "value" : ""
29 | },
30 | {
31 | "enabled" : false,
32 | "key" : "PAINT_PHPMON_SWIFTUI_VIEWS",
33 | "value" : ""
34 | }
35 | ],
36 | "targetForVariableExpansion" : {
37 | "containerPath" : "container:PHP Monitor.xcodeproj",
38 | "identifier" : "C41C1B3222B0097F00E7CF16",
39 | "name" : "PHP Monitor"
40 | }
41 | },
42 | "testTargets" : [
43 | {
44 | "parallelizable" : true,
45 | "target" : {
46 | "containerPath" : "container:PHP Monitor.xcodeproj",
47 | "identifier" : "C4F7807825D7F84B000DBC97",
48 | "name" : "Unit Tests"
49 | }
50 | },
51 | {
52 | "target" : {
53 | "containerPath" : "container:PHP Monitor.xcodeproj",
54 | "identifier" : "C471E7AC28F9B4940021E251",
55 | "name" : "Feature Tests"
56 | }
57 | },
58 | {
59 | "target" : {
60 | "containerPath" : "container:PHP Monitor.xcodeproj",
61 | "identifier" : "C471E7BB28F9B90F0021E251",
62 | "name" : "UI Tests"
63 | }
64 | }
65 | ],
66 | "version" : 1
67 | }
68 |
--------------------------------------------------------------------------------
/tests/Shared/Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Utility.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 14/02/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class Utility {
12 |
13 | public static func copyToTemporaryFile(resourceName: String, fileExtension: String) -> URL? {
14 | if let bundleURL = Bundle(for: Self.self).url(forResource: resourceName, withExtension: fileExtension) {
15 | let tempDirectoryURL = NSURL.fileURL(withPath: NSTemporaryDirectory(), isDirectory: true)
16 | let targetURL = tempDirectoryURL.appendingPathComponent("\(UUID().uuidString).\(fileExtension)")
17 |
18 | do {
19 | try FileManager.default.copyItem(at: bundleURL, to: targetURL)
20 | return targetURL
21 | } catch let error {
22 | Log.err("Unable to copy file: \(error)")
23 | }
24 | }
25 |
26 | return nil
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Shared/XCPMApplication.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XCPMApplication.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 16/10/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class XCPMApplication: XCUIApplication {
12 | public func withConfiguration(_ configuration: TestableConfiguration) {
13 | let path = persistTestable(configuration)
14 | self.launchArguments = ["--configuration:\(path)"]
15 | }
16 |
17 | private func persistTestable(_ configuration: TestableConfiguration) -> String {
18 | let tempDirectoryURL = NSURL.fileURL(withPath: NSTemporaryDirectory(), isDirectory: true)
19 | let targetURL = tempDirectoryURL.appendingPathComponent("\(UUID().uuidString).json")
20 | try! configuration.toJson().write(toFile: targetURL.path, atomically: true, encoding: .utf8)
21 | return targetURL.path
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/feature/FeatureTestCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FeatureTestCase.swift
3 | // Feature Tests
4 | //
5 | // Created by Nico Verbruggen on 07/11/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class FeatureTestCase: XCTestCase {
12 |
13 | var fakeFileSystem: TestableFileSystem {
14 | let fs = ActiveFileSystem.shared
15 |
16 | if fs is TestableFileSystem {
17 | return fs as! TestableFileSystem
18 | }
19 |
20 | fatalError("The active filesystem is not a TestableFileSystem. Please use `ActiveFileSystem` to use the fake filesystem.")
21 | }
22 |
23 | public func assertFileSystemHas(
24 | _ path: String,
25 | file: StaticString = #filePath,
26 | line: UInt = #line
27 | ) {
28 | XCTAssertTrue(fakeFileSystem.files.keys.contains(path), file: file, line: line)
29 | }
30 |
31 | public func assertFileSystemDoesNotHave(
32 | _ path: String,
33 | file: StaticString = #filePath,
34 | line: UInt = #line
35 | ) {
36 | XCTAssertFalse(fakeFileSystem.files.keys.contains(path), file: file, line: line)
37 | }
38 |
39 | public func assertFileHasContents(
40 | _ path: String,
41 | contents: String,
42 | file: StaticString = #filePath,
43 | line: UInt = #line
44 | ) {
45 | XCTAssertEqual(contents, fakeFileSystem.files[path]?.content, file: file, line: line)
46 | }
47 |
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/tests/feature/InternalSwitcherTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Feature_Tests.swift
3 | // Feature Tests
4 | //
5 | // Created by Nico Verbruggen on 14/10/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | final class InternalSwitcherTest: FeatureTestCase {
12 |
13 | public func testDefaultPhpFpmPoolIsMoved() async {
14 | ActiveFileSystem.useTestable([
15 | "/opt/homebrew/etc/php/8.1/php-fpm.d/www.conf": .fake(.text)
16 | ])
17 |
18 | let outcome = await InternalSwitcher().disableDefaultPhpFpmPool("8.1")
19 | XCTAssertTrue(outcome)
20 |
21 | assertFileSystemHas("/opt/homebrew/etc/php/8.1/php-fpm.d/www.conf.disabled-by-phpmon")
22 | assertFileSystemDoesNotHave("/opt/homebrew/etc/php/8.1/php-fpm.d/www.conf")
23 | }
24 |
25 | public func testExistingDisabledByPhpMonFileIsRemoved() async {
26 | ActiveFileSystem.useTestable([
27 | "/opt/homebrew/etc/php/8.1/php-fpm.d/www.conf": .fake(.text, "system generated"),
28 | "/opt/homebrew/etc/php/8.1/php-fpm.d/www.conf.disabled-by-phpmon": .fake(.text, "phpmon generated")
29 | ])
30 |
31 | assertFileHasContents(
32 | "/opt/homebrew/etc/php/8.1/php-fpm.d/www.conf.disabled-by-phpmon",
33 | contents: "phpmon generated"
34 | )
35 |
36 | let outcome = await InternalSwitcher().disableDefaultPhpFpmPool("8.1")
37 | XCTAssertTrue(outcome)
38 |
39 | assertFileSystemHas("/opt/homebrew/etc/php/8.1/php-fpm.d/www.conf.disabled-by-phpmon")
40 | assertFileSystemDoesNotHave("/opt/homebrew/etc/php/8.1/php-fpm.d/www.conf")
41 |
42 | assertFileHasContents(
43 | "/opt/homebrew/etc/php/8.1/php-fpm.d/www.conf.disabled-by-phpmon",
44 | contents: "system generated"
45 | )
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/tests/ui/DomainsListTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StartupTest.swift
3 | // UI Tests
4 | //
5 | // Created by Nico Verbruggen on 14/10/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | final class DomainsListTest: UITestCase {
12 |
13 | override func setUpWithError() throws {
14 | continueAfterFailure = false
15 | }
16 |
17 | override func tearDownWithError() throws {}
18 |
19 | final func test_can_always_open_domains_list() throws {
20 | let app = launch(openMenu: true)
21 |
22 | app.menuItems["mi_domain_list".localized].click()
23 | }
24 |
25 | final func test_can_filter_domains_list() throws {
26 | let app = launch(openMenu: true)
27 |
28 | app.menuItems["mi_domain_list".localized].click()
29 |
30 | let window = app.windows.element(boundBy: 0)
31 | XCTAssertEqual(window.title, "domain_list.title".localized)
32 |
33 | let searchField = window.searchFields.element(boundBy: 0)
34 |
35 | searchField.click()
36 | searchField.typeText("non-existent thing")
37 | Thread.sleep(forTimeInterval: 0.2)
38 | XCTAssertTrue(window.tables.tableRows.count == 0)
39 |
40 | searchField.clearText()
41 | searchField.click()
42 | searchField.typeText("concord")
43 | Thread.sleep(forTimeInterval: 0.2)
44 | XCTAssertTrue(window.tables.tableRows.count == 1)
45 |
46 | sleep(1)
47 | }
48 |
49 | final func test_can_tap_add_domain_button() throws {
50 | let app = launch(openMenu: true)
51 |
52 | app.menuItems["mi_domain_list".localized].click()
53 |
54 | let window = app.windows.element(boundBy: 0)
55 | XCTAssertEqual(window.title, "domain_list.title".localized)
56 |
57 | window.buttons["Add Link"].click()
58 |
59 | assertExists(app.staticTexts["selection.title".localized])
60 | assertExists(app.buttons["selection.create_link".localized])
61 | assertExists(app.buttons["selection.create_proxy".localized])
62 | assertExists(app.buttons["selection.cancel".localized])
63 |
64 | sleep(1)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tests/unit/Commands/CommandTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommandTest.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 13/02/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class CommandTest: XCTestCase {
12 |
13 | func test_determine_php_version() {
14 | let version = Command.execute(
15 | path: Paths.php,
16 | arguments: ["-v"],
17 | trimNewlines: false
18 | )
19 |
20 | XCTAssert(version.contains("(cli)"))
21 | XCTAssert(version.contains("NTS"))
22 | XCTAssert(version.contains("built"))
23 | XCTAssert(version.contains("Zend"))
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/tests/unit/Parsers/CaskFileParserTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CaskFileParserTest.swift
3 | // Unit Tests
4 | //
5 | // Created by Nico Verbruggen on 04/02/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class CaskFileParserTest: XCTestCase {
12 |
13 | // MARK: - Test Files
14 | static var exampleFilePath: URL {
15 | return Bundle(for: Self.self)
16 | .url(forResource: "phpmon-dev", withExtension: "rb")!
17 | }
18 |
19 | func test_can_extract_fields_from_cask_file() async throws {
20 | guard let caskFile = await CaskFile.from(url: CaskFileParserTest.exampleFilePath) else {
21 | return XCTFail("The CaskFile could not be parsed, check the log for more info")
22 | }
23 |
24 | XCTAssertEqual(
25 | caskFile.version,
26 | "5.7.2_1035"
27 | )
28 | XCTAssertEqual(
29 | caskFile.sha256,
30 | "1cb147bd1b1fbd52971d90dff577465b644aee7c878f15ede57f46e8f217067a"
31 | )
32 | XCTAssertEqual(
33 | caskFile.name,
34 | "PHP Monitor DEV"
35 | )
36 | XCTAssertEqual(
37 | caskFile.url,
38 | "https://github.com/nicoverbruggen/phpmon/releases/download/v5.7.2/phpmon-dev.zip"
39 | )
40 | }
41 |
42 | func test_can_extract_fields_from_remote_cask_file() async throws {
43 | guard let caskFile = await CaskFile.from(url: Constants.Urls.StableBuildCaskFile) else {
44 | return XCTFail("The remote CaskFile could not be parsed, check the log for more info")
45 | }
46 |
47 | XCTAssertTrue(caskFile.properties.keys.contains("version"))
48 | XCTAssertTrue(caskFile.properties.keys.contains("homepage"))
49 | XCTAssertTrue(caskFile.properties.keys.contains("url"))
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/unit/Parsers/Config/BytePhpPreferenceTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BytePhpPreferenceTest.swift
3 | // Unit Tests
4 | //
5 | // Created by Nico Verbruggen on 04/09/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class BytePhpPreferenceTest: XCTestCase {
12 |
13 | func test_can_extract_memory_value() throws {
14 | let pref = BytePhpPreference(key: "memory_limit")
15 |
16 | XCTAssertEqual(pref.internalValue, "512M")
17 | XCTAssertEqual(pref.unit, .megabyte)
18 | XCTAssertEqual(pref.value, 512)
19 | }
20 |
21 | func test_can_parse_all_kinds_of_values() throws {
22 | var (unit, value) = BytePhpPreference.readFrom(internalValue: "1G")!
23 | XCTAssertEqual(unit, .gigabyte)
24 | XCTAssertEqual(value, 1)
25 |
26 | (unit, value) = BytePhpPreference.readFrom(internalValue: "256M")!
27 | XCTAssertEqual(unit, .megabyte)
28 | XCTAssertEqual(value, 256)
29 |
30 | (unit, value) = BytePhpPreference.readFrom(internalValue: "512K")!
31 | XCTAssertEqual(unit, .kilobyte)
32 | XCTAssertEqual(value, 512)
33 |
34 | (unit, value) = BytePhpPreference.readFrom(internalValue: "1024")!
35 | XCTAssertEqual(unit, .kilobyte)
36 | XCTAssertEqual(value, 1024)
37 |
38 | (unit, value) = BytePhpPreference.readFrom(internalValue: "-1")!
39 | XCTAssertEqual(unit, .kilobyte)
40 | XCTAssertEqual(value, -1)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/unit/Parsers/ExtensionEnumeratorTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExtensionEnumeratorTest.swift
3 | // Unit Tests
4 | //
5 | // Created by Nico Verbruggen on 30/10/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | final class ExtensionEnumeratorTest: XCTestCase {
12 |
13 | override func setUp() async throws {
14 | ActiveFileSystem.useTestable([
15 | "\(Paths.tapPath)/shivammathur/homebrew-extensions/Formula/xdebug@8.1.rb": .fake(.text, ""),
16 | "\(Paths.tapPath)/shivammathur/homebrew-extensions/Formula/xdebug@8.2.rb": .fake(.text, ""),
17 | "\(Paths.tapPath)/shivammathur/homebrew-extensions/Formula/xdebug@8.3.rb": .fake(.text, ""),
18 | "\(Paths.tapPath)/shivammathur/homebrew-extensions/Formula/xdebug@8.4.rb": .fake(.text, ""),
19 | ])
20 | }
21 |
22 | func testCanReadFormulae() throws {
23 | let directory = "\(Paths.tapPath)/shivammathur/homebrew-extensions/Formula"
24 | let files = try FileSystem.getShallowContentsOfDirectory(directory)
25 |
26 | XCTAssertEqual(
27 | Set(["xdebug@8.1.rb", "xdebug@8.2.rb", "xdebug@8.3.rb", "xdebug@8.4.rb"]),
28 | Set(files)
29 | )
30 | }
31 |
32 | func testCanParseFormulaeBasedOnSyntax() throws {
33 | let formulae = BrewTapFormulae.from(tap: "shivammathur/homebrew-extensions")
34 |
35 | XCTAssertEqual(formulae["8.1"], [BrewPhpExtension(path: "/", name: "xdebug", phpVersion: "8.1")])
36 | XCTAssertEqual(formulae["8.2"], [BrewPhpExtension(path: "/", name: "xdebug", phpVersion: "8.2")])
37 | XCTAssertEqual(formulae["8.3"], [BrewPhpExtension(path: "/", name: "xdebug", phpVersion: "8.3")])
38 | XCTAssertEqual(formulae["8.4"], [BrewPhpExtension(path: "/", name: "xdebug", phpVersion: "8.4")])
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/tests/unit/Parsers/ValetConfigurationTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ValetConfigParserTest.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 29/11/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class ValetConfigurationTest: XCTestCase {
12 |
13 | static var jsonConfigFileUrl: URL {
14 | return Bundle(for: Self.self).url(
15 | forResource: "valet-config",
16 | withExtension: "json"
17 | )!
18 | }
19 |
20 | func test_can_load_config_file() throws {
21 | let json = try? String(
22 | contentsOf: Self.jsonConfigFileUrl,
23 | encoding: .utf8
24 | )
25 | let config = try! JSONDecoder().decode(
26 | Valet.Configuration.self,
27 | from: json!.data(using: .utf8)!
28 | )
29 |
30 | XCTAssertEqual(config.tld, "test")
31 | XCTAssertEqual(config.paths, [
32 | "/Users/username/.config/valet/Sites",
33 | "/Users/username/Sites"
34 | ])
35 | XCTAssertEqual(config.defaultSite, "/Users/username/default-site")
36 | XCTAssertEqual(config.loopback, "127.0.0.1")
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/tests/unit/Parsers/ValetRcTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ValetRcTest.swift
3 | // Unit Tests
4 | //
5 | // Created by Nico Verbruggen on 20/01/2023.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class ValetRcTest: XCTestCase {
12 |
13 | // MARK: - Test Files
14 |
15 | static var validPath: URL {
16 | return Bundle(for: Self.self)
17 | .url(forResource: "valetrc", withExtension: "valid")!
18 | }
19 |
20 | static var brokenPath: URL {
21 | return Bundle(for: Self.self)
22 | .url(forResource: "valetrc", withExtension: "broken")!
23 | }
24 |
25 |
26 | // MARK: - Tests
27 |
28 | func test_can_extract_fields_from_valetrc_file() throws {
29 | let fakeFile = RCFile.fromPath("/Users/fake/file.rc")
30 | XCTAssertNil(fakeFile)
31 |
32 | // Can parse the file
33 | let validFile = RCFile.fromPath(ValetRcTest.validPath.path)
34 | XCTAssertNotNil(validFile)
35 |
36 | let fields = validFile!.fields
37 |
38 | // Correctly parses and trims (and omits double quotes) per line
39 | XCTAssertEqual(fields["PHP"], "php@8.2")
40 | XCTAssertEqual(fields["OTHER"], "thing")
41 | XCTAssertEqual(fields["PHPMON_WATCH"], "true")
42 | XCTAssertEqual(fields["SYNTAX"], "variable")
43 |
44 | // Ignores entries prefixed with #
45 | XCTAssertTrue(!fields.keys.contains("#PHP"))
46 |
47 | // Ignores invalid lines
48 | XCTAssertTrue(!fields.keys.contains("OOF"))
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/unit/Test Files/brew/phpmon-dev.rb:
--------------------------------------------------------------------------------
1 | cask 'phpmon-dev' do
2 | depends_on formula: 'gnu-sed'
3 |
4 | version '5.7.2_1035'
5 | sha256 '1cb147bd1b1fbd52971d90dff577465b644aee7c878f15ede57f46e8f217067a'
6 |
7 | url 'https://github.com/nicoverbruggen/phpmon/releases/download/v5.7.2/phpmon-dev.zip'
8 | name 'PHP Monitor DEV'
9 | homepage 'https://phpmon.app'
10 |
11 | app 'PHP Monitor DEV.app', target: "PHP Monitor DEV.app"
12 | end
13 |
14 |
--------------------------------------------------------------------------------
/tests/unit/Test Files/nginx/nginx-secure-proxy-custom-tld.test:
--------------------------------------------------------------------------------
1 | # valet stub: secure.proxy.valet.conf
2 |
3 | server {
4 | listen 127.0.0.1:80;
5 | #listen 127.0.0.1:80; # valet loopback
6 | server_name live.whatagraph.dev.com www.live.whatagraph.dev.com *.live.whatagraph.dev.com;
7 | return 301 https://$host$request_uri;
8 | }
9 |
10 | server {
11 | listen 127.0.0.1:443 ssl http2;
12 | #listen 127.0.0.1:443 ssl http2; # valet loopback
13 | server_name live.whatagraph.dev.com www.live.whatagraph.dev.com *.live.whatagraph.dev.com;
14 | root /;
15 | charset utf-8;
16 | client_max_body_size 128M;
17 | http2_push_preload on;
18 |
19 | location /41c270e4-5535-4daa-b23e-c269744c2f45/ {
20 | internal;
21 | alias /;
22 | try_files $uri $uri/;
23 | }
24 |
25 | ssl_certificate "/Users/phpmon/.config/valet/Certificates/live.whatagraph.dev.com.crt";
26 | ssl_certificate_key "/Users/phpmon/.config/valet/Certificates/live.whatagraph.dev.com.key";
27 |
28 | access_log off;
29 | error_log "/Users/phpmon/.config/valet/Log/live.whatagraph.dev.com-error.log";
30 |
31 | error_page 404 "/Users/phpmon/.composer/vendor/laravel/valet/server.php";
32 |
33 | location / {
34 | proxy_pass http://localhost:8080/;
35 | proxy_set_header Host $host;
36 | proxy_set_header X-Real-IP $remote_addr;
37 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
38 | proxy_set_header X-Forwarded-Proto $scheme;
39 | proxy_set_header X-Client-Verify SUCCESS;
40 | proxy_set_header X-Client-DN $ssl_client_s_dn;
41 | proxy_set_header X-SSL-Subject $ssl_client_s_dn;
42 | proxy_set_header X-SSL-Issuer $ssl_client_i_dn;
43 | proxy_set_header X-NginX-Proxy true;
44 | proxy_set_header Upgrade $http_upgrade;
45 | proxy_set_header Connection "upgrade";
46 | proxy_http_version 1.1;
47 | proxy_read_timeout 1800;
48 | proxy_connect_timeout 1800;
49 | chunked_transfer_encoding on;
50 | proxy_redirect off;
51 | proxy_buffering off;
52 | }
53 |
54 | location ~ /\.ht {
55 | deny all;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/unit/Test Files/nginx/nginx-secure-proxy.test:
--------------------------------------------------------------------------------
1 | # valet stub: secure.proxy.valet.conf
2 |
3 | server {
4 | listen 127.0.0.1:80;
5 | #listen 127.0.0.1:80; # valet loopback
6 | server_name my-proxy.test www.my-proxy.test *.my-proxy.test;
7 | return 301 https://$host$request_uri;
8 | }
9 |
10 | server {
11 | listen 127.0.0.1:443 ssl http2;
12 | #listen 127.0.0.1:443 ssl http2; # valet loopback
13 | server_name my-proxy.test www.my-proxy.test *.my-proxy.test;
14 | root /;
15 | charset utf-8;
16 | client_max_body_size 128M;
17 | http2_push_preload on;
18 |
19 | location /41c270e4-5535-4daa-b23e-c269744c2f45/ {
20 | internal;
21 | alias /;
22 | try_files $uri $uri/;
23 | }
24 |
25 | ssl_certificate "/Users/nicoverbruggen/.config/valet/Certificates/my-proxy.test.crt";
26 | ssl_certificate_key "/Users/nicoverbruggen/.config/valet/Certificates/my-proxy.test.key";
27 |
28 | access_log off;
29 | error_log "/Users/nicoverbruggen/.config/valet/Log/my-proxy.test-error.log";
30 |
31 | error_page 404 "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
32 |
33 | location / {
34 | proxy_pass http://127.0.0.1:90;
35 | proxy_set_header Host $host;
36 | proxy_set_header X-Real-IP $remote_addr;
37 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
38 | proxy_set_header X-Forwarded-Proto $scheme;
39 | proxy_set_header X-Client-Verify SUCCESS;
40 | proxy_set_header X-Client-DN $ssl_client_s_dn;
41 | proxy_set_header X-SSL-Subject $ssl_client_s_dn;
42 | proxy_set_header X-SSL-Issuer $ssl_client_i_dn;
43 | proxy_set_header X-NginX-Proxy true;
44 | proxy_set_header Upgrade $http_upgrade;
45 | proxy_set_header Connection "upgrade";
46 | proxy_http_version 1.1;
47 | proxy_read_timeout 1800;
48 | proxy_connect_timeout 1800;
49 | chunked_transfer_encoding on;
50 | proxy_redirect off;
51 | proxy_buffering off;
52 | }
53 |
54 | location ~ /\.ht {
55 | deny all;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/unit/Test Files/phpmon/phpmon-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "scan_apps": [],
3 | "presets": [
4 | {
5 | "name": "Default PHP",
6 | "extensions": {
7 | "xdebug": false
8 | },
9 | "configuration": {
10 | "memory_limit": "128M"
11 | }
12 | },
13 | {
14 | "name": "Personal Site",
15 | "extensions": {
16 | "xdebug": true
17 | },
18 | "configuration": {
19 | "xdebug.mode": "coverage",
20 | "memory_limit": "512M"
21 | }
22 | },
23 | {
24 | "name": "PHP Monitor",
25 | "extensions": {
26 | "xdebug": true
27 | },
28 | "configuration": {
29 | "xdebug.mode": "coverage",
30 | "memory_limit": "512M"
31 | }
32 | }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/tests/unit/Test Files/valet/valet-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "tld": "test",
3 | "paths": [
4 | "/Users/username/.config/valet/Sites",
5 | "/Users/username/Sites"
6 | ],
7 | "loopback": "127.0.0.1",
8 | "default": "/Users/username/default-site"
9 | }
10 |
--------------------------------------------------------------------------------
/tests/unit/Test Files/valet/valetrc.broken:
--------------------------------------------------------------------------------
1 | fdsgdfg
2 | dgdfg
3 |
4 | PHP=fsdfs
5 |
6 | ;PHP=8.2
7 |
--------------------------------------------------------------------------------
/tests/unit/Test Files/valet/valetrc.valid:
--------------------------------------------------------------------------------
1 | PHP=php@8.2
2 | #PHP=php
3 | OTHER=thing
4 | PHPMON_WATCH=true
5 | SYNTAX = "variable"
6 | OOF:NICE
7 |
--------------------------------------------------------------------------------
/tests/unit/Testables/TestableConfigurationTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestableConfigurationTest.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 16/10/2022.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class TestableConfigurationTest: XCTestCase {
12 | func test_configuration_can_be_saved_as_json() async {
13 | // WORKING
14 | var configuration = TestableConfigurations.working
15 |
16 | try! configuration.toJson().write(
17 | toFile: NSHomeDirectory() + "/.phpmon_fconf_working.json",
18 | atomically: true,
19 | encoding: .utf8
20 | )
21 |
22 | // WORKING (WITHOUT VALET)
23 | let valetFreeConfiguration = TestableConfigurations.workingWithoutValet
24 |
25 | try! valetFreeConfiguration.toJson().write(
26 | toFile: NSHomeDirectory() + "/.phpmon_fconf_working_no_valet.json",
27 | atomically: true,
28 | encoding: .utf8
29 | )
30 |
31 | // NOT WORKING
32 | configuration.filesystem["/opt/homebrew/bin/php"] = nil
33 |
34 | try! configuration.toJson().write(
35 | toFile: NSHomeDirectory() + "/.phpmon_fconf_broken.json",
36 | atomically: true,
37 | encoding: .utf8
38 | )
39 | }
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/tests/unit/Versions/PhpVersionDetectionTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhpVersionDetectionTest.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 01/04/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class PhpVersionDetectionTest: XCTestCase {
12 |
13 | func test_can_detect_valid_php_versions() async throws {
14 | let outcome = await PhpEnvironments.shared.extractPhpVersions(
15 | from: [
16 | "", // empty lines should be omitted
17 | "php@8.0",
18 | "php@8.0", // should only be detected once
19 | "meta-php@8.0", // should be omitted, invalid
20 | "php@8.0-coolio", // should be omitted, invalid
21 | "php@7.0",
22 | "",
23 | "unrelatedphp@1.0", // should be omitted, invalid
24 | "php@5.6", // should be omitted, not supported
25 | "php@5.4" // should be omitted, not supported
26 | ],
27 | checkBinaries: false,
28 | generateHelpers: false
29 | )
30 |
31 | XCTAssertEqual(outcome, ["8.0", "7.0", "5.6"])
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/unit/Versions/VersionExtractorTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VersionExtractorTest.swift
3 | // PHP Monitor
4 | //
5 | // Created by Nico Verbruggen on 16/12/2021.
6 | // Copyright © 2023 Nico Verbruggen. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class VersionExtractorTest: XCTestCase {
12 |
13 | func test_extract_version() {
14 | XCTAssertEqual(VersionExtractor.from("Laravel Valet 2.17.1"), "2.17.1")
15 | XCTAssertEqual(VersionExtractor.from("Laravel Valet 2.0"), "2.0")
16 | }
17 |
18 | func test_version_comparison() {
19 | XCTAssertEqual("2.0".versionCompare("2.1"), .orderedAscending)
20 | XCTAssertEqual("2.1".versionCompare("2.0"), .orderedDescending)
21 | XCTAssertEqual("2.0".versionCompare("2.0"), .orderedSame)
22 | XCTAssertEqual("2.17.0".versionCompare("2.17.1"), .orderedAscending)
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------