-
Notifications
You must be signed in to change notification settings - Fork 279
Open
Description
Hi, I am using node-pty with xterm as the pseudo-terminal in the electron app I am working on, it works fine in development but when packaged it throws this error
Error: posix_spawnp failedMy ElectronJS environment is
- ElectronForge
- Vite
- TypeScript
Also, I added node-pty to the build.rollupOptions.external option and I had to dynamically copy the files to the asar directory during packaging because vite didn't include the node-pty files during packaging, the following is the code I used to copy the node-pty files
const config: ForgeConfig = {
//...
},
rebuildConfig: {},
hooks: {
// The call to this hook is mandatory for better-sqlite3 to work once the app built
async packageAfterCopy(_forgeConfig, buildPath) {
const requiredNativePackages = ['node-pty', 'es-git'];
// __dirname isn't accessible from here
const dirnamePath: string = ".";
const sourceNodeModulesPath = path.resolve(dirnamePath, "node_modules");
const destNodeModulesPath = path.resolve(buildPath, "node_modules");
// Copy all asked packages in /node_modules directory inside the asar archive
await Promise.all(
requiredNativePackages.map(async (packageName) => {
const sourcePath = path.join(sourceNodeModulesPath, packageName);
const destPath = path.join(destNodeModulesPath, packageName);
await fs.mkdirs(path.dirname(destPath));
await fs.copy(sourcePath, destPath, {
recursive: true,
preserveTimestamps: true
});
})
);
}
},
makers: [/** */],
plugins: [
//...
],
};and this is my the node-pty implementation code
import * as cp from 'child_process';
import { ipcMain, WebContents } from 'electron';
import crypto from "crypto"
import nodepty, { IPty } from "node-pty"
import path from 'path'
import { app_logger } from '../server-actions/functions';
import fixPath from 'fix-path'
import fs from 'fs'
import { APP_HOME_DIRECTORY } from '../server-actions/server-constants';
fixPath()
const terminal_proc: Record<string, IPty> = {}
export const launchTerminal = async (webContents: WebContents, root_path: string) => {
const terminal_proc_id: string = crypto.randomUUID();
const default_shell = process.env.SHELL.split(path.sep).pop() || await getDefaultShell();
try {
terminal_proc[terminal_proc_id] = nodepty.spawn(default_shell, [], {
name: 'xterm-color',
cols: 80,
rows: 30,
cwd: root_path,
env: process.env,
});
webContents.send("terminal-spawn-success", terminal_proc_id)
} catch (error) {
webContents.send("server-toast-broadcast-ev", {type: "random-toast", msg: "Error spawning terminal process: Reason: " + error.toString()})
}
try {
terminal_proc_handler(webContents, terminal_proc[terminal_proc_id], terminal_proc_id);
terminal_proc[terminal_proc_id].onExit((e) => {
app_logger("error occured in the terminal_proc_handler onexit ", JSON.stringify(e))
delete terminal_proc[terminal_proc_id]
})
} catch (error) {
}
}
const terminal_proc_handler = (webContents: WebContents, terminal: IPty, proc_id: string) => {
terminal.onData((data) => {
webContents.send(`terminal-${proc_id}-ev`, {proc_id, chunk: data})
});
const client_message_listener = async (ev: any, data: any) => {
if (data.resize) {
return terminal.resize(data.resize.cols, data.resize.rows)
}
if (data.kill) {
return terminal.kill()
}
terminal.write(data.data)
}
ipcMain.on(`terminal-${proc_id}-message`, client_message_listener)
}
const getDefaultShell = async (): Promise<string> => {
if (process.platform === 'darwin') { // macOS
try {
const result = cp.execSync('dscl . read /Users/$USER UserShell').toString().trim();
const shellPath = result.split(': ').pop() || '';
const baseName = shellPath.split('/').pop();
return baseName || shellPath || 'zsh'; // default to zsh on macOS
} catch (error) {
return 'zsh'; // Fallback to zsh
}
} else if (process.platform === 'linux') {
try {
const result = cp.execSync('getent passwd $USER | cut -d: -f7').toString().trim();
const baseName = result.split('/').pop();
return baseName || result || 'bash'; // default to bash on linux
} catch (error) {
return 'bash'; // Fallback to bash
}
} else if (process.platform === 'win32') {
try {
// first detect powershell
if (process.env.PSModulePath) {
return 'powershell.exe';
}
return 'cmd.exe';
} catch (error) {
return 'cmd.exe';
}
} else {
return 'sh'; // Generic Unix shell
}
}Please what I'm I doing wrong, thank you
Metadata
Metadata
Assignees
Labels
No labels