mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-05 23:09:54 +00:00
136 lines
3.8 KiB
TypeScript
136 lines
3.8 KiB
TypeScript
import { execFile } from "node:child_process"
|
|
import { access, readFile, readdir } from "node:fs/promises"
|
|
import { dirname, extname, join } from "node:path"
|
|
import util from "node:util"
|
|
|
|
const execFilePromise = util.promisify(execFile)
|
|
|
|
const exists = (path: string) =>
|
|
access(path)
|
|
.then(() => true)
|
|
.catch(() => false)
|
|
|
|
export function checkAppExists(appName: string) {
|
|
if (process.platform === "win32") return true
|
|
if (process.platform === "linux") return true
|
|
return checkMacosApp(appName)
|
|
}
|
|
|
|
export function resolveAppPath(appName: string) {
|
|
if (process.platform !== "win32") return appName
|
|
return resolveWindowsAppPath(appName)
|
|
}
|
|
|
|
async function checkMacosApp(appName: string) {
|
|
const locations = [`/Applications/${appName}.app`, `/System/Applications/${appName}.app`]
|
|
|
|
const home = process.env.HOME
|
|
if (home) locations.push(`${home}/Applications/${appName}.app`)
|
|
|
|
for (const location of locations) {
|
|
if (await exists(location)) return true
|
|
}
|
|
|
|
return execFilePromise("which", [appName])
|
|
.then(() => true)
|
|
.catch(() => false)
|
|
}
|
|
|
|
async function resolveWindowsAppPath(appName: string): Promise<string | null> {
|
|
let output: string
|
|
try {
|
|
output = await execFilePromise("where", [appName]).then((r) => r.stdout.toString())
|
|
} catch {
|
|
return null
|
|
}
|
|
|
|
const paths = output
|
|
.split(/\r?\n/)
|
|
.map((line) => line.trim())
|
|
.filter((line) => line.length > 0)
|
|
|
|
const hasExt = (path: string, ext: string) => extname(path).toLowerCase() === `.${ext}`
|
|
|
|
const exe = paths.find((path) => hasExt(path, "exe"))
|
|
if (exe) return exe
|
|
|
|
const resolveCmd = async (path: string) => {
|
|
const content = await readFile(path, "utf8")
|
|
for (const token of content.split('"').map((value: string) => value.trim())) {
|
|
const lower = token.toLowerCase()
|
|
if (!lower.includes(".exe")) continue
|
|
|
|
const index = lower.indexOf("%~dp0")
|
|
if (index >= 0) {
|
|
const base = dirname(path)
|
|
const suffix = token.slice(index + 5)
|
|
const resolved = suffix
|
|
.replace(/\//g, "\\")
|
|
.split("\\")
|
|
.filter((part: string) => part && part !== ".")
|
|
.reduce((current: string, part: string) => {
|
|
if (part === "..") return dirname(current)
|
|
return join(current, part)
|
|
}, base)
|
|
|
|
if (await exists(resolved)) return resolved
|
|
}
|
|
|
|
if (await exists(token)) return token
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
for (const path of paths) {
|
|
if (hasExt(path, "cmd") || hasExt(path, "bat")) {
|
|
const resolved = await resolveCmd(path)
|
|
if (resolved) return resolved
|
|
}
|
|
|
|
if (!extname(path)) {
|
|
const cmd = `${path}.cmd`
|
|
if (await exists(cmd)) {
|
|
const resolved = await resolveCmd(cmd)
|
|
if (resolved) return resolved
|
|
}
|
|
|
|
const bat = `${path}.bat`
|
|
if (await exists(bat)) {
|
|
const resolved = await resolveCmd(bat)
|
|
if (resolved) return resolved
|
|
}
|
|
}
|
|
}
|
|
|
|
const key = appName
|
|
.split("")
|
|
.filter((value: string) => /[a-z0-9]/i.test(value))
|
|
.map((value: string) => value.toLowerCase())
|
|
.join("")
|
|
|
|
if (key) {
|
|
for (const path of paths) {
|
|
const dirs = [dirname(path), dirname(dirname(path)), dirname(dirname(dirname(path)))]
|
|
for (const dir of dirs) {
|
|
try {
|
|
for (const entry of await readdir(dir)) {
|
|
const candidate = join(dir, entry)
|
|
if (!hasExt(candidate, "exe")) continue
|
|
const stem = entry.replace(/\.exe$/i, "")
|
|
const name = stem
|
|
.split("")
|
|
.filter((value: string) => /[a-z0-9]/i.test(value))
|
|
.map((value: string) => value.toLowerCase())
|
|
.join("")
|
|
if (name.includes(key) || key.includes(name)) return candidate
|
|
}
|
|
} catch {
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return paths[0] ?? null
|
|
}
|