Forked from bakit/terminal
Forked from bakit/terminal
src / runtimeResolver.ts
src / runtimeResolver.ts
import * as fs from "fs";
import * as path from "path";
import { expandPath } from "./securityEnhanced";
export type Runtime = "python" | "node" | "bash" | "pwsh" | "powershell" | "cmd" | "java" | "ruby" | "go" | "rust" | "unknown";
export interface RuntimeInfo {
name: Runtime;
executable: string;
extension: string;
}
const RUNTIME_MAP: Record<string, RuntimeInfo> = {
".py": { name: "python", executable: "python3", extension: ".py" },
".js": { name: "node", executable: "node", extension: ".js" },
".ts": { name: "node", executable: "node", extension: ".ts" },
".bash": { name: "bash", executable: "bash", extension: ".bash" },
".sh": { name: "bash", executable: "bash", extension: ".sh" },
".ps1": { name: "pwsh", executable: "pwsh", extension: ".ps1" },
".psm1": { name: "pwsh", executable: "pwsh", extension: ".psm1" },
".psd1": { name: "pwsh", executable: "pwsh", extension: ".psd1" },
".java": { name: "java", executable: "java", extension: ".java" },
".rb": { name: "ruby", executable: "ruby", extension: ".rb" },
".go": { name: "go", executable: "go", extension: ".go" },
".rs": { name: "rust", executable: "rustc", extension: ".rs" },
};
/**
* Detect runtime language from a file extension.
*/
export function detectRuntime(filePath: string): RuntimeInfo {
const ext = path.extname(filePath).toLowerCase();
const info = RUNTIME_MAP[ext];
if (info) return info;
// Fallback: check shebang
try {
const content = fs.readFileSync(expandPath(filePath), "utf8");
const shebang = content.split("\n")[0];
if (shebang.startsWith("#!/bin/bash") || shebang.startsWith("#!/bin/sh")) return { name: "bash", executable: "bash", extension: ext };
if (shebang.startsWith("#!/usr/bin/env python")) return { name: "python", executable: "python3", extension: ext };
if (shebang.startsWith("#!/usr/bin/env node")) return { name: "node", executable: "node", extension: ext };
if (shebang.startsWith("#!/usr/bin/env pwsh")) return { name: "pwsh", executable: "pwsh", extension: ext };
} catch { /* ignore */ }
return { name: "unknown", executable: "unknown", extension: ext };
}
/**
* Resolve the full path of an executable in PATH.
*/
export function which(executable: string): string | null {
const which = require("child_process").execSync;
try {
const platform = require("os").platform();
const cmd = platform === "win32" ? `where ${executable}` : `which ${executable}`;
const result = which(cmd, { encoding: "utf8", timeout: 3000 }).trim();
const lines = result.split("\n").filter((l: string) => l.trim());
return lines.length > 0 ? lines[lines.length - 1].trim() : null;
} catch {
return null;
}
}
import * as fs from "fs";
import * as path from "path";
import { expandPath } from "./securityEnhanced";
export type Runtime = "python" | "node" | "bash" | "pwsh" | "powershell" | "cmd" | "java" | "ruby" | "go" | "rust" | "unknown";
export interface RuntimeInfo {
name: Runtime;
executable: string;
extension: string;
}
const RUNTIME_MAP: Record<string, RuntimeInfo> = {
".py": { name: "python", executable: "python3", extension: ".py" },
".js": { name: "node", executable: "node", extension: ".js" },
".ts": { name: "node", executable: "node", extension: ".ts" },
".bash": { name: "bash", executable: "bash", extension: ".bash" },
".sh": { name: "bash", executable: "bash", extension: ".sh" },
".ps1": { name: "pwsh", executable: "pwsh", extension: ".ps1" },
".psm1": { name: "pwsh", executable: "pwsh", extension: ".psm1" },
".psd1": { name: "pwsh", executable: "pwsh", extension: ".psd1" },
".java": { name: "java", executable: "java", extension: ".java" },
".rb": { name: "ruby", executable: "ruby", extension: ".rb" },
".go": { name: "go", executable: "go", extension: ".go" },
".rs": { name: "rust", executable: "rustc", extension: ".rs" },
};
/**
* Detect runtime language from a file extension.
*/
export function detectRuntime(filePath: string): RuntimeInfo {
const ext = path.extname(filePath).toLowerCase();
const info = RUNTIME_MAP[ext];
if (info) return info;
// Fallback: check shebang
try {
const content = fs.readFileSync(expandPath(filePath), "utf8");
const shebang = content.split("\n")[0];
if (shebang.startsWith("#!/bin/bash") || shebang.startsWith("#!/bin/sh")) return { name: "bash", executable: "bash", extension: ext };
if (shebang.startsWith("#!/usr/bin/env python")) return { name: "python", executable: "python3", extension: ext };
if (shebang.startsWith("#!/usr/bin/env node")) return { name: "node", executable: "node", extension: ext };
if (shebang.startsWith("#!/usr/bin/env pwsh")) return { name: "pwsh", executable: "pwsh", extension: ext };
} catch { /* ignore */ }
return { name: "unknown", executable: "unknown", extension: ext };
}
/**
* Resolve the full path of an executable in PATH.
*/
export function which(executable: string): string | null {
const which = require("child_process").execSync;
try {
const platform = require("os").platform();
const cmd = platform === "win32" ? `where ${executable}` : `which ${executable}`;
const result = which(cmd, { encoding: "utf8", timeout: 3000 }).trim();
const lines = result.split("\n").filter((l: string) => l.trim());
return lines.length > 0 ? lines[lines.length - 1].trim() : null;
} catch {
return null;
}
}