Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified Packages/src/Cli~/Core~/dist/darwin-amd64/uloop-core
Binary file not shown.
Binary file modified Packages/src/Cli~/Core~/dist/darwin-arm64/uloop-core
Binary file not shown.
Binary file modified Packages/src/Cli~/Core~/dist/windows-amd64/uloop-core.exe
Binary file not shown.
16 changes: 14 additions & 2 deletions Packages/src/Cli~/Core~/internal/presentation/cli/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,23 @@ func printOptionsForCommand(command string, cache toolsCache, stdout io.Writer)
}

func detectShell() string {
if runtime.GOOS == "windows" {
return detectShellFromEnvironment(runtime.GOOS, os.Getenv("SHELL"), os.Getenv("MSYSTEM"))
}

func detectShellFromEnvironment(goos string, shellPath string, msystem string) string {
posixShell := detectPosixShell(shellPath)
if goos == "windows" {
if posixShell != "" && msystem != "" {
return posixShell
}
return "powershell"
}

shellPath := os.Getenv("SHELL")
return posixShell
}

func detectPosixShell(shellPath string) string {
shellPath = strings.ToLower(shellPath)
if strings.Contains(shellPath, "zsh") {
return "zsh"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,24 @@ func TestCompletionPrintsShellScriptWithoutProject(t *testing.T) {
}
}

// Tests that Git Bash auto-install writes bash completion instead of PowerShell completion.
func TestDetectShellOnWindowsGitBashUsesBash(t *testing.T) {
shellName := detectShellFromEnvironment("windows", "/usr/bin/bash", "MINGW64")

if shellName != "bash" {
t.Fatalf("windows Git Bash shell mismatch: %s", shellName)
}
}

// Tests that regular Windows terminals still get the native PowerShell completion default.
func TestDetectShellOnWindowsPowerShellDefaultsToPowerShell(t *testing.T) {
shellName := detectShellFromEnvironment("windows", "", "")

if shellName != "powershell" {
t.Fatalf("windows default shell mismatch: %s", shellName)
}
}

func containsString(values []string, expected string) bool {
for _, value := range values {
if value == expected {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestPrintLauncherHelpListsNativeCommandsAndLiveToolGuidance(t *testing.T) {
"uloop list",
"--project-path <path>",
"uloop --project-path /path/to/project list",
"uloop completion --list-options <command>",
"uloop --list-options <command>",
} {
if !strings.Contains(output, expected) {
t.Fatalf("help output missing %q:\n%s", expected, output)
Expand Down
4 changes: 2 additions & 2 deletions Packages/src/Cli~/Core~/internal/presentation/cli/run_help.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ func printMainHelp(stdout io.Writer, description string, cache toolsCache, hasPr
writeLine(stdout, " uloop list Show the live Unity tool list")
writeLine(stdout, " uloop --project-path /path/to/project list Show tools for another Unity project")
writeLine(stdout, " uloop <command> --help Show help for native commands that support it")
writeLine(stdout, " uloop completion --list-commands Print command names for completion")
writeLine(stdout, " uloop completion --list-options <command> Print options for a Unity tool command")
writeLine(stdout, " uloop --list-commands Print command names for completion")
writeLine(stdout, " uloop --list-options <command> Print options for a Unity tool command")
}

func printNativeCommandHelp(stdout io.Writer) {
Expand Down
2 changes: 1 addition & 1 deletion Packages/src/Cli~/Core~/internal/presentation/cli/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func buildToolParams(args []string, tool toolDefinition) (map[string]any, string
message: "Unknown option for " + tool.Name + ": --" + flag.name,
option: "--" + flag.name,
command: tool.Name,
nextActions: []string{"Run `uloop completion --list-options " + tool.Name + "` to inspect supported options."},
nextActions: []string{"Run `uloop --list-options " + tool.Name + "` to inspect supported options."},
}
}

Expand Down
Binary file modified Packages/src/Cli~/Dispatcher~/dist/darwin-amd64/uloop-dispatcher
Binary file not shown.
Binary file modified Packages/src/Cli~/Dispatcher~/dist/darwin-arm64/uloop-dispatcher
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,30 @@ import (
)

func detectShell() string {
return detectShellForPlatform(runtime.GOOS, os.Getenv("SHELL"), exec.LookPath)
return detectShellForPlatform(runtime.GOOS, os.Getenv("SHELL"), os.Getenv("MSYSTEM"), exec.LookPath)
}

func detectShellForPlatform(goos string, shellPath string, lookPath func(string) (string, error)) string {
func detectShellForPlatform(goos string, shellPath string, msystem string, lookPath func(string) (string, error)) string {
shellName := detectShellName(shellPath)
if goos == "windows" {
if isPosixShell(shellName) && msystem != "" {
return shellName
}
if shellName == "pwsh" || shellName == "powershell" {
return shellName
}
if _, err := lookPath("pwsh"); err == nil {
return "pwsh"
}
if _, err := lookPath("powershell"); err == nil {
return "powershell"
}
return ""
}
return shellName
}

func detectShellName(shellPath string) string {
shellPath = strings.ToLower(shellPath)
if strings.Contains(shellPath, "pwsh") {
return "pwsh"
Expand All @@ -25,14 +45,5 @@ func detectShellForPlatform(goos string, shellPath string, lookPath func(string)
if strings.Contains(shellPath, "bash") {
return "bash"
}
if goos == "windows" {
if _, err := lookPath("pwsh"); err == nil {
return "pwsh"
}
if _, err := lookPath("powershell"); err == nil {
return "powershell"
}
return ""
}
return ""
}
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ func TestRunCompletionListsNoUpdateOptions(t *testing.T) {

func TestDetectShellForPlatformPrefersPwshOnWindows(t *testing.T) {
// Verifies that Windows completion install targets PowerShell 7 when it is available.
shell := detectShellForPlatform("windows", "", func(name string) (string, error) {
shell := detectShellForPlatform("windows", "", "", func(name string) (string, error) {
if name == "pwsh" {
return filepath.Join("bin", "pwsh"), nil
}
Expand All @@ -774,7 +774,32 @@ func TestDetectShellForPlatformPrefersPwshOnWindows(t *testing.T) {

func TestDetectShellForPlatformHonorsWindowsPowerShellEnvironment(t *testing.T) {
// Verifies that Windows PowerShell is not mistaken for a pwsh profile.
shell := detectShellForPlatform("windows", `C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`, func(string) (string, error) {
shell := detectShellForPlatform("windows", `C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`, "", func(string) (string, error) {
return "", os.ErrNotExist
})

if shell != "powershell" {
t.Fatalf("shell mismatch: %s", shell)
}
}

func TestDetectShellForPlatformHonorsWindowsGitBashEnvironment(t *testing.T) {
// Verifies that Git Bash completion install does not fall back to PowerShell.
shell := detectShellForPlatform("windows", "/usr/bin/bash", "MINGW64", func(string) (string, error) {
return "", os.ErrNotExist
})

if shell != "bash" {
t.Fatalf("shell mismatch: %s", shell)
}
}

func TestDetectShellForPlatformIgnoresWindowsBashWithoutMsys(t *testing.T) {
// Verifies that a plain SHELL override is not enough to redirect Windows completion install.
shell := detectShellForPlatform("windows", "/usr/bin/bash", "", func(name string) (string, error) {
if name == "powershell" {
return filepath.Join("bin", "powershell"), nil
}
return "", os.ErrNotExist
})

Expand Down
4 changes: 2 additions & 2 deletions Packages/src/Cli~/Dispatcher~/internal/dispatcher/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ func printMainHelp(stdout io.Writer, cache cachedTools, hasProjectToolCache bool
writeLine(stdout, " uloop list Show the live Unity tool list")
writeLine(stdout, " uloop --project-path /path/to/project list Show tools for another Unity project")
writeLine(stdout, " uloop <command> --help Show help for native commands that support it")
writeLine(stdout, " uloop completion --list-commands Print command names for completion")
writeLine(stdout, " uloop completion --list-options <command> Print options for a Unity tool command")
writeLine(stdout, " uloop --list-commands Print command names for completion")
writeLine(stdout, " uloop --list-options <command> Print options for a Unity tool command")
}

func printUnityToolCommandHelp(stdout io.Writer, cache cachedTools, hasProjectToolCache bool) {
Expand Down
116 changes: 103 additions & 13 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ VERSION="${ULOOP_VERSION:-latest}"

report_path_shadowing() {
resolved_uloop=$(command -v uloop 2>/dev/null || true)
expected_uloop="$INSTALL_DIR/uloop"
expected_uloop="$INSTALL_DIR/$installed_command_name"

if [ -z "$resolved_uloop" ] || [ "$resolved_uloop" = "$expected_uloop" ]; then
if [ -z "$resolved_uloop" ] || [ "$resolved_uloop" = "$expected_uloop" ] || [ "$resolved_uloop.exe" = "$expected_uloop" ]; then
return
fi

Expand All @@ -24,6 +24,7 @@ detect_asset_name() {

case "$os" in
Darwin) os_name="darwin" ;;
MINGW*|MSYS*) os_name="windows" ;;
*)
echo "Unsupported OS: $os" >&2
exit 1
Expand All @@ -36,19 +37,105 @@ detect_asset_name() {
*)
echo "Unsupported architecture: $arch" >&2
exit 1
;;
;;
esac

if [ "$os_name" = "windows" ]; then
if [ "$arch_name" != "amd64" ]; then
echo "Unsupported Windows architecture: $arch" >&2
exit 1
fi
echo "uloop-windows-amd64.zip"
return
fi

echo "uloop-$os_name-$arch_name.tar.gz"
}

detect_installed_command_name() {
case "$asset_name" in
*.zip) echo "uloop.exe" ;;
*) echo "uloop" ;;
esac
}

find_latest_asset_url() {
page=1

while :; do
releases_json=$(curl -fsSL "https://api.github.com/repos/$REPOSITORY/releases?per_page=100&page=$page")
asset_url=$(printf '%s\n' "$releases_json" | awk -v asset_name="$asset_name" '
/"browser_download_url":/ {
line = $0
sub(/^[[:space:]]*"browser_download_url": "/, "", line)
sub(/",?[[:space:]]*$/, "", line)
count = split(line, parts, "/")
if (parts[count] == asset_name && found == "") {
found = line
}
}
END {
if (found != "") {
print found
}
}
')

if [ -n "$asset_url" ]; then
echo "$asset_url"
return
fi

release_count=$(printf '%s\n' "$releases_json" | awk '/"tag_name":/ { count++ } END { print count + 0 }')
if [ "$release_count" -lt 100 ]; then
return
fi

page=$((page + 1))
done
}

set_download_urls() {
if [ "$VERSION" != "latest" ]; then
download_url="https://github.com/$REPOSITORY/releases/download/$VERSION/$asset_name"
checksum_url="$download_url.sha256"
return
fi

download_url=$(find_latest_asset_url)
if [ -z "$download_url" ]; then
echo "Could not find a latest release asset named $asset_name." >&2
echo "Set ULOOP_VERSION to a release tag that provides this asset." >&2
exit 1
fi
checksum_url="$download_url.sha256"
}

extract_asset() {
case "$asset_name" in
*.zip)
if ! command -v unzip >/dev/null 2>&1; then
echo "unzip is required to extract $asset_name" >&2
exit 1
fi
unzip -q "$tmp_dir/$asset_name" -d "$tmp_dir"
if [ ! -f "$tmp_dir/$installed_command_name" ]; then
echo "Expected $installed_command_name at archive root after extracting $asset_name." >&2
exit 1
fi
return
;;
*)
tar -xzf "$tmp_dir/$asset_name" -C "$tmp_dir"
;;
esac
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

asset_name=$(detect_asset_name)
if [ "$VERSION" = "latest" ]; then
download_url="https://github.com/$REPOSITORY/releases/latest/download/$asset_name"
else
download_url="https://github.com/$REPOSITORY/releases/download/$VERSION/$asset_name"
fi
checksum_url="$download_url.sha256"
installed_command_name=$(detect_installed_command_name)
download_url=""
checksum_url=""
set_download_urls

tmp_dir=$(mktemp -d)
staged_uloop_path=""
Expand Down Expand Up @@ -81,11 +168,14 @@ mkdir -p "$INSTALL_DIR"
curl -fsSL "$download_url" -o "$tmp_dir/$asset_name"
curl -fsSL "$checksum_url" -o "$tmp_dir/$asset_name.sha256"
verify_checksum
tar -xzf "$tmp_dir/$asset_name" -C "$tmp_dir"
extract_asset
staged_uloop_path="$INSTALL_DIR/.uloop-install-$$"
install -m 0755 "$tmp_dir/uloop" "$staged_uloop_path"
if [ "$installed_command_name" = "uloop.exe" ]; then
staged_uloop_path="$staged_uloop_path.exe"
fi
install -m 0755 "$tmp_dir/$installed_command_name" "$staged_uloop_path"
"$staged_uloop_path" --version >/dev/null
mv -f "$staged_uloop_path" "$INSTALL_DIR/uloop"
mv -f "$staged_uloop_path" "$INSTALL_DIR/$installed_command_name"
staged_uloop_path=""

case ":$PATH:" in
Expand All @@ -97,5 +187,5 @@ case ":$PATH:" in
;;
esac

"$INSTALL_DIR/uloop" --version
"$INSTALL_DIR/$installed_command_name" --version
report_path_shadowing