mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 19:54:44 -07:00
fixing regressions from the previous commit
This commit is contained in:
parent
e1b078b9d9
commit
329e191a49
8 changed files with 375 additions and 364 deletions
|
|
@ -52,11 +52,11 @@ const config = [
|
|||
plugins: {
|
||||
'@typescript-eslint': tseslint,
|
||||
'react-hooks': reactHooks,
|
||||
react: react,
|
||||
import: importPlugin,
|
||||
sonarjs: sonarjs,
|
||||
react,
|
||||
sonarjs,
|
||||
'no-comments': noComments,
|
||||
promise: promise,
|
||||
import: importPlugin,
|
||||
promise,
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
|
|
@ -135,6 +135,18 @@ const config = [
|
|||
message:
|
||||
'Direct setting of currentKoboldBinary config is forbidden. Use window.electronAPI.kobold.setCurrentVersion() instead.',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'ExpressionStatement[expression.type="AwaitExpression"]:has(CallExpression[callee.name="ensureDir"]) + ExpressionStatement[expression.type="AwaitExpression"]:has(CallExpression[callee.name="ensureDir"])',
|
||||
message:
|
||||
'Sequential ensureDir() calls detected. These can run in parallel using Promise.all().',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'ExpressionStatement[expression.type="AwaitExpression"]:has(CallExpression[callee.object.name="fs"][callee.property.name=/^(unlink|rmdir|mkdir|writeFile)$/]) + ExpressionStatement[expression.type="AwaitExpression"]:has(CallExpression[callee.object.name="fs"][callee.property.name=/^(unlink|rmdir|mkdir|writeFile)$/])',
|
||||
message:
|
||||
'Sequential file system operations detected. Independent operations can run in parallel using Promise.all().',
|
||||
},
|
||||
],
|
||||
|
||||
'import/no-default-export': 'error',
|
||||
|
|
@ -156,22 +168,25 @@ const config = [
|
|||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/return-await': ['error', 'never'],
|
||||
'@typescript-eslint/prefer-promise-reject-errors': 'error',
|
||||
|
||||
'sonarjs/cognitive-complexity': ['warn', 25],
|
||||
|
||||
// Promise rules to prevent sequential awaits (no warnings, only errors)
|
||||
'promise/prefer-await-to-then': 'error', // Enforce async/await
|
||||
'promise/prefer-await-to-callbacks': 'off', // Too aggressive for Electron APIs
|
||||
'promise/no-nesting': 'error', // No nested promises
|
||||
'promise/no-promise-in-callback': 'off', // Common in Electron
|
||||
'promise/no-callback-in-promise': 'off', // Common in Electron
|
||||
'promise/avoid-new': 'off', // Sometimes needed for wrapping APIs
|
||||
// Promise rules to prevent sequential awaits (smarter detection)
|
||||
'promise/prefer-await-to-then': 'error',
|
||||
'promise/prefer-await-to-callbacks': 'off',
|
||||
'promise/no-nesting': 'error',
|
||||
'promise/no-promise-in-callback': 'off',
|
||||
'promise/no-callback-in-promise': 'off',
|
||||
'promise/avoid-new': 'off',
|
||||
'promise/no-new-statics': 'error',
|
||||
'promise/no-return-wrap': 'error',
|
||||
'promise/param-names': 'error',
|
||||
'promise/catch-or-return': 'off', // Too strict for some patterns
|
||||
'promise/catch-or-return': 'off',
|
||||
'promise/no-native': 'off',
|
||||
|
||||
// Node.js specific async rules
|
||||
|
||||
'no-comments/disallowComments': 'error',
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "gerbil",
|
||||
"productName": "Gerbil",
|
||||
"version": "1.2.3",
|
||||
"version": "1.2.4",
|
||||
"description": "Run Large Language Models locally",
|
||||
"main": "out/main/index.js",
|
||||
"homepage": "./",
|
||||
|
|
@ -66,6 +66,7 @@
|
|||
"dependencies": {
|
||||
"@mantine/core": "^8.3.1",
|
||||
"@mantine/hooks": "^8.3.1",
|
||||
"@types/yauzl": "^2.10.3",
|
||||
"axios": "^1.12.1",
|
||||
"execa": "^9.6.0",
|
||||
"lucide-react": "^0.544.0",
|
||||
|
|
@ -75,6 +76,7 @@
|
|||
"systeminformation": "^5.27.9",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0",
|
||||
"yauzl": "^3.2.0",
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"build": {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ export const GITHUB_API = {
|
|||
KOBOLDCPP_REPO: 'LostRuins/koboldcpp',
|
||||
KOBOLDCPP_ROCM_REPO: 'YellowRoseCx/koboldcpp-rocm',
|
||||
GERBIL_REPO: 'lone-cloud/gerbil',
|
||||
COMFYUI_REPO: 'comfyanonymous/ComfyUI',
|
||||
get LATEST_RELEASE_URL() {
|
||||
return `${this.BASE_URL}/repos/${this.KOBOLDCPP_REPO}/releases/latest`;
|
||||
},
|
||||
|
|
@ -68,6 +69,9 @@ export const GITHUB_API = {
|
|||
get ROCM_LATEST_RELEASE_URL() {
|
||||
return `${this.BASE_URL}/repos/${this.KOBOLDCPP_ROCM_REPO}/releases/latest`;
|
||||
},
|
||||
get COMFYUI_DOWNLOAD_URL() {
|
||||
return `https://github.com/${this.COMFYUI_REPO}/archive/refs/heads/master.zip`;
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const ASSET_SUFFIXES = {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
import { spawn } from 'child_process';
|
||||
import { join } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { access } from 'fs/promises';
|
||||
import type { ChildProcess } from 'child_process';
|
||||
import yauzl from 'yauzl';
|
||||
|
||||
import { logError } from './logging';
|
||||
import { sendKoboldOutput } from './window';
|
||||
import { getInstallDir } from './config';
|
||||
import { COMFYUI, SERVER_READY_SIGNALS } from '@/constants';
|
||||
import { COMFYUI, SERVER_READY_SIGNALS, GITHUB_API } from '@/constants';
|
||||
import { terminateProcess } from '@/utils/node/process';
|
||||
import { parseKoboldConfig } from '@/utils/node/kobold';
|
||||
import { getAppVersion, ensureDir } from '@/utils/node/fs';
|
||||
import { getGPUData } from '@/utils/node/gpu';
|
||||
import { getUvEnvironment } from './dependencies';
|
||||
|
||||
let comfyUIProcess: ChildProcess | null = null;
|
||||
|
||||
|
|
@ -23,39 +24,6 @@ process.on('SIGTERM', () => {
|
|||
void cleanup();
|
||||
});
|
||||
|
||||
async function getUvEnvironment() {
|
||||
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') {
|
||||
env.PYTHONIOENCODING = 'utf-8';
|
||||
env.PYTHONLEGACYWINDOWSSTDIO = '1';
|
||||
env.PYTHONUTF8 = '1';
|
||||
env.CHCP = '65001';
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
async function getPyTorchInstallArgs(pythonPath: string) {
|
||||
const args = [
|
||||
'pip',
|
||||
|
|
@ -110,8 +78,6 @@ async function ensureComfyUIInstalled() {
|
|||
sendKoboldOutput('ComfyUI not found, installing via uv...');
|
||||
|
||||
const env = await getUvEnvironment();
|
||||
env.PYTHONIOENCODING = 'utf-8';
|
||||
env.PYTHONUNBUFFERED = '1';
|
||||
|
||||
sendKoboldOutput(
|
||||
'Creating virtual environment and installing ComfyUI dependencies...'
|
||||
|
|
@ -134,9 +100,7 @@ async function ensureComfyUIInstalled() {
|
|||
'Virtual environment created, downloading ComfyUI...'
|
||||
);
|
||||
|
||||
const response = await fetch(
|
||||
'https://github.com/comfyanonymous/ComfyUI/archive/refs/heads/master.zip'
|
||||
);
|
||||
const response = await fetch(GITHUB_API.COMFYUI_DOWNLOAD_URL);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to download ComfyUI: ${response.statusText}`
|
||||
|
|
@ -174,36 +138,70 @@ async function ensureComfyUIInstalled() {
|
|||
`ComfyUI downloaded (${Math.round(zipStats.size / 1024 / 1024)}MB), extracting...`
|
||||
);
|
||||
|
||||
const extractProcess = spawn(
|
||||
'unzip',
|
||||
['-o', zipPath, '-d', comfyUIWorkspace],
|
||||
{
|
||||
stdio: 'pipe',
|
||||
env,
|
||||
}
|
||||
);
|
||||
|
||||
extractProcess.on('exit', async (extractCode) => {
|
||||
if (extractCode === 0) {
|
||||
try {
|
||||
const extractedDir = path.join(
|
||||
comfyUIWorkspace,
|
||||
'ComfyUI-master'
|
||||
);
|
||||
const files = await fs.readdir(extractedDir);
|
||||
|
||||
for (const file of files) {
|
||||
const srcPath = path.join(extractedDir, file);
|
||||
const destPath = path.join(comfyUIWorkspace, file);
|
||||
await fs.rename(srcPath, destPath);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
await fs.rmdir(extractedDir);
|
||||
if (!zipfile) {
|
||||
reject(new Error('Failed to open zip file'));
|
||||
return;
|
||||
}
|
||||
|
||||
zipfile.readEntry();
|
||||
|
||||
zipfile.on('entry', (entry) => {
|
||||
if (/\/$/.test(entry.fileName)) {
|
||||
zipfile.readEntry();
|
||||
} else {
|
||||
zipfile.openReadStream(entry, (err, readStream) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!readStream) {
|
||||
reject(new Error('Failed to create read stream'));
|
||||
return;
|
||||
}
|
||||
|
||||
const fileName = entry.fileName.replace(/^[^/]+\//, '');
|
||||
const outputPath = path.join(
|
||||
comfyUIWorkspace,
|
||||
fileName
|
||||
);
|
||||
|
||||
fs.mkdir(path.dirname(outputPath), { recursive: true })
|
||||
.then(() => {
|
||||
const writeStream = createWriteStream(outputPath);
|
||||
readStream.pipe(writeStream);
|
||||
|
||||
writeStream.on('close', () => {
|
||||
zipfile.readEntry();
|
||||
});
|
||||
|
||||
writeStream.on('error', reject);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
zipfile.on('end', () => {
|
||||
resolve();
|
||||
});
|
||||
|
||||
zipfile.on('error', reject);
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
await fs.unlink(zipPath);
|
||||
|
||||
sendKoboldOutput(
|
||||
'ComfyUI extracted, installing dependencies...'
|
||||
);
|
||||
sendKoboldOutput('ComfyUI extracted, installing dependencies...');
|
||||
|
||||
const requirementsPath = join(
|
||||
comfyUIWorkspace,
|
||||
|
|
@ -226,8 +224,7 @@ async function ensureComfyUIInstalled() {
|
|||
'bin',
|
||||
'python'
|
||||
);
|
||||
const torchInstallArgs =
|
||||
await getPyTorchInstallArgs(pythonPath);
|
||||
const torchInstallArgs = await getPyTorchInstallArgs(pythonPath);
|
||||
|
||||
const torchInstallProcess = spawn('uv', torchInstallArgs, {
|
||||
stdio: 'pipe',
|
||||
|
|
@ -276,9 +273,7 @@ async function ensureComfyUIInstalled() {
|
|||
|
||||
verifyProcess.on('exit', (verifyCode) => {
|
||||
if (verifyCode === 0) {
|
||||
sendKoboldOutput(
|
||||
'ComfyUI installed successfully!'
|
||||
);
|
||||
sendKoboldOutput('ComfyUI installed successfully!');
|
||||
resolve();
|
||||
} else {
|
||||
sendKoboldOutput(
|
||||
|
|
@ -360,33 +355,6 @@ async function ensureComfyUIInstalled() {
|
|||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`Failed to extract ComfyUI: unzip exit code ${extractCode}`
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
extractProcess.on('error', (error) => {
|
||||
reject(new Error(`Extraction process error: ${error.message}`));
|
||||
});
|
||||
|
||||
if (extractProcess.stdout) {
|
||||
extractProcess.stdout.on('data', (data: Buffer) => {
|
||||
sendKoboldOutput(data.toString('utf8'), true);
|
||||
});
|
||||
}
|
||||
|
||||
if (extractProcess.stderr) {
|
||||
extractProcess.stderr.on('data', (data: Buffer) => {
|
||||
sendKoboldOutput(
|
||||
`Extract stderr: ${data.toString('utf8')}`,
|
||||
true
|
||||
);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
reject(
|
||||
new Error(
|
||||
|
|
@ -477,8 +445,7 @@ export async function startFrontend(args: string[]) {
|
|||
const comfyUIWorkspace = join(installDir, 'comfyui-workspace');
|
||||
const comfyUIMainPath = join(comfyUIWorkspace, 'main.py');
|
||||
|
||||
await ensureDir(comfyUIDataDir);
|
||||
await ensureDir(comfyUIWorkspace);
|
||||
await Promise.all([ensureDir(comfyUIDataDir), ensureDir(comfyUIWorkspace)]);
|
||||
|
||||
const comfyUIArgs = [
|
||||
comfyUIMainPath,
|
||||
|
|
|
|||
|
|
@ -1,43 +1,172 @@
|
|||
import { spawn } from 'child_process';
|
||||
import { access, readdir } from 'fs/promises';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
|
||||
export async function isUvAvailable() {
|
||||
return new Promise((resolve) => {
|
||||
const uvProcess = spawn('uv', ['--version'], {
|
||||
stdio: 'ignore',
|
||||
});
|
||||
try {
|
||||
const env = await getUvEnvironment();
|
||||
const testProcess = spawn('uv', ['--version'], { stdio: 'pipe', env });
|
||||
|
||||
uvProcess.on('close', (code) => {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
const timeout = setTimeout(() => {
|
||||
testProcess.kill();
|
||||
resolve(false);
|
||||
}, 10000);
|
||||
|
||||
testProcess.on('exit', (code) => {
|
||||
clearTimeout(timeout);
|
||||
resolve(code === 0);
|
||||
});
|
||||
|
||||
uvProcess.on('error', () => {
|
||||
testProcess.on('error', () => {
|
||||
clearTimeout(timeout);
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
uvProcess.kill();
|
||||
resolve(false);
|
||||
}, 5000);
|
||||
});
|
||||
export async function getUvEnvironment() {
|
||||
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') {
|
||||
env.PYTHONIOENCODING = 'utf-8';
|
||||
env.PYTHONLEGACYWINDOWSSTDIO = '1';
|
||||
env.PYTHONUTF8 = '1';
|
||||
env.CHCP = '65001';
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
export async function isNpxAvailable() {
|
||||
return new Promise((resolve) => {
|
||||
const npxProcess = spawn('npx', ['--version'], {
|
||||
stdio: 'ignore',
|
||||
try {
|
||||
const env = await getNodeEnvironment();
|
||||
const testProcess = spawn('npx', ['--version'], {
|
||||
stdio: 'pipe',
|
||||
env,
|
||||
shell: process.platform === 'win32',
|
||||
});
|
||||
|
||||
npxProcess.on('close', (code) => {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
const timeout = setTimeout(() => {
|
||||
testProcess.kill();
|
||||
resolve(false);
|
||||
}, 5000);
|
||||
|
||||
testProcess.on('exit', (code) => {
|
||||
clearTimeout(timeout);
|
||||
resolve(code === 0);
|
||||
});
|
||||
|
||||
npxProcess.on('error', () => {
|
||||
testProcess.on('error', () => {
|
||||
clearTimeout(timeout);
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
npxProcess.kill();
|
||||
resolve(false);
|
||||
}, 5000);
|
||||
});
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getNodeEnvironment() {
|
||||
const env = { ...process.env };
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
return 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');
|
||||
}
|
||||
|
||||
for (const systemPath of systemPaths) {
|
||||
try {
|
||||
await access(systemPath);
|
||||
await tryAddPathToEnv(env, systemPath);
|
||||
return env;
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (const versionPath of versionManagerPaths) {
|
||||
if (await tryVersionManagerPath(versionPath, env)) {
|
||||
return env;
|
||||
}
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
async function tryVersionManagerPath(
|
||||
basePath: string,
|
||||
env: Record<string, string | undefined>
|
||||
) {
|
||||
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 tryAddPathToEnv(env, nodeBinPath);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function tryAddPathToEnv(
|
||||
env: Record<string, string | undefined>,
|
||||
path: string
|
||||
) {
|
||||
const pathSeparator = process.platform === 'win32' ? ';' : ':';
|
||||
if (!env.PATH?.includes(path)) {
|
||||
env.PATH = `${path}${pathSeparator}${env.PATH}`;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import { spawn } from 'child_process';
|
||||
import type { ChildProcess } from 'child_process';
|
||||
import { join } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { access } from 'fs/promises';
|
||||
|
||||
import { logError } from './logging';
|
||||
import { sendKoboldOutput } from './window';
|
||||
|
|
@ -11,6 +9,7 @@ import { OPENWEBUI, SERVER_READY_SIGNALS } from '@/constants';
|
|||
import { terminateProcess } from '@/utils/node/process';
|
||||
import { parseKoboldConfig } from '@/utils/node/kobold';
|
||||
import { getAppVersion } from '@/utils/node/fs';
|
||||
import { getUvEnvironment } from './dependencies';
|
||||
|
||||
let openWebUIProcess: ChildProcess | null = null;
|
||||
|
||||
|
|
@ -24,48 +23,10 @@ process.on('SIGTERM', () => {
|
|||
void cleanup();
|
||||
});
|
||||
|
||||
async function getUvEnvironment() {
|
||||
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') {
|
||||
env.PYTHONIOENCODING = 'utf-8';
|
||||
env.PYTHONLEGACYWINDOWSSTDIO = '1';
|
||||
env.PYTHONUTF8 = '1';
|
||||
env.CHCP = '65001';
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
async function createUvProcess(args: string[], env?: Record<string, string>) {
|
||||
const uvEnv = await getUvEnvironment();
|
||||
const mergedEnv = { ...uvEnv, ...env };
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
mergedEnv.PYTHONIOENCODING = 'utf-8';
|
||||
mergedEnv.PYTHONUTF8 = '1';
|
||||
}
|
||||
|
||||
return spawn('uvx', args, {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
detached: false,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { spawn } from 'child_process';
|
|||
import { createServer, request, type Server } from 'http';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { access, readdir } from 'fs/promises';
|
||||
import type { ChildProcess } from 'child_process';
|
||||
|
||||
import { logError } from './logging';
|
||||
|
|
@ -11,6 +10,7 @@ import { SILLYTAVERN, SERVER_READY_SIGNALS } from '@/constants';
|
|||
import { terminateProcess } from '@/utils/node/process';
|
||||
import { pathExists, readJsonFile, writeJsonFile } from '@/utils/node/fs';
|
||||
import { parseKoboldConfig } from '@/utils/node/kobold';
|
||||
import { getNodeEnvironment } from './dependencies';
|
||||
|
||||
let sillyTavernProcess: ChildProcess | null = null;
|
||||
let proxyServer: Server | null = null;
|
||||
|
|
@ -69,85 +69,6 @@ async function getSillyTavernSettingsPath() {
|
|||
return join(dataRoot, 'default-user', 'settings.json');
|
||||
}
|
||||
|
||||
async function tryAddPathToEnv(
|
||||
env: Record<string, string | undefined>,
|
||||
path: string
|
||||
) {
|
||||
const pathSeparator = process.platform === 'win32' ? ';' : ':';
|
||||
if (!env.PATH?.includes(path)) {
|
||||
env.PATH = `${path}${pathSeparator}${env.PATH}`;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function tryVersionManagerPath(
|
||||
basePath: string,
|
||||
env: Record<string, string | undefined>
|
||||
) {
|
||||
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 tryAddPathToEnv(env, nodeBinPath);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function getNodeEnvironment() {
|
||||
const env = { ...process.env };
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
return 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');
|
||||
}
|
||||
|
||||
for (const systemPath of systemPaths) {
|
||||
try {
|
||||
await access(systemPath);
|
||||
await tryAddPathToEnv(env, systemPath);
|
||||
return env;
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (const versionPath of versionManagerPaths) {
|
||||
if (await tryVersionManagerPath(versionPath, env)) {
|
||||
return env;
|
||||
}
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
async function createNpxProcess(args: string[]) {
|
||||
const env = await getNodeEnvironment();
|
||||
return spawn('npx', args, {
|
||||
|
|
|
|||
14
yarn.lock
14
yarn.lock
|
|
@ -1349,7 +1349,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/yauzl@npm:^2.9.1":
|
||||
"@types/yauzl@npm:^2.10.3, @types/yauzl@npm:^2.9.1":
|
||||
version: 2.10.3
|
||||
resolution: "@types/yauzl@npm:2.10.3"
|
||||
dependencies:
|
||||
|
|
@ -3643,6 +3643,7 @@ __metadata:
|
|||
"@types/node": "npm:^24.3.3"
|
||||
"@types/react": "npm:^19.1.13"
|
||||
"@types/react-dom": "npm:^19.1.9"
|
||||
"@types/yauzl": "npm:^2.10.3"
|
||||
"@typescript-eslint/eslint-plugin": "npm:^8.43.0"
|
||||
"@typescript-eslint/parser": "npm:^8.43.0"
|
||||
"@vitejs/plugin-react": "npm:^5.0.2"
|
||||
|
|
@ -3672,6 +3673,7 @@ __metadata:
|
|||
vite: "npm:^7.1.5"
|
||||
winston: "npm:^3.17.0"
|
||||
winston-daily-rotate-file: "npm:^5.0.0"
|
||||
yauzl: "npm:^3.2.0"
|
||||
zustand: "npm:^5.0.8"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
|
@ -7424,6 +7426,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yauzl@npm:^3.2.0":
|
||||
version: 3.2.0
|
||||
resolution: "yauzl@npm:3.2.0"
|
||||
dependencies:
|
||||
buffer-crc32: "npm:~0.2.3"
|
||||
pend: "npm:~1.2.0"
|
||||
checksum: 10c0/7b40b3dc46b95761a2a764391d257a11f494d365875af73a1b48fe16d4bd103dd178612e60168d12a0e59a8ba4f6411a15a5e8871d5a5f78255d6cc1ce39ee62
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yocto-queue@npm:^0.1.0":
|
||||
version: 0.1.0
|
||||
resolution: "yocto-queue@npm:0.1.0"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue