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
18 changes: 18 additions & 0 deletions packages/electron-app/electron/main/user-shell.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import assert from "node:assert/strict"
import { describe, it } from "node:test"

import { getDefaultShellPath } from "./user-shell"

describe("desktop POSIX shell selection", () => {
it("falls back from incompatible user shells", () => {
assert.equal(getDefaultShellPath("darwin", "/opt/homebrew/bin/nu"), "/bin/zsh")
assert.equal(getDefaultShellPath("linux", "/usr/bin/fish"), "/bin/bash")
assert.equal(getDefaultShellPath("darwin", "/tmp/not-bash"), "/bin/zsh")
assert.equal(getDefaultShellPath("darwin", " "), "/bin/zsh")
})

it("preserves configured bash and zsh paths", () => {
assert.equal(getDefaultShellPath("darwin", "/opt/homebrew/bin/bash"), "/opt/homebrew/bin/bash")
assert.equal(getDefaultShellPath("linux", " /usr/local/bin/zsh "), "/usr/local/bin/zsh")
})
})
19 changes: 15 additions & 4 deletions packages/electron-app/electron/main/user-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,29 @@ interface ShellCommand {

const isWindows = process.platform === "win32"

function getDefaultShellPath(): string {
if (process.env.SHELL && process.env.SHELL.trim().length > 0) {
return process.env.SHELL
export function getDefaultShellPath(
platform: NodeJS.Platform = process.platform,
configuredShell = process.env.SHELL,
): string {
const shellPath = configuredShell?.trim()
if (shellPath && isSupportedPosixShell(shellPath)) {
return shellPath
}

if (process.platform === "darwin") {
// The launch script uses POSIX syntax. Never pass it to arbitrary user shells
// such as Nushell or Fish even when they are configured through $SHELL.
if (platform === "darwin") {
return "/bin/zsh"
}

return "/bin/bash"
}

function isSupportedPosixShell(shellPath: string): boolean {
const shellName = path.basename(shellPath).toLowerCase()
return shellName === "bash" || shellName === "zsh"
}

function wrapCommandForShell(command: string, shellPath: string): string {
const shellName = path.basename(shellPath)

Expand Down
2 changes: 1 addition & 1 deletion packages/electron-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"prebuild": "npm run prepare:resources",
"build": "electron-vite build",
"typecheck": "tsc --noEmit -p tsconfig.json",
"test:native": "node --import tsx --test electron/main/client-state-cross-host.test.ts electron/main/client-state-process.test.ts electron/main/client-state.test.ts electron/main/client-state-ipc.test.ts electron/main/client-state-navigation.test.ts electron/main/developer-mode.test.ts electron/main/local-window-registry.test.ts electron/main/menu-target.test.ts electron/main/multiwindow-lifecycle.test.ts electron/main/native-request.test.ts electron/main/navigation-security.test.ts electron/main/preferences-ipc.test.ts electron/main/preferences-window.test.ts electron/main/process-exit.test.ts electron/main/process-output.test.ts electron/main/process-stop.test.ts electron/main/remote-window-registry.test.ts electron/main/renderer-client-state-flush.test.ts electron/main/renderer-origin.test.ts electron/main/serialized-lifecycle.test.ts electron/main/startup.test.ts electron/main/window-state.test.ts electron/main/workspace-open.test.ts electron/preload/index.test.ts",
"test:native": "node --import tsx --test electron/main/client-state-cross-host.test.ts electron/main/client-state-process.test.ts electron/main/client-state.test.ts electron/main/client-state-ipc.test.ts electron/main/client-state-navigation.test.ts electron/main/developer-mode.test.ts electron/main/local-window-registry.test.ts electron/main/menu-target.test.ts electron/main/multiwindow-lifecycle.test.ts electron/main/native-request.test.ts electron/main/navigation-security.test.ts electron/main/preferences-ipc.test.ts electron/main/preferences-window.test.ts electron/main/process-exit.test.ts electron/main/process-output.test.ts electron/main/process-stop.test.ts electron/main/remote-window-registry.test.ts electron/main/renderer-client-state-flush.test.ts electron/main/renderer-origin.test.ts electron/main/serialized-lifecycle.test.ts electron/main/startup.test.ts electron/main/user-shell.test.ts electron/main/window-state.test.ts electron/main/workspace-open.test.ts electron/preload/index.test.ts",
"preview": "electron-vite preview",
"build:binaries": "node scripts/build.js",
"build:mac": "node scripts/build.js mac",
Expand Down
57 changes: 49 additions & 8 deletions packages/tauri-app/src-tauri/src/cli_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1128,7 +1128,7 @@ impl CliProcessManager {
}

let command_info = if use_user_shell {
log_line("spawning via user shell");
log_line("spawning via POSIX-compatible shell");
ShellCommandType::UserShell(build_shell_command_string(&resolution, &args)?)
} else {
log_line(if resolution.runner == Runner::Tsx {
Expand Down Expand Up @@ -1872,17 +1872,32 @@ fn build_shell_command_string(
);
let wrapped_command = wrap_command_for_shell(&command, &shell);
let args = build_shell_args(&shell, &wrapped_command);
log_line(&format!("user shell command: {} {:?}", shell, args));
log_line(&format!("POSIX shell command: {} {:?}", shell, args));
Ok(ShellCommand { shell, args })
}

fn default_shell() -> String {
if let Ok(shell) = std::env::var("SHELL") {
if !shell.trim().is_empty() {
return shell;
}
}
if cfg!(target_os = "macos") {
select_posix_shell(
std::env::var("SHELL").ok().as_deref(),
cfg!(target_os = "macos"),
)
}

fn select_posix_shell(configured_shell: Option<&str>, macos: bool) -> String {
if let Some(shell) = configured_shell.map(str::trim).filter(|shell| {
let name = std::path::Path::new(shell)
.file_name()
.and_then(OsStr::to_str)
.unwrap_or("")
.to_lowercase();
matches!(name.as_str(), "bash" | "zsh")
}) {
return shell.to_string();
}

// The launch script uses POSIX syntax. Never pass it to arbitrary user shells
// such as Nushell or Fish even when they are configured through $SHELL.
if macos {
"/bin/zsh".to_string()
} else {
"/bin/bash".to_string()
Expand Down Expand Up @@ -2025,6 +2040,32 @@ mod tests {
);
}

#[test]
fn posix_shell_selection_rejects_incompatible_user_shells() {
assert_eq!(
select_posix_shell(Some("/opt/homebrew/bin/nu"), true),
"/bin/zsh"
);
assert_eq!(
select_posix_shell(Some("/usr/bin/fish"), false),
"/bin/bash"
);
assert_eq!(select_posix_shell(Some("/tmp/not-bash"), true), "/bin/zsh");
assert_eq!(select_posix_shell(Some(" "), true), "/bin/zsh");
}

#[test]
fn posix_shell_selection_preserves_bash_and_zsh() {
assert_eq!(
select_posix_shell(Some("/opt/homebrew/bin/bash"), true),
"/opt/homebrew/bin/bash"
);
assert_eq!(
select_posix_shell(Some(" /usr/local/bin/zsh "), false),
"/usr/local/bin/zsh"
);
}

#[test]
fn augment_launch_url_trims_leading_fragment_marker() {
let _guard = ENV_LOCK.lock().expect("env lock poisoned");
Expand Down
Loading