@vanillaes/esmtk 0.14.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/launch.json +109 -1
- package/bin/commands/cp.js +33 -8
- package/bin/esmtk.js +13 -6
- package/package.json +4 -1
- package/src/cp.d.ts +10 -0
- package/src/cp.js +56 -22
- package/src/index.d.ts +1 -1
- package/src/index.js +1 -1
- package/src/util.d.ts +13 -0
- package/src/util.js +35 -1
package/.vscode/launch.json
CHANGED
|
@@ -106,7 +106,55 @@
|
|
|
106
106
|
],
|
|
107
107
|
"program": "${workspaceFolder}/bin/esmtk.js",
|
|
108
108
|
"cwd": "${workspaceFolder}",
|
|
109
|
-
"args": ["cp", "./test/cp/test1.txt", "./test/cp2"],
|
|
109
|
+
"args": ["cp", "./test/cp/test1.txt", "./test/cp2/test1.txt"],
|
|
110
|
+
"console": "integratedTerminal",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"name": "CopyGlobToFolder",
|
|
114
|
+
"type": "node",
|
|
115
|
+
"request": "launch",
|
|
116
|
+
"skipFiles": [
|
|
117
|
+
"<node_internals>/**"
|
|
118
|
+
],
|
|
119
|
+
"program": "${workspaceFolder}/bin/esmtk.js",
|
|
120
|
+
"cwd": "${workspaceFolder}",
|
|
121
|
+
"args": ["cp", "./test/cp/*.js", "./test/cp2/"],
|
|
122
|
+
"console": "integratedTerminal",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"name": "CopyFileToFile - Error source doesn't exist",
|
|
126
|
+
"type": "node",
|
|
127
|
+
"request": "launch",
|
|
128
|
+
"skipFiles": [
|
|
129
|
+
"<node_internals>/**"
|
|
130
|
+
],
|
|
131
|
+
"program": "${workspaceFolder}/bin/esmtk.js",
|
|
132
|
+
"cwd": "${workspaceFolder}",
|
|
133
|
+
"args": ["cp", "./test/cp/testx.txt", "./test/cp2/"],
|
|
134
|
+
"console": "integratedTerminal",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"name": "CopyFileToFile - Error source is a directory",
|
|
138
|
+
"type": "node",
|
|
139
|
+
"request": "launch",
|
|
140
|
+
"skipFiles": [
|
|
141
|
+
"<node_internals>/**"
|
|
142
|
+
],
|
|
143
|
+
"program": "${workspaceFolder}/bin/esmtk.js",
|
|
144
|
+
"cwd": "${workspaceFolder}",
|
|
145
|
+
"args": ["cp", "./test/cp/", "./test/cp2/test1.txt"],
|
|
146
|
+
"console": "integratedTerminal",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"name": "CopyFileToFile - Error target directory doesn't exist",
|
|
150
|
+
"type": "node",
|
|
151
|
+
"request": "launch",
|
|
152
|
+
"skipFiles": [
|
|
153
|
+
"<node_internals>/**"
|
|
154
|
+
],
|
|
155
|
+
"program": "${workspaceFolder}/bin/esmtk.js",
|
|
156
|
+
"cwd": "${workspaceFolder}",
|
|
157
|
+
"args": ["cp", "./test/cp/test1.txt", "./test/cpx/"],
|
|
110
158
|
"console": "integratedTerminal",
|
|
111
159
|
},
|
|
112
160
|
{
|
|
@@ -121,6 +169,66 @@
|
|
|
121
169
|
"args": ["cp", "-r", "./test/cp/", "./test/cp2"],
|
|
122
170
|
"console": "integratedTerminal",
|
|
123
171
|
},
|
|
172
|
+
{
|
|
173
|
+
"name": "CopyMultipleFiles",
|
|
174
|
+
"type": "node",
|
|
175
|
+
"request": "launch",
|
|
176
|
+
"skipFiles": [
|
|
177
|
+
"<node_internals>/**"
|
|
178
|
+
],
|
|
179
|
+
"program": "${workspaceFolder}/bin/esmtk.js",
|
|
180
|
+
"cwd": "${workspaceFolder}",
|
|
181
|
+
"args": ["cp", "./test/cp/test1.txt", "./test/cp/test1.js", "./test/cp/test2.txt", "./test/cp/test2.js", "./test/cp2/"],
|
|
182
|
+
"console": "integratedTerminal",
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"name": "CopyMultipleFiles - Error file doesn't exist",
|
|
186
|
+
"type": "node",
|
|
187
|
+
"request": "launch",
|
|
188
|
+
"skipFiles": [
|
|
189
|
+
"<node_internals>/**"
|
|
190
|
+
],
|
|
191
|
+
"program": "${workspaceFolder}/bin/esmtk.js",
|
|
192
|
+
"cwd": "${workspaceFolder}",
|
|
193
|
+
"args": ["cp", "./test/cp/test1.ts", "./test/cp/test1.js", "./test/cp/test2.txt", "./test/cp/test2.js", "./test/cp2/"],
|
|
194
|
+
"console": "integratedTerminal",
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"name": "CopyMultipleGlobs",
|
|
198
|
+
"type": "node",
|
|
199
|
+
"request": "launch",
|
|
200
|
+
"skipFiles": [
|
|
201
|
+
"<node_internals>/**"
|
|
202
|
+
],
|
|
203
|
+
"program": "${workspaceFolder}/bin/esmtk.js",
|
|
204
|
+
"cwd": "${workspaceFolder}",
|
|
205
|
+
"args": ["cp", "./test/cp/*.txt", "./test/cp/*.js", "./test/cp2/"],
|
|
206
|
+
"console": "integratedTerminal",
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"name": "CopyMultipleGlobs - Error glob not found",
|
|
210
|
+
"type": "node",
|
|
211
|
+
"request": "launch",
|
|
212
|
+
"skipFiles": [
|
|
213
|
+
"<node_internals>/**"
|
|
214
|
+
],
|
|
215
|
+
"program": "${workspaceFolder}/bin/esmtk.js",
|
|
216
|
+
"cwd": "${workspaceFolder}",
|
|
217
|
+
"args": ["cp", "./test/cp/*.ts", "./test/cp/*.js", "./test/cp2/"],
|
|
218
|
+
"console": "integratedTerminal",
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"name": "CopyMultipleMixed",
|
|
222
|
+
"type": "node",
|
|
223
|
+
"request": "launch",
|
|
224
|
+
"skipFiles": [
|
|
225
|
+
"<node_internals>/**"
|
|
226
|
+
],
|
|
227
|
+
"program": "${workspaceFolder}/bin/esmtk.js",
|
|
228
|
+
"cwd": "${workspaceFolder}",
|
|
229
|
+
"args": ["cp", "./test/cp/test1.txt", "./test/cp/test2.txt", "./test/cp/*.js", "./test/cp2/"],
|
|
230
|
+
"console": "integratedTerminal",
|
|
231
|
+
},
|
|
124
232
|
{
|
|
125
233
|
"name": "RemoveFile",
|
|
126
234
|
"type": "node",
|
package/bin/commands/cp.js
CHANGED
|
@@ -1,16 +1,41 @@
|
|
|
1
|
-
import { copyAsync, copyRecursiveAsync } from '../../src/cp.js'
|
|
1
|
+
import { copyAsync, copyMultipleAsync, copyRecursiveAsync } from '../../src/cp.js'
|
|
2
|
+
import { expandSource } from '../../src/index.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* POSIX cp Implemented in Node
|
|
5
|
-
*
|
|
6
|
-
* @param {
|
|
6
|
+
*
|
|
7
|
+
* @param {string[]} paths Variadic of source/destination paths
|
|
8
|
+
* @param {any} options 'cp' options
|
|
7
9
|
*/
|
|
8
|
-
export async function cp (
|
|
9
|
-
if (
|
|
10
|
-
|
|
10
|
+
export async function cp (paths, options) {
|
|
11
|
+
if (paths.length < 2) {
|
|
12
|
+
console.error('cp: Not enough arguments')
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
+
if (paths.length === 2) {
|
|
16
|
+
const source = paths[0]
|
|
17
|
+
const target = paths[1]
|
|
18
|
+
|
|
19
|
+
if (!options?.recursive && !source.includes('*')) {
|
|
20
|
+
await copyAsync(source, target, options?.force)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (options?.recursive) {
|
|
24
|
+
await copyRecursiveAsync(source, target, options?.force)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (source.includes('*')) {
|
|
28
|
+
const sources = await expandSource(source)
|
|
29
|
+
await copyMultipleAsync(sources, target)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (paths.length >= 2) {
|
|
34
|
+
let sources = paths.slice(0, -1)
|
|
35
|
+
sources = await await Promise.all(sources.map(source => expandSource(source)))
|
|
36
|
+
sources = sources.flat()
|
|
37
|
+
const target = paths.slice(-1)[0]
|
|
38
|
+
|
|
39
|
+
await copyMultipleAsync(sources, target, options?.force)
|
|
15
40
|
}
|
|
16
41
|
}
|
package/bin/esmtk.js
CHANGED
|
@@ -51,13 +51,20 @@ program.command('minify <input> <output>')
|
|
|
51
51
|
minify(input, output, options)
|
|
52
52
|
})
|
|
53
53
|
|
|
54
|
-
program.command('cp
|
|
55
|
-
.
|
|
56
|
-
.
|
|
57
|
-
|
|
54
|
+
program.command('cp')
|
|
55
|
+
.argument('[paths...]')
|
|
56
|
+
.usage(`[-r] source target
|
|
57
|
+
|
|
58
|
+
Examples:
|
|
59
|
+
$ cp SOURCE DEST
|
|
60
|
+
$ cp SOURCE... DIRECTORY
|
|
61
|
+
$ cp SOURCEGLOB... DIRECTORY
|
|
62
|
+
$ cp -r SOURCEDIR DIRECTORY
|
|
63
|
+
`)
|
|
64
|
+
.description('Copy files and directories')
|
|
58
65
|
.option('-r, --recursive', 'Copy directories recursively', false)
|
|
59
|
-
.action((
|
|
60
|
-
cp(
|
|
66
|
+
.action((paths, options) => {
|
|
67
|
+
cp(paths, options)
|
|
61
68
|
})
|
|
62
69
|
|
|
63
70
|
program.command('rm <path>')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vanillaes/esmtk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "ES Module Toolkit",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"esm",
|
|
@@ -37,6 +37,9 @@
|
|
|
37
37
|
"preversion": "npm run lint && npm run types",
|
|
38
38
|
"postversion": "git push --follow-tags"
|
|
39
39
|
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=22"
|
|
42
|
+
},
|
|
40
43
|
"dependencies": {
|
|
41
44
|
"commander": "^14.0.3",
|
|
42
45
|
"standard": "^17.1.2"
|
package/src/cp.d.ts
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Copy a single file asynchronously
|
|
3
|
+
*
|
|
3
4
|
* @param {string} source The source file
|
|
4
5
|
* @param {string} target The target file
|
|
5
6
|
* @param {boolean} force If the file already exists, overwrite it (default false)
|
|
6
7
|
*/
|
|
7
8
|
export function copyAsync(source: string, target: string, force?: boolean): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Copy a multiple files/globs asynchronously
|
|
11
|
+
*
|
|
12
|
+
* @param {string[]} sources The source files/globs
|
|
13
|
+
* @param {string} target The target file
|
|
14
|
+
* @param {boolean} force If the file already exists, overwrite it (default false)
|
|
15
|
+
*/
|
|
16
|
+
export function copyMultipleAsync(sources: string[], target: string, force?: boolean): Promise<void>;
|
|
8
17
|
/**
|
|
9
18
|
* Recursively copy a directory asynchronously
|
|
19
|
+
*
|
|
10
20
|
* @param {string} source The source directory
|
|
11
21
|
* @param {string} target The target directory
|
|
12
22
|
* @param {boolean} force If the file already exists, overwrite it (default false)
|
package/src/cp.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { fileExists } from './index.js'
|
|
2
|
-
import { basename,
|
|
2
|
+
import { basename, sep } from 'node:path'
|
|
3
3
|
import { cp, stat } from 'node:fs/promises'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Copy a single file asynchronously
|
|
7
|
+
*
|
|
7
8
|
* @param {string} source The source file
|
|
8
9
|
* @param {string} target The target file
|
|
9
10
|
* @param {boolean} force If the file already exists, overwrite it (default false)
|
|
@@ -11,45 +12,78 @@ import { cp, stat } from 'node:fs/promises'
|
|
|
11
12
|
export async function copyAsync (source, target, force = false) {
|
|
12
13
|
const sExists = await fileExists(source)
|
|
13
14
|
if (!sExists) {
|
|
14
|
-
console.error(`cp:
|
|
15
|
+
console.error(`cp: ${source} No such file or directory`)
|
|
15
16
|
process.exit(1)
|
|
16
17
|
}
|
|
17
18
|
const sStats = await stat(source)
|
|
18
19
|
if (sStats.isSymbolicLink()) {
|
|
19
|
-
console.error(`cp:
|
|
20
|
+
console.error(`cp: ${source} is a sybolic link (not copied)`)
|
|
20
21
|
process.exit(1)
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const tDirExists = await fileExists(tDir)
|
|
25
|
-
if (!tDirExists) {
|
|
26
|
-
console.error(`cp: target directory ${tDir} does not exist`)
|
|
23
|
+
if (!sStats.isFile()) {
|
|
24
|
+
console.error(`cp: ${source} is a directory (not copied)`)
|
|
27
25
|
process.exit(1)
|
|
28
26
|
}
|
|
29
27
|
|
|
30
28
|
const tExists = await fileExists(target)
|
|
31
|
-
|
|
29
|
+
const tIsDir = target.endsWith(sep)
|
|
30
|
+
// copy file-to-directory
|
|
31
|
+
if (tIsDir) {
|
|
32
|
+
if (!tExists) {
|
|
33
|
+
console.error(`cp: ${target} No such file or directory`)
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
32
36
|
const tStats = await stat(target)
|
|
33
37
|
if (tStats.isSymbolicLink()) {
|
|
34
|
-
console.error(`cp:
|
|
38
|
+
console.error(`cp: ${target} is a sybolic link (not copied)`)
|
|
35
39
|
process.exit(1)
|
|
36
40
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
41
|
+
|
|
42
|
+
// append source file name to target directory
|
|
43
|
+
const sourceFile = basename(source)
|
|
44
|
+
target = target.endsWith('/') ? target.slice(0, -1) : target
|
|
45
|
+
target = `${target}/${sourceFile}`
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
try {
|
|
45
|
-
await cp(source, target, { force })
|
|
49
|
+
await cp(source, target, { force: true })
|
|
46
50
|
} catch (err) {
|
|
47
51
|
console.error(`cp: error ${err.message}`)
|
|
48
52
|
}
|
|
49
53
|
}
|
|
50
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Copy a multiple files/globs asynchronously
|
|
57
|
+
*
|
|
58
|
+
* @param {string[]} sources The source files/globs
|
|
59
|
+
* @param {string} target The target file
|
|
60
|
+
* @param {boolean} force If the file already exists, overwrite it (default false)
|
|
61
|
+
*/
|
|
62
|
+
export async function copyMultipleAsync (sources, target, force = false) {
|
|
63
|
+
const tExists = await fileExists(target)
|
|
64
|
+
if (!tExists) {
|
|
65
|
+
console.error(`cp: ${target} No such file or directory`)
|
|
66
|
+
process.exit(1)
|
|
67
|
+
}
|
|
68
|
+
const tStats = await stat(target)
|
|
69
|
+
if (tStats.isSymbolicLink()) {
|
|
70
|
+
console.error(`cp: ${target} is a sybolic link (not copied)`)
|
|
71
|
+
process.exit(1)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
target = target.endsWith('/') ? target.slice(0, -1) : target
|
|
76
|
+
for (const source of sources) {
|
|
77
|
+
await cp(source, `${target}/${basename(source)}`, { force: true })
|
|
78
|
+
}
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error(`cp": error ${err.message}`)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
51
84
|
/**
|
|
52
85
|
* Recursively copy a directory asynchronously
|
|
86
|
+
*
|
|
53
87
|
* @param {string} source The source directory
|
|
54
88
|
* @param {string} target The target directory
|
|
55
89
|
* @param {boolean} force If the file already exists, overwrite it (default false)
|
|
@@ -57,32 +91,32 @@ export async function copyAsync (source, target, force = false) {
|
|
|
57
91
|
export async function copyRecursiveAsync (source, target, force = false) {
|
|
58
92
|
const sExists = await fileExists(source)
|
|
59
93
|
if (!sExists) {
|
|
60
|
-
console.error(`cp:
|
|
94
|
+
console.error(`cp: ${source} No such file or directory`)
|
|
61
95
|
process.exit(1)
|
|
62
96
|
}
|
|
63
97
|
const sStats = await stat(source)
|
|
64
98
|
if (sStats.isSymbolicLink()) {
|
|
65
|
-
console.error(`cp:
|
|
99
|
+
console.error(`cp: ${source} is a sybolic link (not copied)`)
|
|
66
100
|
process.exit(1)
|
|
67
101
|
}
|
|
68
102
|
|
|
69
103
|
const tExists = await fileExists(target)
|
|
70
104
|
if (!tExists) {
|
|
71
|
-
console.error(`cp:
|
|
105
|
+
console.error(`cp: ${target} No such file or directory`)
|
|
72
106
|
process.exit(1)
|
|
73
107
|
}
|
|
74
108
|
const tStats = await stat(target)
|
|
75
109
|
if (tStats.isSymbolicLink()) {
|
|
76
|
-
console.error(`cp:
|
|
110
|
+
console.error(`cp: ${target} is a sybolic link (not copied)`)
|
|
77
111
|
process.exit(1)
|
|
78
112
|
}
|
|
79
113
|
if (!tStats.isDirectory()) {
|
|
80
|
-
console.error(`cp: target ${target}
|
|
114
|
+
console.error(`cp: target ${target} Not a directory`)
|
|
81
115
|
process.exit(1)
|
|
82
116
|
}
|
|
83
117
|
|
|
84
118
|
try {
|
|
85
|
-
await cp(source, target, { force, recursive: true })
|
|
119
|
+
await cp(source, target, { force: true, recursive: true })
|
|
86
120
|
} catch (err) {
|
|
87
121
|
console.error(`cp": error ${err.message}`)
|
|
88
122
|
}
|
package/src/index.d.ts
CHANGED
package/src/index.js
CHANGED
package/src/util.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expand source file/glob into a list of paths
|
|
3
|
+
*
|
|
4
|
+
* @param {*} source the source file/glob
|
|
5
|
+
* @returns {Promise<string[]>} an array of paths
|
|
6
|
+
*/
|
|
7
|
+
export function expandSource(source: any): Promise<string[]>;
|
|
1
8
|
/**
|
|
2
9
|
* Check if a file/folder exists
|
|
3
10
|
* @param {string} path the path to the file/folder
|
|
@@ -10,6 +17,12 @@ export function fileExists(path: string): Promise<boolean>;
|
|
|
10
17
|
* @returns {Promise<boolean>} true if the package is installed, false otherwise
|
|
11
18
|
*/
|
|
12
19
|
export function installed(pkg: string): Promise<boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* Description
|
|
22
|
+
* @param {string} pattern glob pattern(s) to match
|
|
23
|
+
* @returns {Promise<string[]>} an array of paths
|
|
24
|
+
*/
|
|
25
|
+
export function match(pattern: string): Promise<string[]>;
|
|
13
26
|
/**
|
|
14
27
|
* Check to see if an application is installed globally
|
|
15
28
|
* @param {string} program the name of the application
|
package/src/util.js
CHANGED
|
@@ -1,9 +1,34 @@
|
|
|
1
1
|
import { exec } from 'child_process'
|
|
2
|
-
import { access, constants } from 'node:fs/promises'
|
|
2
|
+
import { access, constants, glob } from 'node:fs/promises'
|
|
3
3
|
import { promisify } from 'node:util'
|
|
4
4
|
|
|
5
5
|
const execAsync = promisify(exec)
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Expand source file/glob into a list of paths
|
|
9
|
+
*
|
|
10
|
+
* @param {*} source the source file/glob
|
|
11
|
+
* @returns {Promise<string[]>} an array of paths
|
|
12
|
+
*/
|
|
13
|
+
export async function expandSource (source) {
|
|
14
|
+
const isGlob = source.includes('*')
|
|
15
|
+
if (isGlob) {
|
|
16
|
+
const paths = await match(source)
|
|
17
|
+
if (paths.length === 0) {
|
|
18
|
+
console.error(`cp: ${paths} no matches found`)
|
|
19
|
+
process.exit(1)
|
|
20
|
+
}
|
|
21
|
+
return paths
|
|
22
|
+
} else {
|
|
23
|
+
const exists = await fileExists(source)
|
|
24
|
+
if (!exists) {
|
|
25
|
+
console.error(`cp: ${source} No such file or directory`)
|
|
26
|
+
process.exit(1)
|
|
27
|
+
}
|
|
28
|
+
return [source]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
7
32
|
/**
|
|
8
33
|
* Check if a file/folder exists
|
|
9
34
|
* @param {string} path the path to the file/folder
|
|
@@ -32,6 +57,15 @@ export async function installed (pkg) {
|
|
|
32
57
|
}
|
|
33
58
|
}
|
|
34
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Description
|
|
62
|
+
* @param {string} pattern glob pattern(s) to match
|
|
63
|
+
* @returns {Promise<string[]>} an array of paths
|
|
64
|
+
*/
|
|
65
|
+
export async function match (pattern) {
|
|
66
|
+
return await Array.fromAsync(glob(pattern))
|
|
67
|
+
}
|
|
68
|
+
|
|
35
69
|
/**
|
|
36
70
|
* Check to see if an application is installed globally
|
|
37
71
|
* @param {string} program the name of the application
|