Skip to content

Refactor macOS CLI installation to use ~/.local/bin#2937

Open
ivan-ottinger wants to merge 9 commits intotrunkfrom
stu-1363-auto-install-cli-macos
Open

Refactor macOS CLI installation to use ~/.local/bin#2937
ivan-ottinger wants to merge 9 commits intotrunkfrom
stu-1363-auto-install-cli-macos

Conversation

@ivan-ottinger
Copy link
Copy Markdown
Contributor

@ivan-ottinger ivan-ottinger commented Mar 27, 2026

Related issues

How AI was used in this PR

Claude Code was used to implement the changes, write tests, and create this PR. The solution was developed iteratively through discussion — we explored the existing codebase patterns (macOS and Windows installation managers), identified edge cases (e.g., respecting user preference when CLI is explicitly disabled), and refined the approach based on the Linear issue description and comments.

Proposed Changes

  • Replace /usr/local/bin/studio symlink (requires sudo) with ~/.local/bin/studio (user-writable, no sudo needed)
  • Auto-install CLI on first app startup via autoInstallMacOSCliIfNeeded(), with a cliAutoInstalled flag in app config to avoid re-enabling if the user explicitly disabled it
  • Add shell profile PATH management: appends export PATH="$HOME/.local/bin:$PATH" to the user's shell profile (.zshrc by default) if ~/.local/bin isn't already in PATH. ~/.local/bin is prepended so the bundled CLI takes priority over any standalone npm-installed version
  • Remove sudo shell scripts (install-studio-cli.sh, uninstall-studio-cli.sh) that are no longer needed
  • Add unit tests for the refactored installation manager
  • Auto-install is skipped in development mode (npm start) to avoid creating symlinks pointing to dev resources

Testing Instructions

Note: Auto-install only runs in production mode. You must test with a built app (npm run make), not npm start.

Setup

  1. Remove any existing CLI symlink: rm -f ~/.local/bin/studio
  2. Remove the auto-install flag if present (if you are testing this PR for the first time, it should not be present - since we are introducing it in this PR): edit ~/.studio/app.json and delete the "cliAutoInstalled": true entry
  3. Build and install the app: npm run make, then install the resulting DMG to /Applications/

