diff --git a/apps/remixdesktop/src/plugins/xtermPlugin.ts b/apps/remixdesktop/src/plugins/xtermPlugin.ts index 89c785d1ad..d0d4dd2af7 100644 --- a/apps/remixdesktop/src/plugins/xtermPlugin.ts +++ b/apps/remixdesktop/src/plugins/xtermPlugin.ts @@ -7,6 +7,7 @@ import * as pty from "node-pty" import process from 'node:process'; import { userInfo } from 'node:os'; +import { findExecutable } from "../utils/findExecutable"; export const detectDefaultShell = () => { const { env } = process; @@ -86,12 +87,18 @@ class XtermPluginClient extends ElectronBasePluginClient { } async getShells(): Promise { + if(os.platform() === 'win32') { + const bash = await findExecutable('bash') + if(bash) { + return [bash, 'powershell.exe', 'cmd.exe'] + } + return ['powershell.exe', 'cmd.exe'] + } return [defaultShell] } - async createTerminal(path?: string): Promise { - const shell = defaultShell; + async createTerminal(path?: string, shell?: string): Promise { // filter undefined out of the env @@ -103,7 +110,7 @@ class XtermPluginClient extends ElectronBasePluginClient { }, {} as Record); - const ptyProcess = pty.spawn(shell, [], { + const ptyProcess = pty.spawn(shell || defaultShell, [], { name: 'xterm-color', cols: 80, rows: 20, diff --git a/apps/remixdesktop/src/utils/findExecutable.ts b/apps/remixdesktop/src/utils/findExecutable.ts new file mode 100644 index 0000000000..0df91e4c7b --- /dev/null +++ b/apps/remixdesktop/src/utils/findExecutable.ts @@ -0,0 +1,80 @@ +import path from "path"; +import process from "process"; +import { Stats } from "fs"; +import fs from 'fs/promises' + +export async function findExecutable(command: string, cwd?: string, paths?: string[]): Promise { + // If we have an absolute path then we take it. + if (path.isAbsolute(command)) { + return command; + } + if (cwd === undefined) { + cwd = process.cwd(); + } + + + const dir = path.dirname(command); + if (dir !== '.') { + // We have a directory and the directory is relative (see above). Make the path absolute + // to the current working directory. + return path.join(cwd, command); + } + + + + if (paths === undefined && typeof process.env['PATH'] === 'string') { + paths = (process && process.env['PATH'] && process.env['PATH'].split(path.delimiter)) || []; + } + // No PATH environment. Make path absolute to the cwd. + if (paths === undefined || paths.length === 0) { + return path.join(cwd, command); + } + + + + async function fileExists(path: string): Promise { + + try { + if (await fs.stat(path)) { + let statValue: Stats | undefined; + try { + statValue = await fs.stat(path); + } catch (e: any) { + if (e.message.startsWith('EACCES')) { + // it might be symlink + statValue = await fs.lstat(path); + } + } + + return statValue ? !statValue.isDirectory() : false; + } + } catch (e) { + } + return false; + } + + // We have a simple file name. We get the path variable from the env + // and try to find the executable on the path. + for (const pathEntry of paths) { + + // The path entry is absolute. + let fullPath: string; + if (path.isAbsolute(pathEntry)) { + fullPath = path.join(pathEntry, command); + } else { + fullPath = path.join(cwd, pathEntry, command); + } + if (await fileExists(fullPath)) { + return fullPath; + } + let withExtension = fullPath + '.com'; + if (await fileExists(withExtension)) { + return withExtension; + } + withExtension = fullPath + '.exe'; + if (await fileExists(withExtension)) { + return withExtension; + } + } + return path.join(cwd, command); +} \ No newline at end of file diff --git a/libs/remix-ui/xterm/src/lib/components/remix-ui-xterminals.tsx b/libs/remix-ui/xterm/src/lib/components/remix-ui-xterminals.tsx index 359dfe99bd..0a3a4c9a5e 100644 --- a/libs/remix-ui/xterm/src/lib/components/remix-ui-xterminals.tsx +++ b/libs/remix-ui/xterm/src/lib/components/remix-ui-xterminals.tsx @@ -92,8 +92,8 @@ export const RemixUiXterminals = (props: RemixUiXterminalsProps) => { plugin.call('xterm', 'keystroke', data, pid) } - const createTerminal = async () => { - const pid = await plugin.call('xterm', 'createTerminal', workingDir) + const createTerminal = async (shell?: string) => { + const pid = await plugin.call('xterm', 'createTerminal', workingDir, shell) setShowOutput(false) setTerminals(prevState => { // set all to hidden @@ -161,14 +161,14 @@ export const RemixUiXterminals = (props: RemixUiXterminalsProps) => {
- + {shells.map((shell, index) => { - return ({shell}) + return ( await createTerminal(shell)}>{shell}) })}