This commit is contained in:
David Bureš 2025-10-28 20:22:15 +01:00
parent acdff554a0
commit 7ca67cfe76
No known key found for this signature in database
48 changed files with 1175 additions and 431 deletions

View File

@ -1,13 +0,0 @@
//
// Brewfile Import Stage.swift
// Cork
//
// Created by David Bureš on 11.11.2023.
//
import Foundation
enum BrewfileImportStage
{
case importing, finished
}

View File

@ -1,18 +0,0 @@
//
// Licensing State.swift
// Cork
//
// Created by David Bureš on 18.03.2024.
//
import Foundation
enum LicensingState
{
case notBoughtOrHasNotActivatedDemo
case demo
case bought
case selfCompiled
}

View File

@ -7,64 +7,3 @@
import Foundation
/// Error representing failures while loading
enum PackageLoadingError: LocalizedError, Hashable, Identifiable
{
/// Tried to treat the folder `Cellar` or `Caskroom` itself as a package - means Homebrew itself is broken
case triedToThreatFolderContainingPackagesAsPackage(packageType: BrewPackage.PackageType)
/// The `Cellar` and `Caskroom` folder itself couldn't be loaded
case couldNotReadContentsOfParentFolder(failureReason: String, folderURL: URL)
/// Failed while trying to read contents of package folder
case failedWhileReadingContentsOfPackageFolder(folderURL: URL, reportedError: String)
case failedWhileTryingToDetermineIntentionalInstallation(folderURL: URL, associatedIntentionalDiscoveryError: IntentionalInstallationDiscoveryError)
/// The package root folder exists, but the package itself doesn't have any versions
case packageDoesNotHaveAnyVersionsInstalled(packageURL: URL)
/// A folder that should have contained the package is not actually a folder
case packageIsNotAFolder(String, packageURL: URL)
/// The number of loaded packages does not match the number of package parent folders
case numberOLoadedPackagesDosNotMatchNumberOfPackageFolders
var errorDescription: String?
{
switch self
{
case .couldNotReadContentsOfParentFolder(let failureReason, _):
return String(localized: "error.package-loading.could-not-read-contents-of-parent-folder.\(failureReason)")
case .triedToThreatFolderContainingPackagesAsPackage(let packageType):
switch packageType
{
case .formula:
return "error.package-loading.last-path-component-of-checked-package-url-is-folder-containing-packages-itself.formulae"
case .cask:
return "error.package-loading.last-path-component-of-checked-package-url-is-folder-containing-packages-itself.casks"
}
case .failedWhileReadingContentsOfPackageFolder(let folderURL, let reportedError):
return String(localized: "error.package-loading.could-not-load-\(folderURL.packageNameFromURL())-at-\(folderURL.absoluteString)-because-\(reportedError)", comment: "Couldn't load package (package name) at (package URL) because (failure reason)")
case .failedWhileTryingToDetermineIntentionalInstallation(_, let associatedIntentionalDiscoveryError):
return associatedIntentionalDiscoveryError.localizedDescription
case .packageDoesNotHaveAnyVersionsInstalled(let packageURL):
return String(localized: "error.package-loading.\(packageURL.packageNameFromURL())-does-not-have-any-versions-installed")
case .packageIsNotAFolder(let string, _):
return String(localized: "error.package-loading.\(string)-not-a-folder", comment: "Package folder in this context means a folder that encloses package versions. Every package has its own folder, and this error occurs when the provided URL does not point to a folder that encloses package versions")
case .numberOLoadedPackagesDosNotMatchNumberOfPackageFolders:
return String(localized: "error.package-loading.number-of-loaded-poackages-does-not-match-number-of-package-folders", comment: "This error occurs when there's a mismatch between the number of loaded packages, and the number of package folders in the package folders")
}
}
var id: UUID
{
return UUID()
}
}

View File

@ -8,10 +8,6 @@
import CorkShared
import Foundation
/// A representation of the loaded ``BrewPackage``s
/// Includes packages that were loaded properly, along those whose loading failed
typealias BrewPackages = Set<Result<BrewPackage, PackageLoadingError>>
extension BrewPackagesTracker
{
/// Parent function for loading installed packages from disk

View File

@ -119,104 +119,6 @@ extension BrewPackagesTracker
throw .failedToParseJson(error: casksJsonParsingError.localizedDescription)
}
}
/// A struct for holding a Cask's name and its executable
struct AdoptableApp: Identifiable, Hashable, Sendable
{
let id: UUID = .init()
let caskName: String
let appExecutable: String
let description: String?
let fullAppUrl: URL
var isMarkedForAdoption: Bool
var app: Application?
init(caskName: String, description: String?, appExecutable: String)
{
self.caskName = caskName
self.appExecutable = appExecutable
self.description = description
self.fullAppUrl = URL.applicationDirectory.appendingPathComponent(appExecutable, conformingTo: .application)
self.isMarkedForAdoption = true
}
mutating func changeMarkedState()
{
self.isMarkedForAdoption.toggle()
}
func constructAppBundle() async -> Application?
{
return try? .init(from: self.fullAppUrl)
}
func excludeSelf() async
{
let excludedAppRepresentation: BrewPackagesTracker.ExcludedAdoptableApp = .init(fromAdoptableApp: self)
await excludedAppRepresentation.saveSelfToDatabase()
}
func includeSelf() async
{
let excludedAppRepresentation: BrewPackagesTracker.ExcludedAdoptableApp = .init(fromAdoptableApp: self)
await excludedAppRepresentation.deleteSelfFromDatabase()
}
}
@Model
final class ExcludedAdoptableApp
{
@Attribute(.unique) @Attribute(.spotlight)
var appExecutable: String
init(appExecutable: String)
{
self.appExecutable = appExecutable
}
init(fromAdoptableApp app: BrewPackagesTracker.AdoptableApp)
{
self.appExecutable = app.appExecutable
}
@MainActor
public func saveSelfToDatabase()
{
AppConstants.shared.modelContainer.mainContext.insert(self)
}
@MainActor
public func deleteSelfFromDatabase()
{
let modelContext: ModelContext = AppConstants.shared.modelContainer.mainContext
do
{
let descriptor = FetchDescriptor<ExcludedAdoptableApp>(
predicate: #Predicate { $0.appExecutable == appExecutable }
)
if let existingPackage = try modelContext.fetch(descriptor).first
{
modelContext.delete(existingPackage)
}
}
catch
{
AppConstants.shared.logger.error("Failed to fetch excluded adoptable app for deletion: \(error.localizedDescription)")
}
}
}
/// Take the array that contains all Casks, and transform them into a list of Cask names, associated with their executable names for comparing with the contents of the Applications folder
private nonisolated

View File

