Skip to content

Add Pd (Pure Data) as a macOS FMA - #47056

Merged
allenhouchins merged 1 commit into
mainfrom
allenhouchins-pd
Jun 8, 2026
Merged

Add Pd (Pure Data) as a macOS FMA#47056
allenhouchins merged 1 commit into
mainfrom
allenhouchins-pd

Conversation

@allenhouchins

@allenhouchins allenhouchins commented Jun 8, 2026

Copy link
Copy Markdown
Member

Add support for Pd (Pure Data) on macOS: new Homebrew input manifest and installer script, outputs and apps entry, frontend icon and image, and a version transformer.

  • Add ee/maintained-apps/inputs/homebrew/pd.json and install script scripts/pd-install.sh that unzip the Homebrew archive, mount the embedded DMG, copy the .app, and handle quitting/relaunching the app.
  • Add ee/maintained-apps/outputs/pd/darwin.json with version, installer, hashes, and embedded install/uninstall script refs; add app entry to ee/maintained-apps/outputs/apps.json.
  • Register Pd in Homebrew external refs mapping and add PdVersionTransformer to convert Homebrew version strings like "0.56-3" to the dotted macOS bundle_short_version form "0.56.3" so version_compare and patch detection work correctly.
  • Add frontend icon component and PNG asset, and update the icon index to include Pd.

Summary by CodeRabbit

  • New Features
    • Pure Data (Pd) now supported on macOS with automated installation, uninstallation, and relaunch functionality.
    • Pd icon added to the software page interface for visual identification.

Add support for Pd (Pure Data) on macOS: new Homebrew input manifest and installer script, outputs and apps entry, frontend icon and image, and a version transformer.

- Add ee/maintained-apps/inputs/homebrew/pd.json and install script scripts/pd-install.sh that unzip the Homebrew archive, mount the embedded DMG, copy the .app, and handle quitting/relaunching the app.
- Add ee/maintained-apps/outputs/pd/darwin.json with version, installer, hashes, and embedded install/uninstall script refs; add app entry to ee/maintained-apps/outputs/apps.json.
- Register Pd in Homebrew external refs mapping and add PdVersionTransformer to convert Homebrew version strings like "0.56-3" to the dotted macOS bundle_short_version form "0.56.3" so version_compare and patch detection work correctly.
- Add frontend icon component and PNG asset, and update the icon index to include Pd.
@codecov

codecov Bot commented Jun 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 14.28571% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.03%. Comparing base (7cb898d) to head (0bc7ad2).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
...esters/homebrew/external_refs/version_shortener.go 0.00% 5 Missing ⚠️
...rontend/pages/SoftwarePage/components/icons/Pd.tsx 50.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main   #47056   +/-   ##
=======================================
  Coverage   67.03%   67.03%           
=======================================
  Files        2886     2887    +1     
  Lines      225155   225162    +7     
  Branches    11637    11637           
=======================================
+ Hits       150938   150946    +8     
- Misses      60544    60545    +1     
+ Partials    13673    13671    -2     
Flag Coverage Δ
backend 68.74% <0.00%> (+<0.01%) ⬆️
frontend 56.96% <50.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@allenhouchins
allenhouchins marked this pull request as ready for review June 8, 2026 13:55
@allenhouchins
allenhouchins requested review from a team as code owners June 8, 2026 13:55
Copilot AI review requested due to automatic review settings June 8, 2026 13:55
@fleet-release
fleet-release requested a review from eashaw June 8, 2026 13:55

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds Pd (Pure Data) as a Fleet-maintained app (FMA) for macOS by introducing a Homebrew input manifest and install script, generating the corresponding output manifest and apps catalog entry, wiring a Homebrew version transformer to normalize Pd’s version format for patch/version comparisons, and adding a frontend software icon mapping.

Changes:

  • Add Pd Homebrew input manifest + macOS install script (zip → dmg → app copy flow).
  • Generate Pd macOS output manifest (version/queries/installer + embedded install/uninstall refs) and register Pd in the FMA app catalog.
  • Add a Pd Homebrew version transformer and frontend icon component/mapping.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
