1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
import { execFileSync } from "node:child_process"
import { existsSync, readFileSync, readdirSync } from "node:fs"
import { dirname, extname, join } from "node:path"
export function checkAppExists(appName: string): boolean {
if (process.platform === "win32") return true
if (process.platform === "linux") return true
return checkMacosApp(appName)
}
export function resolveAppPath(appName: string): string | null {
if (process.platform !== "win32") return appName
return resolveWindowsAppPath(appName)
}
export function wslPath(path: string, mode: "windows" | "linux" | null): string {
if (process.platform !== "win32") return path
const flag = mode === "windows" ? "-w" : "-u"
try {
if (path.startsWith("~")) {
const suffix = path.slice(1)
const cmd = `wslpath ${flag} "$HOME${suffix.replace(/"/g, '\\"')}"`
const output = execFileSync("wsl", ["-e", "sh", "-lc", cmd])
return output.toString().trim()
}
const output = execFileSync("wsl", ["-e", "wslpath", flag, path])
return output.toString().trim()
} catch (error) {
throw new Error(`Failed to run wslpath: ${String(error)}`, { cause: error })
}
}
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`)
if (locations.some((location) => existsSync(location))) return true
try {
execFileSync("which", [appName])
return true
} catch {
return false
}
}
function resolveWindowsAppPath(appName: string): string | null {
let output: string
try {
output = execFileSync("where", [appName]).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 = (path: string) => {
const content = readFileSync(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 (existsSync(resolved)) return resolved
}
if (existsSync(token)) return token
}
return null
}
for (const path of paths) {
if (hasExt(path, "cmd") || hasExt(path, "bat")) {
const resolved = resolveCmd(path)
if (resolved) return resolved
}
if (!extname(path)) {
const cmd = `${path}.cmd`
if (existsSync(cmd)) {
const resolved = resolveCmd(cmd)
if (resolved) return resolved
}
const bat = `${path}.bat`
if (existsSync(bat)) {
const resolved = 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 readdirSync(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
}
|