Skip to content

Got Error: posix_spawnp failed in packaged electron js app #789

@paulosabayomi

Description

@paulosabayomi

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 failed

My 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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions