|
|
|
@ -1,41 +1,70 @@ |
|
|
|
|
import { PluginClient } from "@remixproject/plugin"; |
|
|
|
|
import { Profile } from "@remixproject/plugin-utils"; |
|
|
|
|
import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron" |
|
|
|
|
|
|
|
|
|
import os from 'os'; |
|
|
|
|
import * as pty from "node-pty" |
|
|
|
|
|
|
|
|
|
import process from 'node:process'; |
|
|
|
|
import { userInfo } from 'node:os'; |
|
|
|
|
import { findExecutable } from "../utils/findExecutable"; |
|
|
|
|
import {PluginClient} from '@remixproject/plugin' |
|
|
|
|
import {Profile} from '@remixproject/plugin-utils' |
|
|
|
|
import { |
|
|
|
|
ElectronBasePlugin, |
|
|
|
|
ElectronBasePluginClient, |
|
|
|
|
} from '@remixproject/plugin-electron' |
|
|
|
|
|
|
|
|
|
import os from 'os' |
|
|
|
|
import * as pty from 'node-pty' |
|
|
|
|
import process from 'node:process' |
|
|
|
|
import {userInfo} from 'node:os' |
|
|
|
|
import {findExecutable} from '../utils/findExecutable' |
|
|
|
|
import {spawnSync} from 'child_process' |
|
|
|
|
import { stripAnsi } from '../lib' |
|
|
|
|
|
|
|
|
|
export const detectDefaultShell = () => { |
|
|
|
|
const { env } = process; |
|
|
|
|
const {env} = process |
|
|
|
|
|
|
|
|
|
if (process.platform === 'win32') { |
|
|
|
|
return env.SHELL || 'powershell.exe'; |
|
|
|
|
return env.SHELL || 'powershell.exe' |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
const { shell } = userInfo(); |
|
|
|
|
const {shell} = userInfo() |
|
|
|
|
if (shell) { |
|
|
|
|
return shell; |
|
|
|
|
return shell |
|
|
|
|
} |
|
|
|
|
} catch {} |
|
|
|
|
|
|
|
|
|
if (process.platform === 'darwin') { |
|
|
|
|
return env.SHELL || '/bin/zsh'; |
|
|
|
|
return env.SHELL || '/bin/zsh' |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return env.SHELL || '/bin/sh'; |
|
|
|
|
}; |
|
|
|
|
return env.SHELL || '/bin/sh' |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Stores default shell when imported.
|
|
|
|
|
const defaultShell = detectDefaultShell(); |
|
|
|
|
const defaultShell = detectDefaultShell() |
|
|
|
|
|
|
|
|
|
const getShellEnvArgs = [ |
|
|
|
|
'-ilc', |
|
|
|
|
'echo -n "_SHELL_ENV_DELIMITER_"; env; echo -n "_SHELL_ENV_DELIMITER_"; exit', |
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
const getShellEnvEnv = { |
|
|
|
|
// Disables Oh My Zsh auto-update thing that can block the process.
|
|
|
|
|
DISABLE_AUTO_UPDATE: 'true', |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const parseEnv = (env: any) => { |
|
|
|
|
env = env.split('_SHELL_ENV_DELIMITER_')[1] |
|
|
|
|
const returnValue = {} |
|
|
|
|
|
|
|
|
|
export default defaultShell; |
|
|
|
|
for (const line of stripAnsi(env) |
|
|
|
|
.split('\n') |
|
|
|
|
.filter((l) => Boolean(l))) { |
|
|
|
|
const [key, ...values] = line.split('=') |
|
|
|
|
console.log(key, values) |
|
|
|
|
Object.assign(returnValue, { |
|
|
|
|
[key]: values.join('='), |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return returnValue |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export default defaultShell |
|
|
|
|
|
|
|
|
|
const profile: Profile = { |
|
|
|
|
name: 'xterm', |
|
|
|
@ -51,7 +80,7 @@ export class XtermPlugin extends ElectronBasePlugin { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
new(webContentsId: any): void { |
|
|
|
|
const client = this.clients.find(c => c.webContentsId === webContentsId) |
|
|
|
|
const client = this.clients.find((c) => c.webContentsId === webContentsId) |
|
|
|
|
if (client) { |
|
|
|
|
client.new() |
|
|
|
|
} |
|
|
|
@ -62,18 +91,16 @@ export class XtermPlugin extends ElectronBasePlugin { |
|
|
|
|
await client.closeAll() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const clientProfile: Profile = { |
|
|
|
|
name: 'xterm', |
|
|
|
|
displayName: 'xterm', |
|
|
|
|
description: 'xterm plugin', |
|
|
|
|
methods: ['createTerminal', 'close', 'keystroke', 'getShells', 'resize'] |
|
|
|
|
methods: ['createTerminal', 'close', 'keystroke', 'getShells', 'resize'], |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class XtermPluginClient extends ElectronBasePluginClient { |
|
|
|
|
|
|
|
|
|
terminals: pty.IPty[] = [] |
|
|
|
|
constructor(webContentsId: number, profile: Profile) { |
|
|
|
|
super(webContentsId, profile) |
|
|
|
@ -99,32 +126,39 @@ class XtermPluginClient extends ElectronBasePluginClient { |
|
|
|
|
return [defaultShell] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async createTerminal(path?: string, shell?: string): Promise<number> { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let parsedEnv: any = null |
|
|
|
|
if (!(process.platform === 'win32')) { |
|
|
|
|
const {stdout} = spawnSync(defaultShell, getShellEnvArgs, { |
|
|
|
|
encoding: 'utf8', |
|
|
|
|
}) |
|
|
|
|
parsedEnv = parseEnv(stdout) |
|
|
|
|
} |
|
|
|
|
// filter undefined out of the env
|
|
|
|
|
const env = Object.keys(process.env) |
|
|
|
|
.filter(key => process.env[key] !== undefined) |
|
|
|
|
const env = Object.keys(parsedEnv || process.env) |
|
|
|
|
.filter((key) => process.env[key] !== undefined) |
|
|
|
|
.reduce((env, key) => { |
|
|
|
|
env[key] = process.env[key] || ''; |
|
|
|
|
return env; |
|
|
|
|
}, {} as Record<string, string>); |
|
|
|
|
|
|
|
|
|
env[key] = process.env[key] || '' |
|
|
|
|
return env |
|
|
|
|
}, {} as Record<string, string>) |
|
|
|
|
|
|
|
|
|
const ptyProcess = pty.spawn(shell || defaultShell, [], { |
|
|
|
|
name: 'xterm-color', |
|
|
|
|
cols: 80, |
|
|
|
|
rows: 20, |
|
|
|
|
cwd: path || process.cwd(), |
|
|
|
|
env: env |
|
|
|
|
}); |
|
|
|
|
env: env, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
ptyProcess.onData((data: string) => { |
|
|
|
|
this.sendData(data, ptyProcess.pid); |
|
|
|
|
this.sendData(data, ptyProcess.pid) |
|
|
|
|
}) |
|
|
|
|
this.terminals[ptyProcess.pid] = ptyProcess |
|
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
this.sendData(JSON.stringify(env), ptyProcess.pid) |
|
|
|
|
}, 2000) |
|
|
|
|
|
|
|
|
|
return ptyProcess.pid |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -137,13 +171,13 @@ class XtermPluginClient extends ElectronBasePluginClient { |
|
|
|
|
async resize(pid: number, {cols, rows}: {cols: number; rows: number}) { |
|
|
|
|
if (this.terminals[pid]) { |
|
|
|
|
try { |
|
|
|
|
this.terminals[pid].resize(cols, rows); |
|
|
|
|
this.terminals[pid].resize(cols, rows) |
|
|
|
|
} catch (_err) { |
|
|
|
|
const err = _err as {stack: any}; |
|
|
|
|
console.error(err.stack); |
|
|
|
|
const err = _err as {stack: any} |
|
|
|
|
console.error(err.stack) |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
console.warn('Warning: Attempted to resize a session with no pty'); |
|
|
|
|
console.warn('Warning: Attempted to resize a session with no pty') |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -155,12 +189,9 @@ class XtermPluginClient extends ElectronBasePluginClient { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async sendData(data: string, pid: number) { |
|
|
|
|
this.emit('data', data, pid) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async new(): Promise<void> { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async new(): Promise<void> {} |
|
|
|
|
} |