mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2025-11-05 04:20:06 +01:00
✨ Query latest version of Valet via Packagist
This makes it possible for PHP Monitor to request what the latest version of Valet is. This isn't wired up to the UI currently, so this feature isn't enabled yet. To enable this feature, I would need to add: - Conditional check for Valet updates (via setting) - Decide when to run this check (either every X time and at launch)? Additionally, PHP Monitor should be aware of its own uptime in order to make periodic checks if the app hasn't been restarted. A check should ideally occur every week or so if the app is not restarted. The relevant app update check should also be adjusted to run in a very similar way. How frequently the app checks for updates might also be a setting but I don't want to query too often.
This commit is contained in:
@@ -21,6 +21,19 @@
|
||||
033D45A02B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */; };
|
||||
033D45A12B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */; };
|
||||
033D45A32B0D531D00070080 /* PhpExtensionManagerView+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45A22B0D531D00070080 /* PhpExtensionManagerView+Actions.swift */; };
|
||||
036C39022E5C883B008DAEDF /* Packagist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39012E5C883A008DAEDF /* Packagist.swift */; };
|
||||
036C39032E5C883B008DAEDF /* Packagist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39012E5C883A008DAEDF /* Packagist.swift */; };
|
||||
036C39042E5C883B008DAEDF /* Packagist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39012E5C883A008DAEDF /* Packagist.swift */; };
|
||||
036C39052E5C883B008DAEDF /* Packagist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39012E5C883A008DAEDF /* Packagist.swift */; };
|
||||
036C39082E5C88A7008DAEDF /* PackagistTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39072E5C88A2008DAEDF /* PackagistTest.swift */; };
|
||||
036C390A2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39092E5C8CBD008DAEDF /* PackagistP2Response.swift */; };
|
||||
036C390B2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39092E5C8CBD008DAEDF /* PackagistP2Response.swift */; };
|
||||
036C390C2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39092E5C8CBD008DAEDF /* PackagistP2Response.swift */; };
|
||||
036C390D2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39092E5C8CBD008DAEDF /* PackagistP2Response.swift */; };
|
||||
036C390F2E5C8D42008DAEDF /* PackagistError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C390E2E5C8D3B008DAEDF /* PackagistError.swift */; };
|
||||
036C39102E5C8D42008DAEDF /* PackagistError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C390E2E5C8D3B008DAEDF /* PackagistError.swift */; };
|
||||
036C39112E5C8D42008DAEDF /* PackagistError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C390E2E5C8D3B008DAEDF /* PackagistError.swift */; };
|
||||
036C39122E5C8D42008DAEDF /* PackagistError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C390E2E5C8D3B008DAEDF /* PackagistError.swift */; };
|
||||
03BFF5272E312C3D007F96FA /* Startup+Timers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF5262E312C39007F96FA /* Startup+Timers.swift */; };
|
||||
03BFF5282E312C3D007F96FA /* Startup+Timers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF5262E312C39007F96FA /* Startup+Timers.swift */; };
|
||||
03BFF5292E312C3D007F96FA /* Startup+Timers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF5262E312C39007F96FA /* Startup+Timers.swift */; };
|
||||
@@ -936,6 +949,10 @@
|
||||
033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallPhpExtensionCommand.swift; sourceTree = "<group>"; };
|
||||
033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemovePhpExtensionCommand.swift; sourceTree = "<group>"; };
|
||||
033D45A22B0D531D00070080 /* PhpExtensionManagerView+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PhpExtensionManagerView+Actions.swift"; sourceTree = "<group>"; };
|
||||
036C39012E5C883A008DAEDF /* Packagist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Packagist.swift; sourceTree = "<group>"; };
|
||||
036C39072E5C88A2008DAEDF /* PackagistTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackagistTest.swift; sourceTree = "<group>"; };
|
||||
036C39092E5C8CBD008DAEDF /* PackagistP2Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackagistP2Response.swift; sourceTree = "<group>"; };
|
||||
036C390E2E5C8D3B008DAEDF /* PackagistError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackagistError.swift; sourceTree = "<group>"; };
|
||||
03BFF5262E312C39007F96FA /* Startup+Timers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Startup+Timers.swift"; sourceTree = "<group>"; };
|
||||
03BFF52B2E313240007F96FA /* StatusMenu+Driver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Driver.swift"; sourceTree = "<group>"; };
|
||||
03CC1FE42E3D220F0050FC18 /* InstallHomebrew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallHomebrew.swift; sourceTree = "<group>"; };
|
||||
@@ -1275,6 +1292,24 @@
|
||||
path = "PHP Versions";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
036C38FB2E5C8827008DAEDF /* Packagist */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
036C39012E5C883A008DAEDF /* Packagist.swift */,
|
||||
036C390E2E5C8D3B008DAEDF /* PackagistError.swift */,
|
||||
036C39092E5C8CBD008DAEDF /* PackagistP2Response.swift */,
|
||||
);
|
||||
path = Packagist;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
036C39062E5C8890008DAEDF /* integration */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
036C39072E5C88A2008DAEDF /* PackagistTest.swift */,
|
||||
);
|
||||
path = integration;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
03BFF1D12E3CF4F2004C56A9 /* Provision */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1844,6 +1879,7 @@
|
||||
C4F7807A25D7F84B000DBC97 /* unit */,
|
||||
C471E7AE28F9B4940021E251 /* feature */,
|
||||
C471E7BD28F9B90F0021E251 /* ui */,
|
||||
036C39062E5C8890008DAEDF /* integration */,
|
||||
);
|
||||
path = tests;
|
||||
sourceTree = "<group>";
|
||||
@@ -1929,6 +1965,7 @@
|
||||
C4AF9F6B275445D300D44ED0 /* Integrations */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
036C38FB2E5C8827008DAEDF /* Packagist */,
|
||||
C4463FD029804C13007B93D5 /* Common */,
|
||||
C4C0E8DA27F887CC002D32A9 /* Nginx */,
|
||||
C4D89BC42783C98800A02B68 /* Composer */,
|
||||
@@ -2637,6 +2674,7 @@
|
||||
C415D3B72770F294005EF286 /* Actions.swift in Sources */,
|
||||
C4BB39392981AFC700F8E797 /* PhpVersionSource.swift in Sources */,
|
||||
C4C3643928AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */,
|
||||
036C39032E5C883B008DAEDF /* Packagist.swift in Sources */,
|
||||
C4AC51FC27E27F47008528CA /* DomainListKindCell.swift in Sources */,
|
||||
C4CDA893288F1A71007CE25F /* Keys.swift in Sources */,
|
||||
C43931C529C4BD610069165B /* PhpVersionManagerView.swift in Sources */,
|
||||
@@ -2671,6 +2709,7 @@
|
||||
C47699F128A2F3150060FEB8 /* Warning.swift in Sources */,
|
||||
54D9E0B227E4F51E003B9AD9 /* HotKeysController.swift in Sources */,
|
||||
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */,
|
||||
036C39102E5C8D42008DAEDF /* PackagistError.swift in Sources */,
|
||||
C40C7F3027722E8D00DDDCDC /* Logger.swift in Sources */,
|
||||
C41CA5ED2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */,
|
||||
C412E5FC25700D5300A1FB67 /* HomebrewDecodable.swift in Sources */,
|
||||
@@ -2720,6 +2759,7 @@
|
||||
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
|
||||
033D45A32B0D531D00070080 /* PhpExtensionManagerView+Actions.swift in Sources */,
|
||||
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
|
||||
036C390D2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */,
|
||||
C4D5CFCA27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */,
|
||||
C485707028BF452300539B36 /* PhpDoctorWindowController.swift in Sources */,
|
||||
C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */,
|
||||
@@ -2766,6 +2806,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
036C39122E5C8D42008DAEDF /* PackagistError.swift in Sources */,
|
||||
C471E82D28F9BB650021E251 /* AlertableError.swift in Sources */,
|
||||
C471E82E28F9BB650021E251 /* Errors.swift in Sources */,
|
||||
C471E82F28F9BB650021E251 /* Alert.swift in Sources */,
|
||||
@@ -2775,6 +2816,7 @@
|
||||
C4B79EB829CA387F00A483EE /* BrewPhpFormulaeHandler.swift in Sources */,
|
||||
C471E83228F9BB650021E251 /* MenuBarImageGenerator.swift in Sources */,
|
||||
C4BB393B2981AFC700F8E797 /* PhpVersionSource.swift in Sources */,
|
||||
036C39052E5C883B008DAEDF /* Packagist.swift in Sources */,
|
||||
C471E83328F9BB650021E251 /* PMWindowController.swift in Sources */,
|
||||
C471E83428F9BB650021E251 /* VersionExtractor.swift in Sources */,
|
||||
C471E83528F9BB650021E251 /* ValetProxy.swift in Sources */,
|
||||
@@ -2840,6 +2882,7 @@
|
||||
C471E86428F9BB650021E251 /* Warning.swift in Sources */,
|
||||
C40175BA2903108900763A68 /* ValetInteractor.swift in Sources */,
|
||||
C43931C729C4BD610069165B /* PhpVersionManagerView.swift in Sources */,
|
||||
036C390A2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */,
|
||||
C4463FCE29804BCB007B93D5 /* RCFile.swift in Sources */,
|
||||
C45B9150295608E300F4EC78 /* ValetServicesManager.swift in Sources */,
|
||||
C471E86528F9BB650021E251 /* WarningManager.swift in Sources */,
|
||||
@@ -2984,6 +3027,7 @@
|
||||
C471E89228F9BB8F0021E251 /* Alert.swift in Sources */,
|
||||
033D45A12B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */,
|
||||
C471E89328F9BB8F0021E251 /* Application.swift in Sources */,
|
||||
036C39022E5C883B008DAEDF /* Packagist.swift in Sources */,
|
||||
C471E89428F9BB8F0021E251 /* LocalNotification.swift in Sources */,
|
||||
C441CC592AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */,
|
||||
C40934A5298EEB2C00D25014 /* CaskFile.swift in Sources */,
|
||||
@@ -3028,6 +3072,7 @@
|
||||
C471E8B628F9BB8F0021E251 /* MainMenu+Actions.swift in Sources */,
|
||||
C471E8B728F9BB8F0021E251 /* StatusMenu.swift in Sources */,
|
||||
C471E8B828F9BB8F0021E251 /* StatusMenu+Items.swift in Sources */,
|
||||
036C390C2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */,
|
||||
C471E8B928F9BB8F0021E251 /* DomainListCellProtocol.swift in Sources */,
|
||||
C471E8BA28F9BB8F0021E251 /* DomainListTLSCell.swift in Sources */,
|
||||
C43B8FD82BA9C689000C02BE /* UnavailableContentView.swift in Sources */,
|
||||
@@ -3119,6 +3164,7 @@
|
||||
C40934A0298EE8E900D25014 /* AppUpdater.swift in Sources */,
|
||||
C4611E5A2AEAD2E20010BE24 /* ConfigManagerWindowController.swift in Sources */,
|
||||
C471E80E28F9BAE80021E251 /* DateExtension.swift in Sources */,
|
||||
036C390F2E5C8D42008DAEDF /* PackagistError.swift in Sources */,
|
||||
C490E3BA29BCA368006D2DE6 /* App+BrewWatch.swift in Sources */,
|
||||
03BFF5272E312C3D007F96FA /* Startup+Timers.swift in Sources */,
|
||||
C471E7D028F9BA630021E251 /* FileSystemProtocol.swift in Sources */,
|
||||
@@ -3301,6 +3347,7 @@
|
||||
C40C5C9D2846A40600E28255 /* Preset.swift in Sources */,
|
||||
C4CE7F9729683B43000102CF /* PhpVersionNumberCollection.swift in Sources */,
|
||||
C4F30B09278E1A0E00755FCE /* CustomPrefs.swift in Sources */,
|
||||
036C39082E5C88A7008DAEDF /* PackagistTest.swift in Sources */,
|
||||
C40FE738282ABA4F00A302C2 /* AppVersion.swift in Sources */,
|
||||
C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */,
|
||||
C4E49DEE28F764A00026AC4E /* TestableCommand.swift in Sources */,
|
||||
@@ -3321,10 +3368,12 @@
|
||||
C4E2E86528FC2F1B003B070C /* XCPMApplication.swift in Sources */,
|
||||
C4E49DE828F764050026AC4E /* ActiveCommand.swift in Sources */,
|
||||
C489E0BC2A220A4200323F5E /* FakeBrewFormulaeHandler.swift in Sources */,
|
||||
036C390B2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */,
|
||||
C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */,
|
||||
C4B79ECC29CA475900A483EE /* RemovePhpVersionCommand.swift in Sources */,
|
||||
C43B8FD62BA9C689000C02BE /* UnavailableContentView.swift in Sources */,
|
||||
C4FD87AA29AB9ABD0002D701 /* PhpConfigChecker.swift in Sources */,
|
||||
036C39112E5C8D42008DAEDF /* PackagistError.swift in Sources */,
|
||||
C485707D28BF45A200539B36 /* WarningView.swift in Sources */,
|
||||
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */,
|
||||
C44CCD4127AFE2FC00CE40E5 /* AlertableError.swift in Sources */,
|
||||
@@ -3347,6 +3396,7 @@
|
||||
C4EA3C482BA4F947007B0BA7 /* CustomButtonStyles.swift in Sources */,
|
||||
C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */,
|
||||
C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */,
|
||||
036C39042E5C883B008DAEDF /* Packagist.swift in Sources */,
|
||||
C44C198E276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */,
|
||||
C4B79EBD29CA38DB00A483EE /* BrewCommand.swift in Sources */,
|
||||
C485707828BF456300539B36 /* Warning.swift in Sources */,
|
||||
|
||||
74
phpmon/Domain/Integrations/Packagist/Packagist.swift
Normal file
74
phpmon/Domain/Integrations/Packagist/Packagist.swift
Normal file
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// Untitled.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 25/08/2025.
|
||||
// Copyright © 2025 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Packagist {
|
||||
static func getLatestStableVersion(packageName: String) async throws -> VersionNumber {
|
||||
guard let url = URL(string: "https://repo.packagist.org/p2/\(packageName).json") else {
|
||||
throw PackagistError.invalidURL
|
||||
}
|
||||
|
||||
let agent = "phpmon/\(App.shortVersion)"
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.setValue(agent, forHTTPHeaderField: "User-Agent")
|
||||
|
||||
do {
|
||||
let (data, response) = try await URLSession.shared.data(for: request)
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
|
||||
let code = (response as? HTTPURLResponse)?.statusCode ?? 0
|
||||
throw PackagistError.networkError(NSError(domain: "", code: code, userInfo: nil))
|
||||
}
|
||||
|
||||
let decodedResponse = try JSONDecoder()
|
||||
.decode(PackagistP2Response.self, from: data)
|
||||
|
||||
guard let versionsArray = decodedResponse.packages[packageName] else {
|
||||
throw PackagistError.unexpectedResponseStructure
|
||||
}
|
||||
|
||||
// Filter for stable versions using the version_normalized string.
|
||||
// A stable version typically does not have a hyphen (-) indicating a pre-release.
|
||||
let stableVersions = versionsArray.filter { version in
|
||||
guard let versionNormalized = version.version_normalized else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Filter out versions with a hyphen, which are usually unstable.
|
||||
return !versionNormalized.contains("-")
|
||||
}
|
||||
|
||||
// Sort the filtered versions using version_normalized, which is designed for lexicographical sorting.
|
||||
let sortedVersions = stableVersions.sorted { (version1, version2) -> Bool in
|
||||
guard let v1 = version1.version_normalized, let v2 = version2.version_normalized else {
|
||||
return false
|
||||
}
|
||||
return v1.lexicographicallyPrecedes(v2)
|
||||
}
|
||||
|
||||
// The last element of the sorted array is the latest version
|
||||
guard let latestVersionInfo = sortedVersions.last,
|
||||
let latestVersion = latestVersionInfo.version else {
|
||||
throw PackagistError.noStableVersions
|
||||
}
|
||||
|
||||
return try! VersionNumber.parse(latestVersion)
|
||||
} catch {
|
||||
// Catch any errors that occurred and re-throw them as our custom error type for better diagnostics.
|
||||
if let decodingError = error as? DecodingError {
|
||||
throw PackagistError.jsonDecodingError(decodingError)
|
||||
} else if let urlError = error as? URLError {
|
||||
throw PackagistError.networkError(urlError)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
phpmon/Domain/Integrations/Packagist/PackagistError.swift
Normal file
32
phpmon/Domain/Integrations/Packagist/PackagistError.swift
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// PackagistError.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 25/08/2025.
|
||||
// Copyright © 2025 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum PackagistError: Error, LocalizedError {
|
||||
case invalidURL
|
||||
case networkError(Error)
|
||||
case jsonDecodingError(Error)
|
||||
case noStableVersions
|
||||
case unexpectedResponseStructure
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .invalidURL:
|
||||
return "The provided URL is invalid."
|
||||
case .networkError(let error):
|
||||
return "A network error occurred: \(error.localizedDescription)"
|
||||
case .jsonDecodingError(let error):
|
||||
return "Failed to decode JSON: \(error.localizedDescription)"
|
||||
case .noStableVersions:
|
||||
return "No stable versions were found for the package."
|
||||
case .unexpectedResponseStructure:
|
||||
return "The API response structure was not as expected."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// PackagistP2Response.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 25/08/2025.
|
||||
// Copyright © 2025 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
struct PackagistP2Response: Codable {
|
||||
let packages: [String: [PackageInfo]]
|
||||
}
|
||||
|
||||
struct PackageInfo: Codable {
|
||||
let version: String?
|
||||
let version_normalized: String?
|
||||
}
|
||||
18
tests/integration/PackagistTest.swift
Normal file
18
tests/integration/PackagistTest.swift
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// PackagistTest.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 25/08/2025.
|
||||
// Copyright © 2025 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Testing
|
||||
|
||||
struct PackagistTest {
|
||||
@Test func packagistRetrieval() async {
|
||||
let packageToCheck = "laravel/valet"
|
||||
let latestVersion = try? await Packagist.getLatestStableVersion(packageName: packageToCheck)
|
||||
|
||||
#expect(latestVersion != nil)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user