mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-04 12:13:28 -07:00
209 lines
4.7 KiB
TypeScript
209 lines
4.7 KiB
TypeScript
import { spawn } from 'child_process';
|
|
import { access, readdir } from 'fs/promises';
|
|
import { homedir } from 'os';
|
|
import { join } from 'path';
|
|
|
|
interface CommandResult {
|
|
success: boolean;
|
|
output?: string;
|
|
}
|
|
|
|
async function executeCommand(
|
|
command: string,
|
|
args: string[],
|
|
env: Record<string, string | undefined>,
|
|
timeout = 5000,
|
|
useShell = false
|
|
) {
|
|
try {
|
|
const testProcess = spawn(command, args, {
|
|
stdio: 'pipe',
|
|
env,
|
|
shell: useShell,
|
|
});
|
|
|
|
return new Promise<CommandResult>((resolve) => {
|
|
let output = '';
|
|
const timeoutId = setTimeout(() => {
|
|
testProcess.kill();
|
|
resolve({ success: false });
|
|
}, timeout);
|
|
|
|
testProcess.stdout?.on('data', (data) => {
|
|
output += data.toString();
|
|
});
|
|
|
|
testProcess.on('exit', (code) => {
|
|
clearTimeout(timeoutId);
|
|
resolve({
|
|
success: code === 0,
|
|
output: code === 0 ? output.trim() : undefined,
|
|
});
|
|
});
|
|
|
|
testProcess.on('error', () => {
|
|
clearTimeout(timeoutId);
|
|
resolve({ success: false });
|
|
});
|
|
});
|
|
} catch {
|
|
return { success: false };
|
|
}
|
|
}
|
|
|
|
export async function getUvVersion() {
|
|
const env = await getUvEnvironment();
|
|
const result = await executeCommand('uv', ['--version'], env);
|
|
|
|
if (result.success && result.output) {
|
|
const version = result.output.match(/uv\s+(\d+\.\d+\.\d+)/i);
|
|
return version ? version[1] : result.output;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export async function getSystemNodeVersion() {
|
|
const env = await getNodeEnvironment();
|
|
const result = await executeCommand(
|
|
'node',
|
|
['--version'],
|
|
env,
|
|
5000,
|
|
process.platform === 'win32'
|
|
);
|
|
|
|
if (result.success && result.output) {
|
|
return result.output.replace(/^v/, '') || null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export async function isUvAvailable() {
|
|
const env = await getUvEnvironment();
|
|
const result = await executeCommand('uv', ['--version'], env, 10000);
|
|
return result.success;
|
|
}
|
|
|
|
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() {
|
|
const env = await getNodeEnvironment();
|
|
const result = await executeCommand(
|
|
'npx',
|
|
['--version'],
|
|
env,
|
|
5000,
|
|
process.platform === 'win32'
|
|
);
|
|
return result.success;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|