@ -1,141 +0,0 @@
//
// Application.swift
// Cork
//
// Created by David Bureš - P on 07.10.2025.
//
import Foundation
import SwiftUI
import CorkShared
// TODO: Move this over to the `ApplicationInspector` external library once we figure out how to use Tuist projects as external dependencies
public struct Application: Identifiable, Hashable, Sendable
{
public let id: UUID = .init()
public let name: String
public let url: URL
public let iconPath: URL?
public let iconImage: Image?
public static func == (lhs: Application, rhs: Application) -> Bool
{
lhs.id == rhs.id
}
public func hash(into hasher: inout Hasher)
{
hasher.combine(name)
hasher.combine(id)
}
public enum ApplicationInitializationError: LocalizedError
{
public enum MandatoryAppInformation: String, Sendable
{
case name
public var description: String
{
switch self {
case .name:
return "Name"
}
}
}
case applicationExecutableNotReadable(checkedPath: String)
case couldNotAccessApplicationExecutable(error: Error)
case couldNotReadBundle(applicationPath: String)
case couldNotGetInfoDictionary
case couldNotGetMandatoryAppInformation(_ mandatoryInformation: MandatoryAppInformation)
public var errorDescription: String?
{
switch self {
case .applicationExecutableNotReadable(let checkedPath):
return "Couldn't read application executable at \(checkedPath)"
case .couldNotAccessApplicationExecutable(let error):
return "Couldn't read application executable: \(error)"
case .couldNotReadBundle(let applicationPath):
return "Couldn't read application bundle at \(applicationPath)"
case .couldNotGetInfoDictionary:
return "Couldn't read application info.plist"
case .couldNotGetMandatoryAppInformation(let mandatoryInformation):
return "Couldn't read mandatory app information: \(mandatoryInformation.description)"
}
}
}
public init(from appURL: URL) throws(ApplicationInitializationError)
{
do
{
guard FileManager.default.isReadableFile(atPath: appURL.path) == true else
{
throw ApplicationInitializationError.applicationExecutableNotReadable(checkedPath: appURL.path)
}
guard let appBundle: Bundle = .init(url: appURL)
else
{
throw ApplicationInitializationError.couldNotReadBundle(applicationPath: appURL.absoluteString)
}
AppConstants.shared.logger.debug("Will try to initialize and App object form bundle \(appBundle)")
guard let appBundleInfoDictionary: [String: Any] = appBundle.infoDictionary
else
{
throw ApplicationInitializationError.couldNotGetInfoDictionary
}
guard let appName: String = Application.getAppName(fromInfoDictionary: appBundleInfoDictionary) else
{
throw ApplicationInitializationError.couldNotGetMandatoryAppInformation(.name)
}
self.name = appName
self.url = appURL
self.iconPath = Application.getAppIconPath(fromInfoDictionary: appBundleInfoDictionary, appBundle: appBundle)
if let iconPath = self.iconPath
{
self.iconImage = .init(
nsImage: .init(byReferencing: iconPath)
)
}
else
{
self.iconImage = nil
}
}
catch let applicationDirectoryAccessError
{
throw .couldNotAccessApplicationExecutable(error: applicationDirectoryAccessError)
}
}
private static func getAppName(fromInfoDictionary infoDictionary: [String: Any]) -> String?
{
return infoDictionary["CFBundleName"] as? String
}
private static func getAppIconPath(fromInfoDictionary infoDictionary: [String: Any], appBundle: Bundle) -> URL?
{
guard let iconFileName: String = infoDictionary["CFBundleIconFile"] as? String
else
{
return nil
}
return appBundle.resourceURL?.appendingPathComponent(iconFileName, conformingTo: .icns)
}
}

View File

@ -1,14 +0,0 @@
//
// Corrupted Package.swift
// Cork
//
// Created by David Bureš on 28.03.2024.
//
import Foundation
struct CorruptedPackage: Identifiable, Equatable
{
let id: UUID = .init()
let name: String
}

View File

@ -1,20 +0,0 @@
//
// Terminal Output.swift
// Cork
//
// Created by David Bureš on 12.02.2023.
//
import Foundation
struct TerminalOutput
{
var standardOutput: String
var standardError: String
}
enum StreamedTerminalOutput
{
case standardOutput(String)
case standardError(String)
}

View File

@ -6,6 +6,7 @@
//
import SwiftUI
import ApplicationInspector
/// Show the icon of a linked app
struct AppIconDisplay: View

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright ©. All rights reserved.</string>
</dict>
</plist>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright ©. All rights reserved.</string>
</dict>
</plist>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright ©. All rights reserved.</string>
</dict>
</plist>

View File

@ -0,0 +1,153 @@
// swiftlint:disable:this file_name
// swiftlint:disable all
// swift-format-ignore-file
// swiftformat:disable all
// Generated using tuist https://github.com/tuist/tuist
#if os(macOS)
import AppKit
#elseif os(iOS)
import UIKit
#elseif os(tvOS) || os(watchOS)
import UIKit
#endif
#if canImport(SwiftUI)
import SwiftUI
#endif
// MARK: - Asset Catalogs
public enum CorkPackagesLogicAsset: Sendable {
public enum Assets {
public static let accentColor = CorkPackagesLogicColors(name: "AccentColor")
public static let customAppleTerminalBadgeMagnifyingglass = CorkPackagesLogicImages(name: "custom.apple.terminal.badge.magnifyingglass")
public static let customBrainSlash = CorkPackagesLogicImages(name: "custom.brain.slash")
public static let customMacwindowBadgeMagnifyingglass = CorkPackagesLogicImages(name: "custom.macwindow.badge.magnifyingglass")
public static let customMacwindowBadgeXmark = CorkPackagesLogicImages(name: "custom.macwindow.badge.xmark")
public static let customPinFillQuestionmark = CorkPackagesLogicImages(name: "custom.pin.fill.questionmark")
public static let customShippingbox2BadgeArrowDown = CorkPackagesLogicImages(name: "custom.shippingbox.2.badge.arrow.down")
public static let customShippingboxBadgeMagnifyingglass = CorkPackagesLogicImages(name: "custom.shippingbox.badge.magnifyingglass")
public static let customShippingboxBadgePlus = CorkPackagesLogicImages(name: "custom.shippingbox.badge.plus")
public static let customSparklesSlash = CorkPackagesLogicImages(name: "custom.sparkles.slash")
public static let customSpigotBadgePlus = CorkPackagesLogicImages(name: "custom.spigot.badge.plus")
public static let customSpigotBadgeXmark = CorkPackagesLogicImages(name: "custom.spigot.badge.xmark")
public static let customSquareStackBadgePause = CorkPackagesLogicImages(name: "custom.square.stack.badge.pause")
public static let customSquareStackBadgePlay = CorkPackagesLogicImages(name: "custom.square.stack.badge.play")
public static let customSquareStackBadgeQuestionmark = CorkPackagesLogicImages(name: "custom.square.stack.badge.questionmark")
public static let customSquareStackTrianglebadgeExclamationmark = CorkPackagesLogicImages(name: "custom.square.stack.trianglebadge.exclamationmark")
public static let customTerminalBadgeXmark = CorkPackagesLogicImages(name: "custom.terminal.badge.xmark")
public static let customTrashTriangleFill = CorkPackagesLogicImages(name: "custom.trash.triangle.fill")
}
public enum PreviewAssets {
}
}
// MARK: - Implementation Details
public final class CorkPackagesLogicColors: Sendable {
public let name: String
#if os(macOS)
public typealias Color = NSColor
#elseif os(iOS) || os(tvOS) || os(watchOS) || os(visionOS)
public typealias Color = UIColor
#endif
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, visionOS 1.0, *)
public var color: Color {
guard let color = Color(asset: self) else {
fatalError("Unable to load color asset named \(name).")
}
return color
}
#if canImport(SwiftUI)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, visionOS 1.0, *)
public var swiftUIColor: SwiftUI.Color {
return SwiftUI.Color(asset: self)
}
#endif
fileprivate init(name: String) {
self.name = name
}
}
public extension CorkPackagesLogicColors.Color {
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, visionOS 1.0, *)
convenience init?(asset: CorkPackagesLogicColors) {
let bundle = Bundle.module
#if os(iOS) || os(tvOS) || os(visionOS)
self.init(named: asset.name, in: bundle, compatibleWith: nil)
#elseif os(macOS)
self.init(named: NSColor.Name(asset.name), bundle: bundle)
#elseif os(watchOS)
self.init(named: asset.name)
#endif
}
}
#if canImport(SwiftUI)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, visionOS 1.0, *)
public extension SwiftUI.Color {
init(asset: CorkPackagesLogicColors) {
let bundle = Bundle.module
self.init(asset.name, bundle: bundle)
}
}
#endif
public struct CorkPackagesLogicImages: Sendable {
public let name: String
#if os(macOS)
public typealias Image = NSImage
#elseif os(iOS) || os(tvOS) || os(watchOS) || os(visionOS)
public typealias Image = UIImage
#endif
public var image: Image {
let bundle = Bundle.module
#if os(iOS) || os(tvOS) || os(visionOS)
let image = Image(named: name, in: bundle, compatibleWith: nil)
#elseif os(macOS)
let image = bundle.image(forResource: NSImage.Name(name))
#elseif os(watchOS)
let image = Image(named: name)
#endif
guard let result = image else {
fatalError("Unable to load image asset named \(name).")
}
return result
}
#if canImport(SwiftUI)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, visionOS 1.0, *)
public var swiftUIImage: SwiftUI.Image {
SwiftUI.Image(asset: self)
}
#endif
}
#if canImport(SwiftUI)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, visionOS 1.0, *)
public extension SwiftUI.Image {
init(asset: CorkPackagesLogicImages) {
let bundle = Bundle.module
self.init(asset.name, bundle: bundle)
}
init(asset: CorkPackagesLogicImages, label: Text) {
let bundle = Bundle.module
self.init(asset.name, bundle: bundle, label: label)
}
init(decorative asset: CorkPackagesLogicImages) {
let bundle = Bundle.module
self.init(decorative: asset.name, bundle: bundle)
}
}
#endif
// swiftformat:enable all
// swiftlint:enable all

View File

@ -0,0 +1,153 @@
// swiftlint:disable:this file_name
// swiftlint:disable all
// swift-format-ignore-file
// swiftformat:disable all
// Generated using tuist https://github.com/tuist/tuist
#if os(macOS)
import AppKit
#elseif os(iOS)
import UIKit
#elseif os(tvOS) || os(watchOS)
import UIKit
#endif
#if canImport(SwiftUI)
import SwiftUI
#endif
// MARK: - Asset Catalogs
public enum CorkPackagesModelsAsset: Sendable {
public enum Assets {
public static let accentColor = CorkPackagesModelsColors(name: "AccentColor")
public static let customAppleTerminalBadgeMagnifyingglass = CorkPackagesModelsImages(name: "custom.apple.terminal.badge.magnifyingglass")
public static let customBrainSlash = CorkPackagesModelsImages(name: "custom.brain.slash")
public static let customMacwindowBadgeMagnifyingglass = CorkPackagesModelsImages(name: "custom.macwindow.badge.magnifyingglass")
public static let customMacwindowBadgeXmark = CorkPackagesModelsImages(name: "custom.macwindow.badge.xmark")
public static let customPinFillQuestionmark = CorkPackagesModelsImages(name: "custom.pin.fill.questionmark")
public static let customShippingbox2BadgeArrowDown = CorkPackagesModelsImages(name: "custom.shippingbox.2.badge.arrow.down")
public static let customShippingboxBadgeMagnifyingglass = CorkPackagesModelsImages(name: "custom.shippingbox.badge.magnifyingglass")
public static let customShippingboxBadgePlus = CorkPackagesModelsImages(name: "custom.shippingbox.badge.plus")
public static let customSparklesSlash = CorkPackagesModelsImages(name: "custom.sparkles.slash")
public static let customSpigotBadgePlus = CorkPackagesModelsImages(name: "custom.spigot.badge.plus")
public static let customSpigotBadgeXmark = CorkPackagesModelsImages(name: "custom.spigot.badge.xmark")
public static let customSquareStackBadgePause = CorkPackagesModelsImages(name: "custom.square.stack.badge.pause")
public static let customSquareStackBadgePlay = CorkPackagesModelsImages(name: "custom.square.stack.badge.play")
public static let customSquareStackBadgeQuestionmark = CorkPackagesModelsImages(name: "custom.square.stack.badge.questionmark")
public static let customSquareStackTrianglebadgeExclamationmark = CorkPackagesModelsImages(name: "custom.square.stack.trianglebadge.exclamationmark")
public static let customTerminalBadgeXmark = CorkPackagesModelsImages(name: "custom.terminal.badge.xmark")
public static let customTrashTriangleFill = CorkPackagesModelsImages(name: "custom.trash.triangle.fill")
}
public enum PreviewAssets {
}
}
// MARK: - Implementation Details
public final class CorkPackagesModelsColors: Sendable {
public let name: String
#if os(macOS)
public typealias Color = NSColor
#elseif os(iOS) || os(tvOS) || os(watchOS) || os(visionOS)
public typealias Color = UIColor
#endif
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, visionOS 1.0, *)
public var color: Color {
guard let color = Color(asset: self) else {
fatalError("Unable to load color asset named \(name).")
}
return color
}
#if canImport(SwiftUI)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, visionOS 1.0, *)
public var swiftUIColor: SwiftUI.Color {
return SwiftUI.Color(asset: self)
}
#endif
fileprivate init(name: String) {
self.name = name
}
}
public extension CorkPackagesModelsColors.Color {
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, visionOS 1.0, *)
convenience init?(asset: CorkPackagesModelsColors) {
let bundle = Bundle.module
#if os(iOS) || os(tvOS) || os(visionOS)
self.init(named: asset.name, in: bundle, compatibleWith: nil)
#elseif os(macOS)
self.init(named: NSColor.Name(asset.name), bundle: bundle)
#elseif os(watchOS)
self.init(named: asset.name)
#endif
}
}
#if canImport(SwiftUI)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, visionOS 1.0, *)
public extension SwiftUI.Color {
init(asset: CorkPackagesModelsColors) {
let bundle = Bundle.module
self.init(asset.name, bundle: bundle)
}
}
#endif
public struct CorkPackagesModelsImages: Sendable {
public let name: String
#if os(macOS)
public typealias Image = NSImage
#elseif os(iOS) || os(tvOS) || os(watchOS) || os(visionOS)
public typealias Image = UIImage
#endif
public var image: Image {
let bundle = Bundle.module
#if os(iOS) || os(tvOS) || os(visionOS)
let image = Image(named: name, in: bundle, compatibleWith: nil)
#elseif os(macOS)
let image = bundle.image(forResource: NSImage.Name(name))
#elseif os(watchOS)
let image = Image(named: name)
#endif
guard let result = image else {
fatalError("Unable to load image asset named \(name).")
}
return result
}
#if canImport(SwiftUI)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, visionOS 1.0, *)
public var swiftUIImage: SwiftUI.Image {
SwiftUI.Image(asset: self)
}
#endif
}
#if canImport(SwiftUI)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, visionOS 1.0, *)
public extension SwiftUI.Image {
init(asset: CorkPackagesModelsImages) {
let bundle = Bundle.module
self.init(asset.name, bundle: bundle)
}
init(asset: CorkPackagesModelsImages, label: Text) {
let bundle = Bundle.module
self.init(asset.name, bundle: bundle, label: label)
}
init(decorative asset: CorkPackagesModelsImages) {
let bundle = Bundle.module
self.init(decorative: asset.name, bundle: bundle)
}
}
#endif
// swiftformat:enable all
// swiftlint:enable all

View File

@ -0,0 +1,153 @@
// swiftlint:disable:this file_name
// swiftlint:disable all
// swift-format-ignore-file
// swiftformat:disable all
// Generated using tuist https://github.com/tuist/tuist
#if os(macOS)
import AppKit
#elseif os(iOS)
import UIKit
#elseif os(tvOS) || os(watchOS)
import UIKit
#endif
#if canImport(SwiftUI)
import SwiftUI
#endif
// MARK: - Asset Catalogs
public enum CorkSharedAsset: Sendable {
public enum Assets {
public static let accentColor = CorkSharedColors(name: "AccentColor")
public static let customAppleTerminalBadgeMagnifyingglass = CorkSharedImages(name: "custom.apple.terminal.badge.magnifyingglass")
public static let customBrainSlash = CorkSharedImages(name: "custom.brain.slash")
public static let customMacwindowBadgeMagnifyingglass = CorkSharedImages(name: "custom.macwindow.badge.magnifyingglass")
public static let customMacwindowBadgeXmark = CorkSharedImages(name: "custom.macwindow.badge.xmark")
public static let customPinFillQuestionmark = CorkSharedImages(name: "custom.pin.fill.questionmark")
public static let customShippingbox2BadgeArrowDown = CorkSharedImages(name: "custom.shippingbox.2.badge.arrow.down")
public static let customShippingboxBadgeMagnifyingglass = CorkSharedImages(name: "custom.shippingbox.badge.magnifyingglass")
public static let customShippingboxBadgePlus = CorkSharedImages(name: "custom.shippingbox.badge.plus")
public static let customSparklesSlash = CorkSharedImages(name: "custom.sparkles.slash")
public static let customSpigotBadgePlus = CorkSharedImages(name: "custom.spigot.badge.plus")
public static let customSpigotBadgeXmark = CorkSharedImages(name: "custom.spigot.badge.xmark")
public static let customSquareStackBadgePause = CorkSharedImages(name: "custom.square.stack.badge.pause")
public static let customSquareStackBadgePlay = CorkSharedImages(name: "custom.square.stack.badge.play")
public static let customSquareStackBadgeQuestionmark = CorkSharedImages(name: "custom.square.stack.badge.questionmark")
public static let customSquareStackTrianglebadgeExclamationmark = CorkSharedImages(name: "custom.square.stack.trianglebadge.exclamationmark")
public static let customTerminalBadgeXmark = CorkSharedImages(name: "custom.terminal.badge.xmark")
public static let customTrashTriangleFill = CorkSharedImages(name: "custom.trash.triangle.fill")
}
public enum PreviewAssets {
}
}
// MARK: - Implementation Details
public final class CorkSharedColors: Sendable {
public let name: String
#if os(macOS)
public typealias Color = NSColor
#elseif os(iOS) || os(tvOS) || os(watchOS) || os(visionOS)
public typealias Color = UIColor
#endif
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, visionOS 1.0, *)
public var color: Color {
guard let color = Color(asset: self) else {
fatalError("Unable to load color asset named \(name).")
}
return color
}
#if canImport(SwiftUI)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, visionOS 1.0, *)
public var swiftUIColor: SwiftUI.Color {
return SwiftUI.Color(asset: self)
}
#endif
fileprivate init(name: String) {
self.name = name
}
}
public extension CorkSharedColors.Color {
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, visionOS 1.0, *)
convenience init?(asset: CorkSharedColors) {
let bundle = Bundle.module
#if os(iOS) || os(tvOS) || os(visionOS)
self.init(named: asset.name, in: bundle, compatibleWith: nil)
#elseif os(macOS)
self.init(named: NSColor.Name(asset.name), bundle: bundle)
#elseif os(watchOS)
self.init(named: asset.name)
#endif
}
}
#if canImport(SwiftUI)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, visionOS 1.0, *)
public extension SwiftUI.Color {
init(asset: CorkSharedColors) {
let bundle = Bundle.module
self.init(asset.name, bundle: bundle)
}
}
#endif
public struct CorkSharedImages: Sendable {
public let name: String
#if os(macOS)
public typealias Image = NSImage
#elseif os(iOS) || os(tvOS) || os(watchOS) || os(visionOS)
public typealias Image = UIImage
#endif
public var image: Image {
let bundle = Bundle.module
#if os(iOS) || os(tvOS) || os(visionOS)
let image = Image(named: name, in: bundle, compatibleWith: nil)
#elseif os(macOS)
let image = bundle.image(forResource: NSImage.Name(name))
#elseif os(watchOS)
let image = Image(named: name)
#endif
guard let result = image else {
fatalError("Unable to load image asset named \(name).")
}
return result
}
#if canImport(SwiftUI)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, visionOS 1.0, *)
public var swiftUIImage: SwiftUI.Image {
SwiftUI.Image(asset: self)
}
#endif
}
#if canImport(SwiftUI)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, visionOS 1.0, *)
public extension SwiftUI.Image {
init(asset: CorkSharedImages) {
let bundle = Bundle.module
self.init(asset.name, bundle: bundle)
}
init(asset: CorkSharedImages, label: Text) {
let bundle = Bundle.module
self.init(asset.name, bundle: bundle, label: label)
}
init(decorative asset: CorkSharedImages) {
let bundle = Bundle.module
self.init(decorative: asset.name, bundle: bundle)
}
}
#endif
// swiftformat:enable all
// swiftlint:enable all

View File

@ -0,0 +1,64 @@
// periphery:ignore:all
// swiftlint:disable:this file_name
// swiftlint:disable all
// swift-format-ignore-file
// swiftformat:disable all
#if hasFeature(InternalImportsByDefault)
public import Foundation
#else
import Foundation
#endif
// MARK: - Swift Bundle Accessor - for SPM
private class BundleFinder {}
extension Foundation.Bundle {
/// Since CorkPackagesLogic is a static library, the bundle containing the resources is copied into the final product.
static let module: Bundle = {
let bundleName = "Cork_CorkPackagesLogic"
let bundleFinderResourceURL = Bundle(for: BundleFinder.self).resourceURL
var candidates = [
Bundle.main.resourceURL,
bundleFinderResourceURL,
Bundle.main.bundleURL,
]
// This is a fix to make Previews work with bundled resources.
// Logic here is taken from SPM's generated `resource_bundle_accessors.swift` file,
// which is located under the derived data directory after building the project.
if let override = ProcessInfo.processInfo.environment["PACKAGE_RESOURCE_BUNDLE_PATH"] {
candidates.append(URL(fileURLWithPath: override))
// Deleting derived data and not rebuilding the frameworks containing resources may result in a state
// where the bundles are only available in the framework's directory that is actively being previewed.
// Since we don't know which framework this is, we also need to look in all the framework subpaths.
if let subpaths = try? Foundation.FileManager.default.contentsOfDirectory(atPath: override) {
for subpath in subpaths {
if subpath.hasSuffix(".framework") {
candidates.append(URL(fileURLWithPath: override + "/" + subpath))
}
}
}
}
// This is a fix to make unit tests work with bundled resources.
// Making this change allows unit tests to search one directory up for a bundle.
// More context can be found in this PR: https://github.com/tuist/tuist/pull/6895
#if canImport(XCTest)
candidates.append(bundleFinderResourceURL?.appendingPathComponent(".."))
#endif
for candidate in candidates {
let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle")
if let bundle = bundlePath.flatMap(Bundle.init(url:)) {
return bundle
}
}
fatalError("unable to find bundle named Cork_CorkPackagesLogic")
}()
}
// MARK: - Objective-C Bundle Accessor
@objc
public class CorkPackagesLogicResources: NSObject {
@objc public class var bundle: Bundle {
return .module
}
}
// swiftformat:enable all
// swiftlint:enable all

View File

@ -0,0 +1,64 @@
// periphery:ignore:all
// swiftlint:disable:this file_name
// swiftlint:disable all
// swift-format-ignore-file
// swiftformat:disable all
#if hasFeature(InternalImportsByDefault)
public import Foundation
#else
import Foundation
#endif
// MARK: - Swift Bundle Accessor - for SPM
private class BundleFinder {}
extension Foundation.Bundle {
/// Since CorkPackagesModels is a static library, the bundle containing the resources is copied into the final product.
static let module: Bundle = {
let bundleName = "Cork_CorkPackagesModels"
let bundleFinderResourceURL = Bundle(for: BundleFinder.self).resourceURL
var candidates = [
Bundle.main.resourceURL,
bundleFinderResourceURL,
Bundle.main.bundleURL,
]
// This is a fix to make Previews work with bundled resources.
// Logic here is taken from SPM's generated `resource_bundle_accessors.swift` file,
// which is located under the derived data directory after building the project.
if let override = ProcessInfo.processInfo.environment["PACKAGE_RESOURCE_BUNDLE_PATH"] {
candidates.append(URL(fileURLWithPath: override))
// Deleting derived data and not rebuilding the frameworks containing resources may result in a state
// where the bundles are only available in the framework's directory that is actively being previewed.
// Since we don't know which framework this is, we also need to look in all the framework subpaths.
if let subpaths = try? Foundation.FileManager.default.contentsOfDirectory(atPath: override) {
for subpath in subpaths {
if subpath.hasSuffix(".framework") {
candidates.append(URL(fileURLWithPath: override + "/" + subpath))
}
}
}
}
// This is a fix to make unit tests work with bundled resources.
// Making this change allows unit tests to search one directory up for a bundle.
// More context can be found in this PR: https://github.com/tuist/tuist/pull/6895
#if canImport(XCTest)
candidates.append(bundleFinderResourceURL?.appendingPathComponent(".."))
#endif
for candidate in candidates {
let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle")
if let bundle = bundlePath.flatMap(Bundle.init(url:)) {
return bundle
}
}
fatalError("unable to find bundle named Cork_CorkPackagesModels")
}()
}
// MARK: - Objective-C Bundle Accessor
@objc
public class CorkPackagesModelsResources: NSObject {
@objc public class var bundle: Bundle {
return .module
}
}
// swiftformat:enable all
// swiftlint:enable all

View File

@ -0,0 +1,64 @@
// periphery:ignore:all
// swiftlint:disable:this file_name
// swiftlint:disable all
// swift-format-ignore-file
// swiftformat:disable all
#if hasFeature(InternalImportsByDefault)
public import Foundation
#else
import Foundation
#endif
// MARK: - Swift Bundle Accessor - for SPM
private class BundleFinder {}
extension Foundation.Bundle {
/// Since CorkShared is a static library, the bundle containing the resources is copied into the final product.
static let module: Bundle = {
let bundleName = "Cork_CorkShared"
let bundleFinderResourceURL = Bundle(for: BundleFinder.self).resourceURL
var candidates = [
Bundle.main.resourceURL,
bundleFinderResourceURL,
Bundle.main.bundleURL,
]
// This is a fix to make Previews work with bundled resources.
// Logic here is taken from SPM's generated `resource_bundle_accessors.swift` file,
// which is located under the derived data directory after building the project.
if let override = ProcessInfo.processInfo.environment["PACKAGE_RESOURCE_BUNDLE_PATH"] {
candidates.append(URL(fileURLWithPath: override))
// Deleting derived data and not rebuilding the frameworks containing resources may result in a state
// where the bundles are only available in the framework's directory that is actively being previewed.
// Since we don't know which framework this is, we also need to look in all the framework subpaths.
if let subpaths = try? Foundation.FileManager.default.contentsOfDirectory(atPath: override) {
for subpath in subpaths {
if subpath.hasSuffix(".framework") {
candidates.append(URL(fileURLWithPath: override + "/" + subpath))
}
}
}
}
// This is a fix to make unit tests work with bundled resources.
// Making this change allows unit tests to search one directory up for a bundle.
// More context can be found in this PR: https://github.com/tuist/tuist/pull/6895
#if canImport(XCTest)
candidates.append(bundleFinderResourceURL?.appendingPathComponent(".."))
#endif
for candidate in candidates {
let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle")
if let bundle = bundlePath.flatMap(Bundle.init(url:)) {
return bundle
}
}
fatalError("unable to find bundle named Cork_CorkShared")
}()
}
// MARK: - Objective-C Bundle Accessor
@objc
public class CorkSharedResources: NSObject {
@objc public class var bundle: Bundle {
return .module
}
}
// swiftformat:enable all
// swiftlint:enable all

View File

View File