Auto-install on first launch

  1. Launch Studio
  2. Verify the symlink was created: ls -la ~/.local/bin/studio — should point to /Applications/Studio.app/Contents/Resources/bin/studio-cli.sh
  3. Verify the flag was set: grep cliAutoInstalled ~/.studio/app.json — should show "cliAutoInstalled": true
  4. Open a new terminal and run studio --version — should print the current version
  5. Check your shell profile: tail -3 ~/.zshrc — should contain export PATH="$HOME/.local/bin:$PATH" (if it wasn't already there)

Shell profile PATH management

  1. To test that the export line is added correctly, temporarily remove $HOME/.local/bin from your shell profile (e.g., edit ~/.zshrc and delete the export PATH="$HOME/.local/bin:$PATH" line), then remove the CLI symlink and reset the flag:
    • rm -f ~/.local/bin/studio
    • Edit ~/.studio/app.json and delete the "cliAutoInstalled": true entry
  2. Relaunch Studio
  3. Verify the export line was added back: grep '$HOME/.local/bin' ~/.zshrc — should show export PATH="$HOME/.local/bin:$PATH"
  4. Verify it was not duplicated: grep -c '$HOME/.local/bin' ~/.zshrc — should show 1

User preference is respected

  1. In Studio, go to Settings > Preferences, toggle the CLI off, and save
  2. Verify the symlink is gone: ls ~/.local/bin/studio — should not exist
  3. Quit and relaunch Studio
  4. Verify the CLI was not reinstalled: ls ~/.local/bin/studio — should still not exist
  5. Verify the flag is still set: grep cliAutoInstalled ~/.studio/app.json — should still show true

Re-enabling works without sudo

  1. Toggle the CLI back on in Settings > Preferences, and save
  2. Verify the symlink is back: ls -la ~/.local/bin/studio
  3. Verify no password prompt was shown

No-op on subsequent launches

  1. Quit and relaunch Studio
  2. Verify the symlink is unchanged and no errors in the logs

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

Replace the sudo-based /usr/local/bin symlink approach with a
user-writable ~/.local/bin directory. Auto-install CLI on app startup
and manage shell profile PATH if needed.
@ivan-ottinger ivan-ottinger self-assigned this Mar 27, 2026
Add cliAutoInstalled flag to app config so auto-install only runs on
first launch. If the user disables the CLI toggle, it won't be
re-enabled on the next app start.
Use path.join() for test constants and assertions so paths use the
correct separator on Windows CI (backslashes) vs macOS (forward slashes).
Use describe.skipIf for the MacOSCliInstallationManager tests since
they use macOS path conventions. Matches the existing codebase pattern
of using hardcoded forward-slash paths in test mocks.
The old /usr/local/bin/studio symlink almost always requires sudo to
remove, making cleanup a no-op for most users. The old symlink is
harmless since ~/.local/bin takes priority in PATH.
Prevents dev mode from creating a symlink pointing to dev resources
and setting the cliAutoInstalled flag, which would interfere with
production installs.

const ERROR_FILE_ALREADY_EXISTS = 'Studio CLI symlink path already occupied by non-symlink';
// Defined in @vscode/sudo-prompt
const ERROR_PERMISSION = 'User did not grant permission.';
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Since we don't run sudo, the error message is no longer needed.

@ivan-ottinger ivan-ottinger marked this pull request as ready for review March 27, 2026 15:53
@ivan-ottinger ivan-ottinger requested a review from Copilot March 27, 2026 15:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Refactors the macOS Studio CLI installation flow to avoid sudo by installing the studio symlink into ~/.local/bin, adds first-run auto-install behavior, and updates user config/types to track whether auto-install has occurred.

Changes:

  • Switch macOS CLI symlink target from /usr/local/bin/studio to ~/.local/bin/studio and remove the legacy sudo-based shell scripts.
  • Add autoInstallMacOSCliIfNeeded() on app boot (production + darwin only) and persist cliAutoInstalled in app config.
  • Add PATH management by appending export PATH="$HOME/.local/bin:$PATH" to a detected shell profile when needed, plus new unit tests.

Reviewed changes

Copilot reviewed 5 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
apps/studio/src/storage/user-data.ts Allows cliAutoInstalled to be persisted via updateAppdata() safe keys.
apps/studio/src/storage/storage-types.ts Adds cliAutoInstalled?: boolean to the UserData type.
apps/studio/src/modules/cli/lib/macos-installation-manager.ts Implements ~/.local/bin symlink install, profile PATH updates, and auto-install entrypoint.
apps/studio/src/index.ts Calls autoInstallMacOSCliIfNeeded() during app boot.
apps/studio/src/modules/cli/lib/tests/macos-installation-manager.test.ts Adds unit tests for the refactored macOS installation manager and auto-install behavior.
apps/studio/bin/install-studio-cli.sh Removed legacy sudo install script.
apps/studio/bin/uninstall-studio-cli.sh Removed legacy sudo uninstall script.

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

Return early in uninstallCli() when the symlink doesn't exist instead
of proceeding to unlink (which would throw ENOENT). Also narrow the
test skip from all non-darwin platforms to Windows only, since the
POSIX paths work fine on Linux.
@ivan-ottinger ivan-ottinger requested a review from a team March 27, 2026 17:11
@wpmobilebot
Copy link
Copy Markdown
Collaborator

📊 Performance Test Results

Comparing bf34da1 vs trunk

app-size

Metric trunk bf34da1 Diff Change
App Size (Mac) 1268.05 MB 1268.06 MB +0.01 MB ⚪ 0.0%

site-editor

Metric trunk bf34da1 Diff Change
load 1889 ms 1876 ms 13 ms ⚪ 0.0%

site-startup

Metric trunk bf34da1 Diff Change
siteCreation 8150 ms 8184 ms +34 ms ⚪ 0.0%
siteStartup 4828 ms 4845 ms +17 ms ⚪ 0.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

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.

3 participants