need to revert the local path guessing is electron "shell: true" doesn't work as expected

This commit is contained in:
Egor 2025-09-03 02:19:13 -07:00
parent 9a20f0d1b0
commit 1499445762
2 changed files with 118 additions and 27 deletions

View file

@ -1,6 +1,8 @@
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import type { ChildProcess } from 'child_process'; import type { ChildProcess } from 'child_process';
import { join } from 'path'; import { join } from 'path';
import { homedir } from 'os';
import { access } from 'fs/promises';
import { LogManager } from './LogManager'; import { LogManager } from './LogManager';
import { WindowManager } from './WindowManager'; import { WindowManager } from './WindowManager';
@ -53,6 +55,26 @@ export class OpenWebUIManager {
> { > {
const env = { ...process.env }; const env = { ...process.env };
const uvPaths = [
join(homedir(), '.cargo', 'bin'),
join(homedir(), '.local', 'bin'),
];
const existingPaths: string[] = [];
for (const path of uvPaths) {
try {
await access(path);
existingPaths.push(path);
} catch {
void 0;
}
}
if (existingPaths.length > 0) {
const pathSeparator = process.platform === 'win32' ? ';' : ':';
env.PATH = `${existingPaths.join(pathSeparator)}${pathSeparator}${env.PATH}`;
}
if (process.platform === 'win32') { if (process.platform === 'win32') {
env.PYTHONIOENCODING = 'utf-8'; env.PYTHONIOENCODING = 'utf-8';
env.PYTHONLEGACYWINDOWSSTDIO = '1'; env.PYTHONLEGACYWINDOWSSTDIO = '1';
@ -66,11 +88,7 @@ export class OpenWebUIManager {
async isUvAvailable(): Promise<boolean> { async isUvAvailable(): Promise<boolean> {
try { try {
const env = await this.getUvEnvironment(); const env = await this.getUvEnvironment();
const testProcess = spawn('uv', ['--version'], { const testProcess = spawn('uv', ['--version'], { stdio: 'pipe', env });
stdio: 'pipe',
env,
shell: true,
});
return new Promise<boolean>((resolve) => { return new Promise<boolean>((resolve) => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
@ -109,7 +127,6 @@ export class OpenWebUIManager {
stdio: ['pipe', 'pipe', 'pipe'], stdio: ['pipe', 'pipe', 'pipe'],
detached: false, detached: false,
env: mergedEnv, env: mergedEnv,
shell: true,
}); });
} }
@ -118,24 +135,14 @@ export class OpenWebUIManager {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const checkForOutput = (data: Buffer) => { const checkForOutput = (data: Buffer) => {
try { const output = data.toString();
const output = data.toString('utf8'); if (output.includes(SERVER_READY_SIGNALS.OPENWEBUI)) {
if (output.includes(SERVER_READY_SIGNALS.OPENWEBUI)) { this.windowManager.sendKoboldOutput('Open WebUI is now running!');
this.windowManager.sendKoboldOutput('Open WebUI is now running!'); resolve();
resolve();
if (this.openWebUIProcess?.stdout) { if (this.openWebUIProcess?.stdout) {
this.openWebUIProcess.stdout.removeListener( this.openWebUIProcess.stdout.removeListener('data', checkForOutput);
'data',
checkForOutput
);
}
} }
} catch (error) {
this.logManager.logError(
'Error checking OpenWebUI output:',
error as Error
);
} }
}; };

View file

@ -2,6 +2,7 @@ import { spawn } from 'child_process';
import { createServer, request, type Server } from 'http'; import { createServer, request, type Server } from 'http';
import { homedir } from 'os'; import { homedir } from 'os';
import { join } from 'path'; import { join } from 'path';
import { access, readdir } from 'fs/promises';
import type { ChildProcess } from 'child_process'; import type { ChildProcess } from 'child_process';
import { LogManager } from './LogManager'; import { LogManager } from './LogManager';
@ -86,12 +87,94 @@ export class SillyTavernManager {
return join(dataRoot, 'default-user', 'settings.json'); return join(dataRoot, 'default-user', 'settings.json');
} }
private async tryAddPathToEnv(
env: Record<string, string | undefined>,
path: string
): Promise<boolean> {
const pathSeparator = process.platform === 'win32' ? ';' : ':';
if (!env.PATH?.includes(path)) {
env.PATH = `${path}${pathSeparator}${env.PATH}`;
return true;
}
return false;
}
private async tryVersionManagerPath(
basePath: string,
env: Record<string, string | undefined>
): Promise<boolean> {
try {
await access(basePath);
const versions = await readdir(basePath);
if (versions.length > 0) {
const latestVersion = versions.sort().pop();
if (latestVersion) {
const binSubPath = basePath.includes('fnm')
? join('installation', 'bin')
: 'bin';
const nodeBinPath = join(basePath, latestVersion, binSubPath);
try {
await access(nodeBinPath);
return this.tryAddPathToEnv(env, nodeBinPath);
} catch {
return false;
}
}
}
} catch {
return false;
}
return false;
}
private async getNodeEnvironment(): Promise<
Record<string, string | undefined>
> {
const env = { ...process.env };
const versionManagerPaths = [
join(homedir(), '.local', 'share', 'fnm', 'node-versions'),
join(homedir(), '.nvm', 'versions', 'node'),
join(homedir(), '.volta', 'tools', 'image', 'node'),
join(homedir(), '.asdf', 'installs', 'nodejs'),
];
const systemPaths: string[] = [];
if (process.platform === 'darwin') {
systemPaths.push('/opt/homebrew/bin', '/usr/local/bin');
}
if (process.platform === 'win32') {
versionManagerPaths.push(
join(homedir(), 'AppData', 'Local', 'fnm', 'node-versions'),
join(homedir(), 'AppData', 'Roaming', 'nvm')
);
}
for (const systemPath of systemPaths) {
try {
await access(systemPath);
if (await this.tryAddPathToEnv(env, systemPath)) {
return env;
}
} catch {
continue;
}
}
for (const versionPath of versionManagerPaths) {
if (await this.tryVersionManagerPath(versionPath, env)) {
return env;
}
}
return env;
}
async isNpxAvailable(): Promise<boolean> { async isNpxAvailable(): Promise<boolean> {
try { try {
const testProcess = spawn('npx', ['--version'], { const env = await this.getNodeEnvironment();
stdio: 'pipe', const testProcess = spawn('npx', ['--version'], { stdio: 'pipe', env });
shell: true,
});
return new Promise<boolean>((resolve) => { return new Promise<boolean>((resolve) => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
@ -115,10 +198,11 @@ export class SillyTavernManager {
} }
private async createNpxProcess(args: string[]): Promise<ChildProcess> { private async createNpxProcess(args: string[]): Promise<ChildProcess> {
const env = await this.getNodeEnvironment();
return spawn('npx', args, { return spawn('npx', args, {
stdio: ['pipe', 'pipe', 'pipe'], stdio: ['pipe', 'pipe', 'pipe'],
detached: false, detached: false,
shell: true, env,
}); });
} }