├── DevCleaner ├── Resources │ ├── Manual │ │ ├── file.png │ │ ├── menu.png │ │ ├── file-dark.png │ │ ├── file@2x.png │ │ ├── menu-dark.png │ │ ├── menu@2x.png │ │ ├── Icon-128pt.png │ │ ├── main-window.png │ │ ├── preferences.png │ │ ├── Icon-128pt@2x.png │ │ ├── file-dark@2x.png │ │ ├── main-window@2x.png │ │ ├── menu-dark@2x.png │ │ ├── preferences@2x.png │ │ ├── main-window-dark.png │ │ ├── preferences-dark.png │ │ ├── main-window-dark@2x.png │ │ ├── preferences-dark@2x.png │ │ └── manual.css │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── OS │ │ │ ├── iOS │ │ │ │ ├── 2.imageset │ │ │ │ │ ├── 2.png │ │ │ │ │ ├── 2@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 3.imageset │ │ │ │ │ ├── 3.png │ │ │ │ │ ├── 3@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 4.imageset │ │ │ │ │ ├── 4.png │ │ │ │ │ ├── 4@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 5.imageset │ │ │ │ │ ├── 5.png │ │ │ │ │ ├── 5@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 6.imageset │ │ │ │ │ ├── 6.png │ │ │ │ │ ├── 6@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 7.imageset │ │ │ │ │ ├── 7.png │ │ │ │ │ ├── 7@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 8.imageset │ │ │ │ │ ├── 8.png │ │ │ │ │ ├── 8@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 9.imageset │ │ │ │ │ ├── 9.png │ │ │ │ │ ├── 9@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 10.imageset │ │ │ │ │ ├── 10.png │ │ │ │ │ ├── 10@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 11.imageset │ │ │ │ │ ├── 11.png │ │ │ │ │ ├── 11@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 12.imageset │ │ │ │ │ ├── 12.png │ │ │ │ │ ├── 12@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 13.imageset │ │ │ │ │ ├── 13.png │ │ │ │ │ ├── 13@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 14.imageset │ │ │ │ │ ├── 14.png │ │ │ │ │ ├── 14@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 15.imageset │ │ │ │ │ ├── 15.png │ │ │ │ │ ├── 15@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 16.imageset │ │ │ │ │ ├── 16.png │ │ │ │ │ ├── 16@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 17.imageset │ │ │ │ │ ├── 17.png │ │ │ │ │ ├── 17@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 18.imageset │ │ │ │ │ ├── 18.png │ │ │ │ │ ├── 18@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 26.imageset │ │ │ │ │ ├── 26.png │ │ │ │ │ ├── 26@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── Generic.imageset │ │ │ │ │ ├── Generic.png │ │ │ │ │ ├── Generic@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── tvOS │ │ │ │ ├── 9.imageset │ │ │ │ │ ├── 9.png │ │ │ │ │ ├── 9@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 10.imageset │ │ │ │ │ ├── 10.png │ │ │ │ │ ├── 10@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 11.imageset │ │ │ │ │ ├── 11.png │ │ │ │ │ ├── 11@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 12.imageset │ │ │ │ │ ├── 12.png │ │ │ │ │ ├── 12@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 13.imageset │ │ │ │ │ ├── 13.png │ │ │ │ │ ├── 13@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 14.imageset │ │ │ │ │ ├── 14.png │ │ │ │ │ ├── 14@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 15.imageset │ │ │ │ │ ├── 15.png │ │ │ │ │ ├── 15@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 16.imageset │ │ │ │ │ ├── 16.png │ │ │ │ │ ├── 16@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 17.imageset │ │ │ │ │ ├── 17.png │ │ │ │ │ ├── 17@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 18.imageset │ │ │ │ │ ├── 18.png │ │ │ │ │ ├── 18@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 26.imageset │ │ │ │ │ ├── 26.png │ │ │ │ │ ├── 26@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── Generic.imageset │ │ │ │ │ ├── Generic.png │ │ │ │ │ ├── Generic@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── macOS │ │ │ │ ├── 12.imageset │ │ │ │ │ ├── 12.png │ │ │ │ │ ├── 12@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 13.imageset │ │ │ │ │ ├── 13.png │ │ │ │ │ ├── 13@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 14.imageset │ │ │ │ │ ├── 14.png │ │ │ │ │ ├── 14@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 15.imageset │ │ │ │ │ ├── 15.png │ │ │ │ │ ├── 15@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 26.imageset │ │ │ │ │ ├── 26.png │ │ │ │ │ ├── 26@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── Generic.imageset │ │ │ │ │ ├── Generic.png │ │ │ │ │ ├── Generic@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── watchOS │ │ │ │ ├── 10.imageset │ │ │ │ │ ├── 10.png │ │ │ │ │ ├── 10@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 11.imageset │ │ │ │ │ ├── 11.png │ │ │ │ │ ├── 11@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 2.imageset │ │ │ │ │ ├── 2.png │ │ │ │ │ ├── 2@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 3.imageset │ │ │ │ │ ├── 3.png │ │ │ │ │ ├── 3@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 4.imageset │ │ │ │ │ ├── 4.png │ │ │ │ │ ├── 4@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 5.imageset │ │ │ │ │ ├── 5.png │ │ │ │ │ ├── 5@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 6.imageset │ │ │ │ │ ├── 6.png │ │ │ │ │ ├── 6@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 7.imageset │ │ │ │ │ ├── 7.png │ │ │ │ │ ├── 7@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 8.imageset │ │ │ │ │ ├── 8.png │ │ │ │ │ ├── 8@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 9.imageset │ │ │ │ │ ├── 9.png │ │ │ │ │ ├── 9@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 26.imageset │ │ │ │ │ ├── watchOS26.png │ │ │ │ │ ├── watchOS26@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── Generic.imageset │ │ │ │ │ ├── Generic.png │ │ │ │ │ ├── Generic@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── visionOS │ │ │ │ ├── 2.imageset │ │ │ │ │ ├── 2-1.png │ │ │ │ │ ├── 2@0.5x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── 26.imageset │ │ │ │ │ ├── visionOS26.png │ │ │ │ │ ├── visionOS26@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── Generic.imageset │ │ │ │ │ ├── Generic.png │ │ │ │ │ ├── Generic@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Devices │ │ │ ├── iPadIcon.imageset │ │ │ │ ├── iPadIcon.png │ │ │ │ ├── iPadIcon@2x.png │ │ │ │ └── Contents.json │ │ │ ├── WatchIcon.imageset │ │ │ │ ├── WatchIcon.png │ │ │ │ ├── WatchIcon@2x.png │ │ │ │ └── Contents.json │ │ │ ├── AppleTVIcon.imageset │ │ │ │ ├── AppleTVIcon.png │ │ │ │ ├── AppleTVIcon@2x.png │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── MacBookIcon.imageset │ │ │ │ ├── MacBookIcon.png │ │ │ │ ├── MacBookIcon@2x.png │ │ │ │ └── Contents.json │ │ │ ├── iPhoneIcon.imageset │ │ │ │ ├── iPhoneIcon.png │ │ │ │ ├── iPhoneIcon@2x.png │ │ │ │ └── Contents.json │ │ │ └── VisionProIcon.imageset │ │ │ │ ├── VisionProIcon.png │ │ │ │ ├── VisionProIcon@2x.png │ │ │ │ └── Contents.json │ │ ├── Files │ │ │ ├── XCArchive.imageset │ │ │ │ ├── XCArchive.png │ │ │ │ ├── XCArchive@2x.png │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ └── DevCleanerIcon.imageset │ │ │ ├── DevCleaner Icon - Tahoe-macOS-Default-256x256@2x.png │ │ │ └── Contents.json │ ├── DevCleaner.icon │ │ ├── Assets │ │ │ ├── PNG image.png │ │ │ ├── PNG image 3.png │ │ │ └── PNG image 6.png │ │ └── icon.json │ ├── DevCleaner.entitlements │ ├── dev-cleaner.sh │ ├── Info.plist │ ├── DevCleanerStoreKit.storekit │ └── Credits.rtf ├── Utilities │ ├── Extensions │ │ ├── Digest+Helpers.swift │ │ ├── FileManager+HomeFolder.swift │ │ ├── FileManager+DateProperties.swift │ │ ├── URL+AcquireAccessFromSandbox.swift │ │ └── FileManager+DirectorySize.swift │ ├── Weak.swift │ ├── Stack.swift │ ├── AppleDevices.swift │ ├── Profiling │ │ ├── Signposts.swift │ │ └── Profiler.swift │ ├── Alerts.swift │ ├── AppleBuild.swift │ ├── Logger.swift │ ├── Version.swift │ └── ArgumentsParser.swift ├── View Controllers │ ├── Main Window │ │ ├── Views │ │ │ ├── SizeCellView.swift │ │ │ └── XcodeEntryCellView.swift │ │ └── CleaningViewController.swift │ ├── Command Line Install Window │ │ ├── CommandLineInstallViewController.swift │ │ └── CommandLineInstall.storyboard │ └── Help Window │ │ ├── HelpViewController.swift │ │ └── Help.storyboard ├── Model │ └── Entries │ │ ├── OldDocumentationFileEntry.swift │ │ ├── DerivedDataFileEntry.swift │ │ ├── DeviceLogsFileEntry.swift │ │ ├── DocumentationCacheFileEntry.swift │ │ ├── ArchiveFileEntry.swift │ │ └── DeviceSupportFileEntry.swift ├── Views │ ├── MessageView.swift │ └── LoadingView.swift ├── Managers │ ├── ReviewRequests.swift │ ├── Files.swift │ ├── ScanReminders.swift │ └── FeedbackMailer.swift └── Base │ ├── AppDelegate.swift │ └── main.swift ├── Documentation ├── Main Window Screenshot.png ├── Preferences Window Screenshot.png └── Command Line Tool.md ├── .gitignore └── README.md /DevCleaner/Resources/Manual/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/file.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/menu.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/file-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/file-dark.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/file@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/file@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/menu-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/menu-dark.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/menu@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/menu@2x.png -------------------------------------------------------------------------------- /Documentation/Main Window Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/Documentation/Main Window Screenshot.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/Icon-128pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/Icon-128pt.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/main-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/main-window.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/preferences.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/Icon-128pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/Icon-128pt@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/file-dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/file-dark@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/main-window@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/main-window@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/menu-dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/menu-dark@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/preferences@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/preferences@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/main-window-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/main-window-dark.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/preferences-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/preferences-dark.png -------------------------------------------------------------------------------- /Documentation/Preferences Window Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/Documentation/Preferences Window Screenshot.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/main-window-dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/main-window-dark@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/preferences-dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/preferences-dark@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/DevCleaner.icon/Assets/PNG image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/DevCleaner.icon/Assets/PNG image.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/2.imageset/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/2.imageset/2.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/3.imageset/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/3.imageset/3.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/4.imageset/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/4.imageset/4.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/5.imageset/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/5.imageset/5.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/6.imageset/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/6.imageset/6.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/7.imageset/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/7.imageset/7.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/8.imageset/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/8.imageset/8.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/9.imageset/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/9.imageset/9.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/9.imageset/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/9.imageset/9.png -------------------------------------------------------------------------------- /DevCleaner/Resources/DevCleaner.icon/Assets/PNG image 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/DevCleaner.icon/Assets/PNG image 3.png -------------------------------------------------------------------------------- /DevCleaner/Resources/DevCleaner.icon/Assets/PNG image 6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/DevCleaner.icon/Assets/PNG image 6.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/10.imageset/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/10.imageset/10.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/11.imageset/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/11.imageset/11.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/12.imageset/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/12.imageset/12.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/13.imageset/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/13.imageset/13.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/14.imageset/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/14.imageset/14.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/15.imageset/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/15.imageset/15.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/16.imageset/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/16.imageset/16.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/17.imageset/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/17.imageset/17.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/18.imageset/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/18.imageset/18.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/2.imageset/2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/2.imageset/2@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/26.imageset/26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/26.imageset/26.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/3.imageset/3@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/3.imageset/3@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/4.imageset/4@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/4.imageset/4@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/5.imageset/5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/5.imageset/5@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/6.imageset/6@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/6.imageset/6@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/7.imageset/7@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/7.imageset/7@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/8.imageset/8@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/8.imageset/8@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/9.imageset/9@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/9.imageset/9@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/10.imageset/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/10.imageset/10.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/11.imageset/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/11.imageset/11.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/12.imageset/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/12.imageset/12.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/13.imageset/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/13.imageset/13.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/14.imageset/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/14.imageset/14.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/15.imageset/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/15.imageset/15.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/16.imageset/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/16.imageset/16.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/17.imageset/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/17.imageset/17.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/18.imageset/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/18.imageset/18.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/26.imageset/26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/26.imageset/26.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/10.imageset/10@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/10.imageset/10@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/11.imageset/11@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/11.imageset/11@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/12.imageset/12@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/12.imageset/12@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/13.imageset/13@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/13.imageset/13@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/14.imageset/14@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/14.imageset/14@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/15.imageset/15@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/15.imageset/15@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/16.imageset/16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/16.imageset/16@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/17.imageset/17@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/17.imageset/17@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/18.imageset/18@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/18.imageset/18@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/26.imageset/26@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/26.imageset/26@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/12.imageset/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/12.imageset/12.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/13.imageset/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/13.imageset/13.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/14.imageset/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/14.imageset/14.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/15.imageset/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/15.imageset/15.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/26.imageset/26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/26.imageset/26.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/10.imageset/10@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/10.imageset/10@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/11.imageset/11@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/11.imageset/11@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/12.imageset/12@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/12.imageset/12@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/13.imageset/13@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/13.imageset/13@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/14.imageset/14@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/14.imageset/14@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/15.imageset/15@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/15.imageset/15@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/16.imageset/16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/16.imageset/16@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/17.imageset/17@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/17.imageset/17@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/18.imageset/18@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/18.imageset/18@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/26.imageset/26@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/26.imageset/26@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/9.imageset/9@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/9.imageset/9@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/10.imageset/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/10.imageset/10.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/11.imageset/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/11.imageset/11.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/2.imageset/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/2.imageset/2.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/3.imageset/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/3.imageset/3.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/4.imageset/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/4.imageset/4.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/5.imageset/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/5.imageset/5.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/6.imageset/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/6.imageset/6.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/7.imageset/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/7.imageset/7.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/8.imageset/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/8.imageset/8.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/9.imageset/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/9.imageset/9.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/12.imageset/12@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/12.imageset/12@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/13.imageset/13@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/13.imageset/13@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/14.imageset/14@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/14.imageset/14@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/15.imageset/15@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/15.imageset/15@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/26.imageset/26@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/26.imageset/26@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/visionOS/2.imageset/2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/visionOS/2.imageset/2-1.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/2.imageset/2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/2.imageset/2@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/3.imageset/3@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/3.imageset/3@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/4.imageset/4@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/4.imageset/4@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/5.imageset/5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/5.imageset/5@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/6.imageset/6@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/6.imageset/6@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/7.imageset/7@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/7.imageset/7@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/8.imageset/8@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/8.imageset/8@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/9.imageset/9@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/9.imageset/9@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/visionOS/2.imageset/2@0.5x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/visionOS/2.imageset/2@0.5x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/10.imageset/10@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/10.imageset/10@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/11.imageset/11@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/11.imageset/11@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/Generic.imageset/Generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/Generic.imageset/Generic.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/Generic.imageset/Generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/Generic.imageset/Generic.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/26.imageset/watchOS26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/26.imageset/watchOS26.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/iPadIcon.imageset/iPadIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/iPadIcon.imageset/iPadIcon.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Files/XCArchive.imageset/XCArchive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Files/XCArchive.imageset/XCArchive.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/Generic.imageset/Generic@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/Generic.imageset/Generic@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/Generic.imageset/Generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/Generic.imageset/Generic.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/Generic.imageset/Generic@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/Generic.imageset/Generic@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/visionOS/26.imageset/visionOS26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/visionOS/26.imageset/visionOS26.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/26.imageset/watchOS26@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/26.imageset/watchOS26@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/Generic.imageset/Generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/Generic.imageset/Generic.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/WatchIcon.imageset/WatchIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/WatchIcon.imageset/WatchIcon.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/iPadIcon.imageset/iPadIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/iPadIcon.imageset/iPadIcon@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Files/XCArchive.imageset/XCArchive@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Files/XCArchive.imageset/XCArchive@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "properties" : { 7 | "provides-namespace" : true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/Generic.imageset/Generic@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/Generic.imageset/Generic@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/visionOS/26.imageset/visionOS26@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/visionOS/26.imageset/visionOS26@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/visionOS/Generic.imageset/Generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/visionOS/Generic.imageset/Generic.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/AppleTVIcon.imageset/AppleTVIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/AppleTVIcon.imageset/AppleTVIcon.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "properties" : { 7 | "provides-namespace" : true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/MacBookIcon.imageset/MacBookIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/MacBookIcon.imageset/MacBookIcon.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/WatchIcon.imageset/WatchIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/WatchIcon.imageset/WatchIcon@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/iPhoneIcon.imageset/iPhoneIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/iPhoneIcon.imageset/iPhoneIcon.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Files/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "properties" : { 7 | "provides-namespace" : true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "properties" : { 7 | "provides-namespace" : true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "properties" : { 7 | "provides-namespace" : true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/visionOS/Generic.imageset/Generic@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/visionOS/Generic.imageset/Generic@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/Generic.imageset/Generic@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/Generic.imageset/Generic@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/iPhoneIcon.imageset/iPhoneIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/iPhoneIcon.imageset/iPhoneIcon@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "properties" : { 7 | "provides-namespace" : true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/visionOS/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "properties" : { 7 | "provides-namespace" : true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "properties" : { 7 | "provides-namespace" : true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/AppleTVIcon.imageset/AppleTVIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/AppleTVIcon.imageset/AppleTVIcon@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/MacBookIcon.imageset/MacBookIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/MacBookIcon.imageset/MacBookIcon@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/VisionProIcon.imageset/VisionProIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/VisionProIcon.imageset/VisionProIcon.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/VisionProIcon.imageset/VisionProIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/VisionProIcon.imageset/VisionProIcon@2x.png -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/DevCleanerIcon.imageset/DevCleaner Icon - Tahoe-macOS-Default-256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/DevCleanerIcon.imageset/DevCleaner Icon - Tahoe-macOS-Default-256x256@2x.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X noise 2 | .DS_Store 3 | profile 4 | 5 | # XCode noise 6 | build/* 7 | *.pbxuser 8 | *.mode1v3 9 | *.mode2v3 10 | 11 | *.xcodeproj/xcuserdata/* 12 | *.xcodeproj/project.xcworkspace/* 13 | *.xcscheme 14 | 15 | # test suite files 16 | TestSuite/* 17 | 18 | # builds 19 | Builds/* 20 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/DevCleanerIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "DevCleaner Icon - Tahoe-macOS-Default-256x256@2x.png", 5 | "idiom" : "mac" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Manual/manual.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system; 3 | margin-left: 16pt; 4 | margin-right: 16pt; 5 | } 6 | 7 | .header img { 8 | vertical-align: middle; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | body { 13 | background: rgb(37, 37, 37); 14 | color: white; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/10.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "10.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "10@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/11.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "11.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "11@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/12.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "12.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "12@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/13.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "13.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "13@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/14.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "14.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "14@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/15.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "15.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "15@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/16.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "16@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/17.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "17.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "17@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/18.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "18.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "18@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "2.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "2@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/26.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "26.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "26@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "3.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "3@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "4.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "4@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "5.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "5@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "6.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "6@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/7.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "7.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "7@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/8.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "8.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "8@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/9.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "9.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "9@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/10.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "10.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "10@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/11.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "11.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "11@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/12.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "12.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "12@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/13.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "13.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "13@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/14.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "14.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "14@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/15.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "15.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "15@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/16.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "16@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/17.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "17.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "17@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/18.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "18.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "18@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/26.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "26.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "26@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/9.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "9.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "9@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "2.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "2@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "3.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "3@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "4.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "4@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "5.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "5@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "6.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "6@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/7.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "7.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "7@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/8.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "8.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "8@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/9.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "9.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "9@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/12.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "12.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "12@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/13.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "13.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "13@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/14.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "14.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "14@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/15.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "15.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "15@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/26.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "26.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "26@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/visionOS/2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "2@0.5x.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "2-1.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/10.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "10.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "10@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/11.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "11.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "11@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/iOS/Generic.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Generic.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Generic@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/tvOS/Generic.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Generic.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Generic@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/iPadIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "iPadIcon.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "iPadIcon@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Files/XCArchive.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "XCArchive.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "XCArchive@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/macOS/Generic.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Generic.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Generic@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/visionOS/26.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "visionOS26.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "visionOS26@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/visionOS/Generic.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Generic.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Generic@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/26.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "watchOS26.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "watchOS26@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/OS/watchOS/Generic.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Generic.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Generic@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/WatchIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "WatchIcon.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "WatchIcon@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/iPhoneIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "iPhoneIcon.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "iPhoneIcon@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/AppleTVIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppleTVIcon.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "AppleTVIcon@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/MacBookIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "MacBookIcon.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "MacBookIcon@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Assets.xcassets/Devices/VisionProIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "VisionProIcon.png", 5 | "idiom" : "mac", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "VisionProIcon@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DevCleaner/Resources/DevCleaner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.bookmarks.app-scope 8 | 9 | com.apple.security.files.user-selected.read-write 10 | 11 | com.apple.security.network.client 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /DevCleaner/Utilities/Extensions/Digest+Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Digest+Helpers.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 08/02/2022. 6 | // Copyright © 2022 One Minute Games. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CryptoKit 11 | 12 | extension Digest { 13 | public var bytes: [UInt8] { Array(makeIterator()) } 14 | public var data: Data { Data(bytes) } 15 | 16 | public var hexStr: String { 17 | bytes.map { String(format: "%02X", $0) }.joined() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /DevCleaner/Utilities/Weak.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Weak.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 03/10/2018. 6 | // Copyright © 2018 One Minute Games. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class Weak: Equatable { 12 | public private(set) weak var value: T? 13 | 14 | init(value: T?) { 15 | self.value = value 16 | } 17 | 18 | public static func == (lhs: Weak, rhs: Weak) -> Bool { 19 | return lhs.value === rhs.value 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DevCleaner/Utilities/Extensions/FileManager+HomeFolder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+HomeFolder.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 18/02/2019. 6 | // Copyright © 2019 One Minute Games. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension FileManager { 12 | public var realHomeDirectoryForCurrentUser: URL { 13 | let pw = getpwuid(getuid())! 14 | let home = pw.pointee.pw_dir! 15 | let homePath = FileManager.default.string(withFileSystemRepresentation: home, length: Int(strlen(home))) 16 | 17 | return URL(fileURLWithPath: homePath, isDirectory: true) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Documentation/Command Line Tool.md: -------------------------------------------------------------------------------- 1 | # Command Line Tool 2 | 3 | Follow those instructions to install command line tool: 4 | 5 | This instructions assumes you have **DevCleaner** installed in `/Applications` folder. If you moved it, or installed it in a different location, please use it in instructions below. 6 | 7 | You need admistrator rights to install it in the location proposed in this instructions. 8 | 9 | 1. Open "Terminal" app 10 | 2. Type `sudo ln -s /Applications/DevCleaner.app/Contents/Resources/dev-cleaner.sh /usr/local/bin/dev-cleaner` 11 | 3. Enter your password if needed 12 | 13 | You can change destination path if you like, just remember that it has to be in your `PATH` if you want to use it from anywhere in your system. 14 | -------------------------------------------------------------------------------- /DevCleaner/Utilities/Extensions/FileManager+DateProperties.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileManager+DateProperties.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 09/11/2019. 6 | // Copyright © 2019 One Minute Games. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension FileManager { 12 | func dateModified(for url: URL) -> Date { 13 | guard let attrs = try? self.attributesOfItem(atPath: url.path) else { 14 | return Date.distantPast 15 | } 16 | 17 | guard let result = attrs[.modificationDate] as? Date else { 18 | return Date.distantPast 19 | } 20 | 21 | return result 22 | } 23 | 24 | func dateCreated(for url: URL) -> Date { 25 | guard let attrs = try? self.attributesOfItem(atPath: url.path) else { 26 | return Date.distantPast 27 | } 28 | 29 | guard let result = attrs[.creationDate] as? Date else { 30 | return Date.distantPast 31 | } 32 | 33 | return result 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /DevCleaner/View Controllers/Main Window/Views/SizeCellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SizeCellView.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 14.04.2018. 6 | // Copyright © 2018 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Cocoa 22 | 23 | final class SizeCellView: NSTableCellView { 24 | func setup(with xcodeEntry: XcodeFileEntry) { 25 | if let textField = self.textField, let sizeInBytes = xcodeEntry.size.numberOfBytes { 26 | textField.placeholderString = ByteCountFormatter.string(fromByteCount: sizeInBytes, countStyle: .file) 27 | textField.sizeToFit() 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DevCleaner/Resources/dev-cleaner.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # dev-cleaner.sh 4 | # DevCleaner 5 | # 6 | # "dev-cleaner" command line tool wrapper script. 7 | # 8 | # Copyright © 2019-2024 One Minute Games. All rights reserved. 9 | # 10 | # DevCleaner is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation; either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # DevCleaner is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with DevCleaner. If not, see . 22 | 23 | # get app updated app path 24 | DEV_CLEANER_PATH=$(defaults read com.oneminutegames.XcodeCleaner DCAppFolder) 25 | 26 | # set a command line run env value 27 | export DEV_CLEANER_FROM_COMMAND_LINE=1 28 | 29 | if [ -d $DEV_CLEANER_PATH ]; then 30 | "$DEV_CLEANER_PATH/Contents/MacOS/DevCleaner" "$@" 31 | else 32 | echo "DevCleaner cannot be found, check if you haven't uninstalled it or moved. Path where it was expected: $DEV_CLEANER_PATH" 33 | fi 34 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | 1 23 | ITSAppUsesNonExemptEncryption 24 | 25 | LSApplicationCategoryType 26 | public.app-category.developer-tools 27 | LSMinimumSystemVersion 28 | $(MACOSX_DEPLOYMENT_TARGET) 29 | NSHumanReadableCopyright 30 | Copyright © 2018-2025 Konrad Kołakowski. All rights reserved. 31 | NSMainStoryboardFile 32 | Main 33 | NSPrincipalClass 34 | NSApplication 35 | NSUserNotificationAlertStyle 36 | alert 37 | 38 | 39 | -------------------------------------------------------------------------------- /DevCleaner/Model/Entries/OldDocumentationFileEntry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OldDocumentationFileEntry.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 14/07/2019. 6 | // Copyright © 2019 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Foundation 22 | import AppKit 23 | 24 | public final class OldDocumentationFileEntry: XcodeFileEntry { 25 | public override var fullDescription: String { 26 | return "" 27 | } 28 | 29 | public init(selected: Bool) { 30 | super.init(label: "Old Documentation Downloads", 31 | tooltipText: "Old offline documentations, not used anymore in modern Xcodes", 32 | icon: .none, 33 | tooltip: true, 34 | selected: selected) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DevCleaner/Model/Entries/DerivedDataFileEntry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DerivedDataFileEntry.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 27.03.2018. 6 | // Copyright © 2018 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Foundation 22 | import AppKit 23 | 24 | public final class DerivedDataFileEntry: XcodeFileEntry { 25 | // MARK: Properties 26 | public let projectName: String 27 | public let pathUrl: URL 28 | 29 | // MARK: Initialization 30 | public init(projectName: String, pathUrl: URL, selected: Bool) { 31 | self.projectName = projectName 32 | self.pathUrl = pathUrl 33 | 34 | super.init(label: "\(self.projectName)", extraInfo: "\(self.pathUrl.path)", icon: .system(name: NSImage.folderName), tooltip: true, selected: selected) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DevCleaner/Model/Entries/DeviceLogsFileEntry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceLogsFileEntry.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 13/07/2019. 6 | // Copyright © 2019 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Foundation 22 | import AppKit 23 | 24 | public final class DeviceLogsFileEntry: XcodeFileEntry { 25 | // MARK: Properties 26 | public let version: Version 27 | 28 | public init(version: Version, selected: Bool) { 29 | self.version = version 30 | 31 | super.init(label: "Logs from Xcode \(version)", 32 | tooltipText: "Old logs from Xcode \(version)", 33 | icon: .system(name: NSImage.multipleDocumentsName), 34 | tooltip: true, 35 | selected: selected) 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /DevCleaner/Model/Entries/DocumentationCacheFileEntry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DocumentationCacheFileEntry.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 16/10/2022. 6 | // Copyright © 2022 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Foundation 22 | import AppKit 23 | 24 | public final class DocumentationCacheFileEntry: XcodeFileEntry { 25 | // MARK: Properties 26 | public let version: Version 27 | 28 | public init(version: Version, selected: Bool) { 29 | self.version = version 30 | 31 | super.init(label: "Cache from Xcode \(version)", 32 | tooltipText: "Documentation cache files from Xcode \(version)", 33 | icon: .system(name: NSImage.multipleDocumentsName), 34 | tooltip: true, 35 | selected: selected) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /DevCleaner/Utilities/Stack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stack.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 30.04.2018. 6 | // Copyright © 2018 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Foundation 22 | 23 | public struct Stack { 24 | // MARK: Properties 25 | private var array = [T]() 26 | 27 | public var top: T? { 28 | return self.array.last 29 | } 30 | 31 | public var count: Int { 32 | return self.array.count 33 | } 34 | 35 | public var isEmpty: Bool { 36 | return self.count == 0 37 | } 38 | 39 | // MARK: Manipulate stack 40 | public mutating func push(_ e: T) { 41 | self.array.append(e) 42 | } 43 | 44 | @discardableResult 45 | public mutating func pop() -> T? { 46 | return self.array.popLast() 47 | } 48 | } 49 | 50 | extension Stack: CustomStringConvertible { 51 | public var description: String { 52 | let contents = self.array.map { "\($0)" }.reversed().joined(separator: ", ") 53 | 54 | return "[" + contents + "]" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /DevCleaner/View Controllers/Command Line Install Window/CommandLineInstallViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommandLineInstallViewController.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 06/03/2024. 6 | // Copyright © 2024 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Cocoa 22 | 23 | final class CommandLineInstallViewController: NSViewController { 24 | // MARK: Properties & outlets 25 | @IBOutlet weak var commandTextField: NSTextField! 26 | 27 | private var commandString: String { 28 | let appPath = Bundle.main.bundlePath 29 | 30 | return "sudo ln -sf \(appPath)/Contents/Resources/dev-cleaner.sh /usr/local/bin/dev-cleaner" 31 | } 32 | 33 | // MARK: Initialization & overrides 34 | override func viewDidLoad() { 35 | super.viewDidLoad() 36 | 37 | self.commandTextField.stringValue = "$ " + self.commandString 38 | self.commandTextField.isSelectable = true 39 | } 40 | 41 | // MARK: Actions 42 | @IBAction func copyCommand(_ sender: Any) { 43 | let pasteboard = NSPasteboard.general 44 | pasteboard.clearContents() 45 | 46 | pasteboard.setString(self.commandString, forType: .string) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /DevCleaner/Views/MessageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageView.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 17.06.2018. 6 | // Copyright © 2018 One Minute Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | public class MessageView: NSView { 12 | // MARK: Properties 13 | private let label = NSTextField() 14 | 15 | public var message: String = String() { 16 | didSet { 17 | self.label.stringValue = self.message 18 | } 19 | } 20 | 21 | public var backgroundColor: NSColor = .windowBackgroundColor { 22 | didSet { 23 | self.wantsLayer = true 24 | self.layer?.backgroundColor = self.backgroundColor.cgColor 25 | } 26 | } 27 | 28 | // MARK: Initialization & overrides 29 | public convenience init() { 30 | self.init(frame: .zero) 31 | } 32 | 33 | public override init(frame frameRect: NSRect) { 34 | super.init(frame: frameRect) 35 | 36 | // set background 37 | self.backgroundColor = NSColor.windowBackgroundColor 38 | 39 | // set message label 40 | self.label.font = NSFont.systemFont(ofSize: 17.0, weight: .bold) 41 | self.label.isEditable = false 42 | self.label.isSelectable = false 43 | self.label.drawsBackground = false 44 | self.label.isBordered = false 45 | self.label.isBezeled = false 46 | self.label.usesSingleLineMode = true 47 | self.label.alignment = .center 48 | 49 | self.addSubview(self.label) 50 | } 51 | 52 | public required init?(coder decoder: NSCoder) { 53 | fatalError("init(coder:) has not been implemented") 54 | } 55 | 56 | public override func layout() { 57 | let targetHeight: CGFloat = 30.0 58 | self.label.frame = NSRect(x: 0.0, y: (self.frame.height - targetHeight) / 2.0, 59 | width: self.frame.width, height: targetHeight) 60 | 61 | super.layout() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /DevCleaner/Views/LoadingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoadingView.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 05.06.2018. 6 | // Copyright © 2018 One Minute Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | public class LoadingView: NSView { 12 | // MARK: Properties 13 | private let progressIndicator = NSProgressIndicator() 14 | 15 | // MARK: Constants 16 | private let indicatorSize: CGFloat = 32.0 17 | 18 | // MARK: Initialization & overrides 19 | public convenience init() { 20 | self.init(frame: .zero) 21 | } 22 | 23 | public override init(frame frameRect: NSRect) { 24 | super.init(frame: frameRect) 25 | 26 | // set background 27 | self.wantsLayer = true 28 | self.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor 29 | 30 | // add loading indicator 31 | self.progressIndicator.style = .spinning 32 | self.progressIndicator.controlSize = .regular 33 | self.progressIndicator.frame = self.indicatorFrame(size: indicatorSize, in: frameRect) 34 | 35 | self.addSubview(progressIndicator) 36 | } 37 | 38 | public required init?(coder decoder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | public override func layout() { 43 | self.progressIndicator.frame = self.indicatorFrame(size: indicatorSize, in: self.frame) 44 | 45 | super.layout() 46 | } 47 | 48 | public override func viewWillMove(toSuperview newSuperview: NSView?) { 49 | if newSuperview == nil { 50 | self.progressIndicator.stopAnimation(self) 51 | } else { 52 | self.progressIndicator.startAnimation(self) 53 | } 54 | } 55 | 56 | // MARK: Helpers 57 | private func indicatorFrame(size: CGFloat, in frameRect: CGRect) -> CGRect { 58 | return CGRect(x: (frameRect.width - size) / 2.0, y: (frameRect.height - size) / 2.0, 59 | width: size, height: size) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /DevCleaner/Utilities/AppleDevices.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppleDevices.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 09/11/2025. 6 | // Copyright © 2025 One Minute Games. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import TabularData 11 | 12 | public struct AppleDevices { 13 | // MARK: Properties 14 | private static var deviceIdentifiersFile: URL? { 15 | let resourceName = "device_identifiers" 16 | 17 | if let url = Bundle.main.url(forResource: resourceName, withExtension: "csv") { 18 | return url 19 | } 20 | 21 | return Bundle.main.url(forResource: resourceName, withExtension: "csv") 22 | } 23 | 24 | private static let lookupTable: [String: String] = { 25 | guard let csvURL = Self.deviceIdentifiersFile else { 26 | log.warning("AppleDevices: device_identifiers.csv missing in bundle.") 27 | return [:] 28 | } 29 | 30 | do { 31 | let options = CSVReadingOptions(hasHeaderRow: true) 32 | let dataFrame = try DataFrame(contentsOfCSVFile: csvURL, options: options) 33 | var table: [String: String] = [:] 34 | 35 | for row in dataFrame.rows { 36 | guard let identifier = row["identifier"] as? String else { 37 | continue 38 | } 39 | let name = (row["name"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) 40 | table[identifier] = name?.isEmpty == false ? name : identifier 41 | } 42 | 43 | return table 44 | } catch { 45 | log.error("AppleDevices: Unable to load device_identifiers.csv: \(error)") 46 | return [:] 47 | } 48 | }() 49 | 50 | // MARK: Fetch device name 51 | public static func deviceName(for deviceId: String) -> String { 52 | guard !deviceId.isEmpty else { 53 | return "Unknown Device" 54 | } 55 | 56 | return Self.lookupTable[deviceId] ?? deviceId 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /DevCleaner/Resources/DevCleaner.icon/icon.json: -------------------------------------------------------------------------------- 1 | { 2 | "fill" : { 3 | "linear-gradient" : [ 4 | "display-p3:0.34704,0.74825,0.96220,1.00000", 5 | "display-p3:0.20876,0.42456,0.94757,1.00000" 6 | ], 7 | "orientation" : { 8 | "start" : { 9 | "x" : 0.5, 10 | "y" : 0 11 | }, 12 | "stop" : { 13 | "x" : 0.5, 14 | "y" : 0.7 15 | } 16 | } 17 | }, 18 | "groups" : [ 19 | { 20 | "blur-material" : 0.5, 21 | "layers" : [ 22 | { 23 | "image-name" : "PNG image 6.png", 24 | "name" : "PNG image 6" 25 | } 26 | ], 27 | "shadow" : { 28 | "kind" : "neutral", 29 | "opacity" : 1 30 | }, 31 | "specular" : true, 32 | "translucency" : { 33 | "enabled" : false, 34 | "value" : 0.5 35 | } 36 | }, 37 | { 38 | "blur-material" : 0.5, 39 | "layers" : [ 40 | { 41 | "blend-mode" : "normal", 42 | "fill" : "automatic", 43 | "hidden" : true, 44 | "image-name" : "PNG image.png", 45 | "name" : "PNG image", 46 | "position" : { 47 | "scale" : 1, 48 | "translation-in-points" : [ 49 | 0, 50 | 0 51 | ] 52 | } 53 | } 54 | ], 55 | "name" : "Group", 56 | "shadow" : { 57 | "kind" : "neutral", 58 | "opacity" : 0.5 59 | }, 60 | "specular" : false, 61 | "translucency" : { 62 | "enabled" : true, 63 | "value" : 0.5 64 | } 65 | }, 66 | { 67 | "blur-material" : 0.5, 68 | "layers" : [ 69 | { 70 | "glass" : true, 71 | "image-name" : "PNG image 3.png", 72 | "name" : "PNG image 3", 73 | "opacity" : 0.25 74 | } 75 | ], 76 | "shadow" : { 77 | "kind" : "none", 78 | "opacity" : 0.5 79 | }, 80 | "specular" : false, 81 | "translucency" : { 82 | "enabled" : false, 83 | "value" : 0.5 84 | } 85 | } 86 | ], 87 | "supported-platforms" : { 88 | "squares" : [ 89 | "macOS" 90 | ] 91 | } 92 | } -------------------------------------------------------------------------------- /DevCleaner/Managers/ReviewRequests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReviewRequests.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 31/08/2019. 6 | // Copyright © 2019 One Minute Games. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import StoreKit 11 | 12 | public final class ReviewRequests { 13 | // MARK: Properties 14 | public static let shared = ReviewRequests() 15 | 16 | // MARK: Constants 17 | private static let bytesNeededForReviewRequest: Int64 = 20 * 1024 * 1024 * 1024 // 20GB 18 | private static let cleansNeededForReviewRequest = 3 19 | 20 | // MARK: Showing review request 21 | public func requestReviewIfNeeded() { 22 | let totalBytesCleaned = Preferences.shared.totalBytesCleaned 23 | let totalCleansPerformedSinceLastRequest = Preferences.shared.cleansSinceLastReview 24 | 25 | // desired rules: 26 | // we show it either if we passed TOTAL of 20GB of cleaned bytes, which may be even on the first run of the app 27 | // or, if we clean smaller amounts, after 3 cleans 28 | // after we pass those 20GB total cleaned amount, we ask everytime we clean basically (limits according to system) 29 | if totalBytesCleaned > ReviewRequests.bytesNeededForReviewRequest || totalCleansPerformedSinceLastRequest >= ReviewRequests.cleansNeededForReviewRequest { 30 | // workaround due to an issue with presenting review popups in the background 31 | // https://furnacecreek.org/blog/2024-04-14-how-to-prevent-background-mac-app-store-rating-windows 32 | // 33 | if #available(macOS 14.0, *) { 34 | NSApp.yieldActivation(toApplicationWithBundleIdentifier: "com.apple.storeuid") 35 | } 36 | 37 | SKStoreReviewController.requestReview() 38 | 39 | Preferences.shared.cleansSinceLastReview = 0 40 | } 41 | } 42 | 43 | public func showReviewOnTheAppStore() { 44 | guard let appStoreUrl = URL(string: "macappstore://apps.apple.com/app/id1388020431?action=write-review") else { 45 | log.error("ReviewRequests: Can't make a review URL!") 46 | return 47 | } 48 | 49 | NSWorkspace.shared.open(appStoreUrl) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /DevCleaner/Utilities/Profiling/Signposts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Signposts.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kolakowski on 04/08/2019. 6 | // Copyright © 2019 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Foundation 22 | import os 23 | 24 | public final class Signposts { 25 | // MARK: Properties 26 | private static var signpostLog: OSLog = { 27 | guard let bundleId = Bundle.main.bundleIdentifier else { 28 | fatalError("Signposts: No bundleId defined in main bundle Infp.plist file?") 29 | } 30 | 31 | return OSLog(subsystem: bundleId + ".Signposts", category: .pointsOfInterest) 32 | }() 33 | 34 | // MARK: Helpers 35 | private static func placeSignpost(type: OSSignpostType, name: StaticString, details: String, object: AnyObject?) { 36 | let spid: OSSignpostID 37 | if let objectForSpid = object { 38 | spid = OSSignpostID(log: Signposts.signpostLog, object: objectForSpid) 39 | } else { 40 | spid = OSSignpostID(log: Signposts.signpostLog) 41 | } 42 | 43 | os_signpost(type, dso: #dsohandle, log: Signposts.signpostLog, name: name, signpostID: spid, "%@", details) 44 | } 45 | 46 | // MARK: Placing signposts 47 | public static func begin(name: StaticString, details: String = String(), object: AnyObject? = nil) { 48 | placeSignpost(type: .begin, name: name, details: details, object: object) 49 | } 50 | 51 | public static func end(name: StaticString, details: String = String(), object: AnyObject? = nil) { 52 | placeSignpost(type: .end, name: name, details: details, object: object) 53 | } 54 | 55 | public static func event(name: StaticString, details: String = String(), object: AnyObject? = nil) { 56 | placeSignpost(type: .event, name: name, details: details, object: object) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /DevCleaner/Utilities/Alerts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Alerts.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 18.03.2018. 6 | // Copyright © 2018 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Foundation 22 | import Cocoa 23 | 24 | public class Alerts { 25 | class public func fatalErrorAlertAndQuit(title: String, message: String) { 26 | // display a popup that tells us that this is basically a fatal error, and quit! 27 | let alert = NSAlert() 28 | alert.alertStyle = .critical 29 | alert.messageText = title 30 | alert.informativeText = message 31 | alert.addButton(withTitle: "Quit") 32 | 33 | alert.runModal() 34 | NSApp.terminate(nil) 35 | } 36 | 37 | class public func warningAlert(title: String, message: String, okButtonText: String = "OK", cancelButtonText: String = "Cancel", window: NSWindow? = nil, completionHandler: ((NSApplication.ModalResponse) -> Void)? = nil) { 38 | let alert = NSAlert() 39 | alert.alertStyle = .critical 40 | alert.messageText = title 41 | alert.informativeText = message 42 | alert.addButton(withTitle: okButtonText) 43 | alert.addButton(withTitle: cancelButtonText) 44 | 45 | if let currentWindow = window { 46 | alert.beginSheetModal(for: currentWindow, completionHandler: completionHandler) 47 | } else { 48 | let response = alert.runModal() 49 | completionHandler?(response) 50 | } 51 | } 52 | 53 | class public func infoAlert(title: String, message: String, okButtonText: String = "OK") { 54 | let alert = NSAlert() 55 | alert.alertStyle = .informational 56 | alert.messageText = title 57 | alert.informativeText = message 58 | alert.addButton(withTitle: okButtonText) 59 | 60 | alert.runModal() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /DevCleaner/View Controllers/Help Window/HelpViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HelpViewController.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 07/09/2019. 6 | // Copyright © 2019 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Cocoa 22 | @preconcurrency import WebKit 23 | 24 | final class HelpViewController: NSViewController { 25 | // MARK: Properties & outlets 26 | @IBOutlet private weak var helpWebView: WKWebView! 27 | 28 | // MARK: Initialization & overrides 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | 32 | // [1] workaround to prevent "blinking" of help background when in dark mode: 33 | // https://stackoverflow.com/a/67674061 34 | 35 | // configuration 36 | self.helpWebView.setValue(true as NSNumber, forKey: "drawsTransparentBackground") // [1] 37 | self.helpWebView.navigationDelegate = self 38 | 39 | // load manual HTML 40 | guard let helpUrl = Bundle.main.url(forResource: "manual", withExtension: "html", subdirectory: "Manual") else { 41 | log.error("HelpViewController: Can't find manual HTML file!") 42 | return 43 | } 44 | 45 | self.helpWebView.loadFileURL(helpUrl, allowingReadAccessTo: helpUrl.deletingLastPathComponent()) 46 | } 47 | } 48 | 49 | extension HelpViewController: WKNavigationDelegate { 50 | func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { 51 | if navigationAction.navigationType == .linkActivated { 52 | if let url = navigationAction.request.url { 53 | NSWorkspace.shared.open(url) 54 | decisionHandler(.cancel) 55 | return 56 | } 57 | } 58 | 59 | decisionHandler(.allow) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /DevCleaner/Model/Entries/ArchiveFileEntry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArchiveFileEntry.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 27.03.2018. 6 | // Copyright © 2018 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Foundation 22 | 23 | public final class ArchiveFileEntry: XcodeFileEntry { 24 | // MARK: Types 25 | public enum SubmissionStatus { 26 | case success, failure, undefined 27 | 28 | fileprivate var glyph: String { 29 | switch self { 30 | case .success: return "✅" 31 | case .failure: return "❌" 32 | case .undefined: return String() 33 | } 34 | } 35 | } 36 | 37 | // MARK: Properties 38 | public let projectName: String 39 | public let bundleName: String 40 | public let versionString: String 41 | public let version: Version? 42 | public let build: String 43 | public let date: Date 44 | public let submissionStatus: SubmissionStatus 45 | 46 | public override var fullDescription: String { 47 | return "\(projectName) \(self.versionString) (\(self.build)) (\(self.extraInfo))" 48 | } 49 | 50 | // MARK: Initialization 51 | public init(projectName: String, bundleName: String, version: String, build: String, date: Date, submissionStatus: SubmissionStatus, location: URL, selected: Bool) { 52 | self.projectName = projectName 53 | self.bundleName = bundleName 54 | self.versionString = version 55 | self.version = Version(describing: self.versionString) 56 | self.build = build 57 | self.date = date 58 | self.submissionStatus = submissionStatus 59 | 60 | let dateFormatter = DateFormatter() 61 | dateFormatter.dateStyle = .short 62 | dateFormatter.timeStyle = .short 63 | 64 | let dateString = dateFormatter.string(from: self.date) 65 | 66 | super.init(label: "\(self.versionString) (\(self.build)) \(submissionStatus.glyph)", extraInfo: dateString, icon: nil, tooltip: true, selected: selected) 67 | 68 | self.addPath(path: location) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /DevCleaner/Managers/Files.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Files.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 26/08/2019. 6 | // Copyright © 2019 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Foundation 22 | 23 | public final class Files { 24 | // MARK: Paths to common folders 25 | public static var userDeveloperFolder: URL { 26 | let userHomeDirectory = FileManager.default.realHomeDirectoryForCurrentUser 27 | let userDeveloperFolder = userHomeDirectory.appendingPathComponent("Library/Developer", isDirectory: true) 28 | 29 | return userDeveloperFolder 30 | } 31 | 32 | // MARK: Acquire folder permissions 33 | private static func acquireFolderPermissions(folderUrl: URL, allowCancel: Bool = true, openPanelMessage: String? = nil) -> URL? { 34 | let message = openPanelMessage ?? 35 | "DevCleaner needs permission to this folder to scan its contents. Folder should be already selected and all you need to do is to click \"Open\"." 36 | 37 | return folderUrl.acquireAccessFromSandbox(bookmark: Preferences.shared.folderBookmark(for: folderUrl), 38 | allowCancel: allowCancel, 39 | openPanelMessage: message) 40 | } 41 | 42 | public static func acquireUserDeveloperFolderPermissions() -> URL? { 43 | return acquireFolderPermissions(folderUrl: Files.userDeveloperFolder, 44 | openPanelMessage: "DevCleaner needs permission to your Developer folder to scan Xcode cache files. Folder should be already selected and all you need to do is to click \"Open\".") 45 | } 46 | 47 | public static func acquireCustomDerivedDataFolderPermissions() -> URL? { 48 | guard let customDerivedDataFolder = Preferences.shared.customDerivedDataFolder else { 49 | return nil 50 | } 51 | 52 | return acquireFolderPermissions(folderUrl: customDerivedDataFolder) 53 | } 54 | 55 | public static func acquireCustomArchivesFolderPermissions() -> URL? { 56 | guard let customArchivesFolder = Preferences.shared.customArchivesFolder else { 57 | return nil 58 | } 59 | 60 | return acquireFolderPermissions(folderUrl: customArchivesFolder) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /DevCleaner/Base/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 11.02.2018. 6 | // Copyright © 2018 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Cocoa 22 | 23 | class AppDelegate: NSObject, NSApplicationDelegate { 24 | // MARK: App configuration 25 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 26 | return true 27 | } 28 | 29 | // MARK: App lifetime events 30 | func applicationDidFinishLaunching(_ aNotification: Notification) { 31 | // register as transactions observer 32 | Donations.shared.startObservingTransactionsQueue() 33 | 34 | // update notifications 35 | if Preferences.shared.notificationsEnabled { 36 | ScanReminders.scheduleReminder(period: Preferences.shared.notificationsPeriod) 37 | } else { 38 | ScanReminders.disableReminder() 39 | } 40 | 41 | // information about upcoming notifications 42 | if let upcomingReminderDate = ScanReminders.dateOfNextReminder { 43 | log.info("Next reminder: \(upcomingReminderDate.description(with: Locale.current))") 44 | } else { 45 | log.info("No reminder scheduled!") 46 | } 47 | } 48 | 49 | func applicationWillTerminate(_ aNotification: Notification) { 50 | // Insert code here to tear down your application 51 | } 52 | 53 | // MARK: Actions 54 | @IBAction func openAppReview(_ sender: Any) { 55 | ReviewRequests.shared.showReviewOnTheAppStore() 56 | } 57 | 58 | @IBAction func showLogFiles(_ sender: Any) { 59 | // logs folder 60 | guard let logsUrl = log.logFilePath?.deletingLastPathComponent() else { 61 | return 62 | } 63 | 64 | NSWorkspace.shared.open(logsUrl) 65 | } 66 | 67 | @IBAction func installCommandLineTool(_ sender: Any) { 68 | guard let commandLineToolInstallInstructionsURL = URL(string: "https://github.com/vashpan/xcode-dev-cleaner/blob/566afe767c90001ba397f5907df11e09c68a1634/Documentation/Command%20Line%20Tool.md") else { return } 69 | NSWorkspace.shared.open(commandLineToolInstallInstructionsURL) 70 | } 71 | 72 | @IBAction func sendFeedback(_ sender: Any) { 73 | FeedbackMailer.shared.sendFeedback() 74 | } 75 | 76 | @IBAction func reportAnIssue(_ sender: Any) { 77 | FeedbackMailer.shared.reportAnIssue() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /DevCleaner/Base/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 20/08/2019. 6 | // Copyright © 2019 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Cocoa 22 | 23 | internal let log = Logger(name: "MainLog", level: .info, toFile: true) 24 | 25 | // MARK: Helpers 26 | private func commandLineDebugEnabled() -> Bool { 27 | #if DEBUG 28 | return Preferences.shared.envKeyPresent(key: "DCCmdLineDebug") 29 | #else 30 | return false 31 | #endif 32 | } 33 | 34 | private func isRunningFromCommandLine(args: [String]) -> Bool { 35 | // We have few ways of checking it, first is checking if our STDOUT is bound to some terminal 36 | // and second is checking for presence of env variable set in dev-cleaner.sh script. It helps 37 | // in cases where we want to run the script from headless environments where there's no TTY. 38 | // 39 | // Maybe there're better & more sure ways of handling/checking that, but I could't really found them. 40 | // 41 | 42 | let isTTY = isatty(STDIN_FILENO) 43 | let haveProperEnvValue = Preferences.shared.envKeyPresent(key: "DEV_CLEANER_FROM_COMMAND_LINE") 44 | 45 | let runningFromCommandLine = isTTY == 1 || haveProperEnvValue 46 | 47 | #if DEBUG 48 | let runningFromXcode = args.contains("-NSDocumentRevisionsDebugMode") 49 | #else 50 | let runningFromXcode = false 51 | #endif 52 | 53 | return commandLineDebugEnabled() || (runningFromCommandLine && !runningFromXcode) 54 | } 55 | 56 | private func cleanedCommandLineArguments(args: [String]) -> [String] { 57 | var resultArgs = args 58 | 59 | // we have to remove some Xcode stuff here 60 | if commandLineDebugEnabled() { 61 | if let index = resultArgs.firstIndex(of: "-NSDocumentRevisionsDebugMode") { 62 | resultArgs.remove(at: index) 63 | resultArgs.remove(at: index) // twice as there's "YES" afterwards 64 | } 65 | } 66 | 67 | return resultArgs 68 | } 69 | 70 | // MARK: App Start 71 | 72 | // save app path to defaults 73 | Preferences.shared.appFolder = Bundle.main.bundleURL 74 | 75 | let cleanedArgs = cleanedCommandLineArguments(args: CommandLine.arguments) 76 | if isRunningFromCommandLine(args: cleanedArgs) { 77 | log.consoleLogging = false // disable console logging to not interfere with console output, file log will still be available 78 | CmdLine.shared.start(args: cleanedArgs) 79 | } else { 80 | let _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) 81 | } 82 | -------------------------------------------------------------------------------- /DevCleaner/Resources/DevCleanerStoreKit.storekit: -------------------------------------------------------------------------------- 1 | { 2 | "identifier" : "3AD57D62", 3 | "nonRenewingSubscriptions" : [ 4 | 5 | ], 6 | "products" : [ 7 | { 8 | "displayPrice" : "3.99", 9 | "familyShareable" : false, 10 | "internalID" : "1390773822", 11 | "localizations" : [ 12 | { 13 | "description" : "A tip that could buy me a big coffee!", 14 | "displayName" : "Big Coffee", 15 | "locale" : "en_US" 16 | } 17 | ], 18 | "productID" : "BIG_COFFEE", 19 | "referenceName" : "Big Coffee", 20 | "type" : "Consumable" 21 | }, 22 | { 23 | "displayPrice" : "9.99", 24 | "familyShareable" : false, 25 | "internalID" : "1390773830", 26 | "localizations" : [ 27 | { 28 | "description" : "A tip that could buy me a lunch!", 29 | "displayName" : "Lunch", 30 | "locale" : "en_US" 31 | } 32 | ], 33 | "productID" : "LUNCH", 34 | "referenceName" : "Lunch", 35 | "type" : "Consumable" 36 | }, 37 | { 38 | "displayPrice" : "1.99", 39 | "familyShareable" : false, 40 | "internalID" : "1390773318", 41 | "localizations" : [ 42 | { 43 | "description" : "A tip that could buy me a small coffee!", 44 | "displayName" : "Small Coffee", 45 | "locale" : "en_US" 46 | } 47 | ], 48 | "productID" : "SMALL_COFFEE", 49 | "referenceName" : "Small Coffee", 50 | "type" : "Consumable" 51 | } 52 | ], 53 | "settings" : { 54 | "_applicationInternalID" : "1388020431", 55 | "_developerTeamID" : "3DS5SGYDG6", 56 | "_failTransactionsEnabled" : false, 57 | "_lastSynchronizedDate" : 723659050.64460695, 58 | "_locale" : "en_US", 59 | "_storefront" : "USA", 60 | "_storeKitErrors" : [ 61 | { 62 | "current" : { 63 | "index" : 2, 64 | "type" : "generic" 65 | }, 66 | "enabled" : false, 67 | "name" : "Load Products" 68 | }, 69 | { 70 | "current" : null, 71 | "enabled" : false, 72 | "name" : "Purchase" 73 | }, 74 | { 75 | "current" : null, 76 | "enabled" : false, 77 | "name" : "Verification" 78 | }, 79 | { 80 | "current" : null, 81 | "enabled" : false, 82 | "name" : "App Store Sync" 83 | }, 84 | { 85 | "current" : null, 86 | "enabled" : false, 87 | "name" : "Subscription Status" 88 | }, 89 | { 90 | "current" : null, 91 | "enabled" : false, 92 | "name" : "App Transaction" 93 | }, 94 | { 95 | "current" : null, 96 | "enabled" : false, 97 | "name" : "Manage Subscriptions Sheet" 98 | }, 99 | { 100 | "current" : null, 101 | "enabled" : false, 102 | "name" : "Refund Request Sheet" 103 | }, 104 | { 105 | "current" : null, 106 | "enabled" : false, 107 | "name" : "Offer Code Redeem Sheet" 108 | } 109 | ] 110 | }, 111 | "subscriptionGroups" : [ 112 | 113 | ], 114 | "version" : { 115 | "major" : 3, 116 | "minor" : 0 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /DevCleaner/Utilities/AppleBuild.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppleBuild.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 15/02/2022. 6 | // Copyright © 2022 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Foundation 22 | 23 | // MARK: AppleBuild struct 24 | public struct AppleBuild { 25 | // MARK: Properties 26 | public let textual: String 27 | 28 | public let major: Int? 29 | public let minor: Character? 30 | public let daily: String? 31 | 32 | // MARK: Constants 33 | public static let empty = AppleBuild() 34 | 35 | // MARK: Initialization 36 | init() { 37 | self.textual = String() 38 | 39 | self.major = nil 40 | self.minor = nil 41 | self.daily = nil 42 | } 43 | 44 | init(string: String) { 45 | self.textual = string 46 | 47 | // parse build number according to this: 48 | // https://tidbits.com/2020/07/08/how-to-decode-apple-version-and-build-numbers/ 49 | let scanner = Scanner(string: self.textual) 50 | scanner.caseSensitive = false 51 | 52 | self.major = scanner.scanInt() 53 | self.minor = scanner.scanCharacter() 54 | self.daily = scanner.string[scanner.currentIndex...].description 55 | } 56 | } 57 | 58 | // MARK: - Comparable implementation 59 | extension AppleBuild: Comparable { 60 | public static func ==(lhs: AppleBuild, rhs: AppleBuild) -> Bool { 61 | if lhs.major == rhs.major { 62 | if lhs.minor == rhs.minor { 63 | let lhsDaily = lhs.daily ?? "" 64 | let rhsDaily = rhs.daily ?? "" 65 | 66 | if lhsDaily == rhsDaily { 67 | return true 68 | } 69 | } 70 | } 71 | 72 | return false 73 | } 74 | 75 | public static func <(lhs: AppleBuild, rhs: AppleBuild) -> Bool { 76 | if lhs.major == rhs.major { 77 | if lhs.minor == rhs.minor { 78 | let lhsDaily = lhs.daily ?? "" 79 | let rhsDaily = rhs.daily ?? "" 80 | 81 | return lhsDaily.compare(rhsDaily) == .orderedAscending 82 | } else { 83 | let lhsMinor = lhs.minor ?? Character("") 84 | let rhsMinor = rhs.minor ?? Character("") 85 | 86 | return lhsMinor < rhsMinor 87 | } 88 | } else { 89 | let lhsMajor = lhs.major ?? 0 90 | let rhsMajor = rhs.major ?? 0 91 | 92 | return lhsMajor < rhsMajor 93 | } 94 | } 95 | } 96 | 97 | // MARK: - CustomStringConvertible conformance 98 | extension AppleBuild: CustomStringConvertible { 99 | public var description: String { 100 | return self.textual 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /DevCleaner/Utilities/Profiling/Profiler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Profiler.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kolakowski on 04/08/2019. 6 | // Copyright © 2019 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import os 22 | import Foundation 23 | import QuartzCore 24 | 25 | public final class Profiler { 26 | private typealias ProfilerEntry = (name: StaticString, description: String, usingSignpost: Bool, start: CFTimeInterval) 27 | private static var starts = [ProfilerEntry]() 28 | 29 | private static let signpostLog: OSLog = { 30 | guard let bundleId = Bundle.main.bundleIdentifier else { 31 | fatalError("Profiler: No bundleId defined in main bundle Infp.plist file?") 32 | } 33 | 34 | return OSLog(subsystem: bundleId + ".Profiler", category: .pointsOfInterest) 35 | }() 36 | 37 | public static func tick(name: StaticString = StaticString(), description: String = String(), useSignpost: Bool = false) { 38 | let tickTime = CACurrentMediaTime() 39 | let entry = (name: name, description: description, usingSignpost: useSignpost, start: tickTime) 40 | 41 | if useSignpost { 42 | os_signpost(.begin, dso: #dsohandle, log: Profiler.signpostLog, name: name, "%@", description as NSString) 43 | } 44 | 45 | self.starts.append(entry) 46 | } 47 | 48 | @discardableResult 49 | public static func tock(noLog silent: Bool = false) -> CFTimeInterval { 50 | let tockTime = CACurrentMediaTime() // get time here to avoid any influence of "tock" function logic 51 | var time = CFTimeInterval(0.0) 52 | 53 | if let lastEntry = self.starts.popLast() { 54 | time = tockTime - lastEntry.start 55 | 56 | if !silent { 57 | if lastEntry.usingSignpost { 58 | if #available(iOS 12.0, *) { 59 | os_signpost(.end, dso: #dsohandle, log: Profiler.signpostLog, name: lastEntry.name) 60 | } 61 | } 62 | 63 | let number = self.starts.count 64 | let finalMessage: String 65 | 66 | if !lastEntry.name.description.isEmpty && !lastEntry.description.isEmpty { 67 | finalMessage = "[Profile: \(number)][\(lastEntry.name) - \(lastEntry.description)]" 68 | } else if !lastEntry.name.description.isEmpty { 69 | finalMessage = "[Profile: \(number)][\(lastEntry.name)]" 70 | } else { 71 | finalMessage = "[Profile: \(number)]" 72 | } 73 | 74 | print(String(format: "\(finalMessage): %.f ms", time * 1000.0)) 75 | } 76 | } else { 77 | print("Cannot stop profiling that haven't started yet!") 78 | } 79 | 80 | return time 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /DevCleaner/Resources/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1250\cocoartf2822 2 | \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;} 3 | {\colortbl;\red255\green255\blue255;\red154\green154\blue154;} 4 | {\*\expandedcolortbl;;\csgray\c66667;} 5 | \paperw12240\paperh15840\margl1440\margr1440\vieww9000\viewh8400\viewkind0 6 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 7 | 8 | \f0\b\fs24 \cf0 \ 9 | Development\ 10 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 11 | 12 | \f1\b0 \cf2 Konrad Ko\uc0\u322 akowski\ 13 | \ 14 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 15 | 16 | \f0\b \cf0 Special thanks\ 17 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 18 | 19 | \f1\b0 \cf2 Mladen Mladenov\ 20 | Valeriy Van\ 21 | Enrique Garcia\ 22 | \pard\pardeftab720\partightenfactor0 23 | \cf2 24 | \f0\b \cf0 \ 25 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 26 | \cf0 \ 27 | Icon uses assets from 28 | \f1\b0 \ 29 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 30 | \cf2 Broom graphics from IconDrawer on {\field{\*\fldinst{HYPERLINK "http://www.iconfinder.com"}}{\fldrslt www.iconfinder.com}}\ 31 | Hard disk icon from Freepik on {\field{\*\fldinst{HYPERLINK "http://www.flaticon.com"}}{\fldrslt www.flaticon.com}} \cf0 \ 32 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 33 | \cf0 \ 34 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 35 | 36 | \f0\b \cf0 You can follow me on Twitter: 37 | \f1\b0 {\field{\*\fldinst{HYPERLINK "https://twitter.com/vashpan"}}{\fldrslt @vashpan}}, {\field{\*\fldinst{HYPERLINK "https://twitter.com/OneMinuteGames"}}{\fldrslt @OneMinuteGames}}\ 38 | 39 | \f0\b Find me on {\field{\*\fldinst{HYPERLINK "http://www.linkedin.com/in/konrad-kolakowski"}}{\fldrslt 40 | \f1\b0 LinkedIn}} 41 | \f1\b0 \ 42 | 43 | \f0\b Check out my GitHub account: {\field{\*\fldinst{HYPERLINK "https://github.com/vashpan"}}{\fldrslt 44 | \f1\b0 @vashpan}} 45 | \f1\b0 \ 46 | \ 47 | 48 | \f0\b DevCleaner {\field{\*\fldinst{HYPERLINK "https://github.com/vashpan/xcode-dev-cleaner"}}{\fldrslt GitHub page}} 49 | \f1\b0 \ 50 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 51 | \cf0 \ 52 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 53 | 54 | \f0\b \cf0 Xcode is trademark of Apple Inc., registered in the U.S. and other countries. 55 | \f1\b0 \ 56 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 57 | \cf0 \ 58 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 59 | \cf2 This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\ 60 | \ 61 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR PARTICULAR PURPOSE. See the GNU General Public License for more details.\ 62 | \ 63 | You should have received a copy of the GNU General Public License along with this program. If not, see {\field{\*\fldinst{HYPERLINK "http://www.gnu.org/licenses"}}{\fldrslt http://www.gnu.org/licenses}}\ 64 | } -------------------------------------------------------------------------------- /DevCleaner/Managers/ScanReminders.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScanReminders.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 11.05.2018. 6 | // Copyright © 2018 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Foundation 22 | import Cocoa 23 | import NotificationCenter 24 | 25 | public final class ScanReminders { 26 | // MARK: Types 27 | public enum Period: Int { 28 | case everyWeek, every2weeks, everyMonth, every2Months 29 | 30 | private var dateComponents: DateComponents { 31 | var result = DateComponents() 32 | 33 | switch self { 34 | case .everyWeek: 35 | result.day = 7 36 | case .every2weeks: 37 | result.day = 7 * 2 38 | case .everyMonth: 39 | result.month = 1 40 | case .every2Months: 41 | result.month = 2 42 | } 43 | 44 | return result 45 | } 46 | 47 | internal var repeatInterval: DateComponents { 48 | var result = DateComponents() 49 | 50 | #if DEBUG 51 | if Preferences.shared.envKeyPresent(key: "DCNotificationsTest") { 52 | result.day = 1 // for debug we change our periods to one day 53 | } else { 54 | result = self.dateComponents 55 | } 56 | #else 57 | result = self.dateComponents 58 | #endif 59 | 60 | return result 61 | } 62 | } 63 | 64 | // MARK: Properties 65 | public static var dateOfNextReminder: Date? { 66 | if let firstScheduledNotification = NSUserNotificationCenter.default.scheduledNotifications.first { 67 | return firstScheduledNotification.deliveryDate 68 | } else { 69 | return nil 70 | } 71 | } 72 | 73 | // MARK: Constants 74 | private static let reminderIdentifier = "com.oneminutegames.DevCleaner.scanReminder" 75 | 76 | // MARK: Manage reminders 77 | public static func scheduleReminder(period: Period) { 78 | // notification 79 | let notification = NSUserNotification() 80 | notification.identifier = reminderIdentifier 81 | notification.title = "Scan Xcode cache?" 82 | notification.informativeText = "It's been a while since your last scan, check if you can reclaim some storage." 83 | notification.soundName = NSUserNotificationDefaultSoundName 84 | 85 | // buttons 86 | notification.hasActionButton = true 87 | notification.otherButtonTitle = "Close" 88 | notification.actionButtonTitle = "Scan" 89 | 90 | // schedule & repeat periodically 91 | if let initialDeliveryDate = NSCalendar.current.date(byAdding: period.repeatInterval, to: Date()) { 92 | notification.deliveryDate = initialDeliveryDate 93 | notification.deliveryRepeatInterval = period.repeatInterval 94 | } 95 | 96 | // schedule a notification 97 | let notificationCenter = NSUserNotificationCenter.default 98 | notificationCenter.scheduleNotification(notification) 99 | } 100 | 101 | public static func disableReminder() { 102 | NSUserNotificationCenter.default.scheduledNotifications = [] 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DevCleaner 2 | 3 | ![Main Window Screenshot](https://github.com/vashpan/xcode-cleaner/raw/master/Documentation/Main%20Window%20Screenshot.png) 4 | 5 | *Currently tested with macOS 14.x and Xcode 15.x. Should support earlier Xcode versions as well* 6 | 7 | Available on the [Mac App Store](https://itunes.apple.com/app/devcleaner/id1388020431) 8 | 9 | If you want to reclaim tens of gigabytes of your storage used for various Xcode caches - this tool is for you! 10 | 11 | Xcode could store tens of gigabytes in `~/Developer` folder. Most of those cached files & symbols is not reclaimed over time 12 | and could consume a large amount of your storage, which is especially important if you have relatively small SSD drive. 13 | 14 | DevCleaner gives you an easy way to inspect auto-generated files and clean them if necessary. It could also remind you about 15 | scan after a while. 16 | 17 | Please note that **this application is relying on internal folder structures and undocumented features**. It could stop working with 18 | newer versions of Xcode! I tried to make sure this application is safe, but if you want to be sure, please **make backup before use it**. 19 | 20 | ## What DevCleaner could actually clean? 21 | 22 | ### Device Support 23 | 24 | It consumes the largest part of the Xcode caches. Everytime you connect a device with the new iOS/visionOS/watchOS/tvOS, its symbols must be downloaded 25 | to your computer, for efficient debugging. It consumes around 2-5GB per version. Even smallest updates requires new set of symbols. 26 | 27 | It could accumulate to hold tens of gigabytes of data. 28 | 29 | This section is selected by default, with exception of the latest version of each system. 30 | 31 | ### Archives 32 | 33 | When we create build for distrubution or export, a new archive is created. For each version it contains a build, debug symbols and 34 | other informations. Usually we need those archived items for crashes symbolications for example, but for sure we don't need all of them. 35 | Xcode Cleaner allows to quickly inspect the archives and delete older ones or builds that are not on store. 36 | 37 | ### Derived Data 38 | 39 | This is the major "cache" part of Xcode files, where autocompletion data, logs, debug builds, intermediate products and other stuff lives. 40 | The point is that it could be regenerated if necessary. Also some older projects could be removed completely because its rather unusual that 41 | we would use them again. 42 | 43 | ### Documentation Cache 44 | 45 | Modern Xcodes are caching documentation that is accessed via the web. Unfortunately older caches are not cleaned up by Xcode and significant 46 | amount of space may be wasted due to that. 47 | 48 | ### Old Simulator & Device Logs 49 | 50 | Old device logs & crashes databases, only most recent ones are needed. It seems that new versions of Xcodes migrates old logs database, but keeping older ones on disk. 51 | 52 | ### Old Documentation Downloads 53 | 54 | Old Xcodes had ability to download documentation to browse offline, it could've been many gigabytes. Although newer Xcodes has online documentation browser, 55 | those old documentations may be still on your drive if you're an old Xcode user. 56 | 57 | ## Contact 58 | 59 | If you enjoy this application, consider support me by making a tip in the app, or by downloading/buying some of my other apps & games from the AppStore 😎 60 | 61 | Mastodon: [@kkolakowski](https://mastodon.social/@kkolakowski) 62 | Twitter: [@vashpan](https://twitter.com/vashpan), [@oneminutegames](https://twitter.com/OneMinuteGames) 63 | 64 | Website: http://www.one-minute-games.com 65 | AppStore: https://apps.apple.com/developer/one-minute-games/id395763583 66 | 67 | ## Contribution 68 | 69 | This application is my first (larger) macOS app, so feel free to give me some feedback or pull requests! If there's some new feature to support, 70 | maybe some caches I missed, let me know as well in issues. 71 | 72 | Application is licensed using GPL3. You may freely modify, download, redistribute and use this application from this source code, but only me, 73 | as copyright holder can submit it to the Mac App Store. 74 | 75 | Copyright © 2018-2025 One Minute Games Konrad Kołakowski. 76 | -------------------------------------------------------------------------------- /DevCleaner/View Controllers/Help Window/Help.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /DevCleaner/Utilities/Logger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Logger.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 20.03.2018. 6 | // Copyright © 2018 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Foundation 22 | 23 | public class Logger { 24 | // MARK: Types 25 | public enum Level { 26 | case info, warning, error 27 | } 28 | 29 | // MARK: Properties 30 | public let level: Level 31 | public let name: String 32 | 33 | public var consoleLogging: Bool 34 | public var fileLogging: Bool { 35 | return self.logFileHandle != nil 36 | } 37 | 38 | public let logFilePath: URL? 39 | public let oldLogFilePath: URL? 40 | 41 | private let logFileHandle: FileHandle? 42 | 43 | // MARK: Initialization 44 | public init(name: String, level: Level = .error, toFile: Bool = false) { 45 | self.name = name 46 | self.level = level 47 | self.consoleLogging = true 48 | 49 | if toFile { 50 | // create logfile path 51 | let bundleId = Bundle.main.bundleIdentifier ?? "UnknownApp" 52 | let newLogFileName = "\(bundleId)-\(self.name)-LogFile-latest.log" 53 | let oldLogFileName = "\(bundleId)-\(self.name)-LogFile-previous.log" 54 | let documentsFolder = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) 55 | 56 | // create if needed & open log file to write 57 | if let newLogFilePath = documentsFolder?.appendingPathComponent(newLogFileName), let oldLogFilePath = documentsFolder?.appendingPathComponent(oldLogFileName) { 58 | // first rename current log to old if exists 59 | if FileManager.default.fileExists(atPath: newLogFilePath.path) { 60 | try? FileManager.default.moveItem(at: newLogFilePath, to: oldLogFilePath) 61 | self.oldLogFilePath = oldLogFilePath 62 | } else { 63 | self.oldLogFilePath = nil 64 | } 65 | 66 | do { 67 | FileManager.default.createFile(atPath: newLogFilePath.path, contents: nil, attributes: nil) 68 | self.logFileHandle = try FileHandle(forWritingTo: newLogFilePath) 69 | self.logFilePath = newLogFilePath 70 | } catch(let error) { 71 | self.logFileHandle = nil 72 | self.logFilePath = nil 73 | NSLog("❌ Can't create log file: \(newLogFilePath.path). Error: \(error)") 74 | } 75 | } else { 76 | self.logFileHandle = nil 77 | self.logFilePath = nil 78 | self.oldLogFilePath = nil 79 | } 80 | } else { 81 | self.logFileHandle = nil 82 | self.logFilePath = nil 83 | self.oldLogFilePath = nil 84 | } 85 | } 86 | 87 | deinit { 88 | self.logFileHandle?.closeFile() 89 | } 90 | 91 | // MARK: Helpers 92 | private func writeLog(text: String, level: Level) { 93 | if self.consoleLogging { 94 | NSLog(text) 95 | } 96 | 97 | if let fileHandle = self.logFileHandle { 98 | let textToLogToFile = text + "\n" // add new line for each entry 99 | if let logData = textToLogToFile.data(using: .utf8) { 100 | fileHandle.write(logData) 101 | fileHandle.synchronizeFile() 102 | } 103 | } 104 | } 105 | 106 | // MARK: Log methods 107 | public func info(_ message: String) { 108 | switch self.level { 109 | case .info: 110 | self.writeLog(text: "❕ \(message)", level: .info) 111 | default: 112 | return 113 | } 114 | } 115 | 116 | public func warning(_ message: String) { 117 | switch self.level { 118 | case .info, .warning: 119 | self.writeLog(text: "⚠️ \(message)", level: .warning) 120 | default: 121 | return 122 | } 123 | } 124 | 125 | public func error(_ message: String) { 126 | switch self.level { 127 | case .info, .warning, .error: 128 | self.writeLog(text: "❌ \(message)", level: .error) 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /DevCleaner/Managers/FeedbackMailer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FeedbackMailer.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 03/06/2021. 6 | // Copyright © 2021 One Minute Games. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import IOKit 11 | import Cocoa 12 | 13 | public final class FeedbackMailer { 14 | // MARK: Properties 15 | public static let shared = FeedbackMailer() 16 | 17 | // MARK: Constants 18 | private static let feedbackMailBase64 = "a29ucmFkLmtvbGFrb3dza2lAbWUuY29t" // base64 encoded to avoid spam bots 19 | 20 | // MARK: System profile 21 | 22 | private func macModelIdentifier() -> String? { 23 | let service = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("IOPlatformExpertDevice")) 24 | var modelIdentifier: String? 25 | if let modelData = IORegistryEntryCreateCFProperty(service, "model" as CFString, kCFAllocatorDefault, 0).takeRetainedValue() as? Data { 26 | modelIdentifier = String(data: modelData, encoding: .utf8)?.trimmingCharacters(in: .controlCharacters) 27 | } 28 | 29 | IOObjectRelease(service) 30 | return modelIdentifier 31 | } 32 | 33 | private func prepareSystemProfileInfo() -> String { 34 | let fm = FileManager.default 35 | let appBundleInfoDict = Bundle.main.infoDictionary 36 | let appVersion = (appBundleInfoDict?["CFBundleShortVersionString"] as? String) ?? "-" 37 | let appBuildNumber = (appBundleInfoDict?["CFBundleVersion"] as? String) ?? "-" 38 | let osVersionInfo = ProcessInfo.processInfo.operatingSystemVersionString 39 | let osInfoString = "macOS \(osVersionInfo)" 40 | let macModelIdentifier = self.macModelIdentifier() ?? "-" 41 | let devFolderPath = Files.userDeveloperFolder.path 42 | let isDevFolderExists = XcodeFiles.isDeveloperFolderExists() 43 | let developerFolderReadable = fm.isReadableFile(atPath: devFolderPath) 44 | let developerFolderWriteable = fm.isWritableFile(atPath: devFolderPath) 45 | let customDerivedDataFolder = Preferences.shared.customDerivedDataFolder?.path ?? "-" 46 | let customArchivesFolder = Preferences.shared.customArchivesFolder?.path ?? "-" 47 | 48 | return """ 49 | DevCleaner \(appVersion) (\(appBuildNumber)) 50 | Does '~/Library/Developer' folder exists: \(isDevFolderExists ? "YES" : "NO") 51 | Can read '~/Library/Developer' folder: \(developerFolderReadable ? "YES" : "NO") 52 | Can write '~/Library/Developer' folder: \(developerFolderWriteable ? "YES" : "NO") 53 | 54 | Custom derived data folder: \(customDerivedDataFolder) 55 | Custom archives folder: \(customArchivesFolder) 56 | 57 | System: \(osInfoString) 58 | Mac model: \(macModelIdentifier) 59 | 60 | """ 61 | } 62 | 63 | // MARK: Sending e-mails 64 | private func sendEmail(subject: String, body: String, attachments: [URL]) { 65 | guard let emailService = NSSharingService(named: .composeEmail) else { 66 | log.warning("FeedbackMailer: Can't create compose e-mail NSSharingService") 67 | return 68 | } 69 | 70 | guard let decodedEmailData = Data(base64Encoded: Self.feedbackMailBase64), 71 | let decodedEmail = String(data: decodedEmailData, encoding: .utf8) else { 72 | log.error("FeedbackMailer: Error while decoding support e-mail") 73 | return 74 | } 75 | 76 | var emailItems: [Any] = [] 77 | emailItems.append(body) 78 | if !attachments.isEmpty { // for some reason it won't work if we have empty attachments array 79 | emailItems.append(contentsOf: attachments) 80 | } 81 | 82 | emailService.recipients = [decodedEmail] 83 | emailService.subject = subject 84 | 85 | if emailService.canPerform(withItems: emailItems) { 86 | emailService.perform(withItems: emailItems) 87 | } else { 88 | Alerts.infoAlert(title: "Can't send e-mail", 89 | message: "It seems your system doesn't have e-mail configured. You can send your feedback manually to \(decodedEmail)") 90 | } 91 | } 92 | 93 | public func sendFeedback() { 94 | self.sendEmail(subject: "DevCleaner Feedback", body: "", attachments: []) 95 | } 96 | 97 | public func reportAnIssue() { 98 | // logs attachments 99 | var logAttachments: [URL] = [] 100 | if let currentLogPath = log.logFilePath { logAttachments.append(currentLogPath) } 101 | if let oldLogPath = log.oldLogFilePath { logAttachments.append(oldLogPath) } 102 | 103 | // some system informations 104 | let systemProfile = self.prepareSystemProfileInfo() 105 | 106 | let reportBody = """ 107 | Write your report here 108 | 109 | ================================================= 110 | 111 | \(systemProfile) 112 | """ 113 | 114 | self.sendEmail(subject: "DevCleaner Issue Report", body: reportBody, attachments: logAttachments) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /DevCleaner/Utilities/Version.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Version.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 18.03.2018. 6 | // Copyright © 2018 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Foundation 22 | 23 | // MARK: Version struct 24 | public struct Version { 25 | // MARK: Properties 26 | public let major: UInt 27 | public let minor: UInt 28 | public let patch: UInt? 29 | 30 | // MARK: Initialization 31 | init(major: UInt, minor: UInt, patch: UInt? = nil) { 32 | self.major = major 33 | self.minor = minor 34 | self.patch = patch 35 | } 36 | 37 | init?(string: String) { 38 | let components = string.split(separator: ".", maxSplits: 3, omittingEmptySubsequences: true) 39 | 40 | if components.count == 3 { 41 | if let majorInt = UInt(components[0]) { 42 | self.major = majorInt 43 | } else { 44 | return nil 45 | } 46 | 47 | if let minorInt = UInt(components[1]) { 48 | self.minor = minorInt 49 | } else { 50 | return nil 51 | } 52 | 53 | if let patchInt = UInt(components[2]) { 54 | self.patch = patchInt 55 | } else { 56 | return nil 57 | } 58 | } else if components.count == 2 { 59 | if let majorInt = UInt(components[0]) { 60 | self.major = majorInt 61 | } else { 62 | return nil 63 | } 64 | 65 | if let minorInt = UInt(components[1]) { 66 | self.minor = minorInt 67 | } else { 68 | return nil 69 | } 70 | 71 | self.patch = nil 72 | 73 | } else { 74 | return nil 75 | } 76 | } 77 | 78 | init?(osVersionRawString: String) { 79 | // regex to get version from strings like those: NSOperatingSystemVersion(majorVersion/ 16, minorVersion/ 2, patchVersion/ 0) 80 | let pattern = #"majorVersion[:/]\s*(\d+).*?minorVersion[:/]\s*(\d+).*?patchVersion[:/]\s*(\d+)"# 81 | guard let regex = try? NSRegularExpression(pattern: pattern) else { return nil } 82 | 83 | let range = NSRange(osVersionRawString.startIndex..., in: osVersionRawString) 84 | guard let match = regex.firstMatch(in: osVersionRawString, range: range), match.numberOfRanges == 4 else { return nil } 85 | 86 | let nsString = osVersionRawString as NSString 87 | let major = nsString.substring(with: match.range(at: 1)) 88 | let minor = nsString.substring(with: match.range(at: 2)) 89 | let patch = nsString.substring(with: match.range(at: 3)) 90 | 91 | // if we have "0" in patch, ignore it as Apple convention is to use 16.2 instead of 16.2.0 92 | let versionString: String 93 | if patch == "0" { 94 | versionString = "\(major).\(minor)" 95 | } else { 96 | versionString = "\(major).\(minor).\(patch)" 97 | } 98 | 99 | self.init(string: versionString) 100 | } 101 | 102 | init?(describing: String) { 103 | let isRawOSVersionString = describing.starts(with: "NSOperatingSystemVersion") 104 | switch isRawOSVersionString { 105 | case true: self.init(osVersionRawString: describing) 106 | case false: self.init(string: describing) 107 | } 108 | } 109 | } 110 | 111 | // MARK: - Comparable implementation 112 | extension Version: Comparable { 113 | public static func ==(lhs: Version, rhs: Version) -> Bool { 114 | if lhs.major == rhs.major { 115 | if lhs.minor == rhs.minor { 116 | let lhsPatch = lhs.patch ?? 0 117 | let rhsPatch = rhs.patch ?? 0 118 | 119 | if lhsPatch == rhsPatch { 120 | return true 121 | } 122 | } 123 | } 124 | 125 | return false 126 | } 127 | 128 | public static func <(lhs: Version, rhs: Version) -> Bool { 129 | if lhs.major == rhs.major { 130 | if lhs.minor == rhs.minor { 131 | let lhsPatch = lhs.patch ?? 0 132 | let rhsPatch = rhs.patch ?? 0 133 | 134 | return lhsPatch < rhsPatch 135 | } else { 136 | return lhs.minor < rhs.minor 137 | } 138 | } else { 139 | return lhs.major < rhs.major 140 | } 141 | } 142 | } 143 | 144 | // MARK: - CustomStringConvertible conformance 145 | extension Version: CustomStringConvertible { 146 | public var description: String { 147 | var result = "\(self.major).\(self.minor)" 148 | if let patch = self.patch { 149 | result += ".\(patch)" 150 | } 151 | 152 | return result 153 | } 154 | } 155 | 156 | -------------------------------------------------------------------------------- /DevCleaner/Utilities/Extensions/URL+AcquireAccessFromSandbox.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+AcquireAccessFromSandbox.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 16.09.2018. 6 | // Copyright © 2018 One Minute Games. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | // 13 | // An article that tries to describe intricacies of sanbos file access: 14 | // https://benscheirman.com/2019/10/troubleshooting-appkit-file-permissions/ 15 | // 16 | 17 | extension URL { 18 | private struct SandboxFolderAccessError: Error { 19 | 20 | } 21 | 22 | public func acquireAccessFromSandbox(bookmark: Data? = nil, allowCancel: Bool = true, openPanelMessage: String = "Application needs permission to access this folder") -> URL? { 23 | func doWeHaveAccess(for url: URL) -> Bool { 24 | let fm = FileManager.default 25 | 26 | let isAccesible: Bool 27 | let _ = url.startAccessingSecurityScopedResource() 28 | isAccesible = fm.isReadableFile(atPath: url.path) && fm.isWritableFile(atPath: url.path) 29 | url.stopAccessingSecurityScopedResource() 30 | 31 | return isAccesible 32 | } 33 | 34 | // check if we already have access, then we don't need to show the dialog or use security bookmarks 35 | if doWeHaveAccess(for: self) { 36 | return self 37 | } 38 | 39 | // if we don't have access, so first try to load security bookmark 40 | if let bookmarkData = bookmark { 41 | do { 42 | var isBookmarkStale = false 43 | let bookmarkedUrl = try URL(resolvingBookmarkData: bookmarkData, options: [.withSecurityScope], bookmarkDataIsStale: &isBookmarkStale) 44 | 45 | if !isBookmarkStale { 46 | if doWeHaveAccess(for: bookmarkedUrl) { 47 | return bookmarkedUrl 48 | } else { 49 | log.warning("URL+AcquireAccessFromSandbox: Access denied after using bookmark but bookmark is not stale!") 50 | throw SandboxFolderAccessError() 51 | } 52 | } else { 53 | // refresh bookmark as it's stale, it can happen on some system changes, updates etc. 54 | if let bookmarkData = try? self.bookmarkData(options: [.withSecurityScope]) { 55 | Preferences.shared.setFolderBookmark(bookmarkData: bookmarkData, for: self) 56 | 57 | return self.acquireAccessFromSandbox(bookmark: bookmarkData, allowCancel: allowCancel, openPanelMessage: openPanelMessage) 58 | } else { 59 | log.warning("URL+AcquireAccessFromSandbox: Bookmark was stale, but cannot refresh a bookmark for some reason!") 60 | throw SandboxFolderAccessError() 61 | } 62 | } 63 | } catch(let error) { // in case of stale bookmark or fail to get one, try again without it 64 | log.warning("URL+AcquireAccessFromSandbox: Failed to resolve bookmark: \(error.localizedDescription)") 65 | 66 | return self.acquireAccessFromSandbox(bookmark: nil, allowCancel: allowCancel, openPanelMessage: openPanelMessage) 67 | } 68 | } 69 | 70 | // well, so maybe first acquire the bookmark by opening open panel? 71 | let openPanel = NSOpenPanel() 72 | openPanel.directoryURL = self 73 | openPanel.message = openPanelMessage 74 | openPanel.prompt = "Open" 75 | 76 | openPanel.allowedContentTypes = [] 77 | openPanel.allowsOtherFileTypes = false 78 | openPanel.canChooseDirectories = true 79 | 80 | let openPanelResponse = openPanel.runModal() 81 | 82 | // check if we get proper file & save bookmark to it, if not, repeat 83 | if let folderUrl = openPanel.urls.first { 84 | if folderUrl != self { 85 | Alerts.infoAlert(title: "Can't get access to \(self.path) folder", 86 | message: "Did you choose the right folder?", 87 | okButtonText: "Repeat") 88 | 89 | return self.acquireAccessFromSandbox(bookmark: nil, allowCancel: allowCancel, openPanelMessage: openPanelMessage) 90 | } 91 | 92 | if doWeHaveAccess(for: folderUrl) { 93 | if let bookmarkData = try? folderUrl.bookmarkData(options: [.withSecurityScope]) { 94 | Preferences.shared.setFolderBookmark(bookmarkData: bookmarkData, for: self) 95 | 96 | return folderUrl 97 | } 98 | } else { 99 | // well, we tried but we can't get access to this folder 100 | log.error("URL+AcquireAccessFromSandbox: Can't access folder after selecting it from Open panel, no access: \(self.path)") 101 | 102 | // delete folder bookmark just in case 103 | Preferences.shared.setFolderBookmark(bookmarkData: nil, for: self) 104 | 105 | return nil 106 | } 107 | } else { 108 | log.warning("URL+AcquireAccessFromSandbox: Didn't get folder from Open panel! Modal response: \(openPanelResponse)") 109 | 110 | // if we allow cancel, then legitimately return 111 | if allowCancel { 112 | return nil 113 | } 114 | } 115 | 116 | return self.acquireAccessFromSandbox(bookmark: nil, allowCancel: allowCancel, openPanelMessage: openPanelMessage) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /DevCleaner/Model/Entries/DeviceSupportFileEntry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceSupportFileEntry.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 27.03.2018. 6 | // Copyright © 2018 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Foundation 22 | 23 | public final class DeviceSupportFileEntry: XcodeFileEntry { 24 | // MARK: Types 25 | public enum OSType { 26 | case iOS, watchOS, tvOS, macOS, visionOS, other 27 | 28 | public init(label: String) { 29 | switch label { 30 | case "iOS": 31 | self = .iOS 32 | case "watchOS": 33 | self = .watchOS 34 | case "tvOS": 35 | self = .tvOS 36 | case "macOS": 37 | self = .macOS 38 | case "visionOS": 39 | self = .visionOS 40 | default: 41 | self = .other 42 | } 43 | } 44 | 45 | public var description: String { 46 | switch self { 47 | case .iOS: 48 | return "iOS" 49 | case .watchOS: 50 | return "watchOS" 51 | case .tvOS: 52 | return "tvOS" 53 | case .macOS: 54 | return "macOS" 55 | case .visionOS: 56 | return "visionOS" 57 | case .other: 58 | return "" 59 | } 60 | } 61 | } 62 | 63 | // MARK: Properties 64 | public let device: String? 65 | public let osType: OSType 66 | public let version: Version 67 | public let build: AppleBuild? 68 | public let date: Date 69 | public let architecture: String? 70 | 71 | // MARK: Initialization 72 | public init(device: String?, osType: OSType, version: Version, build: AppleBuild?, date: Date, arch: String?, selected: Bool) { 73 | self.device = device 74 | self.osType = osType 75 | self.version = version 76 | self.build = build 77 | self.date = date 78 | self.architecture = arch 79 | 80 | let label = Self.label(for: osType, device: device, version: version, build: build) 81 | let icon = Self.icon(for: osType, version: version) 82 | let tooltip = label + " " + DateFormatter.localizedString(from: self.date, dateStyle: .medium, timeStyle: .none) 83 | 84 | super.init(label: label, tooltipText: tooltip, icon: icon, tooltip: true, selected: selected) 85 | } 86 | 87 | // MARK: Helpers 88 | private static func label(for os: OSType, device: String?, version: Version, build: AppleBuild?) -> String { 89 | let appleBuild = build ?? .empty 90 | 91 | var result = "\(os.description) \(version) \(appleBuild)" 92 | if let device { 93 | result = result + " (\(Self.modelName(from: device)))" 94 | } 95 | 96 | return result 97 | } 98 | 99 | private static func icon(for os: OSType, version: Version) -> Icon { 100 | var result: Icon 101 | 102 | switch os { 103 | case .iOS: 104 | if version.major >= 2 && version.major <= 18 || version.major == 26 { 105 | result = .image(name: "OS/iOS/\(version.major)") 106 | } else { 107 | result = .image(name: "OS/iOS/Generic") 108 | } 109 | 110 | case .watchOS: 111 | if version.major >= 2 && version.major <= 11 || version.major == 26 { 112 | result = .image(name: "OS/watchOS/\(version.major)") 113 | } else { 114 | result = .image(name: "OS/watchOS/Generic") 115 | } 116 | 117 | case .tvOS: 118 | if version.major >= 9 && version.major <= 18 || version.major == 26 { 119 | result = .image(name: "OS/tvOS/\(version.major)") 120 | } else { 121 | result = .image(name: "OS/tvOS/Generic") 122 | } 123 | 124 | case .macOS: 125 | if version.major >= 12 && version.major <= 15 || version.major == 26 { 126 | result = .image(name: "OS/macOS/\(version.major)") 127 | } else { 128 | result = .image(name: "OS/macOS/Generic") 129 | } 130 | 131 | case .visionOS: 132 | if version.major >= 2 && version.major <= 2 || version.major == 26 { 133 | result = .image(name: "OS/visionOS/\(version.major)") 134 | } else { 135 | result = .image(name: "OS/visionOS/Generic") 136 | } 137 | 138 | default: 139 | result = .image(name: "OS/iOS/Generic") 140 | } 141 | 142 | return result 143 | } 144 | 145 | // MARK: Map device model to device name 146 | private static func modelName(from deviceId: String) -> String { 147 | return AppleDevices.deviceName(for: deviceId) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /DevCleaner/View Controllers/Main Window/CleaningViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CleaningViewController.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 29.04.2018. 6 | // Copyright © 2018 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Cocoa 22 | 23 | // MARK: Cleaning view controller delegate 24 | internal protocol CleaningViewControllerDelegate: AnyObject { 25 | func cleaningDidFinish(_ vc: CleaningViewController) 26 | } 27 | 28 | // MARK: - Cleaning view controller 29 | internal final class CleaningViewController: NSViewController { 30 | // MARK: Types 31 | internal enum State { 32 | case undefined 33 | 34 | case idle(title: String, progress: Double) 35 | case working(title: String, details: String, progress: Double) 36 | } 37 | 38 | // MARK: Properties & outlets 39 | @IBOutlet private weak var headerLabel: NSTextField! 40 | @IBOutlet private weak var currentFileLabel: NSTextField! 41 | @IBOutlet private weak var progressIndicator: NSProgressIndicator! 42 | 43 | internal weak var delegate: CleaningViewControllerDelegate? 44 | 45 | internal var state: State = .undefined { 46 | didSet { 47 | if self.isViewLoaded { 48 | self.update(state: self.state) 49 | } 50 | } 51 | } 52 | 53 | // MARK: Initialization & overrides 54 | override func viewDidLoad() { 55 | super.viewDidLoad() 56 | 57 | // update first state we set 58 | self.update(state: self.state) 59 | 60 | // check if we are in dry run and mark it 61 | let dryRunText = Preferences.shared.dryRunEnabled ? "(Dry run) " : String() 62 | self.headerLabel.stringValue = "\(dryRunText)Cleaning Xcode cache files..." 63 | } 64 | 65 | override func viewDidAppear() { 66 | super.viewDidAppear() 67 | 68 | self.view.window?.styleMask.remove(.resizable) 69 | } 70 | 71 | // MARK: Updating state 72 | private func update(state: State) { 73 | switch state { 74 | case .idle(let title, let progress): 75 | self.currentFileLabel.stringValue = title 76 | 77 | self.progressIndicator.isIndeterminate = false 78 | self.progressIndicator.stopAnimation(self) 79 | 80 | self.progressIndicator.doubleValue = progress 81 | 82 | case .working(let title, let details, let progress): 83 | self.currentFileLabel.stringValue = "\(title): \(details)" 84 | 85 | self.progressIndicator.isIndeterminate = false 86 | self.progressIndicator.stopAnimation(self) 87 | 88 | self.progressIndicator.doubleValue = progress 89 | 90 | case .undefined: 91 | assert(false, "CleaningViewController: Cannot update to state 'undefined'") 92 | } 93 | } 94 | 95 | // MARK: Action 96 | @IBAction func dismissCleaningView(_ sender: Any) { 97 | self.dismiss(sender) 98 | 99 | self.delegate?.cleaningDidFinish(self) 100 | } 101 | 102 | @IBAction func stopCleaning(_ sender: Any) { 103 | self.dismiss(sender) 104 | 105 | self.delegate?.cleaningDidFinish(self) 106 | } 107 | } 108 | 109 | extension CleaningViewController: XcodeFilesDeleteDelegate { 110 | func deleteWillBegin(xcodeFiles: XcodeFiles) { 111 | DispatchQueue.main.async { 112 | self.state = .idle(title: "Initialization...", progress: 0.0) 113 | } 114 | } 115 | 116 | func deleteInProgress(xcodeFiles: XcodeFiles, location: String, label: String, url: URL?, current: Int, total: Int) { 117 | let progress = Double(current) / Double(total) * 100.0 118 | DispatchQueue.main.async { 119 | self.state = .working(title: location.capitalized, details: label, progress: progress) 120 | } 121 | } 122 | 123 | func deleteItemFailed(xcodeFiles: XcodeFiles, error: Error, location: String, label: String, url: URL?) { 124 | // prepare error message 125 | let message = """ 126 | Following file couldn't be removed:\n\(location.capitalized): \(url?.path ?? "-")\n\n 127 | \(error.localizedDescription) 128 | """ 129 | 130 | // show error message 131 | DispatchQueue.main.async { 132 | let alert = NSAlert() 133 | alert.alertStyle = .critical 134 | alert.messageText = "Failed to delete item" 135 | alert.informativeText = message 136 | alert.addButton(withTitle: "OK") 137 | alert.runModal() 138 | } 139 | } 140 | 141 | func deleteDidFinish(xcodeFiles: XcodeFiles) { 142 | DispatchQueue.main.async { 143 | self.state = .idle(title: "Finished!", progress: 100.0) 144 | 145 | // wait a little bit and then dismiss to avoid too abtrupt transition 146 | DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 0.5) { 147 | self.dismiss(self) 148 | self.delegate?.cleaningDidFinish(self) 149 | } 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /DevCleaner/View Controllers/Main Window/Views/XcodeEntryCellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XcodeEntryCellView.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 14.04.2018. 6 | // Copyright © 2018 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Cocoa 22 | 23 | // MARK: Xcode Entry Cell View Delegate 24 | protocol XcodeEntryCellViewDelegate: AnyObject { 25 | func xcodeEntryCellSelectedChanged(_ cell: XcodeEntryCellView, state: NSControl.StateValue, xcodeEntry: XcodeFileEntry?) 26 | } 27 | 28 | // MARK: Xcode Entry Cell View 29 | final class XcodeEntryCellView: NSTableCellView { 30 | // MARK: Properties & outlets 31 | @IBOutlet private weak var checkBox: NSButton! 32 | 33 | internal weak var delegate: XcodeEntryCellViewDelegate? = nil 34 | 35 | internal private(set) weak var entry: XcodeFileEntry? 36 | 37 | // MARK: Setup 38 | internal func setup(with xcodeEntry: XcodeFileEntry, delegate: XcodeEntryCellViewDelegate) { 39 | // reassing entry 40 | self.entry = xcodeEntry 41 | 42 | // delegate 43 | self.delegate = delegate 44 | 45 | // checkbox 46 | self.checkBox.state = self.entrySelectionToControlState(xcodeEntry.selection) 47 | 48 | // label 49 | self.textField?.font = NSFont.monospacedDigitSystemFont(ofSize: self.textField?.font?.pointSize ?? 13, weight: .regular) 50 | self.textField?.attributedStringValue = self.attributedString(for: xcodeEntry) 51 | self.textField?.sizeToFit() 52 | 53 | // tooltip 54 | if xcodeEntry.tooltip { 55 | self.toolTip = xcodeEntry.tooltipText 56 | } else { 57 | self.toolTip = nil 58 | } 59 | 60 | // icon 61 | self.imageView?.image = self.iconForEntry(xcodeEntry) 62 | 63 | // disable if no children and path 64 | if xcodeEntry.isEmpty { 65 | self.checkBox.isEnabled = false 66 | self.checkBox.state = .off 67 | 68 | self.imageView?.isEnabled = false 69 | self.textField?.isEnabled = false 70 | } 71 | } 72 | 73 | // MARK: Helpers 74 | override func prepareForReuse() { 75 | super.prepareForReuse() 76 | 77 | self.checkBox.isEnabled = true 78 | self.checkBox.state = .off 79 | 80 | self.imageView?.isEnabled = true 81 | self.textField?.isEnabled = true 82 | } 83 | 84 | private func attributedString(for xcodeEntry: XcodeFileEntry) -> NSAttributedString { 85 | let result = NSMutableAttributedString() 86 | 87 | // label 88 | let label = NSAttributedString(string: xcodeEntry.label) 89 | result.append(label) 90 | 91 | // extra info if present 92 | if !xcodeEntry.extraInfo.isEmpty { 93 | let extraInfo = NSAttributedString(string: " " + xcodeEntry.extraInfo, attributes: [ 94 | NSAttributedString.Key.foregroundColor: NSColor.secondaryLabelColor 95 | ]) 96 | result.append(extraInfo) 97 | } 98 | 99 | // add truncating options 100 | let style = NSMutableParagraphStyle() 101 | style.lineBreakMode = .byTruncatingTail 102 | result.addAttributes([NSAttributedString.Key.paragraphStyle: style], range: NSRange(location: 0, length: result.length)) 103 | 104 | return result 105 | } 106 | 107 | private func entrySelectionToControlState(_ entrySelection: XcodeFileEntry.Selection) -> NSControl.StateValue { 108 | switch entrySelection { 109 | case .on: 110 | return .on 111 | case .off: 112 | return .off 113 | case .mixed: 114 | return .mixed 115 | } 116 | } 117 | 118 | private func iconForEntry(_ xcodeEntry: XcodeFileEntry) -> NSImage? { 119 | let result: NSImage? 120 | 121 | if let entryIcon = xcodeEntry.icon { 122 | switch entryIcon { 123 | case .path(let url): 124 | result = NSImage(byReferencing: url) 125 | case .image(let name): 126 | result = NSImage(imageLiteralResourceName: name) 127 | case .system(let name): 128 | result = NSImage(named: name) 129 | } 130 | } else { 131 | result = nil 132 | } 133 | 134 | return result 135 | } 136 | 137 | // MARK: Actions 138 | internal func toggleSelection() { 139 | let targetStateValue: NSControl.StateValue 140 | if self.checkBox.state == .on { 141 | targetStateValue = .off 142 | } else { 143 | targetStateValue = .on 144 | } 145 | 146 | self.checkBox.state = targetStateValue 147 | self.delegate?.xcodeEntryCellSelectedChanged(self, state: targetStateValue, xcodeEntry: self.entry) 148 | } 149 | 150 | @IBAction func checkBoxSwitched(_ sender: NSButton) { 151 | // when we click, disallow mixed state 152 | if sender.state == .mixed { 153 | sender.setNextState() 154 | } 155 | 156 | self.delegate?.xcodeEntryCellSelectedChanged(self, state: sender.state, xcodeEntry: self.entry) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /DevCleaner/Utilities/Extensions/FileManager+DirectorySize.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileManager+DirectorySize.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 18.03.2018. 6 | // Copyright © 2018 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | // 22 | // Original code: 23 | // 24 | // Created by Nikolai Ruhe on 2016-02-10. 25 | // Copyright (c) 2016 Nikolai Ruhe. All rights reserved. 26 | // 27 | // https://gist.github.com/NikolaiRuhe/eeb135d20c84a7097516 28 | // https://gist.github.com/blender/a75f589e6bd86aa2121618155cbdf827 29 | // 30 | 31 | import Foundation 32 | 33 | extension FileManager { 34 | /// This method calculates the accumulated size of a directory on the volume in bytes. 35 | /// 36 | /// As there's no simple way to get this information from the file system it has to crawl the entire hierarchy, 37 | /// accumulating the overall sum on the way. The resulting value is roughly equivalent with the amount of bytes 38 | /// that would become available on the volume if the directory would be deleted. 39 | /// 40 | /// - note: There are a couple of oddities that are not taken into account (like symbolic links, meta data of 41 | /// directories, hard links, ...). 42 | public func allocatedSizeOfDirectory(atUrl url: URL) throws -> Int64 { 43 | // We'll sum up content size here: 44 | var accumulatedSize: Int64 = 0 45 | 46 | // prefetching some properties during traversal will speed up things a bit. 47 | let prefetchedProperties: [URLResourceKey] = [ 48 | .isRegularFileKey, 49 | .fileAllocatedSizeKey, 50 | .totalFileAllocatedSizeKey 51 | ] 52 | 53 | // The error handler simply signals errors to outside code. 54 | var errorDidOccur: Error? 55 | let errorHandler: (URL, Error) -> Bool = { _, error in 56 | errorDidOccur = error 57 | return false 58 | } 59 | 60 | 61 | // We have to enumerate all directory contents, including subdirectories. 62 | let enumerator = self.enumerator(at: url, 63 | includingPropertiesForKeys: prefetchedProperties, 64 | options: FileManager.DirectoryEnumerationOptions.init(rawValue: 0), 65 | errorHandler: errorHandler) 66 | 67 | // Start the traversal: 68 | while let contentURL = (enumerator?.nextObject() as? URL) { 69 | // Bail out on errors from the errorHandler. 70 | if let error = errorDidOccur { throw error } 71 | 72 | // Get the type of this item, making sure we only sum up sizes of regular files. 73 | let isRegularFileResourceValues = try contentURL.resourceValues(forKeys: [.isRegularFileKey]) 74 | 75 | guard isRegularFileResourceValues.isRegularFile ?? false else { 76 | continue 77 | } 78 | 79 | // Get size values only if we're sure we calculating file size 80 | let resourceValues = try contentURL.resourceValues(forKeys: [.fileAllocatedSizeKey, .totalFileAllocatedSizeKey]) 81 | 82 | // To get the file's size we first try the most comprehensive value in terms of what the file may use on disk. 83 | // This includes metadata, compression (on file system level) and block size. 84 | var fileSize = resourceValues.totalFileAllocatedSize 85 | 86 | // In case the value is unavailable we use the fallback value (excluding meta data and compression) 87 | // This value should always be available. 88 | fileSize = fileSize ?? resourceValues.fileAllocatedSize 89 | 90 | // We're good, add up the value. 91 | accumulatedSize += Int64(fileSize ?? 0) 92 | } 93 | 94 | // Bail out on errors from the errorHandler. 95 | if let error = errorDidOccur { throw error } 96 | 97 | // We finally got it. 98 | return accumulatedSize 99 | } 100 | 101 | public func allocatedSizeOfFile(at url: URL) throws -> Int64 { 102 | // Get the type of this item, making sure we only sum up sizes of regular files. 103 | let resourceValues = try url.resourceValues(forKeys: [.isRegularFileKey, .totalFileAllocatedSizeKey, .fileAllocatedSizeKey]) 104 | 105 | guard resourceValues.isRegularFile ?? false else { 106 | return 0 107 | } 108 | 109 | // To get the file's size we first try the most comprehensive value in terms of what the file may use on disk. 110 | // This includes metadata, compression (on file system level) and block size. 111 | var fileSize = resourceValues.totalFileAllocatedSize 112 | 113 | // In case the value is unavailable we use the fallback value (excluding meta data and compression) 114 | // This value should always be available. 115 | fileSize = fileSize ?? resourceValues.fileAllocatedSize 116 | 117 | return Int64(fileSize ?? 0) 118 | } 119 | 120 | public func volumeFreeDiskSpace(at url: URL) throws -> Int64 { 121 | do { 122 | let values = try url.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey]) 123 | if let capacity = values.volumeAvailableCapacityForImportantUsage { 124 | return capacity 125 | } 126 | } catch let error { 127 | log.warning("FileManager+DirectorySize: Problem while requesting volume capacity: \(error)") 128 | } 129 | 130 | return 0 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /DevCleaner/Utilities/ArgumentsParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArgumentsParser.swift 3 | // DevCleaner 4 | // 5 | // Created by Konrad Kołakowski on 24/08/2019. 6 | // Copyright © 2019 One Minute Games. All rights reserved. 7 | // 8 | // DevCleaner is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation; either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // DevCleaner is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with DevCleaner. If not, see . 20 | 21 | import Foundation 22 | 23 | // If you ever want to publish this as a separate library (or part of some utils code) 24 | // it will require some improvements: 25 | // 26 | // - support for "commands" in a separation of options 27 | // - built-in parsing of values 28 | // - shorthands 29 | // - improved help printing, with better tabulation (check out "swift" command help output) 30 | 31 | // MARK: Command line options 32 | public protocol CommandLineOption { 33 | var name: String {get} 34 | var description: String {get} 35 | 36 | var enabled: Bool {get} 37 | } 38 | 39 | public struct OptionWithValue: CommandLineOption { 40 | public let name: String 41 | public let description: String 42 | 43 | public let possibleValues: [String]? 44 | public var value: String? 45 | 46 | public var enabled: Bool { 47 | return self.value != nil 48 | } 49 | } 50 | 51 | public struct Option: CommandLineOption { 52 | public let name: String 53 | public let description: String 54 | 55 | public internal(set) var enabled: Bool 56 | } 57 | 58 | // MARK: - Arguments parser 59 | public class ArgumentsParser { 60 | // MARK: Types 61 | public enum Error: Swift.Error { 62 | case wrongArgument(name: String), noValue(optionName: String), insufficientArguments 63 | } 64 | 65 | // MARK: Properties 66 | private let toolName: String 67 | private let description: String 68 | private var options: [CommandLineOption] 69 | 70 | // MARK: Initialization 71 | public init(toolName: String? = nil, description: String) { 72 | self.toolName = toolName ?? ProcessInfo.processInfo.processName 73 | self.description = description 74 | self.options = [] 75 | } 76 | 77 | // MARK: Adding arguments 78 | public func addOption(name: String, description: String) { 79 | let option = Option(name: name, description: description, enabled: false) 80 | self.options.append(option) 81 | } 82 | 83 | public func addOptionWithValue(name: String, description: String, possibleValues: [String]? = nil) { 84 | let option = OptionWithValue(name: name, description: description, possibleValues: possibleValues, value: nil) 85 | self.options.append(option) 86 | } 87 | 88 | // MARK: Parsing 89 | public func parse(using args: [String]) throws -> [CommandLineOption] { 90 | // we have options but non were given 91 | if self.options.count > 0 && args.count == 1 { 92 | throw Error.insufficientArguments 93 | } else if self.options.count == 0 { 94 | return [] // if no options then we can just return an empty array early on 95 | } 96 | 97 | var results = [CommandLineOption]() 98 | 99 | var i = 1 // ignore first one as its a name of program 100 | while i < args.count { 101 | let arg = args[i] 102 | 103 | var currentArgParsed = false 104 | for option in self.options { 105 | if arg == option.name && !results.contains(where: { $0.name == option.name }) { 106 | // option with value 107 | if var currentOptionWithValue = option as? OptionWithValue { 108 | // find and parse value 109 | let nextArgIndex = i + 1 110 | if nextArgIndex < args.count { 111 | currentOptionWithValue.value = args[nextArgIndex] 112 | 113 | i = nextArgIndex 114 | } else { 115 | throw Error.noValue(optionName: currentOptionWithValue.name) 116 | } 117 | 118 | results.append(currentOptionWithValue) 119 | } 120 | // just option 121 | else if var currentNewOption = option as? Option { 122 | currentNewOption.enabled = true 123 | results.append(currentNewOption) 124 | } 125 | 126 | currentArgParsed = true 127 | break 128 | } 129 | } 130 | 131 | if !currentArgParsed { 132 | throw Error.wrongArgument(name: arg) 133 | } 134 | 135 | i += 1 136 | } 137 | 138 | return results 139 | } 140 | 141 | // MARK: Utilities 142 | public func printHelp() { 143 | print("OVERVIEW: \(self.description)\n") 144 | print("USAGE: \(self.toolName + " [options]")\n") 145 | print("OPTIONS:\n") 146 | 147 | for option in self.options { 148 | if let optionWithValue = option as? OptionWithValue { 149 | let valueString: String 150 | if let possibleValues = optionWithValue.possibleValues { 151 | valueString = "<\(possibleValues.reduce("") { $0 + $1 + "," }.dropLast(1))>" 152 | } else { 153 | valueString = "" 154 | } 155 | 156 | print(" \(optionWithValue.name) \(valueString)") 157 | print("\t\(optionWithValue.description)") 158 | } else { 159 | print(" \(option.name)") 160 | print("\t\(option.description)") 161 | } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /DevCleaner/View Controllers/Command Line Install Window/CommandLineInstall.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | You can use DevCleaner from the command line. To make it easier, you can link command line tool script to one of your directories in PATH, for example /usr/local/bin. 59 | 60 | 

 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | --------------------------------------------------------------------------------