1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-04 16:49:41 +00:00

test: add checks for uninstallable Apple apps in should_protect_from_uninstall function

This commit is contained in:
tw93
2026-01-30 13:57:43 +08:00
parent e96868824b
commit 2865a788e8
2 changed files with 457 additions and 336 deletions

View File

@@ -20,20 +20,92 @@ fi
# Application Management # Application Management
# Critical system components protected from uninstallation # Critical system components protected from uninstallation
# Note: We explicitly list system components instead of using "com.apple.*" wildcard
# to allow uninstallation of user-installed Apple apps (Xcode, Final Cut Pro, etc.)
readonly SYSTEM_CRITICAL_BUNDLES=( readonly SYSTEM_CRITICAL_BUNDLES=(
"com.apple.*" # System essentials # Core system applications (in /System/Applications/)
"com.apple.finder"
"com.apple.dock"
"com.apple.Safari"
"com.apple.mail"
"com.apple.systempreferences"
"com.apple.SystemSettings"
"com.apple.Settings*"
"com.apple.controlcenter*"
"com.apple.Spotlight"
"com.apple.notificationcenterui"
"com.apple.loginwindow"
"com.apple.Preview"
"com.apple.TextEdit"
"com.apple.Notes"
"com.apple.reminders"
"com.apple.iCal"
"com.apple.AddressBook"
"com.apple.Photos"
"com.apple.AppStore"
"com.apple.calculator"
"com.apple.Dictionary"
"com.apple.ScreenSharing"
"com.apple.ActivityMonitor"
"com.apple.Console"
"com.apple.DiskUtility"
"com.apple.KeychainAccess"
"com.apple.DigitalColorMeter"
"com.apple.grapher"
"com.apple.Terminal"
"com.apple.ScriptEditor2"
"com.apple.VoiceOverUtility"
"com.apple.BluetoothFileExchange"
"com.apple.print.PrinterProxy"
"com.apple.systempreferences*"
"com.apple.SystemProfiler"
"com.apple.FontBook"
"com.apple.ColorSyncUtility"
"com.apple.audio.AudioMIDISetup"
"com.apple.DirectoryUtility"
"com.apple.NetworkUtility"
"com.apple.exposelauncher"
"com.apple.MigrateAssistant"
"com.apple.RAIDUtility"
"com.apple.BootCampAssistant"
# System services and daemons
"com.apple.SecurityAgent"
"com.apple.CoreServices*"
"com.apple.SystemUIServer"
"com.apple.backgroundtaskmanagement*"
"com.apple.loginitems*"
"com.apple.sharedfilelist*"
"com.apple.sfl*"
"com.apple.coreservices*"
"com.apple.metadata*"
"com.apple.MobileSoftwareUpdate*"
"com.apple.SoftwareUpdate*"
"com.apple.installer*"
"com.apple.frameworks*"
"com.apple.security*"
"com.apple.keychain*"
"com.apple.trustd*"
"com.apple.securityd*"
"com.apple.cloudd*"
"com.apple.iCloud*"
"com.apple.WiFi*"
"com.apple.airport*"
"com.apple.Bluetooth*"
# Input methods (system built-in)
"com.apple.inputmethod.*"
"com.apple.inputsource*"
"com.apple.TextInput*"
"com.apple.CharacterPicker*"
"com.apple.PressAndHold*"
# Legacy pattern-based entries (non com.apple.*)
"loginwindow" "loginwindow"
"dock" "dock"
"systempreferences" "systempreferences"
"finder" "finder"
"safari" "safari"
"com.apple.Settings*"
"com.apple.SystemSettings*"
"com.apple.controlcenter*"
"com.apple.backgroundtaskmanagement*"
"com.apple.loginitems*"
"com.apple.sharedfilelist*"
"com.apple.sfl*"
"backgroundtaskmanagementagent" "backgroundtaskmanagementagent"
"keychain*" "keychain*"
"security*" "security*"
@@ -55,11 +127,22 @@ readonly SYSTEM_CRITICAL_BUNDLES=(
"KeyLayout*" "KeyLayout*"
"GlobalPreferences" "GlobalPreferences"
".GlobalPreferences" ".GlobalPreferences"
"com.apple.inputmethod.*"
"org.pqrs.Karabiner*" "org.pqrs.Karabiner*"
"com.apple.inputsource*" )
"com.apple.TextInputMenuAgent"
"com.apple.TextInputSwitcher" # Apple apps that CAN be uninstalled (from App Store or developer.apple.com)
readonly APPLE_UNINSTALLABLE_APPS=(
"com.apple.dt.*" # Xcode, Instruments, FileMerge
"com.apple.FinalCut*" # Final Cut Pro
"com.apple.Motion"
"com.apple.Compressor"
"com.apple.logic*" # Logic Pro
"com.apple.garageband*" # GarageBand
"com.apple.iMovie"
"com.apple.iWork.*" # Pages, Numbers, Keynote
"com.apple.MainStage*"
"com.apple.server.*" # macOS Server
"com.apple.Playgrounds" # Swift Playgrounds
) )
# Applications with sensitive data; protected during cleanup but removable # Applications with sensitive data; protected during cleanup but removable
@@ -74,360 +157,355 @@ readonly DATA_PROTECTED_BUNDLES=(
"*.InputMethod" "*.InputMethod"
"*IME" "*IME"
# System Utilities & Cleanup Tools # System Utilities & Cleanup
"com.nektony.*" # App Cleaner & Uninstaller "com.nektony.*"
"com.macpaw.*" # CleanMyMac, CleanMaster "com.macpaw.*"
"com.freemacsoft.AppCleaner" # AppCleaner "com.freemacsoft.AppCleaner"
"com.omnigroup.omnidisksweeper" # OmniDiskSweeper "com.omnigroup.omnidisksweeper"
"com.daisydiskapp.*" # DaisyDisk "com.daisydiskapp.*"
"com.tunabellysoftware.*" # Disk Utility apps "com.tunabellysoftware.*"
"com.grandperspectiv.*" # GrandPerspective "com.grandperspectiv.*"
"com.binaryfruit.*" # FusionCast "com.binaryfruit.*"
# Password Managers & Security # Password Managers
"com.1password.*" # 1Password "com.1password.*"
"com.agilebits.*" # 1Password legacy "com.agilebits.*"
"com.lastpass.*" # LastPass "com.lastpass.*"
"com.dashlane.*" # Dashlane "com.dashlane.*"
"com.bitwarden.*" # Bitwarden "com.bitwarden.*"
"com.keepassx.*" # KeePassXC (Legacy) "com.keepassx.*"
"org.keepassx.*" # KeePassX "org.keepassx.*"
"org.keepassxc.*" # KeePassXC "org.keepassxc.*"
"com.authy.*" # Authy "com.authy.*"
"com.yubico.*" # YubiKey Manager "com.yubico.*"
# Development Tools - IDEs & Editors # IDEs & Editors
"com.jetbrains.*" # JetBrains IDEs (IntelliJ, DataGrip, etc.) "com.jetbrains.*"
"JetBrains*" # JetBrains Application Support folders "JetBrains*"
"com.microsoft.VSCode" # Visual Studio Code "com.microsoft.VSCode"
"com.visualstudio.code.*" # VS Code variants "com.visualstudio.code.*"
"com.sublimetext.*" # Sublime Text "com.sublimetext.*"
"com.sublimehq.*" # Sublime Merge "com.sublimehq.*"
"com.microsoft.VSCodeInsiders" # VS Code Insiders "com.microsoft.VSCodeInsiders"
"com.apple.dt.Xcode" # Xcode (keep settings) "com.apple.dt.Xcode"
"com.coteditor.CotEditor" # CotEditor "com.coteditor.CotEditor"
"com.macromates.TextMate" # TextMate "com.macromates.TextMate"
"com.panic.Nova" # Nova "com.panic.Nova"
"abnerworks.Typora" # Typora (Markdown editor) "abnerworks.Typora"
"com.uranusjr.macdown" # MacDown "com.uranusjr.macdown"
# AI & LLM Tools # AI & LLM Tools
"com.todesktop.*" # Cursor (often uses generic todesktop ID) "com.todesktop.*"
"Cursor" # Cursor App Support "Cursor"
"com.anthropic.claude*" # Claude "com.anthropic.claude*"
"Claude" # Claude App Support "Claude"
"com.openai.chat*" # ChatGPT "com.openai.chat*"
"ChatGPT" # ChatGPT App Support "ChatGPT"
"com.ollama.ollama" # Ollama "com.ollama.ollama"
"Ollama" # Ollama App Support "Ollama"
"com.lmstudio.lmstudio" # LM Studio "com.lmstudio.lmstudio"
"LM Studio" # LM Studio App Support "LM Studio"
"co.supertool.chatbox" # Chatbox "co.supertool.chatbox"
"page.jan.jan" # Jan "page.jan.jan"
"com.huggingface.huggingchat" # HuggingChat "com.huggingface.huggingchat"
"Gemini" # Gemini "Gemini"
"com.perplexity.Perplexity" # Perplexity "com.perplexity.Perplexity"
"com.drawthings.DrawThings" # Draw Things "com.drawthings.DrawThings"
"com.divamgupta.diffusionbee" # DiffusionBee "com.divamgupta.diffusionbee"
"com.exafunction.windsurf" # Windsurf "com.exafunction.windsurf"
"com.quora.poe.electron" # Poe "com.quora.poe.electron"
"chat.openai.com.*" # OpenAI web wrappers "chat.openai.com.*"
# Development Tools - Database Clients # Database Clients
"com.sequelpro.*" # Sequel Pro "com.sequelpro.*"
"com.sequel-ace.*" # Sequel Ace "com.sequel-ace.*"
"com.tinyapp.*" # TablePlus "com.tinyapp.*"
"com.dbeaver.*" # DBeaver "com.dbeaver.*"
"com.navicat.*" # Navicat "com.navicat.*"
"com.mongodb.compass" # MongoDB Compass "com.mongodb.compass"
"com.redis.RedisInsight" # Redis Insight "com.redis.RedisInsight"
"com.pgadmin.pgadmin4" # pgAdmin "com.pgadmin.pgadmin4"
"com.eggerapps.Sequel-Pro" # Sequel Pro legacy "com.eggerapps.Sequel-Pro"
"com.valentina-db.Valentina-Studio" # Valentina Studio "com.valentina-db.Valentina-Studio"
"com.dbvis.DbVisualizer" # DbVisualizer "com.dbvis.DbVisualizer"
# Development Tools - API & Network # API & Network Tools
"com.postmanlabs.mac" # Postman "com.postmanlabs.mac"
"com.konghq.insomnia" # Insomnia "com.konghq.insomnia"
"com.CharlesProxy.*" # Charles Proxy "com.CharlesProxy.*"
"com.proxyman.*" # Proxyman "com.proxyman.*"
"com.getpaw.*" # Paw "com.getpaw.*"
"com.luckymarmot.Paw" # Paw legacy "com.luckymarmot.Paw"
"com.charlesproxy.charles" # Charles "com.charlesproxy.charles"
"com.telerik.Fiddler" # Fiddler "com.telerik.Fiddler"
"com.usebruno.app" # Bruno (API client) "com.usebruno.app"
# Network Proxy & VPN Tools (pattern-based protection) # Network Proxy & VPN Tools
# Clash variants "*clash*"
"*clash*" # All Clash variants (ClashX, ClashX Pro, Clash Verge, etc) "*Clash*"
"*Clash*" # Capitalized variants "com.nssurge.surge-mac"
"com.nssurge.surge-mac" # Surge "*surge*"
"*surge*" # Surge variants "*Surge*"
"*Surge*" # Surge variants "mihomo*"
"mihomo*" # Mihomo Party and variants "*openvpn*"
"*openvpn*" # OpenVPN Connect and variants "*OpenVPN*"
"*OpenVPN*" # OpenVPN capitalized variants "net.openvpn.*"
"net.openvpn.*" # OpenVPN bundle IDs
# Proxy Clients (Shadowsocks, V2Ray, etc) # Proxy Clients
"*ShadowsocksX-NG*" # ShadowsocksX-NG "*ShadowsocksX-NG*"
"com.qiuyuzhou.*" # ShadowsocksX-NG bundle "com.qiuyuzhou.*"
"*v2ray*" # V2Ray variants "*v2ray*"
"*V2Ray*" # V2Ray variants "*V2Ray*"
"*v2box*" # V2Box "*v2box*"
"*V2Box*" # V2Box "*V2Box*"
"*nekoray*" # Nekoray "*nekoray*"
"*sing-box*" # Sing-box "*sing-box*"
"*OneBox*" # OneBox "*OneBox*"
"*hiddify*" # Hiddify "*hiddify*"
"*Hiddify*" # Hiddify "*Hiddify*"
"*loon*" # Loon "*loon*"
"*Loon*" # Loon "*Loon*"
"*quantumult*" # Quantumult X "*quantumult*"
# Mesh & Corporate VPNs # Mesh & Corporate VPNs
"*tailscale*" # Tailscale "*tailscale*"
"io.tailscale.*" # Tailscale bundle "io.tailscale.*"
"*zerotier*" # ZeroTier "*zerotier*"
"com.zerotier.*" # ZeroTier bundle "com.zerotier.*"
"*1dot1dot1dot1*" # Cloudflare WARP "*1dot1dot1dot1*" # Cloudflare WARP
"*cloudflare*warp*" # Cloudflare WARP "*cloudflare*warp*"
# Commercial VPNs # Commercial VPNs
"*nordvpn*" # NordVPN "*nordvpn*"
"*expressvpn*" # ExpressVPN "*expressvpn*"
"*protonvpn*" # ProtonVPN "*protonvpn*"
"*surfshark*" # Surfshark "*surfshark*"
"*windscribe*" # Windscribe "*windscribe*"
"*mullvad*" # Mullvad "*mullvad*"
"*privateinternetaccess*" # PIA "*privateinternetaccess*"
# Screensaver & Dynamic Wallpaper # Screensaver & Wallpaper
"*Aerial*" # Aerial screensaver (all case variants) "*Aerial*"
"*aerial*" # Aerial lowercase "*aerial*"
"*Fliqlo*" # Fliqlo screensaver (all case variants) "*Fliqlo*"
"*fliqlo*" # Fliqlo lowercase "*fliqlo*"
# Development Tools - Git & Version Control # Git & Version Control
"com.github.GitHubDesktop" # GitHub Desktop "com.github.GitHubDesktop"
"com.sublimemerge" # Sublime Merge "com.sublimemerge"
"com.torusknot.SourceTreeNotMAS" # SourceTree "com.torusknot.SourceTreeNotMAS"
"com.git-tower.Tower*" # Tower "com.git-tower.Tower*"
"com.gitfox.GitFox" # GitFox "com.gitfox.GitFox"
"com.github.Gitify" # Gitify "com.github.Gitify"
"com.fork.Fork" # Fork "com.fork.Fork"
"com.axosoft.gitkraken" # GitKraken "com.axosoft.gitkraken"
# Development Tools - Terminal & Shell # Terminal & Shell
"com.googlecode.iterm2" # iTerm2 "com.googlecode.iterm2"
"net.kovidgoyal.kitty" # Kitty "net.kovidgoyal.kitty"
"io.alacritty" # Alacritty "io.alacritty"
"com.github.wez.wezterm" # WezTerm "com.github.wez.wezterm"
"com.hyper.Hyper" # Hyper "com.hyper.Hyper"
"com.mizage.divvy" # Divvy "com.mizage.divvy"
"com.fig.Fig" # Fig (terminal assistant) "com.fig.Fig"
"dev.warp.Warp-Stable" # Warp "dev.warp.Warp-Stable"
"com.termius-dmg" # Termius (SSH client) "com.termius-dmg"
# Development Tools - Docker & Virtualization # Docker & Virtualization
"com.docker.docker" # Docker Desktop "com.docker.docker"
"com.getutm.UTM" # UTM "com.getutm.UTM"
"com.vmware.fusion" # VMware Fusion "com.vmware.fusion"
"com.parallels.desktop.*" # Parallels Desktop "com.parallels.desktop.*"
"org.virtualbox.app.VirtualBox" # VirtualBox "org.virtualbox.app.VirtualBox"
"com.vagrant.*" # Vagrant "com.vagrant.*"
"com.orbstack.OrbStack" # OrbStack "com.orbstack.OrbStack"
# System Monitoring & Performance # System Monitoring
"com.bjango.istatmenus*" # iStat Menus "com.bjango.istatmenus*"
"eu.exelban.Stats" # Stats "eu.exelban.Stats"
"com.monitorcontrol.*" # MonitorControl "com.monitorcontrol.*"
"com.bresink.system-toolkit.*" # TinkerTool System "com.bresink.system-toolkit.*"
"com.mediaatelier.MenuMeters" # MenuMeters "com.mediaatelier.MenuMeters"
"com.activity-indicator.app" # Activity Indicator "com.activity-indicator.app"
"net.cindori.sensei" # Sensei "net.cindori.sensei"
# Window Management & Productivity # Window Management
"com.macitbetter.*" # BetterTouchTool, BetterSnapTool "com.macitbetter.*" # BetterTouchTool, BetterSnapTool
"com.hegenberg.*" # BetterTouchTool legacy "com.hegenberg.*"
"com.manytricks.*" # Moom, Witch, Name Mangler, Resolutionator "com.manytricks.*" # Moom, Witch, etc.
"com.divisiblebyzero.*" # Spectacle "com.divisiblebyzero.*"
"com.koingdev.*" # Koingg apps "com.koingdev.*"
"com.if.Amphetamine" # Amphetamine "com.if.Amphetamine"
"com.lwouis.alt-tab-macos" # AltTab "com.lwouis.alt-tab-macos"
"net.matthewpalmer.Vanilla" # Vanilla "net.matthewpalmer.Vanilla"
"com.lightheadsw.Caffeine" # Caffeine "com.lightheadsw.Caffeine"
"com.contextual.Contexts" # Contexts "com.contextual.Contexts"
"com.amethyst.Amethyst" # Amethyst "com.amethyst.Amethyst"
"com.knollsoft.Rectangle" # Rectangle "com.knollsoft.Rectangle"
"com.knollsoft.Hookshot" # Hookshot "com.knollsoft.Hookshot"
"com.surteesstudios.Bartender" # Bartender "com.surteesstudios.Bartender"
"com.gaosun.eul" # eul (system monitor) "com.gaosun.eul"
"com.pointum.hazeover" # HazeOver "com.pointum.hazeover"
# Launcher & Automation # Launcher & Automation
"com.runningwithcrayons.Alfred" # Alfred "com.runningwithcrayons.Alfred"
"com.raycast.macos" # Raycast "com.raycast.macos"
"com.blacktree.Quicksilver" # Quicksilver "com.blacktree.Quicksilver"
"com.stairways.keyboardmaestro.*" # Keyboard Maestro "com.stairways.keyboardmaestro.*"
"com.manytricks.Butler" # Butler "com.manytricks.Butler"
"com.happenapps.Quitter" # Quitter "com.happenapps.Quitter"
"com.pilotmoon.scroll-reverser" # Scroll Reverser "com.pilotmoon.scroll-reverser"
"org.pqrs.Karabiner-Elements" # Karabiner-Elements "org.pqrs.Karabiner-Elements"
"com.apple.Automator" # Automator (system, but keep user workflows) "com.apple.Automator"
# Note-Taking & Documentation # Note-Taking
"com.bear-writer.*" # Bear "com.bear-writer.*"
"com.typora.*" # Typora "com.typora.*"
"com.ulyssesapp.*" # Ulysses "com.ulyssesapp.*"
"com.literatureandlatte.*" # Scrivener "com.literatureandlatte.*"
"com.dayoneapp.*" # Day One "com.dayoneapp.*"
"notion.id" # Notion "notion.id"
"md.obsidian" # Obsidian "md.obsidian"
"com.logseq.logseq" # Logseq "com.logseq.logseq"
"com.evernote.Evernote" # Evernote "com.evernote.Evernote"
"com.onenote.mac" # OneNote "com.onenote.mac"
"com.omnigroup.OmniOutliner*" # OmniOutliner "com.omnigroup.OmniOutliner*"
"net.shinyfrog.bear" # Bear legacy "net.shinyfrog.bear"
"com.goodnotes.GoodNotes" # GoodNotes "com.goodnotes.GoodNotes"
"com.marginnote.MarginNote*" # MarginNote "com.marginnote.MarginNote*"
"com.roamresearch.*" # Roam Research "com.roamresearch.*"
"com.reflect.ReflectApp" # Reflect "com.reflect.ReflectApp"
"com.inkdrop.*" # Inkdrop "com.inkdrop.*"
# Design & Creative Tools # Design & Creative
"com.adobe.*" # Adobe Creative Suite "com.adobe.*"
"com.bohemiancoding.*" # Sketch "com.bohemiancoding.*"
"com.figma.*" # Figma "com.figma.*"
"com.framerx.*" # Framer "com.framerx.*"
"com.zeplin.*" # Zeplin "com.zeplin.*"
"com.invisionapp.*" # InVision "com.invisionapp.*"
"com.principle.*" # Principle "com.principle.*"
"com.pixelmatorteam.*" # Pixelmator "com.pixelmatorteam.*"
"com.affinitydesigner.*" # Affinity Designer "com.affinitydesigner.*"
"com.affinityphoto.*" # Affinity Photo "com.affinityphoto.*"
"com.affinitypublisher.*" # Affinity Publisher "com.affinitypublisher.*"
"com.linearity.curve" # Linearity Curve "com.linearity.curve"
"com.canva.CanvaDesktop" # Canva "com.canva.CanvaDesktop"
"com.maxon.cinema4d" # Cinema 4D "com.maxon.cinema4d"
"com.autodesk.*" # Autodesk products "com.autodesk.*"
"com.sketchup.*" # SketchUp "com.sketchup.*"
# Communication & Collaboration # Communication
"com.tencent.xinWeChat" # WeChat (Chinese users) "com.tencent.xinWeChat"
"com.tencent.qq" # QQ "com.tencent.qq"
"com.alibaba.DingTalkMac" # DingTalk "com.alibaba.DingTalkMac"
"com.alibaba.AliLang.osx" # AliLang (retain login/config data) "com.alibaba.AliLang.osx"
"com.alibaba.alilang3.osx.ShipIt" # AliLang updater component "com.alibaba.alilang3.osx.ShipIt"
"com.alibaba.AlilangMgr.QueryNetworkInfo" # AliLang network helper "com.alibaba.AlilangMgr.QueryNetworkInfo"
"us.zoom.xos" # Zoom "us.zoom.xos"
"com.microsoft.teams*" # Microsoft Teams "com.microsoft.teams*"
"com.slack.Slack" # Slack "com.slack.Slack"
"com.hnc.Discord" # Discord "com.hnc.Discord"
"app.legcord.Legcord" # Legcord "app.legcord.Legcord"
"org.telegram.desktop" # Telegram "org.telegram.desktop"
"ru.keepcoder.Telegram" # Telegram legacy "ru.keepcoder.Telegram"
"net.whatsapp.WhatsApp" # WhatsApp "net.whatsapp.WhatsApp"
"com.skype.skype" # Skype "com.skype.skype"
"com.cisco.webexmeetings" # Webex "com.cisco.webexmeetings"
"com.ringcentral.RingCentral" # RingCentral "com.ringcentral.RingCentral"
"com.readdle.smartemail-Mac" # Spark Email "com.readdle.smartemail-Mac"
"com.airmail.*" # Airmail "com.airmail.*"
"com.postbox-inc.postbox" # Postbox "com.postbox-inc.postbox"
"com.tinyspeck.slackmacgap" # Slack legacy "com.tinyspeck.slackmacgap"
# Task Management & Productivity # Task Management
"com.omnigroup.OmniFocus*" # OmniFocus "com.omnigroup.OmniFocus*"
"com.culturedcode.*" # Things "com.culturedcode.*"
"com.todoist.*" # Todoist "com.todoist.*"
"com.any.do.*" # Any.do "com.any.do.*"
"com.ticktick.*" # TickTick "com.ticktick.*"
"com.microsoft.to-do" # Microsoft To Do "com.microsoft.to-do"
"com.trello.trello" # Trello "com.trello.trello"
"com.asana.nativeapp" # Asana "com.asana.nativeapp"
"com.clickup.*" # ClickUp "com.clickup.*"
"com.monday.desktop" # Monday.com "com.monday.desktop"
"com.airtable.airtable" # Airtable "com.airtable.airtable"
"com.notion.id" # Notion (also note-taking) "com.notion.id"
"com.linear.linear" # Linear "com.linear.linear"
# File Transfer & Sync # File Transfer & Sync
"com.panic.transmit*" # Transmit (FTP/SFTP) "com.panic.transmit*"
"com.binarynights.ForkLift*" # ForkLift "com.binarynights.ForkLift*"
"com.noodlesoft.Hazel" # Hazel "com.noodlesoft.Hazel"
"com.cyberduck.Cyberduck" # Cyberduck "com.cyberduck.Cyberduck"
"io.filezilla.FileZilla" # FileZilla "io.filezilla.FileZilla"
"com.apple.Xcode.CloudDocuments" # Xcode Cloud Documents "com.apple.Xcode.CloudDocuments"
"com.synology.*" # Synology apps "com.synology.*"
# Cloud Storage & Backup (Issue #204) # Cloud Storage & Backup
"com.dropbox.*" # Dropbox "com.dropbox.*"
"com.getdropbox.*" # Dropbox legacy "com.getdropbox.*"
"*dropbox*" # Dropbox helpers/updaters "*dropbox*"
"ws.agile.*" # 1Password sync helpers "ws.agile.*"
"com.backblaze.*" # Backblaze "com.backblaze.*"
"*backblaze*" # Backblaze helpers "*backblaze*"
"com.box.desktop*" # Box "com.box.desktop*"
"*box.desktop*" # Box helpers "*box.desktop*"
"com.microsoft.OneDrive*" # Microsoft OneDrive "com.microsoft.OneDrive*"
"com.microsoft.SyncReporter" # OneDrive sync reporter "com.microsoft.SyncReporter"
"*OneDrive*" # OneDrive helpers/updaters "*OneDrive*"
"com.google.GoogleDrive" # Google Drive "com.google.GoogleDrive"
"com.google.keystone*" # Google updaters (Drive, Chrome, etc.) "com.google.keystone*"
"*GoogleDrive*" # Google Drive helpers "*GoogleDrive*"
"com.amazon.drive" # Amazon Drive "com.amazon.drive"
"com.apple.bird" # iCloud Drive daemon "com.apple.bird"
"com.apple.CloudDocs*" # iCloud Documents "com.apple.CloudDocs*"
"com.displaylink.*" # DisplayLink "com.displaylink.*"
"com.fujitsu.pfu.ScanSnap*" # ScanSnap "com.fujitsu.pfu.ScanSnap*"
"com.citrix.*" # Citrix Workspace "com.citrix.*"
"org.xquartz.*" # XQuartz "org.xquartz.*"
"us.zoom.updater*" # Zoom updaters "us.zoom.updater*"
"com.DigiDNA.iMazing*" # iMazing "com.DigiDNA.iMazing*"
"com.shirtpocket.*" # SuperDuper backup "com.shirtpocket.*"
"homebrew.mxcl.*" # Homebrew services "homebrew.mxcl.*"
# Screenshot & Recording # Screenshot & Recording
"com.cleanshot.*" # CleanShot X "com.cleanshot.*"
"com.xnipapp.xnip" # Xnip "com.xnipapp.xnip"
"com.reincubate.camo" # Camo "com.reincubate.camo"
"com.tunabellysoftware.ScreenFloat" # ScreenFloat "com.tunabellysoftware.ScreenFloat"
"net.telestream.screenflow*" # ScreenFlow "net.telestream.screenflow*"
"com.techsmith.snagit*" # Snagit "com.techsmith.snagit*"
"com.techsmith.camtasia*" # Camtasia "com.techsmith.camtasia*"
"com.obsidianapp.screenrecorder" # Screen Recorder "com.obsidianapp.screenrecorder"
"com.kap.Kap" # Kap "com.kap.Kap"
"com.getkap.*" # Kap legacy "com.getkap.*"
"com.linebreak.CloudApp" # CloudApp "com.linebreak.CloudApp"
"com.droplr.droplr-mac" # Droplr "com.droplr.droplr-mac"
# Media & Entertainment # Media & Entertainment
"com.spotify.client" # Spotify "com.spotify.client"
"com.apple.Music" # Apple Music "com.apple.Music"
"com.apple.podcasts" # Apple Podcasts "com.apple.podcasts"
"com.apple.BKAgentService" # Apple Books (Agent) "com.apple.BKAgentService"
"com.apple.iBooksX" # Apple Books "com.apple.iBooksX"
"com.apple.iBooks" # Apple Books (Legacy) "com.apple.iBooks"
"com.apple.FinalCutPro" # Final Cut Pro "com.blackmagic-design.*"
"com.apple.Motion" # Motion "com.colliderli.iina"
"com.apple.Compressor" # Compressor "org.videolan.vlc"
"com.blackmagic-design.*" # DaVinci Resolve "io.mpv"
"com.colliderli.iina" # IINA "tv.plex.player.desktop"
"org.videolan.vlc" # VLC "com.netease.163music"
"io.mpv" # MPV
"com.noodlesoft.Hazel" # Hazel (automation)
"tv.plex.player.desktop" # Plex
"com.netease.163music" # NetEase Music
# Web Browsers (protect complex storage like IndexedDB, localStorage) # Web Browsers
"Firefox" # Firefox Application Support "Firefox"
"org.mozilla.*" # Firefox bundle IDs "org.mozilla.*"
# License Management & App Stores # License & App Stores
"com.paddle.Paddle*" # Paddle (license management) "com.paddle.Paddle*"
"com.setapp.DesktopClient" # Setapp "com.setapp.DesktopClient"
"com.devmate.*" # DevMate (license framework) "com.devmate.*"
"org.sparkle-project.Sparkle" # Sparkle (update framework) "org.sparkle-project.Sparkle"
) )
# Centralized check for critical system components (case-insensitive) # Centralized check for critical system components (case-insensitive)
@@ -470,11 +548,22 @@ bundle_matches_pattern() {
# Check if application is a protected system component # Check if application is a protected system component
should_protect_from_uninstall() { should_protect_from_uninstall() {
local bundle_id="$1" local bundle_id="$1"
for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}"; do
# First check if it's an uninstallable Apple app
# These apps have com.apple.* bundle IDs but are NOT system-critical
for pattern in "${APPLE_UNINSTALLABLE_APPS[@]}"; do
if bundle_matches_pattern "$bundle_id" "$pattern"; then if bundle_matches_pattern "$bundle_id" "$pattern"; then
return 0 return 1 # Can be uninstalled
fi fi
done done
# Then check system-critical components
for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}"; do
if bundle_matches_pattern "$bundle_id" "$pattern"; then
return 0 # Protected
fi
done
return 1 return 1
} }
@@ -582,7 +671,13 @@ should_protect_path() {
# This catches things like /Users/tw93/Library/Caches/Claude when pattern is *Claude* # This catches things like /Users/tw93/Library/Caches/Claude when pattern is *Claude*
# In uninstall mode, only check system-critical bundles (user explicitly chose to uninstall) # In uninstall mode, only check system-critical bundles (user explicitly chose to uninstall)
if [[ "${MOLE_UNINSTALL_MODE:-0}" == "1" ]]; then if [[ "${MOLE_UNINSTALL_MODE:-0}" == "1" ]]; then
# Uninstall mode: only protect system-critical components # Uninstall mode: first check if it's an uninstallable Apple app
for pattern in "${APPLE_UNINSTALLABLE_APPS[@]}"; do
if bundle_matches_pattern "$path" "$pattern"; then
return 1 # Can be uninstalled
fi
done
# Then check system-critical components
for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}"; do for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}"; do
if bundle_matches_pattern "$path" "$pattern"; then if bundle_matches_pattern "$path" "$pattern"; then
return 0 return 0

