/
io-util.ts
173 lines (153 loc) · 4.25 KB
/
io-util.ts
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import * as fs from 'fs'
import * as path from 'path'
export const {
chmod,
copyFile,
lstat,
mkdir,
readdir,
readlink,
rename,
rmdir,
stat,
symlink,
unlink
} = fs.promises
export const IS_WINDOWS = process.platform === 'win32'
export async function exists(fsPath: string): Promise<boolean> {
try {
await stat(fsPath)
} catch (err) {
if (err.code === 'ENOENT') {
return false
}
throw err
}
return true
}
export async function isDirectory(
fsPath: string,
useStat = false
): Promise<boolean> {
const stats = useStat ? await stat(fsPath) : await lstat(fsPath)
return stats.isDirectory()
}
/**
* On OSX/Linux, true if path starts with '/'. On Windows, true for paths like:
* \, \hello, \\hello\share, C:, and C:\hello (and corresponding alternate separator cases).
*/
export function isRooted(p: string): boolean {
p = normalizeSeparators(p)
if (!p) {
throw new Error('isRooted() parameter "p" cannot be empty')
}
if (IS_WINDOWS) {
return (
p.startsWith('\\') || /^[A-Z]:/i.test(p) // e.g. \ or \hello or \\hello
) // e.g. C: or C:\hello
}
return p.startsWith('/')
}
/**
* Best effort attempt to determine whether a file exists and is executable.
* @param filePath file path to check
* @param extensions additional file extensions to try
* @return if file exists and is executable, returns the file path. otherwise empty string.
*/
export async function tryGetExecutablePath(
filePath: string,
extensions: string[]
): Promise<string> {
let stats: fs.Stats | undefined = undefined
try {
// test file exists
stats = await stat(filePath)
} catch (err) {
if (err.code !== 'ENOENT') {
// eslint-disable-next-line no-console
console.log(
`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`
)
}
}
if (stats && stats.isFile()) {
if (IS_WINDOWS) {
// on Windows, test for valid extension
const upperExt = path.extname(filePath).toUpperCase()
if (extensions.some(validExt => validExt.toUpperCase() === upperExt)) {
return filePath
}
} else {
if (isUnixExecutable(stats)) {
return filePath
}
}
}
// try each extension
const originalFilePath = filePath
for (const extension of extensions) {
filePath = originalFilePath + extension
stats = undefined
try {
stats = await stat(filePath)
} catch (err) {
if (err.code !== 'ENOENT') {
// eslint-disable-next-line no-console
console.log(
`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`
)
}
}
if (stats && stats.isFile()) {
if (IS_WINDOWS) {
// preserve the case of the actual file (since an extension was appended)
try {
const directory = path.dirname(filePath)
const upperName = path.basename(filePath).toUpperCase()
for (const actualName of await readdir(directory)) {
if (upperName === actualName.toUpperCase()) {
filePath = path.join(directory, actualName)
break
}
}
} catch (err) {
// eslint-disable-next-line no-console
console.log(
`Unexpected error attempting to determine the actual case of the file '${filePath}': ${err}`
)
}
return filePath
} else {
if (isUnixExecutable(stats)) {
return filePath
}
}
}
}
return ''
}
function normalizeSeparators(p: string): string {
p = p || ''
if (IS_WINDOWS) {
// convert slashes on Windows
p = p.replace(/\//g, '\\')
// remove redundant slashes
return p.replace(/\\\\+/g, '\\')
}
// remove redundant slashes
return p.replace(/\/\/+/g, '/')
}
// on Mac/Linux, test the execute bit
// R W X R W X R W X
// 256 128 64 32 16 8 4 2 1
function isUnixExecutable(stats: fs.Stats): boolean {
return (
(stats.mode & 1) > 0 ||
((stats.mode & 8) > 0 && stats.gid === process.getgid()) ||
((stats.mode & 64) > 0 && stats.uid === process.getuid())
)
}
// Get the path of cmd.exe in windows
export function getCmdPath(): string {
return process.env['COMSPEC'] ?? `C:\\WINDOWS\\system32\\cmd.exe`
}