frontend/pages/SoftwarePage/components/icons/Pd.tsx Adds Pd icon component for software UI.
frontend/pages/SoftwarePage/components/icons/index.ts Registers Pd icon and maps software name key to the new icon.
ee/maintained-apps/outputs/pd/darwin.json Adds generated Pd macOS output manifest including embedded scripts.
ee/maintained-apps/outputs/apps.json Adds Pd entry to the FMA apps catalog.
ee/maintained-apps/inputs/homebrew/scripts/pd-install.sh Adds Pd install script to extract zip, mount DMG, install .app, and relaunch if needed.
ee/maintained-apps/inputs/homebrew/pd.json Adds Pd Homebrew input manifest (token/slug/identifier/categories/script path).
ee/maintained-apps/ingesters/homebrew/external_refs/version_shortener.go Adds PdVersionTransformer to normalize Homebrew version strings for macOS comparisons.
ee/maintained-apps/ingesters/homebrew/external_refs/main.go Registers PdVersionTransformer in the external refs mapping.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +3 to +6
# variables
APPDIR="/Applications/"
TMPDIR=$(dirname "$(realpath "$INSTALLER_PATH")")
# functions
Comment on lines +78 to +87
// PdVersionTransformer converts Homebrew's Pd version scheme (e.g. "0.56-3") to
// the dotted form macOS reports as bundle_short_version (e.g. "0.56.3").
// Without this, osquery's version_compare treats "0.56-3" and "0.56.3" as
// different versions and patch policy detection breaks.
func PdVersionTransformer(app *maintained_apps.FMAManifestApp) (*maintained_apps.FMAManifestApp, error) {
if app.Version == "" {
return app, errors.New("empty version for Pd")
}
app.Version = strings.ReplaceAll(app.Version, "-", ".")
return app, nil
],
"refs": {
"2eac86b0": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\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\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move 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 # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\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 mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nsudo rm -rf \"$APPDIR/Pd-0.56-3.app\"\ntrash $LOGGED_IN_USER '~/Library/Preferences/org.puredata.pd.pd-gui.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/org.puredata.pd.pd-gui.savedState'\n",
"8a005b0c": "#!/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_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 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\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\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\n# Pd's download is a .zip that contains a .dmg, which in turn contains the .app.\n# The app folder name carries the version (e.g. \"Pd-0.56-3.app\"), so unzip\n# first, then mount the embedded DMG and copy whichever .app it contains. This\n# keeps the script version-agnostic across Homebrew bumps.\nEXTRACT_DIR=$(mktemp -d /tmp/pd_extract_XXXXXX)\nunzip -q \"$INSTALLER_PATH\" -d \"$EXTRACT_DIR\"\nDMG_PATH=$(find \"$EXTRACT_DIR\" -maxdepth 2 -name \"*.dmg\" | head -1)\nif [ -z \"$DMG_PATH\" ]; then\n echo \"No DMG found inside the Pd archive\" >&2\n exit 1\nfi\nMOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX)\nyes | hdiutil attach -plist -nobrowse -readonly -mountpoint \"$MOUNT_POINT\" \"$DMG_PATH\" || exit 1\nAPP_BUNDLE=$(find \"$MOUNT_POINT\" -maxdepth 1 -name \"*.app\" | head -1)\nif [ -z \"$APP_BUNDLE\" ]; then\n echo \"No .app found inside the Pd DMG\" >&2\n hdiutil detach \"$MOUNT_POINT\" || true\n exit 1\nfi\nAPP_NAME=$(basename \"$APP_BUNDLE\")\n# copy to the applications folder\nquit_and_track_application 'org.puredata.pd.pd-gui'\nif [ -d \"$APPDIR/$APP_NAME\" ]; then\n\tsudo mv \"$APPDIR/$APP_NAME\" \"$TMPDIR/$APP_NAME.bkp\"\nfi\nsudo cp -R \"$APP_BUNDLE\" \"$APPDIR\"\nhdiutil detach \"$MOUNT_POINT\" || true\nrelaunch_application 'org.puredata.pd.pd-gui'\n"
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR adds comprehensive support for Pure Data (Pd) as a managed macOS application. It introduces a version transformer that normalizes Homebrew's dash-delimited Pd versions to dot-delimited format, implements installation and uninstallation scripts with app lifecycle management (quit before replacement, relaunch in correct GUI session), defines Homebrew input metadata and a complete macOS maintenance manifest with version checks and embedded scripts, and adds a frontend icon component registered for UI display.

Possibly related PRs

  • fleetdm/fleet#46424: Both PRs extend frontend/pages/SoftwarePage/components/icons/index.ts by adding new icon entries to SOFTWARE_NAME_TO_ICON_MAP (Pd vs Joplin).
  • fleetdm/fleet#46738: Both PRs update the same SOFTWARE_NAME_TO_ICON_MAP to register icon components for managed applications.
  • fleetdm/fleet#45858: Both PRs add a new managed app with input config, installation scripts, maintenance manifest, and icon registration (Pd vs Secretive).
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description provides comprehensive details about all changes made, but the template's required checklist items are not addressed. Complete the provided template by addressing checklist items such as changes files, security validation, testing, and other applicable requirements for this PR.
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding Pd (Pure Data) support as a macOS Flexible Managed App.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch allenhouchins-pd

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ee/maintained-apps/inputs/homebrew/scripts/pd-install.sh`:
- Around line 117-123: The script only handles the exact APP_NAME, leaving older
versioned bundles (e.g., Pd-1.0.app, Pd-1.1.app) behind; before copying, derive
a stable prefix from APP_NAME (e.g., PREFIX="${APP_NAME%%-*}" or strip version
suffix) and locate all existing bundles in APPDIR that start with that prefix
(use APPDIR/"${PREFIX}"*.app), then quit via quit_and_track_application and move
or remove those matches to TMPDIR as backups (instead of only moving
"$APPDIR/$APP_NAME"), ensuring all prior Pd installs are cleaned up before sudo
cp -R "$APP_BUNDLE" "$APPDIR".

In `@ee/maintained-apps/outputs/pd/darwin.json`:
- Around line 19-20: The uninstall step currently runs sudo rm -rf
"$APPDIR/Pd-0.56-3.app" which only removes one hard-coded version; replace that
with logic that removes all Pd-*.app bundles (e.g. iterate over
"$APPDIR"/Pd-*.app and rm -rf each if it exists, or use find -maxdepth 1 -name
'Pd-*.app' -exec rm -rf {} \;) to ensure older/newer versions are removed.
Locate the hard-coded removal in the script (search for the literal sudo rm -rf
"$APPDIR/Pd-0.56-3.app") and update it there; keep quoting to handle spaces and
avoid accidental glob expansion when no matches.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 588c7c30-4d63-4bed-9905-bec0f4577b26

📥 Commits

Reviewing files that changed from the base of the PR and between 7cb898d and 0bc7ad2.

⛔ Files ignored due to path filters (1)
  • website/assets/images/app-icon-pd-60x60@2x.png is excluded by !**/*.png
📒 Files selected for processing (8)
  • ee/maintained-apps/ingesters/homebrew/external_refs/main.go
  • ee/maintained-apps/ingesters/homebrew/external_refs/version_shortener.go
  • ee/maintained-apps/inputs/homebrew/pd.json
  • ee/maintained-apps/inputs/homebrew/scripts/pd-install.sh
  • ee/maintained-apps/outputs/apps.json
  • ee/maintained-apps/outputs/pd/darwin.json
  • frontend/pages/SoftwarePage/components/icons/Pd.tsx
  • frontend/pages/SoftwarePage/components/icons/index.ts

Comment on lines +117 to +123
APP_NAME=$(basename "$APP_BUNDLE")
# copy to the applications folder
quit_and_track_application 'org.puredata.pd.pd-gui'
if [ -d "$APPDIR/$APP_NAME" ]; then
sudo mv "$APPDIR/$APP_NAME" "$TMPDIR/$APP_NAME.bkp"
fi
sudo cp -R "$APP_BUNDLE" "$APPDIR"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Versioned bundle names will leave stale Pd installs after upgrades.

The replacement path only targets /Applications/$APP_NAME for the new version name, so prior Pd-*.app versions remain installed. That can keep old inventory rows and prevent hosts from ever evaluating as fully patched.

Suggested fix
 APP_NAME=$(basename "$APP_BUNDLE")
 # copy to the applications folder
 quit_and_track_application 'org.puredata.pd.pd-gui'
-if [ -d "$APPDIR/$APP_NAME" ]; then
-	sudo mv "$APPDIR/$APP_NAME" "$TMPDIR/$APP_NAME.bkp"
-fi
+# Remove previously installed versioned Pd bundles (e.g. Pd-0.56-2.app).
+while IFS= read -r existing_pd_app; do
+  [ -d "$existing_pd_app" ] || continue
+  sudo mv "$existing_pd_app" "$TMPDIR/$(basename "$existing_pd_app").bkp"
+done < <(find "$APPDIR" -maxdepth 1 -type d -name "Pd-*.app")
+
 sudo cp -R "$APP_BUNDLE" "$APPDIR"
 hdiutil detach "$MOUNT_POINT" || true
 relaunch_application 'org.puredata.pd.pd-gui'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
APP_NAME=$(basename "$APP_BUNDLE")
# copy to the applications folder
quit_and_track_application 'org.puredata.pd.pd-gui'
if [ -d "$APPDIR/$APP_NAME" ]; then
sudo mv "$APPDIR/$APP_NAME" "$TMPDIR/$APP_NAME.bkp"
fi
sudo cp -R "$APP_BUNDLE" "$APPDIR"
APP_NAME=$(basename "$APP_BUNDLE")
# copy to the applications folder
quit_and_track_application 'org.puredata.pd.pd-gui'
# Remove previously installed versioned Pd bundles (e.g. Pd-0.56-2.app).
while IFS= read -r existing_pd_app; do
[ -d "$existing_pd_app" ] || continue
sudo mv "$existing_pd_app" "$TMPDIR/$(basename "$existing_pd_app").bkp"
done < <(find "$APPDIR" -maxdepth 1 -type d -name "Pd-*.app")
sudo cp -R "$APP_BUNDLE" "$APPDIR"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/inputs/homebrew/scripts/pd-install.sh` around lines 117 -
123, The script only handles the exact APP_NAME, leaving older versioned bundles
(e.g., Pd-1.0.app, Pd-1.1.app) behind; before copying, derive a stable prefix
from APP_NAME (e.g., PREFIX="${APP_NAME%%-*}" or strip version suffix) and
locate all existing bundles in APPDIR that start with that prefix (use
APPDIR/"${PREFIX}"*.app), then quit via quit_and_track_application and move or
remove those matches to TMPDIR as backups (instead of only moving
"$APPDIR/$APP_NAME"), ensuring all prior Pd installs are cleaned up before sudo
cp -R "$APP_BUNDLE" "$APPDIR".

Comment on lines +19 to +20
"2eac86b0": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\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\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move 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 # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\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 mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nsudo rm -rf \"$APPDIR/Pd-0.56-3.app\"\ntrash $LOGGED_IN_USER '~/Library/Preferences/org.puredata.pd.pd-gui.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/org.puredata.pd.pd-gui.savedState'\n",
"8a005b0c": "#!/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_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 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\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\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\n# Pd's download is a .zip that contains a .dmg, which in turn contains the .app.\n# The app folder name carries the version (e.g. \"Pd-0.56-3.app\"), so unzip\n# first, then mount the embedded DMG and copy whichever .app it contains. This\n# keeps the script version-agnostic across Homebrew bumps.\nEXTRACT_DIR=$(mktemp -d /tmp/pd_extract_XXXXXX)\nunzip -q \"$INSTALLER_PATH\" -d \"$EXTRACT_DIR\"\nDMG_PATH=$(find \"$EXTRACT_DIR\" -maxdepth 2 -name \"*.dmg\" | head -1)\nif [ -z \"$DMG_PATH\" ]; then\n echo \"No DMG found inside the Pd archive\" >&2\n exit 1\nfi\nMOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX)\nyes | hdiutil attach -plist -nobrowse -readonly -mountpoint \"$MOUNT_POINT\" \"$DMG_PATH\" || exit 1\nAPP_BUNDLE=$(find \"$MOUNT_POINT\" -maxdepth 1 -name \"*.app\" | head -1)\nif [ -z \"$APP_BUNDLE\" ]; then\n echo \"No .app found inside the Pd DMG\" >&2\n hdiutil detach \"$MOUNT_POINT\" || true\n exit 1\nfi\nAPP_NAME=$(basename \"$APP_BUNDLE\")\n# copy to the applications folder\nquit_and_track_application 'org.puredata.pd.pd-gui'\nif [ -d \"$APPDIR/$APP_NAME\" ]; then\n\tsudo mv \"$APPDIR/$APP_NAME\" \"$TMPDIR/$APP_NAME.bkp\"\nfi\nsudo cp -R \"$APP_BUNDLE\" \"$APPDIR\"\nhdiutil detach \"$MOUNT_POINT\" || true\nrelaunch_application 'org.puredata.pd.pd-gui'\n"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Embedded uninstall script removes only one versioned app path.

sudo rm -rf "$APPDIR/Pd-0.56-3.app" won’t remove older/newer Pd-*.app directories, so uninstall can leave Pd installed.

Suggested fix (script body before JSON escaping)
-sudo rm -rf "$APPDIR/Pd-0.56-3.app"
+find "$APPDIR" -maxdepth 1 -type d -name "Pd-*.app" -exec sudo rm -rf {} +
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/pd/darwin.json` around lines 19 - 20, The
uninstall step currently runs sudo rm -rf "$APPDIR/Pd-0.56-3.app" which only
removes one hard-coded version; replace that with logic that removes all
Pd-*.app bundles (e.g. iterate over "$APPDIR"/Pd-*.app and rm -rf each if it
exists, or use find -maxdepth 1 -name 'Pd-*.app' -exec rm -rf {} \;) to ensure
older/newer versions are removed. Locate the hard-coded removal in the script
(search for the literal sudo rm -rf "$APPDIR/Pd-0.56-3.app") and update it
there; keep quoting to handle spaces and avoid accidental glob expansion when no
matches.

@allenhouchins
allenhouchins merged commit 4ce91c3 into main Jun 8, 2026
50 checks passed
@allenhouchins
allenhouchins deleted the allenhouchins-pd branch June 8, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants