Skip to content

feat: uloop can uninstall its global command from terminal and Settings#1135

Merged
hatayama merged 3 commits into
v3-betafrom
feature/hatayama/add-uloop-uninstall-command
May 16, 2026
Merged

feat: uloop can uninstall its global command from terminal and Settings#1135
hatayama merged 3 commits into
v3-betafrom
feature/hatayama/add-uloop-uninstall-command

Conversation

@hatayama
Copy link
Copy Markdown
Owner

@hatayama hatayama commented May 15, 2026

Summary

  • Users can run uloop uninstall from a terminal to remove the globally installed uloop command.
  • The Settings uninstall action now uses the same uninstall command path, so terminal and UI removals share behavior.

User Impact

  • Before this change, the terminal dispatcher reported Unknown command: uninstall, and Settings used a separate editor-side deletion path.
  • After this change, macOS removes the current-user launcher directly, Windows schedules removal after the running process exits, and Settings delegates to that same installed launcher command.
  • PATH cleanup remains a manual, explicit step so shared user PATH entries are not removed unexpectedly.

Changes

  • Add a project-independent uninstall command to the native dispatcher.
  • Route Settings uninstall through the installed launcher with ULOOP_INSTALL_DIR for custom install directories.
  • Bump the CLI contract and minimum required CLI version to 3.0.0-beta.8 and refresh checked-in native binaries.
  • Refresh completions, command listing, help coverage, and focused tests.

Verification

  • go test ./...
  • scripts/check-go-cli.sh
  • Packages/src/Cli~/dist/darwin-arm64/uloop compile --project-path /Users/a12115/ghq/hatayama/unity-cli-loop
  • Packages/src/Cli~/dist/darwin-arm64/uloop run-tests --project-path /Users/a12115/ghq/hatayama/unity-cli-loop --test-mode EditMode --filter-type regex --filter-value NativeCliInstallerTests|CliSetupApplicationServiceTests
  • Packages/src/Cli~/dist/darwin-arm64/uloop uninstall --help
  • codex-review --mode branch --parallel-tests "scripts/check-go-cli.sh" v3-beta

PR Summary: uloop Global Uninstall Command

Overview

This PR adds a project-independent uloop uninstall native dispatcher command that enables users to remove the globally installed uloop launcher from their system. The feature addresses the previous limitation where the terminal dispatcher had no CLI method to uninstall the launcher, reporting Unknown command: uninstall instead.

Core Changes

Go CLI Implementation (Packages/src/Cli~/)

  • New uninstall package (internal/uninstall/command.go): Implements OS-specific uninstall command construction

    • macOS: Builds a shell command (rm -f) to remove the installed binary
    • Windows: Builds a PowerShell command that waits for the parent process to exit before deletion
    • Unsupported platforms: Returns a descriptive error message
    • Includes helper functions for path resolution, shell quoting, and PowerShell command encoding
  • CLI command handler (internal/cli/uninstall.go): Processes uloop uninstall invocations

    • Parses arguments and detects help flag
    • Resolves install directory (from ULOOP_INSTALL_DIR env var or runtime OS detection)
    • Executes OS-specific uninstall command via uninstall.CommandForOS
    • Prints status messages indicating immediate removal (macOS) or scheduled removal (Windows)
    • Explicitly notes that PATH cleanup remains a manual step
  • Command registry update (internal/cli/command_registry.go): Registers uninstall as a native command

    • Automatically included in shell completions and --list-commands output

C# Editor Integration (Packages/src/Editor/)

  • NativeCliInstaller refactoring (Infrastructure/CLI/NativeCliInstaller.cs):

    • Updated UninstallAsync to accept CancellationToken parameter
    • Refactored execution pipeline into generic RunCliSetupCommand method used by both install and uninstall
    • Now delegates uninstall to the native CLI command instead of directly managing file deletion
    • Injects INSTALL_DIR_ENVIRONMENT_VARIABLE into child process
    • Improved failure messaging with command-agnostic helpers (BuildCliSetupCommandFailure, BuildCliSetupCommandTimeoutFailure)
  • CLI version requirement (Domain/CliConstants.cs): Updated MINIMUM_REQUIRED_CLI_VERSION from 3.0.0-beta.7 to 3.0.0-beta.8

  • Settings integration: Routes editor uninstall action through the installed native CLI instead of performing inline removal

Contract & Manifest Updates

  • Packages/src/Cli~/contract.json: Bumped cliVersion to 3.0.0-beta.8
  • Packages/src/Cli~/internal/tools/default-tools.json: Updated version field to 3.0.0-beta.8

Test Coverage

Go Tests

  • internal/uninstall/command_test.go:

    • Validates macOS shell command generation for uninstall
    • Validates Windows PowerShell command generation with process wait logic
    • Verifies unsupported OS rejection with appropriate error message
  • internal/cli/completion_test.go: Updated to include uninstall in expected command list

  • internal/cli/help_test.go: Updated to verify uninstall appears in launcher and project-local help output

  • internal/cli/uninstall_test.go:

    • TestRunProjectLocalUninstallHelpDoesNotRequireUnityProject: Confirms uninstall help works without active project context

C# Tests

  • Assets/Tests/Editor/NativeCliInstallerTests.cs: Added two test cases

    • BuildUninstallCommand_OnMacRunsInstalledLauncher(): Verifies macOS uninstall command construction
    • BuildUninstallCommand_OnWindowsRunsInstalledLauncher(): Verifies Windows uninstall command construction
  • Assets/Tests/Editor/CliSetupApplicationServiceTests.cs: Renamed test method from GetMinimumRequiredCliVersion_RequiresSingleBinaryCliRelease() to GetMinimumRequiredCliVersion_RequiresTerminalUninstallCliRelease() and updated assertion to expect version 3.0.0-beta.8

Architecture

classDiagram
    class CLI["CLI Entry Point (uloop uninstall)"]
    class UninstallHandler["cli.RunProjectLocal<br/>(tryHandleUninstallRequest)"]
    class CommandBuilder["uninstall.CommandForOS<br/>- Options<br/>- Command"]
    class MacOSImpl["macOS Implementation<br/>shell command with rm -f"]
    class WindowsImpl["Windows Implementation<br/>PowerShell with Wait-Process"]
    class EditorIntegration["NativeCliInstaller<br/>(UninstallAsync)"]
    
    CLI --> UninstallHandler
    UninstallHandler --> CommandBuilder
    CommandBuilder --> MacOSImpl
    CommandBuilder --> WindowsImpl
    EditorIntegration --> UninstallHandler
Loading

User Impact

  • Previously: Terminal displayed Unknown command: uninstall with no way to remove the launcher via CLI
  • After PR: Users can now run uloop uninstall to remove the launcher
    • macOS: Immediate removal of the launcher binary
    • Windows: Scheduled self-removal after the running process exits
    • Manual PATH cleanup remains user responsibility on both platforms

Version Requirement Change

Editor now requires CLI version 3.0.0-beta.8 (previously 3.0.0-beta.7) due to dependency on the new uninstall subcommand.

Review Change Stack

Provide a project-independent native command that removes the installed launcher binary on macOS and schedules Windows self-removal after the running process exits. Keep PATH cleanup manual to avoid removing shared user PATH entries, and refresh checked-in CLI binaries for the new command surface.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

Warning

Rate limit exceeded

@hatayama has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 25 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d20efc44-660d-4bee-9b2c-416a52e55ba3

📥 Commits

Reviewing files that changed from the base of the PR and between b50db44 and 56364a2.

📒 Files selected for processing (2)
  • Assets/Tests/Editor/NativeCliInstallerTests.cs
  • Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs
📝 Walkthrough

Walkthrough

This PR adds terminal uninstall support to the uloop CLI launcher. It implements OS-specific uninstall command construction (POSIX shell for macOS, deferred PowerShell for Windows), integrates the uninstall handler into the CLI command dispatcher, updates the editor's NativeCliInstaller to orchestrate uninstall execution, and bumps the version to 3.0.0-beta.8.

Changes

Uninstall Feature Implementation

