diff --git a/ee/maintained-apps/outputs/bitwarden/windows.json b/ee/maintained-apps/outputs/bitwarden/windows.json index 3978e8132dd..ac6ecc144a9 100644 --- a/ee/maintained-apps/outputs/bitwarden/windows.json +++ b/ee/maintained-apps/outputs/bitwarden/windows.json @@ -1,16 +1,16 @@ { "versions": [ { - "version": "2026.10.3", + "version": "2026.10.4", "queries": { "exists": "SELECT 1 FROM programs WHERE name = 'Bitwarden' AND publisher = 'Bitwarden Inc.';", - "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Bitwarden' AND publisher = 'Bitwarden Inc.' AND version_compare(version, '2026.10.3') < 0);", + "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Bitwarden' AND publisher = 'Bitwarden Inc.' AND version_compare(version, '2026.10.4') < 0);", "open": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM processes WHERE LOWER(name) = 'bitwarden.exe');" }, - "installer_url": "https://github.com/bitwarden/clients/releases/download/desktop-v2026.10.3/Bitwarden-Installer-2026.10.3.exe", + "installer_url": "https://github.com/bitwarden/clients/releases/download/desktop-v2026.10.4/Bitwarden-Installer-2026.10.4.exe", "install_script_ref": "234a3679", "uninstall_script_ref": "365a2ac0", - "sha256": "95fdabb86b73df3324710f1dea16eb52d92362ff1fee6ff04f390e173f7738d7", + "sha256": "aaedfeaf73511b9095a5009454bb037a053c9a60e7c5ed23717b7b6047193a13", "default_categories": [ "Productivity" ] diff --git a/ee/maintained-apps/outputs/cyberduck/windows.json b/ee/maintained-apps/outputs/cyberduck/windows.json index e7bb69b1587..2c01ddf27e0 100644 --- a/ee/maintained-apps/outputs/cyberduck/windows.json +++ b/ee/maintained-apps/outputs/cyberduck/windows.json @@ -1,16 +1,16 @@ { "versions": [ { - "version": "9.5.4.45528", + "version": "9.6.0.46406", "queries": { "exists": "SELECT 1 FROM programs WHERE name = 'Cyberduck' AND publisher = 'iterate GmbH';", - "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Cyberduck' AND publisher = 'iterate GmbH' AND version_compare(version, '9.5.4.45528') < 0);", + "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Cyberduck' AND publisher = 'iterate GmbH' AND version_compare(version, '9.6.0.46406') < 0);", "open": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM processes WHERE LOWER(name) = 'cyberduck.exe');" }, - "installer_url": "https://update.cyberduck.io//Cyberduck-Installer-9.5.4.45528.msi", + "installer_url": "https://update.cyberduck.io/release/Cyberduck-Installer-9.6.0.46406.msi", "install_script_ref": "22e48c46", "uninstall_script_ref": "3b278238", - "sha256": "082ed656a00c81516a8b7d3651d32aee8afb8963f7dced9b753ed0db204a0607", + "sha256": "a59c54fb56957c2451222b0f6ba5584ba159a4c2c75cfe89633856a64ba15aac", "default_categories": [ "Productivity" ], diff --git a/ee/maintained-apps/outputs/datagrip/windows.json b/ee/maintained-apps/outputs/datagrip/windows.json index 6b9be0dbe3b..270e80caf88 100644 --- a/ee/maintained-apps/outputs/datagrip/windows.json +++ b/ee/maintained-apps/outputs/datagrip/windows.json @@ -1,16 +1,16 @@ { "versions": [ { - "version": "2026.2.6.1", + "version": "2026.3", "queries": { "exists": "SELECT 1 FROM programs WHERE name LIKE 'DataGrip %' AND publisher = 'JetBrains s.r.o.';", - "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name LIKE 'DataGrip %' AND publisher = 'JetBrains s.r.o.' AND version_compare(version, '262.11879.103') < 0);", + "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name LIKE 'DataGrip %' AND publisher = 'JetBrains s.r.o.' AND version_compare(version, '263.7307.216') < 0);", "open": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM processes WHERE LOWER(name) IN ('datagrip.exe','datagrip64.exe'));" }, - "installer_url": "https://download.jetbrains.com/datagrip/datagrip-2026.2.6.1.exe", + "installer_url": "https://download.jetbrains.com/datagrip/datagrip-2026.3.exe", "install_script_ref": "0897461e", "uninstall_script_ref": "020465dd", - "sha256": "bf5a6ea7fba46a0e3f4e451db4603850ac2c6456466b3b4eaf679519fdadae17", + "sha256": "98557e953a47db74f5d4acf4c93a40bed4ede49bb362c2b1b36684b6ad9911d9", "default_categories": [ "Developer tools" ] diff --git a/ee/maintained-apps/outputs/evernote/windows.json b/ee/maintained-apps/outputs/evernote/windows.json index 559bb1987ea..2cd3bb9e736 100644 --- a/ee/maintained-apps/outputs/evernote/windows.json +++ b/ee/maintained-apps/outputs/evernote/windows.json @@ -1,16 +1,16 @@ { "versions": [ { - "version": "11.41.7", + "version": "11.41.9", "queries": { "exists": "SELECT 1 FROM programs WHERE name LIKE 'Evernote%' AND publisher = 'Bending Spoons US Inc.';", - "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name LIKE 'Evernote%' AND publisher = 'Bending Spoons US Inc.' AND version_compare(version, '11.41.7') < 0);", + "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name LIKE 'Evernote%' AND publisher = 'Bending Spoons US Inc.' AND version_compare(version, '11.41.9') < 0);", "open": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM processes WHERE LOWER(name) = 'evernote.exe');" }, - "installer_url": "https://win.desktop.evernote.com/builds/Evernote-11.41.7-win-ddl-stage-20261102181103-0e9695c2352e9119cfab3cb7fa20a2790c6ed8f0-setup.exe", + "installer_url": "https://win.desktop.evernote.com/builds/Evernote-11.41.9-win-ddl-stage-20261105124610-5dbe4cf2e9e6083847d479f8365250d7bed5559e-setup.exe", "install_script_ref": "30e766be", "uninstall_script_ref": "0c86d350", - "sha256": "b26fb126edd3ff714c38bf89e79c5fe282d4d45660ee19e23184c1aaaf531fd7", + "sha256": "7af25efe7acfad00b91069b3de7aecaf4f0274d73f2dfaa0be3d400706bcb3c7", "default_categories": [ "Productivity" ] diff --git a/ee/maintained-apps/outputs/phpstorm/darwin.json b/ee/maintained-apps/outputs/phpstorm/darwin.json index e412f073576..ea72fd897d7 100644 --- a/ee/maintained-apps/outputs/phpstorm/darwin.json +++ b/ee/maintained-apps/outputs/phpstorm/darwin.json @@ -1,23 +1,23 @@ { "versions": [ { - "version": "2026.2.4", + "version": "2026.3", "queries": { "exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'com.jetbrains.PhpStorm';", - "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.jetbrains.PhpStorm' AND version_compare(bundle_short_version, '2026.2.4') < 0);", + "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.jetbrains.PhpStorm' AND version_compare(bundle_short_version, '2026.3') < 0);", "open": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps a JOIN processes p ON p.path = concat(a.path, '/Contents/MacOS/', a.bundle_executable) WHERE a.bundle_identifier = 'com.jetbrains.PhpStorm' AND a.bundle_executable != '');" }, - "installer_url": "https://download.jetbrains.com/webide/PhpStorm-2026.2.4-aarch64.dmg", + "installer_url": "https://download.jetbrains.com/webide/PhpStorm-2026.3-aarch64.dmg", "install_script_ref": "61b1e2c9", - "uninstall_script_ref": "02b44eb7", - "sha256": "20b5d42d561dfe90d3190e25326f8d0ceafb683765b8ae1c7176cd8961aa77b7", + "uninstall_script_ref": "02d55451", + "sha256": "596814016d9f79a5fec096dcd863d699f134adba165340040505c835f8c7fed2", "default_categories": [ "Developer tools" ] } ], "refs": { - "02b44eb7": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e 'on run argv' -e 'return application id (item 1 of argv) is running' -e 'end run' -- \"$bundle_id\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false still_running\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n osascript -e 'on run argv' -e 'tell application id (item 1 of argv) to quit' -e 'end run' -- \"$bundle_id\" >/dev/null 2>&1\n sleep 1\n if still_running=$(osascript -e 'on run argv' -e 'return application id (item 1 of argv) is running' -e 'end run' -- \"$bundle_id\" 2>/dev/null) && [[ \"$still_running\" == \"false\" ]]; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n return 1\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n local console_user\n local console_uid=\"\"\n if [[ $EUID -eq 0 ]]; then\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -n \"$console_user\" && \"$console_user\" != \"root\" && \"$console_user\" != \"loginwindow\" ]]; then\n console_uid=$(id -u \"$console_user\")\n fi\n fi\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(\n launchctl list 2>/dev/null | tail -n +2\n if [[ -n \"$console_uid\" ]]; then\n launchctl print \"gui/${console_uid}\" 2>/dev/null | awk '/^\\tservices = [{]$/ { f = 1; next } f && /^\\t[}]$/ { f = 0 } f'\n fi\n )\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n if [[ $should_sudo == \"false\" && -n \"$console_uid\" ]]; then\n if launchctl print \"gui/${console_uid}/${service_label}\" &>/dev/null; then\n launchctl bootout \"gui/${console_uid}/${service_label}\"\n sleep 1\n fi\n else\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n local user_home=\"${HOME}\"\n if [[ -n \"$console_uid\" ]]; then\n user_home=\"/Users/${console_user}\"\n fi\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${user_home}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nremove_system_path() {\n local target=\"$1\"\n local parent name rel top expected\n parent=\"$(dirname \"$target\")\"\n name=\"$(basename \"$target\")\"\n rel=\"${parent#/}\"\n top=\"/${rel%%/*}\"\n expected=\"$(cd -P \"$top\" 2>/dev/null && pwd -P)${parent#\"$top\"}\"\n\n (\n cd -P \"$parent\" 2>/dev/null || exit 1\n if [[ \"$(pwd -P)\" != \"$expected\" ]]; then\n echo \"Skipped $target: a folder in its path is a symlink.\"\n exit 0\n fi\n rm -rf \"./$name\"\n ) || echo \"Failed to remove $target.\"\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n local trash=\"\"\n\n # Paths in the user's home are moved to their Trash as the user: the user\n # controls those paths and ~/.Trash, so as root the move could be redirected\n # into a root-only directory. Other paths are deleted as root by\n # remove_system_path. At the login window there's no user Trash, so home\n # paths are skipped. Removal is best-effort and never fails the uninstall.\n if [[ \"$target_file\" == ~* ]]; then\n if [[ -z \"$logged_in_user\" || \"$logged_in_user\" == \"root\" || \"$logged_in_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping $target_file.\"\n return\n fi\n # replace ~ with /Users/$logged_in_user\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n trash=\"/Users/$logged_in_user/.Trash\"\n fi\n\n # If the target contains glob characters, expand it and remove each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n if [[ -z \"$trash\" ]]; then\n remove_system_path \"$file\"\n else\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n sudo -u \"$logged_in_user\" mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\" || echo \"Failed to move $file to the Trash.\"\n fi\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n if [[ -z \"$trash\" ]]; then\n remove_system_path \"$target_file\"\n else\n sudo -u \"$logged_in_user\" mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\" || echo \"Failed to move $target_file to the Trash.\"\n fi\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'application.com.jetbrains.app.daemon.*'\nquit_application 'com.jetbrains.PhpStorm' || exit 1\nsudo rm -rf \"$APPDIR/PhpStorm.app\"\ntrash \"$LOGGED_IN_USER\" '~/Library/Application Support/JetBrains/consentOptions'\ntrash \"$LOGGED_IN_USER\" '~/Library/Application Support/JetBrains/PhpStorm2026.2'\ntrash \"$LOGGED_IN_USER\" '~/Library/Caches/JetBrains/PhpStorm2026.2'\ntrash \"$LOGGED_IN_USER\" '~/Library/Logs/JetBrains/PhpStorm2026.2'\ntrash \"$LOGGED_IN_USER\" '~/Library/Preferences/com.jetbrains.PhpStorm.plist'\ntrash \"$LOGGED_IN_USER\" '~/Library/Preferences/jetbrains.jetprofile.asset.plist'\n", + "02d55451": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e 'on run argv' -e 'return application id (item 1 of argv) is running' -e 'end run' -- \"$bundle_id\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false still_running\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n osascript -e 'on run argv' -e 'tell application id (item 1 of argv) to quit' -e 'end run' -- \"$bundle_id\" >/dev/null 2>&1\n sleep 1\n if still_running=$(osascript -e 'on run argv' -e 'return application id (item 1 of argv) is running' -e 'end run' -- \"$bundle_id\" 2>/dev/null) && [[ \"$still_running\" == \"false\" ]]; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n return 1\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n local console_user\n local console_uid=\"\"\n if [[ $EUID -eq 0 ]]; then\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -n \"$console_user\" && \"$console_user\" != \"root\" && \"$console_user\" != \"loginwindow\" ]]; then\n console_uid=$(id -u \"$console_user\")\n fi\n fi\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(\n launchctl list 2>/dev/null | tail -n +2\n if [[ -n \"$console_uid\" ]]; then\n launchctl print \"gui/${console_uid}\" 2>/dev/null | awk '/^\\tservices = [{]$/ { f = 1; next } f && /^\\t[}]$/ { f = 0 } f'\n fi\n )\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n if [[ $should_sudo == \"false\" && -n \"$console_uid\" ]]; then\n if launchctl print \"gui/${console_uid}/${service_label}\" &>/dev/null; then\n launchctl bootout \"gui/${console_uid}/${service_label}\"\n sleep 1\n fi\n else\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n local user_home=\"${HOME}\"\n if [[ -n \"$console_uid\" ]]; then\n user_home=\"/Users/${console_user}\"\n fi\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${user_home}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nremove_system_path() {\n local target=\"$1\"\n local parent name rel top expected\n parent=\"$(dirname \"$target\")\"\n name=\"$(basename \"$target\")\"\n rel=\"${parent#/}\"\n top=\"/${rel%%/*}\"\n expected=\"$(cd -P \"$top\" 2>/dev/null && pwd -P)${parent#\"$top\"}\"\n\n (\n cd -P \"$parent\" 2>/dev/null || exit 1\n if [[ \"$(pwd -P)\" != \"$expected\" ]]; then\n echo \"Skipped $target: a folder in its path is a symlink.\"\n exit 0\n fi\n rm -rf \"./$name\"\n ) || echo \"Failed to remove $target.\"\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n local trash=\"\"\n\n # Paths in the user's home are moved to their Trash as the user: the user\n # controls those paths and ~/.Trash, so as root the move could be redirected\n # into a root-only directory. Other paths are deleted as root by\n # remove_system_path. At the login window there's no user Trash, so home\n # paths are skipped. Removal is best-effort and never fails the uninstall.\n if [[ \"$target_file\" == ~* ]]; then\n if [[ -z \"$logged_in_user\" || \"$logged_in_user\" == \"root\" || \"$logged_in_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping $target_file.\"\n return\n fi\n # replace ~ with /Users/$logged_in_user\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n trash=\"/Users/$logged_in_user/.Trash\"\n fi\n\n # If the target contains glob characters, expand it and remove each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n if [[ -z \"$trash\" ]]; then\n remove_system_path \"$file\"\n else\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n sudo -u \"$logged_in_user\" mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\" || echo \"Failed to move $file to the Trash.\"\n fi\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n if [[ -z \"$trash\" ]]; then\n remove_system_path \"$target_file\"\n else\n sudo -u \"$logged_in_user\" mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\" || echo \"Failed to move $target_file to the Trash.\"\n fi\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'application.com.jetbrains.app.daemon.*'\nquit_application 'com.jetbrains.PhpStorm' || exit 1\nsudo rm -rf \"$APPDIR/PhpStorm.app\"\ntrash \"$LOGGED_IN_USER\" '~/Library/Application Support/JetBrains/consentOptions'\ntrash \"$LOGGED_IN_USER\" '~/Library/Application Support/JetBrains/PhpStorm2026.3'\ntrash \"$LOGGED_IN_USER\" '~/Library/Caches/JetBrains/PhpStorm2026.3'\ntrash \"$LOGGED_IN_USER\" '~/Library/Logs/JetBrains/PhpStorm2026.3'\ntrash \"$LOGGED_IN_USER\" '~/Library/Preferences/com.jetbrains.PhpStorm.plist'\ntrash \"$LOGGED_IN_USER\" '~/Library/Preferences/jetbrains.jetprofile.asset.plist'\n", "61b1e2c9": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath \"$INSTALLER_PATH\")\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_${bundle_id//[^[:alnum:]_]/_}\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e 'on run argv' -e 'return application id (item 1 of argv) is running' -e 'end run' -- \"$bundle_id\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false still_running\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n osascript -e 'on run argv' -e 'tell application id (item 1 of argv) to quit' -e 'end run' -- \"$bundle_id\" >/dev/null 2>&1\n sleep 1\n if still_running=$(osascript -e 'on run argv' -e 'return application id (item 1 of argv) is running' -e 'end run' -- \"$bundle_id\" 2>/dev/null) && [[ \"$still_running\" == \"false\" ]]; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n return 1\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_${bundle_id//[^[:alnum:]_]/_}\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Launch the app in the logged-in user's GUI session. Apps launched by root\n # won't register with the user's Dock/GUI, so run 'open' as the console user.\n # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace\n # and GUI session — 'sudo -u' alone doesn't do this, which can cause\n # LSOpenURLsWithRole() failures even when 'open' exits 0.\n local open_status=0\n if [[ $EUID -eq 0 ]]; then\n local console_uid\n console_uid=$(id -u \"$console_user\")\n /bin/launchctl asuser \"$console_uid\" sudo -u \"$console_user\" open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n else\n open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n fi\n\n if [[ $open_status -eq 0 ]]; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# extract contents\nMOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX)\nyes | hdiutil attach -plist -nobrowse -readonly -mountpoint \"$MOUNT_POINT\" \"$INSTALLER_PATH\" || exit 1\nsudo cp -R \"$MOUNT_POINT\"/* \"$TMPDIR\"\nhdiutil detach \"$MOUNT_POINT\" || true\n# copy to the applications folder\nquit_and_track_application 'com.jetbrains.PhpStorm' || exit 1\nif [ -d \"$APPDIR/PhpStorm.app\" ]; then\n\tsudo mv \"$APPDIR/PhpStorm.app\" \"$TMPDIR/PhpStorm.app.bkp\" || exit $?\nfi\nif ! sudo cp -R \"$TMPDIR/PhpStorm.app\" \"$APPDIR\"; then\n\t# remove the partial copy so a failed install isn't inventoried as the new\n\t# version, then restore the previous version if there was one\n\tsudo rm -rf \"$APPDIR/PhpStorm.app\"\n\tif [ -d \"$TMPDIR/PhpStorm.app.bkp\" ]; then\n\t\tsudo mv \"$TMPDIR/PhpStorm.app.bkp\" \"$APPDIR/PhpStorm.app\"\n\tfi\n\texit 1\nfi\nrelaunch_application 'com.jetbrains.PhpStorm'\n" } } diff --git a/ee/maintained-apps/outputs/pycharm/darwin.json b/ee/maintained-apps/outputs/pycharm/darwin.json index 91cfc1a2427..f765eabfead 100644 --- a/ee/maintained-apps/outputs/pycharm/darwin.json +++ b/ee/maintained-apps/outputs/pycharm/darwin.json @@ -1,23 +1,23 @@ { "versions": [ { - "version": "2026.2.4", + "version": "2026.3", "queries": { "exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'com.jetbrains.pycharm';", - "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.jetbrains.pycharm' AND version_compare(bundle_short_version, '2026.2.4') < 0);", + "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.jetbrains.pycharm' AND version_compare(bundle_short_version, '2026.3') < 0);", "open": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps a JOIN processes p ON p.path = concat(a.path, '/Contents/MacOS/', a.bundle_executable) WHERE a.bundle_identifier = 'com.jetbrains.pycharm' AND a.bundle_executable != '');" }, - "installer_url": "https://download.jetbrains.com/python/pycharm-professional-2026.2.4-aarch64.dmg", + "installer_url": "https://download.jetbrains.com/python/pycharm-professional-2026.3-aarch64.dmg", "install_script_ref": "d698aed7", - "uninstall_script_ref": "6ff6d8fb", - "sha256": "55fd0247b903bb6da79a3da1bf47dac19dacbd2f6abc4fc7df80e5daaa6cdf18", + "uninstall_script_ref": "48832005", + "sha256": "6a4ba27899d8ff6462bec0e84aa8251810d151215643ef36806cd47f9c89df32", "default_categories": [ "Developer tools" ] } ], "refs": { - "6ff6d8fb": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e 'on run argv' -e 'return application id (item 1 of argv) is running' -e 'end run' -- \"$bundle_id\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false still_running\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n osascript -e 'on run argv' -e 'tell application id (item 1 of argv) to quit' -e 'end run' -- \"$bundle_id\" >/dev/null 2>&1\n sleep 1\n if still_running=$(osascript -e 'on run argv' -e 'return application id (item 1 of argv) is running' -e 'end run' -- \"$bundle_id\" 2>/dev/null) && [[ \"$still_running\" == \"false\" ]]; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n return 1\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n local console_user\n local console_uid=\"\"\n if [[ $EUID -eq 0 ]]; then\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -n \"$console_user\" && \"$console_user\" != \"root\" && \"$console_user\" != \"loginwindow\" ]]; then\n console_uid=$(id -u \"$console_user\")\n fi\n fi\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(\n launchctl list 2>/dev/null | tail -n +2\n if [[ -n \"$console_uid\" ]]; then\n launchctl print \"gui/${console_uid}\" 2>/dev/null | awk '/^\\tservices = [{]$/ { f = 1; next } f && /^\\t[}]$/ { f = 0 } f'\n fi\n )\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n if [[ $should_sudo == \"false\" && -n \"$console_uid\" ]]; then\n if launchctl print \"gui/${console_uid}/${service_label}\" &>/dev/null; then\n launchctl bootout \"gui/${console_uid}/${service_label}\"\n sleep 1\n fi\n else\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n local user_home=\"${HOME}\"\n if [[ -n \"$console_uid\" ]]; then\n user_home=\"/Users/${console_user}\"\n fi\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${user_home}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nremove_system_path() {\n local target=\"$1\"\n local parent name rel top expected\n parent=\"$(dirname \"$target\")\"\n name=\"$(basename \"$target\")\"\n rel=\"${parent#/}\"\n top=\"/${rel%%/*}\"\n expected=\"$(cd -P \"$top\" 2>/dev/null && pwd -P)${parent#\"$top\"}\"\n\n (\n cd -P \"$parent\" 2>/dev/null || exit 1\n if [[ \"$(pwd -P)\" != \"$expected\" ]]; then\n echo \"Skipped $target: a folder in its path is a symlink.\"\n exit 0\n fi\n rm -rf \"./$name\"\n ) || echo \"Failed to remove $target.\"\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n local trash=\"\"\n\n # Paths in the user's home are moved to their Trash as the user: the user\n # controls those paths and ~/.Trash, so as root the move could be redirected\n # into a root-only directory. Other paths are deleted as root by\n # remove_system_path. At the login window there's no user Trash, so home\n # paths are skipped. Removal is best-effort and never fails the uninstall.\n if [[ \"$target_file\" == ~* ]]; then\n if [[ -z \"$logged_in_user\" || \"$logged_in_user\" == \"root\" || \"$logged_in_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping $target_file.\"\n return\n fi\n # replace ~ with /Users/$logged_in_user\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n trash=\"/Users/$logged_in_user/.Trash\"\n fi\n\n # If the target contains glob characters, expand it and remove each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n if [[ -z \"$trash\" ]]; then\n remove_system_path \"$file\"\n else\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n sudo -u \"$logged_in_user\" mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\" || echo \"Failed to move $file to the Trash.\"\n fi\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n if [[ -z \"$trash\" ]]; then\n remove_system_path \"$target_file\"\n else\n sudo -u \"$logged_in_user\" mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\" || echo \"Failed to move $target_file to the Trash.\"\n fi\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'application.com.jetbrains.app.daemon.*'\nquit_application 'com.jetbrains.pycharm' || exit 1\nsudo rm -rf \"$APPDIR/PyCharm.app\"\ntrash \"$LOGGED_IN_USER\" '~/Library/Application Support/JetBrains/PyCharm2026.2'\ntrash \"$LOGGED_IN_USER\" '~/Library/Application Support/PyCharm2026.2'\ntrash \"$LOGGED_IN_USER\" '~/Library/Caches/JetBrains/PyCharm2026.2'\ntrash \"$LOGGED_IN_USER\" '~/Library/Logs/JetBrains/PyCharm2026.2'\ntrash \"$LOGGED_IN_USER\" '~/Library/Preferences/com.jetbrains.pycharm.plist'\ntrash \"$LOGGED_IN_USER\" '~/Library/Preferences/jetbrains.pc.*.plist'\ntrash \"$LOGGED_IN_USER\" '~/Library/Preferences/jetbrains.py.*.plist'\ntrash \"$LOGGED_IN_USER\" '~/Library/Preferences/jetbrains.pycharm.*.plist'\ntrash \"$LOGGED_IN_USER\" '~/Library/Preferences/PyCharm2026.2'\ntrash \"$LOGGED_IN_USER\" '~/Library/Saved Application State/com.jetbrains.pycharm.savedState'\n", + "48832005": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e 'on run argv' -e 'return application id (item 1 of argv) is running' -e 'end run' -- \"$bundle_id\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false still_running\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n osascript -e 'on run argv' -e 'tell application id (item 1 of argv) to quit' -e 'end run' -- \"$bundle_id\" >/dev/null 2>&1\n sleep 1\n if still_running=$(osascript -e 'on run argv' -e 'return application id (item 1 of argv) is running' -e 'end run' -- \"$bundle_id\" 2>/dev/null) && [[ \"$still_running\" == \"false\" ]]; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n return 1\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n local console_user\n local console_uid=\"\"\n if [[ $EUID -eq 0 ]]; then\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -n \"$console_user\" && \"$console_user\" != \"root\" && \"$console_user\" != \"loginwindow\" ]]; then\n console_uid=$(id -u \"$console_user\")\n fi\n fi\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(\n launchctl list 2>/dev/null | tail -n +2\n if [[ -n \"$console_uid\" ]]; then\n launchctl print \"gui/${console_uid}\" 2>/dev/null | awk '/^\\tservices = [{]$/ { f = 1; next } f && /^\\t[}]$/ { f = 0 } f'\n fi\n )\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n if [[ $should_sudo == \"false\" && -n \"$console_uid\" ]]; then\n if launchctl print \"gui/${console_uid}/${service_label}\" &>/dev/null; then\n launchctl bootout \"gui/${console_uid}/${service_label}\"\n sleep 1\n fi\n else\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n local user_home=\"${HOME}\"\n if [[ -n \"$console_uid\" ]]; then\n user_home=\"/Users/${console_user}\"\n fi\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${user_home}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nremove_system_path() {\n local target=\"$1\"\n local parent name rel top expected\n parent=\"$(dirname \"$target\")\"\n name=\"$(basename \"$target\")\"\n rel=\"${parent#/}\"\n top=\"/${rel%%/*}\"\n expected=\"$(cd -P \"$top\" 2>/dev/null && pwd -P)${parent#\"$top\"}\"\n\n (\n cd -P \"$parent\" 2>/dev/null || exit 1\n if [[ \"$(pwd -P)\" != \"$expected\" ]]; then\n echo \"Skipped $target: a folder in its path is a symlink.\"\n exit 0\n fi\n rm -rf \"./$name\"\n ) || echo \"Failed to remove $target.\"\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n local trash=\"\"\n\n # Paths in the user's home are moved to their Trash as the user: the user\n # controls those paths and ~/.Trash, so as root the move could be redirected\n # into a root-only directory. Other paths are deleted as root by\n # remove_system_path. At the login window there's no user Trash, so home\n # paths are skipped. Removal is best-effort and never fails the uninstall.\n if [[ \"$target_file\" == ~* ]]; then\n if [[ -z \"$logged_in_user\" || \"$logged_in_user\" == \"root\" || \"$logged_in_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping $target_file.\"\n return\n fi\n # replace ~ with /Users/$logged_in_user\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n trash=\"/Users/$logged_in_user/.Trash\"\n fi\n\n # If the target contains glob characters, expand it and remove each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n if [[ -z \"$trash\" ]]; then\n remove_system_path \"$file\"\n else\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n sudo -u \"$logged_in_user\" mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\" || echo \"Failed to move $file to the Trash.\"\n fi\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n if [[ -z \"$trash\" ]]; then\n remove_system_path \"$target_file\"\n else\n sudo -u \"$logged_in_user\" mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\" || echo \"Failed to move $target_file to the Trash.\"\n fi\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'application.com.jetbrains.app.daemon.*'\nquit_application 'com.jetbrains.pycharm' || exit 1\nsudo rm -rf \"$APPDIR/PyCharm.app\"\ntrash \"$LOGGED_IN_USER\" '~/Library/Application Support/JetBrains/PyCharm2026.3'\ntrash \"$LOGGED_IN_USER\" '~/Library/Application Support/PyCharm2026.3'\ntrash \"$LOGGED_IN_USER\" '~/Library/Caches/JetBrains/PyCharm2026.3'\ntrash \"$LOGGED_IN_USER\" '~/Library/Logs/JetBrains/PyCharm2026.3'\ntrash \"$LOGGED_IN_USER\" '~/Library/Preferences/com.jetbrains.pycharm.plist'\ntrash \"$LOGGED_IN_USER\" '~/Library/Preferences/jetbrains.pc.*.plist'\ntrash \"$LOGGED_IN_USER\" '~/Library/Preferences/jetbrains.py.*.plist'\ntrash \"$LOGGED_IN_USER\" '~/Library/Preferences/jetbrains.pycharm.*.plist'\ntrash \"$LOGGED_IN_USER\" '~/Library/Preferences/PyCharm2026.3'\ntrash \"$LOGGED_IN_USER\" '~/Library/Saved Application State/com.jetbrains.pycharm.savedState'\n", "d698aed7": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath \"$INSTALLER_PATH\")\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_${bundle_id//[^[:alnum:]_]/_}\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e 'on run argv' -e 'return application id (item 1 of argv) is running' -e 'end run' -- \"$bundle_id\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false still_running\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n osascript -e 'on run argv' -e 'tell application id (item 1 of argv) to quit' -e 'end run' -- \"$bundle_id\" >/dev/null 2>&1\n sleep 1\n if still_running=$(osascript -e 'on run argv' -e 'return application id (item 1 of argv) is running' -e 'end run' -- \"$bundle_id\" 2>/dev/null) && [[ \"$still_running\" == \"false\" ]]; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n return 1\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_${bundle_id//[^[:alnum:]_]/_}\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Launch the app in the logged-in user's GUI session. Apps launched by root\n # won't register with the user's Dock/GUI, so run 'open' as the console user.\n # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace\n # and GUI session — 'sudo -u' alone doesn't do this, which can cause\n # LSOpenURLsWithRole() failures even when 'open' exits 0.\n local open_status=0\n if [[ $EUID -eq 0 ]]; then\n local console_uid\n console_uid=$(id -u \"$console_user\")\n /bin/launchctl asuser \"$console_uid\" sudo -u \"$console_user\" open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n else\n open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n fi\n\n if [[ $open_status -eq 0 ]]; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# extract contents\nMOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX)\nyes | hdiutil attach -plist -nobrowse -readonly -mountpoint \"$MOUNT_POINT\" \"$INSTALLER_PATH\" || exit 1\nsudo cp -R \"$MOUNT_POINT\"/* \"$TMPDIR\"\nhdiutil detach \"$MOUNT_POINT\" || true\n# copy to the applications folder\nquit_and_track_application 'com.jetbrains.pycharm' || exit 1\nif [ -d \"$APPDIR/PyCharm.app\" ]; then\n\tsudo mv \"$APPDIR/PyCharm.app\" \"$TMPDIR/PyCharm.app.bkp\" || exit $?\nfi\nif ! sudo cp -R \"$TMPDIR/PyCharm.app\" \"$APPDIR\"; then\n\t# remove the partial copy so a failed install isn't inventoried as the new\n\t# version, then restore the previous version if there was one\n\tsudo rm -rf \"$APPDIR/PyCharm.app\"\n\tif [ -d \"$TMPDIR/PyCharm.app.bkp\" ]; then\n\t\tsudo mv \"$TMPDIR/PyCharm.app.bkp\" \"$APPDIR/PyCharm.app\"\n\tfi\n\texit 1\nfi\nrelaunch_application 'com.jetbrains.pycharm'\n" } } diff --git a/ee/maintained-apps/outputs/retcon/darwin.json b/ee/maintained-apps/outputs/retcon/darwin.json index 2495aa4b2c4..97c2ee70895 100644 --- a/ee/maintained-apps/outputs/retcon/darwin.json +++ b/ee/maintained-apps/outputs/retcon/darwin.json @@ -1,16 +1,16 @@ { "versions": [ { - "version": "1.6.3", + "version": "1.6.4", "queries": { "exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'garden.lemon.Retcon';", - "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'garden.lemon.Retcon' AND version_compare(bundle_short_version, '1.6.3') < 0);", + "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'garden.lemon.Retcon' AND version_compare(bundle_short_version, '1.6.4') < 0);", "open": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps a JOIN processes p ON p.path = concat(a.path, '/Contents/MacOS/', a.bundle_executable) WHERE a.bundle_identifier = 'garden.lemon.Retcon' AND a.bundle_executable != '');" }, - "installer_url": "https://downloads.lemon.garden/retcon/retcon-1.6.3.dmg", + "installer_url": "https://downloads.lemon.garden/retcon/retcon-1.6.4.dmg", "install_script_ref": "3b456a04", "uninstall_script_ref": "700ab070", - "sha256": "09d1f705f03622013562196c4972e44635e70dce324c6aa2d782c1265e1337bd", + "sha256": "d18c9c9eadba7a03e0dda14c7a05fadb634b39379c61eecfc91bf36f0fb922df", "default_categories": [ "Productivity" ] diff --git a/ee/maintained-apps/outputs/scribe/windows.json b/ee/maintained-apps/outputs/scribe/windows.json index 2e77562dbca..197a065b0c7 100644 --- a/ee/maintained-apps/outputs/scribe/windows.json +++ b/ee/maintained-apps/outputs/scribe/windows.json @@ -1,16 +1,16 @@ { "versions": [ { - "version": "6.10.20.0", + "version": "6.11.3.0", "queries": { "exists": "SELECT 1 FROM programs WHERE name = 'Scribe' AND publisher = 'Colony Labs, Inc';", - "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Scribe' AND publisher = 'Colony Labs, Inc' AND version_compare(version, '6.10.20.0') < 0);", + "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Scribe' AND publisher = 'Colony Labs, Inc' AND version_compare(version, '6.11.3.0') < 0);", "open": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM processes WHERE LOWER(name) = 'scribe.exe');" }, - "installer_url": "https://colony-labs-public.s3.us-east-2.amazonaws.com/Scribe_6.10.20.msi", + "installer_url": "https://colony-labs-public.s3.us-east-2.amazonaws.com/Scribe_6.11.3.msi", "install_script_ref": "cf2d0de4", "uninstall_script_ref": "4475c279", - "sha256": "973d9c652c6f6f4d6b0df3eba266ab7896fa9646f5b876d182521c0777df3b49", + "sha256": "bc067a7dc323b973b7b989afb218c4f92bf392faca7f382fd8538350c96d6107", "default_categories": [ "Productivity" ], diff --git a/ee/maintained-apps/outputs/signal/darwin.json b/ee/maintained-apps/outputs/signal/darwin.json index 94cbae484ef..4b158021ae7 100644 --- a/ee/maintained-apps/outputs/signal/darwin.json +++ b/ee/maintained-apps/outputs/signal/darwin.json @@ -1,16 +1,16 @@ { "versions": [ { - "version": "8.33.0", + "version": "8.34.0", "queries": { "exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'org.whispersystems.signal-desktop';", - "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'org.whispersystems.signal-desktop' AND version_compare(bundle_short_version, '8.33.0') < 0);", + "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'org.whispersystems.signal-desktop' AND version_compare(bundle_short_version, '8.34.0') < 0);", "open": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps a JOIN processes p ON p.path = concat(a.path, '/Contents/MacOS/', a.bundle_executable) WHERE a.bundle_identifier = 'org.whispersystems.signal-desktop' AND a.bundle_executable != '');" }, - "installer_url": "https://updates.signal.org/desktop/signal-desktop-mac-arm64-8.33.0.zip", + "installer_url": "https://updates.signal.org/desktop/signal-desktop-mac-arm64-8.34.0.zip", "install_script_ref": "979c581f", "uninstall_script_ref": "bcdede20", - "sha256": "4cb13e49041388c70cf7d9603c0c28cf62d7b6d12687418b39316bbe6ff1eb1a", + "sha256": "434c018ca95f8bee716a2dbeef0a17dba072c1242ad420ae312399f7934ed2f5", "default_categories": [ "Communication" ] diff --git a/ee/maintained-apps/outputs/superwhisper/darwin.json b/ee/maintained-apps/outputs/superwhisper/darwin.json index e341f252c54..f5ae62befc7 100644 --- a/ee/maintained-apps/outputs/superwhisper/darwin.json +++ b/ee/maintained-apps/outputs/superwhisper/darwin.json @@ -1,16 +1,16 @@ { "versions": [ { - "version": "3.1.0", + "version": "3.2.0", "queries": { "exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'com.superduper.superwhisper';", - "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.superduper.superwhisper' AND version_compare(bundle_short_version, '3.1.0') < 0);", + "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.superduper.superwhisper' AND version_compare(bundle_short_version, '3.2.0') < 0);", "open": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps a JOIN processes p ON p.path = concat(a.path, '/Contents/MacOS/', a.bundle_executable) WHERE a.bundle_identifier = 'com.superduper.superwhisper' AND a.bundle_executable != '');" }, - "installer_url": "https://builds.superwhisper.com/v3.1.0/superwhisper.zip", + "installer_url": "https://builds.superwhisper.com/v3.2.0/superwhisper.zip", "install_script_ref": "ebaae9ef", "uninstall_script_ref": "05eb1962", - "sha256": "55af78e18bc0c3ee35c908495bdb506701f8ed81b472d4b882a2db99ad010b08", + "sha256": "c6fc59ad7ba183cef9abf9e86da913f7275342eb92a3270ea83068816f84b585", "default_categories": [ "Productivity" ] diff --git a/ee/maintained-apps/outputs/telegram/darwin.json b/ee/maintained-apps/outputs/telegram/darwin.json index cc8f073df1a..20dfad03a98 100644 --- a/ee/maintained-apps/outputs/telegram/darwin.json +++ b/ee/maintained-apps/outputs/telegram/darwin.json @@ -1,16 +1,16 @@ { "versions": [ { - "version": "13.1.0", + "version": "13.2.0", "queries": { "exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'ru.keepcoder.Telegram';", - "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'ru.keepcoder.Telegram' AND version_compare(bundle_short_version, '13.1.0') < 0);", + "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'ru.keepcoder.Telegram' AND version_compare(bundle_short_version, '13.2.0') < 0);", "open": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps a JOIN processes p ON p.path = concat(a.path, '/Contents/MacOS/', a.bundle_executable) WHERE a.bundle_identifier = 'ru.keepcoder.Telegram' AND a.bundle_executable != '');" }, - "installer_url": "https://osx.telegram.org/updates/Telegram-13.1.0.283357.app.zip", + "installer_url": "https://osx.telegram.org/updates/Telegram-13.2.0.283458.app.zip", "install_script_ref": "51c2b694", "uninstall_script_ref": "fb4cb63b", - "sha256": "79ebcfe5e9f19371e83410f9678e7f3de13fa22535f2d84f84316543405be722", + "sha256": "cefb613a7c54a63a35bdaec0c639e49b7ccadd6f033e43dba1c5f4c085aaafe4", "default_categories": [ "Communication" ]