@ -1,14 +1,14 @@
//
// Check if Package Was Installed Intentionally.swift
// Check if Package was Installed Intentionally.swift
// Cork
//
// Created by David Bureš on 13.11.2024.
// Created by David Bureš - P on 28.10.2025.
//
import Foundation
import CorkShared
enum IntentionalInstallationDiscoveryError: Error, Hashable
public enum IntentionalInstallationDiscoveryError: Error, Hashable
{
/// The function could not determine the most relevant version of the package to read the recepit from
case failedToDetermineMostRelevantVersion(packageURL: URL)
@ -26,7 +26,7 @@ enum IntentionalInstallationDiscoveryError: Error, Hashable
case unexpectedFolderName(packageURL: URL)
}
extension URL
public extension URL
{
/// This function checks whether the package was installed intentionally.
/// - For Formulae, this info gets read from the install receipt

View File

@ -2,7 +2,7 @@
// App State.swift
// Cork
//
// Created by David Bureš on 05.02.2023.
// Created by David Bureš - P on 28.10.2025.
//
import AppKit
@ -14,9 +14,19 @@ import Observation
/// Class that holds the global state of the app, excluding services
@Observable @MainActor
final class AppState
public final class AppState
{
// MARK: - Licensing
public enum LicensingState
{
case notBoughtOrHasNotActivatedDemo
case demo
case bought
case selfCompiled
}
var licensingState: LicensingState = .notBoughtOrHasNotActivatedDemo

View File

@ -8,7 +8,7 @@
import Foundation
import SwiftUI
enum DisplayableAlert: LocalizedError
public enum DisplayableAlert: LocalizedError
{
case couldNotGetContentsOfPackageFolder(String), couldNotLoadAnyPackages(LocalizedError), couldNotLoadCertainPackage(String, URL, failureReason: String)
case licenseCheckingFailedDueToAuthorizationComplexNotBeingEncodedProperly, licenseCheckingFailedDueToNoInternet, licenseCheckingFailedDueToTimeout, licenseCheckingFailedForOtherReason(localizedDescription: String)

View File

@ -7,7 +7,7 @@
import Foundation
extension DisplayableAlert
public extension DisplayableAlert
{
/// The bold text at the top of the error
var errorDescription: String?

View File

@ -7,7 +7,7 @@
import Foundation
extension DisplayableAlert
public extension DisplayableAlert
{
/// Message in the alert
var recoverySuggestion: String?

View File

@ -0,0 +1,13 @@
//
// Brewfile import Stage.swift
// Cork
//
// Created by David Bureš - P on 28.10.2025.
//
import Foundation
public enum BrewfileImportStage
{
case importing, finished
}

View File

@ -1,25 +1,25 @@
//
// Confirmation Dialog - Main Window.swift
// Confirmation Dialog.swift
// Cork
//
// Created by David Bureš - P on 21.04.2025.
// Created by David Bureš - P on 28.10.2025.
//
import Foundation
import SwiftUI
enum ConfirmationDialog: Identifiable, Equatable
public enum ConfirmationDialog: Identifiable, Equatable
{
case uninstallPackage(_ packageToUninstall: BrewPackage)
case purgePackage(_ packageToPurge: BrewPackage)
var id: UUID
public var id: UUID
{
return .init()
}
var title: LocalizedStringKey
public var title: LocalizedStringKey
{
switch self
{

View File

@ -1,13 +1,13 @@
//
// Sheets - Main Window.swift
// Displayable Sheet.swift
// Cork
//
// Created by David Bureš - P on 19.01.2025.
// Created by David Bureš - P on 28.10.2025.
//
import Foundation
enum DisplayableSheet: Identifiable, Equatable
public enum DisplayableSheet: Identifiable, Equatable
{
case packageInstallation
@ -20,13 +20,13 @@ enum DisplayableSheet: Identifiable, Equatable
case corruptedPackageFix(corruptedPackage: CorruptedPackage)
case sudoRequiredForPackageRemoval
case sudoRequiredForPackageRemoval
case maintenance(fastCacheDeletion: Bool)
case brewfileExport, brewfileImport
var id: UUID
public var id: UUID
{
return .init()
}

View File

@ -0,0 +1,77 @@
//
// Package Loading Error.swift
// CorkPackagesModels
//
// Created by David Bureš - P on 28.10.2025.
//
import Foundation
import CorkPackagesLogic
public extension BrewPackage
{
/// Error representing failures while loading
enum PackageLoadingError: LocalizedError, Hashable, Identifiable
{
/// Tried to treat the folder `Cellar` or `Caskroom` itself as a package - means Homebrew itself is broken
case triedToThreatFolderContainingPackagesAsPackage(packageType: BrewPackage.PackageType)
/// The `Cellar` and `Caskroom` folder itself couldn't be loaded
case couldNotReadContentsOfParentFolder(failureReason: String, folderURL: URL)
/// Failed while trying to read contents of package folder
case failedWhileReadingContentsOfPackageFolder(folderURL: URL, reportedError: String)
case failedWhileTryingToDetermineIntentionalInstallation(
folderURL: URL,
associatedIntentionalDiscoveryError: IntentionalInstallationDiscoveryError
)
/// The package root folder exists, but the package itself doesn't have any versions
case packageDoesNotHaveAnyVersionsInstalled(packageURL: URL)
/// A folder that should have contained the package is not actually a folder
case packageIsNotAFolder(String, packageURL: URL)
/// The number of loaded packages does not match the number of package parent folders
case numberOLoadedPackagesDosNotMatchNumberOfPackageFolders
public var errorDescription: String?
{
switch self
{
case .couldNotReadContentsOfParentFolder(let failureReason, _):
return String(localized: "error.package-loading.could-not-read-contents-of-parent-folder.\(failureReason)")
case .triedToThreatFolderContainingPackagesAsPackage(let packageType):
switch packageType
{
case .formula:
return "error.package-loading.last-path-component-of-checked-package-url-is-folder-containing-packages-itself.formulae"
case .cask:
return "error.package-loading.last-path-component-of-checked-package-url-is-folder-containing-packages-itself.casks"
}
case .failedWhileReadingContentsOfPackageFolder(let folderURL, let reportedError):
return String(localized: "error.package-loading.could-not-load-\(folderURL.packageNameFromURL())-at-\(folderURL.absoluteString)-because-\(reportedError)", comment: "Couldn't load package (package name) at (package URL) because (failure reason)")
case .failedWhileTryingToDetermineIntentionalInstallation(_, let associatedIntentionalDiscoveryError):
return associatedIntentionalDiscoveryError.localizedDescription
case .packageDoesNotHaveAnyVersionsInstalled(let packageURL):
return String(localized: "error.package-loading.\(packageURL.packageNameFromURL())-does-not-have-any-versions-installed")
case .packageIsNotAFolder(let string, _):
return String(localized: "error.package-loading.\(string)-not-a-folder", comment: "Package folder in this context means a folder that encloses package versions. Every package has its own folder, and this error occurs when the provided URL does not point to a folder that encloses package versions")
case .numberOLoadedPackagesDosNotMatchNumberOfPackageFolders:
return String(localized: "error.package-loading.number-of-loaded-poackages-does-not-match-number-of-package-folders", comment: "This error occurs when there's a mismatch between the number of loaded packages, and the number of package folders in the package folders")
}
}
public var id: UUID
{
return UUID()
}
}
}

View File

@ -0,0 +1,65 @@
//
// Adoptable App.swift
// Cork
//
// Created by David Bureš - P on 28.10.2025.
//
import Foundation
import ApplicationInspector
public extension BrewPackagesTracker
{
/// A struct for holding a Cask's name and its executable
struct AdoptableApp: Identifiable, Hashable, Sendable
{
public let id: UUID = .init()
let caskName: String
let appExecutable: String
let description: String?
let fullAppUrl: URL
var isMarkedForAdoption: Bool
var app: Application?
init(caskName: String, description: String?, appExecutable: String)
{
self.caskName = caskName
self.appExecutable = appExecutable
self.description = description
self.fullAppUrl = URL.applicationDirectory.appendingPathComponent(appExecutable, conformingTo: .application)
self.isMarkedForAdoption = true
}
mutating func changeMarkedState()
{
self.isMarkedForAdoption.toggle()
}
func constructAppBundle() async -> Application?
{
return try? .init(from: self.fullAppUrl)
}
func excludeSelf() async
{
let excludedAppRepresentation: BrewPackagesTracker.ExcludedAdoptableApp = .init(fromAdoptableApp: self)
await excludedAppRepresentation.saveSelfToDatabase()
}
func includeSelf() async
{
let excludedAppRepresentation: BrewPackagesTracker.ExcludedAdoptableApp = .init(fromAdoptableApp: self)
await excludedAppRepresentation.deleteSelfFromDatabase()
}
}
}

View File

@ -0,0 +1,58 @@
//
// Excluded Adptable App.swift
// CorkPackagesModels
//
// Created by David Bureš - P on 28.10.2025.
//
import Foundation
import SwiftData
import CorkShared
public extension BrewPackagesTracker
{
@Model
final class ExcludedAdoptableApp
{
@Attribute(.unique) @Attribute(.spotlight)
var appExecutable: String
init(appExecutable: String)
{
self.appExecutable = appExecutable
}
init(fromAdoptableApp app: BrewPackagesTracker.AdoptableApp)
{
self.appExecutable = app.appExecutable
}
@MainActor
public func saveSelfToDatabase()
{
AppConstants.shared.modelContainer.mainContext.insert(self)
}
@MainActor
public func deleteSelfFromDatabase()
{
let modelContext: ModelContext = AppConstants.shared.modelContainer.mainContext
do
{
let descriptor = FetchDescriptor<ExcludedAdoptableApp>(
predicate: #Predicate { $0.appExecutable == appExecutable }
)
if let existingPackage = try modelContext.fetch(descriptor).first
{
modelContext.delete(existingPackage)
}
}
catch
{
AppConstants.shared.logger.error("Failed to fetch excluded adoptable app for deletion: \(error.localizedDescription)")
}
}
}
}

View File

@ -0,0 +1,8 @@
//
// Application.swift
// Cork
//
// Created by David Bureš - P on 28.10.2025.
//
import Foundation

View File

@ -13,11 +13,17 @@ import SwiftData
import Charts
import AppIntents
import SwiftUI
import CorkTerminalFunctions
import CorkPackagesLogic
/// A representation of the loaded ``BrewPackage``s
/// Includes packages that were loaded properly, along those whose loading failed
typealias BrewPackages = Set<Result<BrewPackage, BrewPackage.PackageLoadingError>>
/// A representation of a Homebrew package
struct BrewPackage: Identifiable, Equatable, Hashable, Codable
public struct BrewPackage: Identifiable, Equatable, Hashable, Codable, Sendable
{
var id: UUID = .init()
public var id: UUID = .init()
let name: String
let type: PackageType
@ -44,13 +50,13 @@ struct BrewPackage: Identifiable, Equatable, Hashable, Codable
return versions.formatted(.list(type: .and))
}
enum PackageType: String, CustomStringConvertible, Plottable, AppEntity, Codable
public enum PackageType: String, CustomStringConvertible, Plottable, AppEntity, Codable
{
case formula
case cask
/// User-readable description of the package type
var description: String
public var description: String
{
switch self
{
@ -62,7 +68,7 @@ struct BrewPackage: Identifiable, Equatable, Hashable, Codable
}
/// Localization keys for description of the package type
var localizableDescription: LocalizedStringKey
public var localizableDescription: LocalizedStringKey
{
switch self
{
@ -97,9 +103,9 @@ struct BrewPackage: Identifiable, Equatable, Hashable, Codable
}
}
static let typeDisplayRepresentation: TypeDisplayRepresentation = .init(name: "package-details.type")
public static let typeDisplayRepresentation: TypeDisplayRepresentation = .init(name: "package-details.type")
var displayRepresentation: DisplayRepresentation
public var displayRepresentation: DisplayRepresentation
{
switch self
{

View File

@ -2,14 +2,14 @@
// Brew Tap.swift
// Cork
//
// Created by David Bureš on 10.02.2023.
// Created by David Bureš - P on 28.10.2025.
//
import Foundation
struct BrewTap: Identifiable, Hashable
public struct BrewTap: Identifiable, Hashable
{
let id: UUID = .init()
public let id: UUID = .init()
let name: String
var isBeingModified: Bool = false

View File

@ -0,0 +1,14 @@
//
// Corrupted Package.swift
// Cork
//
// Created by David Bureš - P on 28.10.2025.
//
import Foundation
public struct CorruptedPackage: Identifiable, Equatable
{
public let id: UUID = .init()
let name: String
}

View File

@ -2,7 +2,7 @@
// Minimal Homebrew Package.swift
// Cork
//
// Created by David Bureš on 25.05.2024.
// Created by David Bureš - P on 28.10.2025.
//
import AppIntents

View File

@ -2,12 +2,12 @@
// Outdated Package.swift
// Cork
//
// Created by David Bureš on 05.04.2023.
// Created by David Bureš - P on 28.10.2025.
//
import Foundation
struct OutdatedPackage: Identifiable, Equatable, Hashable
public struct OutdatedPackage: Identifiable, Equatable, Hashable
{
enum PackageUpdatingType
{
@ -29,7 +29,7 @@ struct OutdatedPackage: Identifiable, Equatable, Hashable
}
}
let id: UUID = .init()
public let id: UUID = .init()
let package: BrewPackage
@ -39,13 +39,14 @@ struct OutdatedPackage: Identifiable, Equatable, Hashable
var isMarkedForUpdating: Bool = true
var updatingManagedBy: PackageUpdatingType
static func == (lhs: OutdatedPackage, rhs: OutdatedPackage) -> Bool
public static func == (lhs: OutdatedPackage, rhs: OutdatedPackage) -> Bool
{
return lhs.package.name == rhs.package.name
}
func hash(into hasher: inout Hasher) {
public func hash(into hasher: inout Hasher)
{
hasher.combine(package.name)
}
}

View File

@ -1,14 +1,15 @@
//
// Get Names of Pinned Packages.swift
// Cork
// CorkPackagesModels
//
// Created by David Bureš - P on 12.06.2025.
// Created by David Bureš - P on 28.10.2025.
//
import Foundation
import CorkShared
import CorkTerminalFunctions
extension BrewPackagesTracker
public extension BrewPackagesTracker
{
/// Get the names of tagged packages as set, for fas comparing
///

View File

@ -1,13 +1,13 @@
//
// URL - Package Name from URL.swift
// Get Package Name from URL.swift
// Cork
//
// Created by David Bureš - P on 18.01.2025.
// Created by David Bureš - P on 28.10.2025.
//
import Foundation
extension URL
public extension URL
{
func packageNameFromURL() -> String
{

View File

@ -7,9 +7,10 @@
import Foundation
import SwiftUI
import Defaults
@Observable @MainActor
class BrewPackagesTracker
public class BrewPackagesTracker
{
var installedFormulae: BrewPackages = .init()
var installedCasks: BrewPackages = .init()
@ -47,7 +48,7 @@ class BrewPackagesTracker
}
/// Collected errors from failed Formulae loading
var unsuccessfullyLoadedFormulaeErrors: [PackageLoadingError]
var unsuccessfullyLoadedFormulaeErrors: [BrewPackage.PackageLoadingError]
{
return installedFormulae.compactMap
{ rawResult in
@ -92,7 +93,7 @@ class BrewPackagesTracker
}
/// Collected errors from failed Casks loading
var unsuccessfullyLoadedCasksErrors: [PackageLoadingError]
var unsuccessfullyLoadedCasksErrors: [BrewPackage.PackageLoadingError]
{
return installedCasks.compactMap
{ rawResult in

View File

@ -2,13 +2,12 @@
// Check if URL is Symlink.swift
// Cork
//
// Created by David Bureš on 25.02.2023.
// Created by David Bureš - P on 28.10.2025.
//
import Foundation
import CorkShared
extension URL
public extension URL
{
func isSymlink() -> Bool?
{

View File

@ -1,13 +1,13 @@
//
// Filter Symlinks.swift
// Filter Out Symlinks.swift
// Cork
//
// Created by David Bureš on 13.11.2024.
// Created by David Bureš - P on 28.10.2025.
//
import Foundation
extension [URL]
public extension [URL]
{
/// Filter out all symlinks from an array of URLs
var withoutSymlinks: [URL]

View File

@ -2,16 +2,16 @@
// REGEX Match.swift
// Cork
//
// Created by David Bureš on 19.02.2023.
// Created by David Bureš - P on 28.10.2025.
//
import Foundation
enum RegexError: LocalizedError
public enum RegexError: LocalizedError
{
case regexFunctionCouldNotMatchAnything
var errorDescription: String?
public var errorDescription: String?
{
switch self
{
@ -21,7 +21,7 @@ enum RegexError: LocalizedError
}
}
extension String
public extension String
{
/// Match a string according to a specified REGEX

View File

@ -1,15 +1,15 @@
//
// Shell Interface.swift
// Shell Commands.swift
// Cork
//
// Created by David Bureš on 03.07.2022.
// Created by David Bureš - P on 28.10.2025.
//
import Foundation
import CorkShared
@discardableResult
func shell(
public func shell(
_ launchPath: URL,
_ arguments: [String],
environment: [String: String]? = nil,
@ -46,7 +46,7 @@ func shell(
/// // Do something with `errorLine`
/// }
/// }
func shell(
public func shell(
_ launchPath: URL,
_ arguments: [String],
environment: [String: String]? = nil,
@ -166,7 +166,7 @@ func shell(
}
}
func shell(
public func shell(
_ launchPath: URL,
_ arguments: [String],
environment: [String: String]? = nil,

View File

@ -0,0 +1,20 @@
//
// Terminal Output.swift
// Cork
//
// Created by David Bureš - P on 28.10.2025.
//
import Foundation
public struct TerminalOutput
{
public var standardOutput: String
public var standardError: String
}
public enum StreamedTerminalOutput
{
case standardOutput(String)
case standardError(String)
}

View File

@ -33,9 +33,14 @@ func corkTarget(configureWithSelfCompiled: Bool) -> ProjectDescription.Target {
.target(corkSharedTarget),
.target(corkNotificationsTarget),
.target(corkPackages_modelsTarget),
.target(corkPackages_logicTarget),
.target(corkTerminalFunctionsTarget),
.target(corkIntentsTarget),
.external(name: "LaunchAtLogin"),
.external(name: "DavidFoundation"),
.external(name: "ApplicationInspector"),
.external(name: "ButtonKit"),
.external(name: "FactoryKit"),
.package(product: "SwiftLintBuildToolPlugin", type: .plugin),
.external(name: "Defaults"),
.external(name: "DefaultsMacros")
@ -63,8 +68,15 @@ let corkSharedTarget: ProjectDescription.Target = .target(
sources: [
"Modules/Shared/**/*.swift"
],
resources: [
"Cork/**/*.xcassets",
"Cork/**/*.xcstrings",
"PrivacyInfo.xcprivacy",
"Cork/Logic/Helpers/Programs/Sudo Helper",
],
dependencies: [
.external(name: "Defaults")
.external(name: "Defaults"),
.external(name: "FactoryKit")
],
settings: .settings(configurations: [
.debug(
@ -102,6 +114,30 @@ let corkNotificationsTarget: ProjectDescription.Target = .target(
])
)
let corkTerminalFunctionsTarget: ProjectDescription.Target = .target(
name: "CorkTerminalFunctions",
destinations: [.mac],
product: .staticLibrary,
bundleId: "eu.davidbures.cork-terminal-functions",
deploymentTargets: .macOS("14.0.0"),
sources: [
"Modules/TerminalSupport/**/*.swift"
],
dependencies: [
.external(name: "FactoryKit")
],
settings: .settings(configurations: [
.debug(
name: "Debug",
xcconfig: .relativeToRoot("xcconfigs/Cork.xcconfig")
),
.release(
name: "Release",
xcconfig: .relativeToRoot("xcconfigs/Cork.xcconfig")
)
])
)
let corkPackages_modelsTarget: ProjectDescription.Target = .target(
name: "CorkPackagesModels",
destinations: [.mac],
@ -111,6 +147,69 @@ let corkPackages_modelsTarget: ProjectDescription.Target = .target(
sources: [
"Modules/Packages/PackagesModels/**/*.swift"
],
resources: [
"Cork/**/*.xcassets",
"Cork/**/*.xcstrings",
"PrivacyInfo.xcprivacy",
"Cork/Logic/Helpers/Programs/Sudo Helper",
],
dependencies: [
.external(name: "FactoryKit")
],
settings: .settings(configurations: [
.debug(
name: "Debug",
xcconfig: .relativeToRoot("xcconfigs/Cork.xcconfig")
),
.release(
name: "Release",
xcconfig: .relativeToRoot("xcconfigs/Cork.xcconfig")
)
])
)
let corkPackages_logicTarget: ProjectDescription.Target = .target(
name: "CorkPackagesLogic",
destinations: [.mac],
product: .staticLibrary,
bundleId: "eu.davidbures.cork-packages-logic",
deploymentTargets: .macOS("14.0.0"),
sources: [
"Modules/Packages/PackagesLogic/**/*.swift"
],
resources: [
"Cork/**/*.xcassets",
"Cork/**/*.xcstrings",
"PrivacyInfo.xcprivacy",
"Cork/Logic/Helpers/Programs/Sudo Helper",
],
dependencies: [
.external(name: "FactoryKit")
],
settings: .settings(configurations: [
.debug(
name: "Debug",
xcconfig: .relativeToRoot("xcconfigs/Cork.xcconfig")
),
.release(
name: "Release",
xcconfig: .relativeToRoot("xcconfigs/Cork.xcconfig")
)
])
)
let corkIntentsTarget: ProjectDescription.Target = .target(
name: "CorkIntents",
destinations: [.mac],
product: .staticLibrary,
bundleId: "eu.davidbures.cork-intents",
deploymentTargets: .macOS("14.0.0"),
sources: [
"Modules/Intents/**/*.swift"
],
dependencies: [
.external(name: "FactoryKit")
],
settings: .settings(configurations: [
.debug(
name: "Debug",
@ -186,7 +285,10 @@ let project = Project(
corkTarget(configureWithSelfCompiled: false),
corkTarget(configureWithSelfCompiled: true),
corkSharedTarget,
corkTerminalFunctionsTarget,
corkPackages_modelsTarget,
corkPackages_logicTarget,
corkIntentsTarget,
corkNotificationsTarget,
corkHelpTarget,
corkTestsTarget

View File

@ -1,5 +1,14 @@
{
"pins" : [
{
"identity" : "applicationinspector",
"kind" : "remoteSourceControl",
"location" : "https://github.com/buresdv/ApplicationInspector",
"state" : {
"revision" : "72d9888b3da342df026254a940553c31b72daec8",
"version" : "1.0.0"
}
},
{
"identity" : "buttonkit",
"kind" : "remoteSourceControl",
@ -27,6 +36,15 @@
"version" : "9.0.2"
}
},
{
"identity" : "factory",
"kind" : "remoteSourceControl",
"location" : "https://github.com/hmlongco/Factory",
"state" : {
"branch" : "develop",
"revision" : "a46ce210c1535064beffd1f00caf9835507b656f"
}
},
{
"identity" : "launchatlogin-modern",
"kind" : "remoteSourceControl",

View File

@ -25,8 +25,10 @@ let package = Package(
.package(url: "https://github.com/sindresorhus/LaunchAtLogin-Modern", .upToNextMajor(from: "1.0.0")),
.package(url: "https://github.com/sindresorhus/Defaults", .upToNextMajor(from: "9.0.2")),
.package(url: "https://github.com/buresdv/DavidFoundation", .upToNextMajor(from: "2.0.1")),
.package(url: "https://github.com/buresdv/ApplicationInspector", .upToNextMajor(from: "1.0.0")),
.package(url: "https://github.com/Dean151/ButtonKit", .upToNextMajor(from: "0.6.1")),
.package(url: "https://github.com/SimplyDanny/SwiftLintPlugins", .upToNextMajor(from: "0.56.1")),
.package(url: "https://github.com/hmlongco/Factory", branch: "develop")
],
targets: [
.target(