Layer / File(s) Summary
Version bump to 3.0.0-beta.8
Packages/src/Cli~/contract.json, Packages/src/Cli~/internal/tools/default-tools.json, Packages/src/Editor/Domain/CliConstants.cs, Assets/Tests/Editor/CliSetupApplicationServiceTests.cs
Configuration and constant version fields updated from 3.0.0-beta.7 to 3.0.0-beta.8; test expectations adjusted to match new minimum version.
Uninstall command specification and OS-specific construction
Packages/src/Cli~/internal/uninstall/command.go, Packages/src/Cli~/internal/uninstall/command_test.go
CommandForOS generates OS-specific commands: POSIX shell rm -f removal for macOS, PowerShell deferred deletion (waits for parent process exit) for Windows. Includes UTF-16LE encoding, PowerShell escaping, and shell quoting utilities. Tested on both supported platforms and validates unsupported OS rejection.
CLI uninstall handler and command registration
Packages/src/Cli~/internal/cli/uninstall.go, Packages/src/Cli~/internal/cli/uninstall_test.go, Packages/src/Cli~/internal/cli/command_registry.go, Packages/src/Cli~/internal/cli/run.go, Packages/src/Cli~/internal/cli/error_envelope.go, Packages/src/Cli~/internal/cli/help_test.go, Packages/src/Cli~/internal/cli/completion_test.go
tryHandleUninstallRequest recognizes the uninstall subcommand, resolves the install directory (respects ULOOP_INSTALL_DIR override, defaults to platform-specific paths), builds and executes the OS-specific command, and outputs removal confirmation (deferred vs. immediate). Uninstall is registered as a native command and appears in help and completion output. Unsupported OS errors are classified with instructional next actions.
C# editor uninstall infrastructure
Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs, Assets/Tests/Editor/NativeCliInstallerTests.cs, Packages/src/Editor/Infrastructure/CLI/NativeCliInstallerService.cs
NativeCliInstaller.UninstallAsync added with cancellation token support; process execution refactored into generic RunCliSetupCommand used by both install and uninstall paths. Uninstall passes INSTALL_DIR_ENVIRONMENT_VARIABLE to the child process. Error and timeout message builders generalized to incorporate command description. NativeCliInstallerService propagates cancellation token to uninstall. Tests verify BuildUninstallCommand returns correct FileName, Arguments, and ManualCommand for both macOS and Windows.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant RunProjectLocal
  participant tryHandleUninstallRequest
  participant resolveUninstallInstallDir
  participant CommandForOS
  participant exec.CommandContext
  participant stdout_stderr
  User->>RunProjectLocal: uloop uninstall
  RunProjectLocal->>tryHandleUninstallRequest: args
  alt uninstall --help
    tryHandleUninstallRequest->>stdout_stderr: print help
  else uninstall
    tryHandleUninstallRequest->>resolveUninstallInstallDir: detect OS
    resolveUninstallInstallDir-->>tryHandleUninstallRequest: install_dir
    tryHandleUninstallRequest->>CommandForOS: (goos, install_dir, pid)
    alt darwin
      CommandForOS-->>tryHandleUninstallRequest: shell rm command
    else windows
      CommandForOS-->>tryHandleUninstallRequest: powershell deferred
    end
    tryHandleUninstallRequest->>exec.CommandContext: execute
    exec.CommandContext-->>tryHandleUninstallRequest: exit_code
    tryHandleUninstallRequest->>stdout_stderr: print result
  end
  stdout_stderr-->>User: removal confirmation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • hatayama/unity-cli-loop#1101: Updates minimum required CLI version and test expectations in parallel with this version bump to 3.0.0-beta.8.
  • hatayama/unity-cli-loop#1038: Refactors native CLI uninstall and PATH cleanup logic in NativeCliInstaller, directly related to the uninstall infrastructure changes in this PR.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: uloop can uninstall its global command from terminal and Settings' directly and specifically describes the main change: adding a new uninstall feature for the uloop global command accessible from both terminal and Settings.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/hatayama/add-uloop-uninstall-command

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.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 13 files

Re-trigger cubic

Route the Settings uninstall action through the installed native CLI so terminal and UI removal use the same command contract. Bump the required CLI contract to 3.0.0-beta.8 because the editor now depends on the uninstall subcommand.
@hatayama hatayama changed the title feat: uloop can uninstall its global command from the terminal feat: uloop can uninstall its global command from terminal and Settings May 16, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
Packages/src/Cli~/internal/uninstall/command_test.go (1)

50-55: ⚡ Quick win

Assert the encoded PowerShell payload path, not only the helper output.

This assertion currently validates windowsDeletionScript(...) directly, so it won’t catch regressions where command.Args no longer embeds that script correctly. Decode the encoded argument and assert Wait-Process and PID there.

♻️ Suggested test hardening
 import (
+	"encoding/base64"
 	"strings"
 	"testing"
+	"unicode/utf16"
 )