View File

@@ -160,6 +160,32 @@ EOF
[ "$result" = "protected" ] [ "$result" = "protected" ]
} }
@test "Apple apps from App Store can be uninstalled (Issue #386)" {
# Xcode should NOT be protected from uninstall
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; should_protect_from_uninstall 'com.apple.dt.Xcode' && echo 'protected' || echo 'not-protected'")
[ "$result" = "not-protected" ]
# Final Cut Pro should NOT be protected from uninstall
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; should_protect_from_uninstall 'com.apple.FinalCutPro' && echo 'protected' || echo 'not-protected'")
[ "$result" = "not-protected" ]
# GarageBand should NOT be protected from uninstall
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; should_protect_from_uninstall 'com.apple.GarageBand' && echo 'protected' || echo 'not-protected'")
[ "$result" = "not-protected" ]
# iWork apps should NOT be protected from uninstall
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; should_protect_from_uninstall 'com.apple.iWork.Pages' && echo 'protected' || echo 'not-protected'")
[ "$result" = "not-protected" ]
# But Safari (system app) should still be protected
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; should_protect_from_uninstall 'com.apple.Safari' && echo 'protected' || echo 'not-protected'")
[ "$result" = "protected" ]
# And Finder should still be protected
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; should_protect_from_uninstall 'com.apple.finder' && echo 'protected' || echo 'not-protected'")
[ "$result" = "protected" ]
}
@test "print_summary_block formats output correctly" { @test "print_summary_block formats output correctly" {
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; print_summary_block 'success' 'Test Summary' 'Detail 1' 'Detail 2'") result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; print_summary_block 'success' 'Test Summary' 'Detail 1' 'Detail 2'")
[[ "$result" == *"Test Summary"* ]] [[ "$result" == *"Test Summary"* ]]