From 1dee0ad5ae262e7ade9f5eaf9954298bfeef94c0 Mon Sep 17 00:00:00 2001 From: LimLLL Date: Fri, 8 May 2026 04:53:11 +0000 Subject: [PATCH] fix: resolve node-pty reliably --- package.json | 2 +- src/server.ts | 64 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index b93e584..4db970c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "dev": "tsc --watch" }, "dependencies": { - "node-pty": "^1.1.0", + "node-pty": "1.2.0-beta.12", "ws": "^8.14.0" }, "devDependencies": { diff --git a/src/server.ts b/src/server.ts index f903f52..7b12e50 100644 --- a/src/server.ts +++ b/src/server.ts @@ -44,10 +44,31 @@ interface WsMessage { // ── Module finder ───────────────────────────────────────────────────────────── -function findModule(name: string): any { - try { return require(name); } catch { /* continue */ } +function isModuleNotFoundFor(error: unknown, requestedPath: string): boolean { + const err = error as NodeJS.ErrnoException; + return err?.code === 'MODULE_NOT_FOUND' && String(err.message || '').includes(requestedPath); +} + +function tryRequire(modulePath: string): any | undefined { + try { + return require(modulePath); + } catch (error) { + if (isModuleNotFoundFor(error, modulePath)) return undefined; + throw error; + } +} + +function findModuleRoot(name: string): string { + try { + return path.dirname(require.resolve(`${name}/package.json`)); + } catch (error) { + if (!isModuleNotFoundFor(error, `${name}/package.json`)) throw error; + } const roots = [ + path.join(__dirname, 'node_modules', name), + path.join(path.dirname(__dirname), 'node_modules', name), + path.join('/usr/local/lib/node_modules/@cloudcli-ai/cloudcli/node_modules', name), path.join('/opt', 'claudecodeui', 'node_modules', name), path.join('/workspace', 'claudecodeui', 'node_modules', name), path.join('/app', 'node_modules', name), @@ -55,23 +76,27 @@ function findModule(name: string): any { ]; for (const p of roots) { - if (fs.existsSync(p)) { - try { return require(p); } catch { /* continue */ } - } + if (fs.existsSync(p)) return p; } let dir = __dirname; for (let i = 0; i < 10; i++) { const candidate = path.join(dir, 'node_modules', name); - if (fs.existsSync(candidate)) { - try { return require(candidate); } catch { /* continue */ } - } + if (fs.existsSync(candidate)) return candidate; const parent = path.dirname(dir); if (parent === dir) break; dir = parent; } - throw new Error(`[web-terminal] Cannot find module '${name}' - run npm install in ${__dirname}`); + throw new Error(`[web-terminal] Cannot find module '${name}' - run npm install in ${path.dirname(__dirname)} or ${__dirname}`); +} + +function findModule(name: string): any { + const direct = tryRequire(name); + if (direct) return direct; + + const moduleRoot = findModuleRoot(name); + return require(moduleRoot); } // ── Dependencies ────────────────────────────────────────────────────────────── @@ -84,6 +109,26 @@ const { WebSocketServer, WebSocket } = findModule('ws') as WsModule; const sessions = new Map(); let sessionCounter = 0; +function ensureSpawnHelperExecutable(): void { + if (process.platform !== 'darwin') return; + + try { + const archDir = process.arch === 'arm64' ? 'darwin-arm64' : process.arch === 'x64' ? 'darwin-x64' : null; + if (!archDir) return; + + const nodePtyRoot = findModuleRoot('node-pty'); + const spawnHelperPath = path.join(nodePtyRoot, 'prebuilds', archDir, 'spawn-helper'); + if (!fs.existsSync(spawnHelperPath)) return; + + const mode = fs.statSync(spawnHelperPath).mode & 0o777; + if ((mode & 0o100) === 0) { + fs.chmodSync(spawnHelperPath, mode | 0o100); + } + } catch (error) { + console.warn('[web-terminal] Failed to ensure spawn-helper permissions:', (error as Error).message); + } +} + function getShell(): string { if (process.platform === 'win32') return 'powershell.exe'; return process.env.SHELL || '/bin/bash'; @@ -122,6 +167,7 @@ wss.on('connection', (ws: any) => { let ptyProc: PtyProcess; try { + ensureSpawnHelperExecutable(); ptyProc = pty.spawn(shell, [], { name: 'xterm-256color', cols: 80,