diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index c42713c..9f2a95e 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -153,10 +153,8 @@ C4DEB7D427A5D60B00834718 /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DEB7D327A5D60B00834718 /* Stats.swift */; }; C4E0F7ED27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */; }; C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */; }; - C4E4404627C56F4700D225E1 /* Valet.Site.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* Valet.Site.swift */; }; - C4E4404727C56F4700D225E1 /* Valet.Site.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* Valet.Site.swift */; }; - C4E4404927C56F5F00D225E1 /* Valet.Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404827C56F5F00D225E1 /* Valet.Configuration.swift */; }; - C4E4404A27C56F5F00D225E1 /* Valet.Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404827C56F5F00D225E1 /* Valet.Configuration.swift */; }; + C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; + C4E4404727C56F4700D225E1 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; C4EC1E66279DE0380010F296 /* ServicesView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C4EC1E65279DE0380010F296 /* ServicesView.xib */; }; C4EC1E68279DE0540010F296 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E67279DE0540010F296 /* ServicesView.swift */; }; C4EC1E73279DFCF40010F296 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; }; @@ -307,8 +305,7 @@ C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalSwitcher.swift; sourceTree = ""; }; C4DEB7D327A5D60B00834718 /* Stats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stats.swift; sourceTree = ""; }; C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSWindowExtension.swift; sourceTree = ""; }; - C4E4404527C56F4700D225E1 /* Valet.Site.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Valet.Site.swift; sourceTree = ""; }; - C4E4404827C56F5F00D225E1 /* Valet.Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Valet.Configuration.swift; sourceTree = ""; }; + C4E4404527C56F4700D225E1 /* ValetSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetSite.swift; sourceTree = ""; }; C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = ""; }; C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = ""; }; C4EC1E65279DE0380010F296 /* ServicesView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ServicesView.xib; sourceTree = ""; }; @@ -578,8 +575,7 @@ isa = PBXGroup; children = ( C4AF9F792754499000D44ED0 /* Valet.swift */, - C4E4404527C56F4700D225E1 /* Valet.Site.swift */, - C4E4404827C56F5F00D225E1 /* Valet.Configuration.swift */, + C4E4404527C56F4700D225E1 /* ValetSite.swift */, ); path = Valet; sourceTree = ""; @@ -856,7 +852,6 @@ C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */, C4B585412770FE3900DA4FBE /* Shell.swift in Sources */, C4998F0A2617633900B2526E /* PrefsWC.swift in Sources */, - C4E4404927C56F5F00D225E1 /* Valet.Configuration.swift in Sources */, C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */, C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */, 5420395926135DC100FB00FA /* PrefsVC.swift in Sources */, @@ -868,7 +863,7 @@ C4EE55AD27708B9E001DF387 /* PMStatsView.swift in Sources */, C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */, 54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */, - C4E4404627C56F4700D225E1 /* Valet.Site.swift in Sources */, + C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */, C4EC1E68279DE0540010F296 /* ServicesView.swift in Sources */, C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */, C41E871A2763D42300161EE0 /* SiteListVC+ContextMenu.swift in Sources */, @@ -1009,9 +1004,8 @@ C40B24F127A3106D0018C7D2 /* ServicesView.swift in Sources */, C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */, C4F780B725D80B5D000DBC97 /* App.swift in Sources */, - C4E4404A27C56F5F00D225E1 /* Valet.Configuration.swift in Sources */, C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */, - C4E4404727C56F4700D225E1 /* Valet.Site.swift in Sources */, + C4E4404727C56F4700D225E1 /* ValetSite.swift in Sources */, C44CCD4A27AFF3BC00CE40E5 /* MainMenu+Async.swift in Sources */, C48D6C71279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */, C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */, diff --git a/phpmon/Domain/Integrations/Composer/ComposerJson.swift b/phpmon/Domain/Integrations/Composer/ComposerJson.swift index d06697d..f18601b 100644 --- a/phpmon/Domain/Integrations/Composer/ComposerJson.swift +++ b/phpmon/Domain/Integrations/Composer/ComposerJson.swift @@ -39,7 +39,7 @@ struct ComposerJson: Decodable { Checks what the PHP version constraint is. Returns a tuple (constraint, location of constraint). */ - public func getPhpVersion() -> (String, Valet.Site.VersionSource) + public func getPhpVersion() -> (String, ValetSite.VersionSource) { // Check if in platform if configuration?.platform?.php != nil { diff --git a/phpmon/Domain/Integrations/Valet/Valet.Configuration.swift b/phpmon/Domain/Integrations/Valet/Valet.Configuration.swift deleted file mode 100644 index 36329cf..0000000 --- a/phpmon/Domain/Integrations/Valet/Valet.Configuration.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// Valet.Configuration.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 22/02/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. -// - -import Foundation - -extension Valet { - - struct Configuration: Decodable { - /// Top level domain suffix. Usually "test" but can be set to something else. - /// - Important: Does not include the actual dot. ("test", not ".test"!) - let tld: String - - /// The paths that need to be checked. - let paths: [String] - - /// The loopback address. Optional. - let loopback: String? - - /// The default site that is served if the domain is not found. Optional. - let defaultSite: String? - - private enum CodingKeys: String, CodingKey { - case tld, paths, loopback, defaultSite = "default" - } - } - -} diff --git a/phpmon/Domain/Integrations/Valet/Valet.Site.swift b/phpmon/Domain/Integrations/Valet/Valet.Site.swift deleted file mode 100644 index 2e4e5fb..0000000 --- a/phpmon/Domain/Integrations/Valet/Valet.Site.swift +++ /dev/null @@ -1,188 +0,0 @@ -// -// Valet+Subclasses.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 22/02/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. -// - -import Foundation - -extension Valet { - - class Site { - - /// Name of the site. Does not include the TLD. - var name: String! - - /// The absolute path to the directory that is served. - var absolutePath: String! - - /// The absolute path to the directory that is served, - /// replacing the user's home folder with ~. - lazy var absolutePathRelative: String = { - return self.absolutePath - .replacingOccurrences(of: "/Users/\(Paths.whoami)", with: "~") - }() - - /// Location of the alias. If set, this is a linked domain. - var aliasPath: String? - - /// Whether the site has been secured. - var secured: Bool! - - /// What driver is currently in use. If not detected, defaults to nil. - var driver: String? = nil - - /// Whether the driver was determined by checking the Composer file. - var driverDeterminedByComposer: Bool = false - - /// A list of notable Composer dependencies. - var notableComposerDependencies: [String: String] = [:] - - /// The PHP version as discovered in `composer.json` or in .valetphprc. - var composerPhp: String = "???" - - /// Check whether the PHP version is valid for the currently linked version. - var composerPhpCompatibleWithLinked: Bool = false - - /// How the PHP version was determined. - var composerPhpSource: VersionSource = .unknown - - enum VersionSource: String { - case unknown = "unknown" - case require = "require" - case platform = "platform" - case valetphprc = "valetphprc" - } - - init() {} - - convenience init(absolutePath: String, tld: String) { - self.init() - self.absolutePath = absolutePath - self.name = URL(fileURLWithPath: absolutePath).lastPathComponent - self.aliasPath = nil - determineSecured(tld) - determineComposerPhpVersion() - determineDriver() - } - - convenience init(aliasPath: String, tld: String) { - self.init() - self.absolutePath = try! FileManager.default.destinationOfSymbolicLink(atPath: aliasPath) - self.name = URL(fileURLWithPath: aliasPath).lastPathComponent - self.aliasPath = aliasPath - determineSecured(tld) - determineComposerPhpVersion() - determineDriver() - } - - /** - Checks if a certificate file can be found in the `valet/Certificates` directory. - - Note: The file is not validated, only its presence is checked. - */ - public func determineSecured(_ tld: String) { - secured = Filesystem.fileExists("~/.config/valet/Certificates/\(self.name!).\(tld).key") - } - - /** - Checks if `composer.json` exists in the folder, and extracts notable information: - - - The PHP version required (the constraint, so it could be `^8.0`, for example) - - Where the PHP version was found (`require` or `platform` or via .valetphprc) - - Notable PHP dependencies (determined via `PhpFrameworks.DependencyList`) - - The method then also checks if the determined constraint (if found) is compatible - with the currently linked version of PHP (see `composerPhpMatchesSystem`). - */ - public func determineComposerPhpVersion() { - do { - let path = "\(absolutePath!)/composer.json" - - if Filesystem.fileExists(path) { - let decoded = try JSONDecoder().decode( - ComposerJson.self, - from: String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8).data(using: .utf8)! - ) - - (self.composerPhp, self.composerPhpSource) = decoded.getPhpVersion() - self.notableComposerDependencies = decoded.getNotableDependencies() - } - } catch { - Log.err("Something went wrong reading the Composer JSON file.") - } - - do { - let path = "\(absolutePath!)/.valetphprc" - - if Filesystem.fileExists(path) { - let contents = try String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8) - if let version = VersionExtractor.from(contents) { - self.composerPhp = version - self.composerPhpSource = .valetphprc - } - } - } catch { - Log.err("Something went wrong parsing the .valetphprc file") - } - - // TODO: Check if .valetphprc exists and if it contains a valid version number - - if self.composerPhp == "???" { - return - } - - // Split the composer list (on "|") to evaluate multiple constraints - // For example, for Laravel 8 projects the value is "^7.3|^8.0" - self.composerPhpCompatibleWithLinked = - self.composerPhp.split(separator: "|").map { string in - return PhpVersionNumberCollection.make(from: [PhpEnv.phpInstall.version.long]) - .matching(constraint: string.trimmingCharacters(in: .whitespacesAndNewlines)) - .count > 0 - }.contains(true) - } - - /** - Determine the driver to be displayed in the list of sites. In v5.0, this has been changed - to load the "framework" or "project type" instead. - */ - public func determineDriver() { - self.determineDriverViaComposer() - - if self.driver == nil { - self.driver = PhpFrameworks.detectFallbackDependency(self.absolutePath) - } - } - - /** - Check the dependency list and see if a particular dependency can't be found. - We'll revert the dependency list so that Laravel and Symfony are detected last. - - (Some other frameworks might use Laravel, so if we found it first the detection would be incorrect: - this would happen with Statamic, for example.) - */ - private func determineDriverViaComposer() { - self.driverDeterminedByComposer = true - - PhpFrameworks.DependencyList.reversed().forEach { (key: String, value: String) in - if self.notableComposerDependencies.keys.contains(key) { - self.driver = value - } - } - } - - @available(*, deprecated, renamed: "determineDriver") - private func determineDriverViaValet() { - let driver = Shell.pipe("cd '\(absolutePath!)' && valet which", requiresPath: true) - if driver.contains("This site is served by") { - self.driver = driver - .replacingOccurrences(of: "This site is served by [", with: "") - .replacingOccurrences(of: "ValetDriver].\n", with: "") - } else { - self.driver = nil - } - } - } - -} diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index d7b4b9f..1315029 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -19,7 +19,7 @@ class Valet { var config: Valet.Configuration! /// A cached list of sites that were detected after analyzing the paths set up for Valet. - var sites: [Site] = [] + var sites: [ValetSite] = [] /// Whether we're busy with some blocking operation. var isBusy: Bool = false @@ -174,9 +174,28 @@ class Valet { } if type == FileAttributeType.typeSymbolicLink { - sites.append(Site(aliasPath: siteDir, tld: tld)) + sites.append(ValetSite(aliasPath: siteDir, tld: tld)) } else if type == FileAttributeType.typeDirectory { - sites.append(Site(absolutePath: siteDir, tld: tld)) + sites.append(ValetSite(absolutePath: siteDir, tld: tld)) + } + } + + struct Configuration: Decodable { + /// Top level domain suffix. Usually "test" but can be set to something else. + /// - Important: Does not include the actual dot. ("test", not ".test"!) + let tld: String + + /// The paths that need to be checked. + let paths: [String] + + /// The loopback address. Optional. + let loopback: String? + + /// The default site that is served if the domain is not found. Optional. + let defaultSite: String? + + private enum CodingKeys: String, CodingKey { + case tld, paths, loopback, defaultSite = "default" } } diff --git a/phpmon/Domain/Integrations/Valet/ValetSite.swift b/phpmon/Domain/Integrations/Valet/ValetSite.swift new file mode 100644 index 0000000..0e2f619 --- /dev/null +++ b/phpmon/Domain/Integrations/Valet/ValetSite.swift @@ -0,0 +1,170 @@ +// +// Valet+Subclasses.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 22/02/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class ValetSite { + + /// Name of the site. Does not include the TLD. + var name: String! + + /// The absolute path to the directory that is served. + var absolutePath: String! + + /// The absolute path to the directory that is served, + /// replacing the user's home folder with ~. + lazy var absolutePathRelative: String = { + return self.absolutePath + .replacingOccurrences(of: "/Users/\(Paths.whoami)", with: "~") + }() + + /// Location of the alias. If set, this is a linked domain. + var aliasPath: String? + + /// Whether the site has been secured. + var secured: Bool! + + /// What driver is currently in use. If not detected, defaults to nil. + var driver: String? = nil + + /// Whether the driver was determined by checking the Composer file. + var driverDeterminedByComposer: Bool = false + + /// A list of notable Composer dependencies. + var notableComposerDependencies: [String: String] = [:] + + /// The PHP version as discovered in `composer.json` or in .valetphprc. + var composerPhp: String = "???" + + /// Check whether the PHP version is valid for the currently linked version. + var composerPhpCompatibleWithLinked: Bool = false + + /// How the PHP version was determined. + var composerPhpSource: VersionSource = .unknown + + enum VersionSource: String { + case unknown = "unknown" + case require = "require" + case platform = "platform" + case valetphprc = "valetphprc" + } + + init() {} + + convenience init(absolutePath: String, tld: String) { + self.init() + self.absolutePath = absolutePath + self.name = URL(fileURLWithPath: absolutePath).lastPathComponent + self.aliasPath = nil + determineSecured(tld) + determineComposerPhpVersion() + determineDriver() + } + + convenience init(aliasPath: String, tld: String) { + self.init() + self.absolutePath = try! FileManager.default.destinationOfSymbolicLink(atPath: aliasPath) + self.name = URL(fileURLWithPath: aliasPath).lastPathComponent + self.aliasPath = aliasPath + determineSecured(tld) + determineComposerPhpVersion() + determineDriver() + } + + /** + Checks if a certificate file can be found in the `valet/Certificates` directory. + - Note: The file is not validated, only its presence is checked. + */ + public func determineSecured(_ tld: String) { + secured = Filesystem.fileExists("~/.config/valet/Certificates/\(self.name!).\(tld).key") + } + + /** + Checks if `composer.json` exists in the folder, and extracts notable information: + + - The PHP version required (the constraint, so it could be `^8.0`, for example) + - Where the PHP version was found (`require` or `platform` or via .valetphprc) + - Notable PHP dependencies (determined via `PhpFrameworks.DependencyList`) + + The method then also checks if the determined constraint (if found) is compatible + with the currently linked version of PHP (see `composerPhpMatchesSystem`). + */ + public func determineComposerPhpVersion() { + do { + let path = "\(absolutePath!)/composer.json" + + if Filesystem.fileExists(path) { + let decoded = try JSONDecoder().decode( + ComposerJson.self, + from: String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8).data(using: .utf8)! + ) + + (self.composerPhp, self.composerPhpSource) = decoded.getPhpVersion() + self.notableComposerDependencies = decoded.getNotableDependencies() + } + } catch { + Log.err("Something went wrong reading the Composer JSON file.") + } + + do { + let path = "\(absolutePath!)/.valetphprc" + + if Filesystem.fileExists(path) { + let contents = try String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8) + if let version = VersionExtractor.from(contents) { + self.composerPhp = version + self.composerPhpSource = .valetphprc + } + } + } catch { + Log.err("Something went wrong parsing the .valetphprc file") + } + + if self.composerPhp == "???" { + return + } + + // Split the composer list (on "|") to evaluate multiple constraints + // For example, for Laravel 8 projects the value is "^7.3|^8.0" + self.composerPhpCompatibleWithLinked = self.composerPhp.split(separator: "|") + .map { string in + return PhpVersionNumberCollection.make(from: [PhpEnv.phpInstall.version.long]) + .matching(constraint: string.trimmingCharacters(in: .whitespacesAndNewlines)) + .count > 0 + }.contains(true) + } + + /** + Determine the driver to be displayed in the list of sites. In v5.0, this has been changed + to load the "framework" or "project type" instead. + */ + public func determineDriver() { + self.determineDriverViaComposer() + + if self.driver == nil { + self.driver = PhpFrameworks.detectFallbackDependency(self.absolutePath) + } + } + + /** + Check the dependency list and see if a particular dependency can't be found. + We'll revert the dependency list so that Laravel and Symfony are detected last. + + (Some other frameworks might use Laravel, so if we found it first the detection would be incorrect: + this would happen with Statamic, for example.) + */ + private func determineDriverViaComposer() { + self.driverDeterminedByComposer = true + + PhpFrameworks.DependencyList.reversed().forEach { (key: String, value: String) in + if self.notableComposerDependencies.keys.contains(key) { + self.driver = value + } + } + } +} diff --git a/phpmon/Domain/SiteList/SiteListCell.swift b/phpmon/Domain/SiteList/SiteListCell.swift index f4593a4..824ca24 100644 --- a/phpmon/Domain/SiteList/SiteListCell.swift +++ b/phpmon/Domain/SiteList/SiteListCell.swift @@ -11,7 +11,7 @@ import AppKit class SiteListCell: NSTableCellView { - var site: Valet.Site? = nil + var site: ValetSite? = nil @IBOutlet weak var labelSiteName: NSTextField! @IBOutlet weak var labelPathName: NSTextField! @@ -29,7 +29,7 @@ class SiteListCell: NSTableCellView super.draw(dirtyRect) } - func populateCell(with site: Valet.Site) { + func populateCell(with site: ValetSite) { self.site = site // Make sure to show the TLD diff --git a/phpmon/Domain/SiteList/SiteListVC+ContextMenu.swift b/phpmon/Domain/SiteList/SiteListVC+ContextMenu.swift index b1e246b..98e08d7 100644 --- a/phpmon/Domain/SiteList/SiteListVC+ContextMenu.swift +++ b/phpmon/Domain/SiteList/SiteListVC+ContextMenu.swift @@ -65,7 +65,7 @@ extension SiteListVC { } } - private func addUnlink(to menu: NSMenu, with site: Valet.Site) { + private func addUnlink(to menu: NSMenu, with site: ValetSite) { if (site.aliasPath != nil) { menu.addItem( withTitle: "site_list.unlink".localized, @@ -76,7 +76,7 @@ extension SiteListVC { } } - private func addToggleSecure(to menu: NSMenu, with site: Valet.Site) { + private func addToggleSecure(to menu: NSMenu, with site: ValetSite) { menu.addItem( withTitle: site.secured ? "site_list.unsecure".localized diff --git a/phpmon/Domain/SiteList/SiteListVC.swift b/phpmon/Domain/SiteList/SiteListVC.swift index 642cdbd..57ca9c0 100644 --- a/phpmon/Domain/SiteList/SiteListVC.swift +++ b/phpmon/Domain/SiteList/SiteListVC.swift @@ -20,7 +20,7 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { // MARK: - Variables /// List of sites that will be displayed in this view. Originates from the `Valet` object. - var sites: [Valet.Site] = [] + var sites: [ValetSite] = [] /// Array that contains various apps that might open a particular site directory. var applications: [Application] { @@ -32,7 +32,7 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { // MARK: - Helper Variables - var selectedSite: Valet.Site? { + var selectedSite: ValetSite? { if tableView.selectedRow == -1 { return nil }