mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
81 lines
2.5 KiB
TypeScript
81 lines
2.5 KiB
TypeScript
import { execFile as execFileCallback } from 'node:child_process'
|
|
import { constants } from 'node:fs'
|
|
import { access, readlink, realpath, stat } from 'node:fs/promises'
|
|
import { delimiter, isAbsolute, resolve } from 'node:path'
|
|
import { promisify } from 'node:util'
|
|
|
|
const execFile = promisify(execFileCallback)
|
|
const PROCESS_READINESS_TIMEOUT_MS = 3000
|
|
const DEFAULT_POSIX_EXEC_PATH = '/usr/bin:/bin'
|
|
|
|
export type ShellProcessReadiness = {
|
|
executablePath: string
|
|
foreground: boolean
|
|
}
|
|
|
|
export function parseDarwinExecutablePath(stdout: string): string | null {
|
|
const lines = stdout.split(/\r?\n/)
|
|
const textIndex = lines.indexOf('ftxt')
|
|
const pathLine = textIndex === -1 ? undefined : lines[textIndex + 1]
|
|
return pathLine?.startsWith('n') ? pathLine.slice(1) : null
|
|
}
|
|
|
|
async function readExecutablePath(pid: number): Promise<string | null> {
|
|
if (process.platform === 'linux') {
|
|
return readlink(`/proc/${pid}/exe`)
|
|
}
|
|
if (process.platform !== 'darwin') {
|
|
return null
|
|
}
|
|
const { stdout } = await execFile(
|
|
'/usr/sbin/lsof',
|
|
['-a', '-p', String(pid), '-d', 'txt', '-Fn'],
|
|
{
|
|
encoding: 'utf8',
|
|
timeout: PROCESS_READINESS_TIMEOUT_MS
|
|
}
|
|
)
|
|
return parseDarwinExecutablePath(stdout)
|
|
}
|
|
|
|
export async function readShellProcessReadiness(
|
|
pid: number
|
|
): Promise<ShellProcessReadiness | null> {
|
|
const [executablePath, { stdout }] = await Promise.all([
|
|
readExecutablePath(pid),
|
|
execFile('ps', ['-p', String(pid), '-o', 'stat='], {
|
|
encoding: 'utf8',
|
|
timeout: PROCESS_READINESS_TIMEOUT_MS
|
|
})
|
|
])
|
|
const status = stdout.trim()
|
|
return status && executablePath
|
|
? { executablePath: await realpath(executablePath), foreground: status.includes('+') }
|
|
: null
|
|
}
|
|
|
|
export async function resolveShellExecutablePath(
|
|
shellPath: string,
|
|
cwd: string,
|
|
pathEnv: string | undefined
|
|
): Promise<string | null> {
|
|
const candidates = shellPath.includes('/')
|
|
? [isAbsolute(shellPath) ? shellPath : resolve(cwd, shellPath)]
|
|
: (
|
|
pathEnv ??
|
|
(process.platform === 'win32' ? (process.env.PATH ?? '') : DEFAULT_POSIX_EXEC_PATH)
|
|
)
|
|
.split(delimiter)
|
|
.map((entry) => resolve(isAbsolute(entry) ? entry : resolve(cwd, entry), shellPath))
|
|
for (const candidate of candidates) {
|
|
try {
|
|
await access(candidate, constants.X_OK)
|
|
const canonicalPath = await realpath(candidate)
|
|
if ((await stat(canonicalPath)).isFile()) {
|
|
return canonicalPath
|
|
}
|
|
} catch {}
|
|
}
|
|
return null
|
|
}
|