+func decodePowerShellEncodedCommand(t *testing.T, encoded string) string {
+	t.Helper()
+	raw, err := base64.StdEncoding.DecodeString(encoded)
+	if err != nil {
+		t.Fatalf("failed to decode base64 payload: %v", err)
+	}
+	if len(raw)%2 != 0 {
+		t.Fatalf("invalid UTF-16LE payload length: %d", len(raw))
+	}
+	u16 := make([]uint16, 0, len(raw)/2)
+	for i := 0; i < len(raw); i += 2 {
+		u16 = append(u16, uint16(raw[i])|uint16(raw[i+1])<<8)
+	}
+	return string(utf16.Decode(u16))
+}
+
 func TestCommandForWindowsSchedulesRemovalAfterCurrentProcessExits(t *testing.T) {
@@
-	deletionScript := windowsDeletionScript(command.TargetPath, 5678)
-	for _, expected := range []string{"Wait-Process -Id $ParentPid", "$ParentPid = 5678"} {
-		if !strings.Contains(deletionScript, expected) {
-			t.Fatalf("expected %s in deletion script: %s", expected, deletionScript)
-		}
-	}
+	outer := decodePowerShellEncodedCommand(t, command.Args[len(command.Args)-1])
+	if !strings.Contains(outer, "$EncodedDeletion = '") {
+		t.Fatalf("outer script does not contain encoded deletion payload: %s", outer)
+	}
🤖 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 `@Packages/src/Cli`~/internal/uninstall/command_test.go around lines 50 - 55,
The test currently asserts substrings in windowsDeletionScript(...) instead of
the actual encoded payload passed in command.Args; update the test to extract
the encoded PowerShell argument from command.Args (the value passed to
-EncodedCommand), decode it using the same encoding PowerShell expects (Base64
of UTF-16LE/Unicode), and then assert that the decoded payload contains
"Wait-Process -Id $ParentPid" and "$ParentPid = 5678"; locate and modify the
assertions around windowsDeletionScript and command.Args to perform the
extraction, decode, and checks so regressions embedding the script into
command.Args are caught.
🤖 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 `@Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs`:
- Around line 200-205: The cancellation branch in RunCliSetupCommand currently
returns a hardcoded message ("Release CLI installer was canceled.") which is
incorrect when this helper is used for uninstall; change the cancellation result
to use the provided commandDescription (e.g., build the returned
CliInstallResult with $"{commandDescription} was canceled." or equivalent) so
the reported operation matches the invoked action; apply the same change to the
other cancellation branch referenced around the second usage (the other return
that currently uses the hardcoded string) so both uninstall and install report
the correct commandDescription.

---

Nitpick comments:
In `@Packages/src/Cli`~/internal/uninstall/command_test.go:
- Around line 50-55: The test currently asserts substrings in
windowsDeletionScript(...) instead of the actual encoded payload passed in
command.Args; update the test to extract the encoded PowerShell argument from
command.Args (the value passed to -EncodedCommand), decode it using the same
encoding PowerShell expects (Base64 of UTF-16LE/Unicode), and then assert that
the decoded payload contains "Wait-Process -Id $ParentPid" and "$ParentPid =
5678"; locate and modify the assertions around windowsDeletionScript and
command.Args to perform the extraction, decode, and checks so regressions
embedding the script into command.Args are caught.
🪄 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: b0036f02-c57b-49b3-8bf8-3bd368d45c84

📥 Commits

Reviewing files that changed from the base of the PR and between 6601ed3 and b50db44.

⛔ Files ignored due to path filters (3)
  • Packages/src/Cli~/dist/darwin-amd64/uloop is excluded by !**/dist/** and included by none
  • Packages/src/Cli~/dist/darwin-arm64/uloop is excluded by !**/dist/** and included by none
  • Packages/src/Cli~/dist/windows-amd64/uloop.exe is excluded by !**/dist/**, !**/*.exe and included by none
📒 Files selected for processing (16)
  • Assets/Tests/Editor/CliSetupApplicationServiceTests.cs
  • Assets/Tests/Editor/NativeCliInstallerTests.cs
  • Packages/src/Cli~/contract.json
  • Packages/src/Cli~/internal/architecture/architecture_test.go
  • Packages/src/Cli~/internal/cli/command_registry.go
  • Packages/src/Cli~/internal/cli/completion_test.go
  • Packages/src/Cli~/internal/cli/error_envelope.go
  • Packages/src/Cli~/internal/cli/help_test.go
  • Packages/src/Cli~/internal/cli/run.go
  • Packages/src/Cli~/internal/cli/uninstall.go
  • Packages/src/Cli~/internal/cli/uninstall_test.go
  • Packages/src/Cli~/internal/tools/default-tools.json
  • Packages/src/Cli~/internal/uninstall/command.go
  • Packages/src/Cli~/internal/uninstall/command_test.go
  • Packages/src/Editor/Domain/CliConstants.cs
  • Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs

Comment thread Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 9 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs">

<violation number="1" location="Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs:230">
P2: The cancellation branch in `RunCliSetupCommand` still returns the hardcoded message `"Release CLI installer was canceled."` instead of using the `commandDescription` parameter. When this shared method is called for uninstall, cancelling will incorrectly report "Release CLI installer was canceled." Use `BuildSentenceSubject(commandDescription)` consistently, e.g.:
```csharp
return new CliInstallResult(false, $"{BuildSentenceSubject(commandDescription)} was canceled.");
```</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Re-trigger cubic

Comment thread Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs
Use the shared setup command description when cancellation happens so editor uninstall no longer reports the release installer path. Add focused coverage for the uninstall cancellation message.
@hatayama hatayama merged commit 4122d57 into v3-beta May 16, 2026
17 checks passed
@hatayama hatayama deleted the feature/hatayama/add-uloop-uninstall-command branch May 16, 2026 00:26
@github-actions github-actions Bot mentioned this pull request May 16, 2026
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.

1 participant