theslopmachine 0.3.5 → 0.3.7
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/RELEASE.md +3 -3
- package/assets/slopmachine/beads-init.js +10 -1
- package/package.json +1 -1
- package/src/cli.js +2 -1
- package/src/constants.js +1 -1
- package/src/init.js +17 -3
- package/src/install.js +197 -20
- package/src/utils.js +14 -5
package/RELEASE.md
CHANGED
|
@@ -41,13 +41,13 @@ npm pack
|
|
|
41
41
|
This should produce a tarball such as:
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
theslopmachine-0.3.
|
|
44
|
+
theslopmachine-0.3.7.tgz
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
## Inspect package contents
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
tar -tzf theslopmachine-0.3.
|
|
50
|
+
tar -tzf theslopmachine-0.3.7.tgz
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
Check that the tarball includes:
|
|
@@ -78,4 +78,4 @@ npm publish --dry-run
|
|
|
78
78
|
|
|
79
79
|
- bump `package.json` version before each release
|
|
80
80
|
- keep the CLI command as `slopmachine`
|
|
81
|
-
- keep the npm package name as `
|
|
81
|
+
- keep the npm package name as `theslopmachine`
|
|
@@ -17,6 +17,14 @@ function die(message) {
|
|
|
17
17
|
process.exit(1)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
function quoteShellArg(value) {
|
|
21
|
+
if (/^[A-Za-z0-9_./:-]+$/.test(value)) {
|
|
22
|
+
return value
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return `'${value.replaceAll(`'`, `'\\''`)}'`
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
function run(command, args, options = {}) {
|
|
21
29
|
return new Promise((resolve, reject) => {
|
|
22
30
|
const child = spawn(command, args, {
|
|
@@ -86,9 +94,10 @@ async function chooseNoninteractiveInitModeFlag(isGitRepo) {
|
|
|
86
94
|
async function configureGitMergeDriver() {
|
|
87
95
|
const attributesFile = path.join(target, '.git', 'info', 'attributes')
|
|
88
96
|
const attributesLine = '.beads/*.jsonl merge=beads'
|
|
97
|
+
const mergeDriverCommand = `${quoteShellArg(bdCommand)} merge %A %O %A %B`
|
|
89
98
|
|
|
90
99
|
log('Configuring local git merge driver for Beads')
|
|
91
|
-
let result = await run('git', ['-C', target, 'config', 'merge.beads.driver',
|
|
100
|
+
let result = await run('git', ['-C', target, 'config', 'merge.beads.driver', mergeDriverCommand])
|
|
92
101
|
if (result.code !== 0) die(result.stderr || 'Failed configuring git merge driver')
|
|
93
102
|
|
|
94
103
|
await fs.mkdir(path.dirname(attributesFile), { recursive: true })
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { PACKAGE_VERSION } from './constants.js'
|
|
1
2
|
import { runInit } from './init.js'
|
|
2
3
|
import { runInstall } from './install.js'
|
|
3
4
|
|
|
4
5
|
function printHelp() {
|
|
5
|
-
console.log(`theslopmachine
|
|
6
|
+
console.log(`theslopmachine ${PACKAGE_VERSION}
|
|
6
7
|
|
|
7
8
|
Commands:
|
|
8
9
|
setup Configure theslopmachine in the local user environment
|
package/src/constants.js
CHANGED
|
@@ -2,7 +2,7 @@ import os from 'node:os'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
4
|
|
|
5
|
-
export const PACKAGE_VERSION = '0.3.
|
|
5
|
+
export const PACKAGE_VERSION = '0.3.7'
|
|
6
6
|
export const OPCODE_VERSION = '1.3.5'
|
|
7
7
|
export const BEADS_VERSION = '0.52.0'
|
|
8
8
|
export const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
package/src/init.js
CHANGED
|
@@ -21,6 +21,16 @@ const GITIGNORE_ENTRIES = [
|
|
|
21
21
|
'antigravity-logs/',
|
|
22
22
|
]
|
|
23
23
|
|
|
24
|
+
function getUnixBeadsCommandCandidates(paths) {
|
|
25
|
+
return [
|
|
26
|
+
path.join(paths.home, '.local', 'bin', 'bd'),
|
|
27
|
+
path.join(paths.home, '.linuxbrew', 'bin', 'bd'),
|
|
28
|
+
'/opt/homebrew/bin/bd',
|
|
29
|
+
'/usr/local/bin/bd',
|
|
30
|
+
'/home/linuxbrew/.linuxbrew/bin/bd',
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
function getWindowsBeadsCommandCandidates() {
|
|
25
35
|
if (process.platform !== 'win32') {
|
|
26
36
|
return []
|
|
@@ -32,8 +42,12 @@ function getWindowsBeadsCommandCandidates() {
|
|
|
32
42
|
].filter(Boolean)
|
|
33
43
|
}
|
|
34
44
|
|
|
35
|
-
async function resolveBeadsCommand() {
|
|
36
|
-
|
|
45
|
+
async function resolveBeadsCommand(paths) {
|
|
46
|
+
const candidates = process.platform === 'win32'
|
|
47
|
+
? getWindowsBeadsCommandCandidates()
|
|
48
|
+
: getUnixBeadsCommandCandidates(paths)
|
|
49
|
+
|
|
50
|
+
return resolveCommand('bd', { additionalCandidates: candidates, preferCandidates: true })
|
|
37
51
|
}
|
|
38
52
|
|
|
39
53
|
function parseInitArgs(args) {
|
|
@@ -119,7 +133,7 @@ async function runBeadsBootstrap(paths, targetPath, beadsScript) {
|
|
|
119
133
|
throw new Error('Unable to locate the current Node.js executable for Beads bootstrap.')
|
|
120
134
|
}
|
|
121
135
|
|
|
122
|
-
const beadsCommand = await resolveBeadsCommand()
|
|
136
|
+
const beadsCommand = await resolveBeadsCommand(paths)
|
|
123
137
|
|
|
124
138
|
log('Running Beads setup')
|
|
125
139
|
const result = await runCommand(nodeExecutable, [beadsScript, targetPath], {
|
package/src/install.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs/promises'
|
|
2
|
+
import os from 'node:os'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
|
|
4
5
|
import {
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
copyDirIfMissing,
|
|
17
18
|
copyFileIfMissing,
|
|
18
19
|
ensureDir,
|
|
20
|
+
findBashExecutable,
|
|
19
21
|
getGlobalNpmPackageVersion,
|
|
20
22
|
log,
|
|
21
23
|
makeExecutableIfShellScript,
|
|
@@ -34,6 +36,26 @@ function assetsRoot() {
|
|
|
34
36
|
return path.join(PACKAGE_ROOT, 'assets')
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
function getHomeDir() {
|
|
40
|
+
return buildPaths().home
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getUnixBeadsBinDirs() {
|
|
44
|
+
const home = getHomeDir()
|
|
45
|
+
return [
|
|
46
|
+
path.join(home, '.local', 'bin'),
|
|
47
|
+
path.join(home, 'go', 'bin'),
|
|
48
|
+
path.join(home, '.linuxbrew', 'bin'),
|
|
49
|
+
'/opt/homebrew/bin',
|
|
50
|
+
'/usr/local/bin',
|
|
51
|
+
'/home/linuxbrew/.linuxbrew/bin',
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getLinuxBeadsBinDir() {
|
|
56
|
+
return path.join(getHomeDir(), '.local', 'bin')
|
|
57
|
+
}
|
|
58
|
+
|
|
37
59
|
function getWindowsBeadsBinDirs() {
|
|
38
60
|
if (process.platform !== 'win32') {
|
|
39
61
|
return []
|
|
@@ -47,12 +69,16 @@ function getWindowsBeadsBinDirs() {
|
|
|
47
69
|
return [...new Set(dirs)]
|
|
48
70
|
}
|
|
49
71
|
|
|
50
|
-
function
|
|
51
|
-
|
|
72
|
+
function getBeadsCommandCandidates() {
|
|
73
|
+
if (process.platform === 'win32') {
|
|
74
|
+
return getWindowsBeadsBinDirs().map((dir) => path.join(dir, 'bd.exe'))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return getUnixBeadsBinDirs().map((dir) => path.join(dir, 'bd'))
|
|
52
78
|
}
|
|
53
79
|
|
|
54
80
|
async function resolveBeadsCommand() {
|
|
55
|
-
return resolveCommand('bd', { additionalCandidates:
|
|
81
|
+
return resolveCommand('bd', { additionalCandidates: getBeadsCommandCandidates(), preferCandidates: true })
|
|
56
82
|
}
|
|
57
83
|
|
|
58
84
|
async function getBeadsVersion() {
|
|
@@ -69,6 +95,25 @@ async function getBeadsVersion() {
|
|
|
69
95
|
return { command, version: (result.stdout || result.stderr).trim() }
|
|
70
96
|
}
|
|
71
97
|
|
|
98
|
+
async function probeBeadsRuntime(command) {
|
|
99
|
+
const probeDir = await fs.mkdtemp(path.join(os.tmpdir(), 'slopmachine-beads-probe-'))
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const result = await runCommand(command, ['init', '--quiet', '--stealth', '--skip-hooks'], {
|
|
103
|
+
cwd: probeDir,
|
|
104
|
+
env: { ...process.env, CI: '1' },
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const output = `${result.stdout}${result.stderr}`.trim()
|
|
108
|
+
return {
|
|
109
|
+
ok: result.code === 0,
|
|
110
|
+
output,
|
|
111
|
+
}
|
|
112
|
+
} finally {
|
|
113
|
+
await fs.rm(probeDir, { recursive: true, force: true })
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
72
117
|
function quotePowerShell(value) {
|
|
73
118
|
return `'${String(value).replaceAll(`'`, `''`)}'`
|
|
74
119
|
}
|
|
@@ -116,6 +161,61 @@ async function ensureWindowsBeadsPath() {
|
|
|
116
161
|
}
|
|
117
162
|
}
|
|
118
163
|
|
|
164
|
+
async function ensureUnixBeadsPath() {
|
|
165
|
+
if (process.platform === 'win32') {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (const dir of getUnixBeadsBinDirs()) {
|
|
170
|
+
if (!(await pathExists(path.join(dir, 'bd')))) {
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
prependToProcessPath(dir)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function installLinuxBeadsPrerequisites() {
|
|
179
|
+
const managers = await detectPackageManagers()
|
|
180
|
+
|
|
181
|
+
if (managers.apt) {
|
|
182
|
+
log('Installing Linux Beads build dependencies via apt')
|
|
183
|
+
const updateResult = await runCommand('sudo', ['apt-get', 'update'], { stdio: 'inherit' })
|
|
184
|
+
if (updateResult.code !== 0) {
|
|
185
|
+
return updateResult
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return runCommand('sudo', [
|
|
189
|
+
'apt-get',
|
|
190
|
+
'install',
|
|
191
|
+
'-y',
|
|
192
|
+
'build-essential',
|
|
193
|
+
'pkg-config',
|
|
194
|
+
'libicu-dev',
|
|
195
|
+
'libzstd-dev',
|
|
196
|
+
'golang-go',
|
|
197
|
+
], { stdio: 'inherit' })
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (managers.dnf) {
|
|
201
|
+
log('Installing Linux Beads build dependencies via dnf')
|
|
202
|
+
return runCommand('sudo', [
|
|
203
|
+
'dnf',
|
|
204
|
+
'install',
|
|
205
|
+
'-y',
|
|
206
|
+
'gcc',
|
|
207
|
+
'gcc-c++',
|
|
208
|
+
'make',
|
|
209
|
+
'pkgconf-pkg-config',
|
|
210
|
+
'libicu-devel',
|
|
211
|
+
'libzstd-devel',
|
|
212
|
+
'golang',
|
|
213
|
+
], { stdio: 'inherit' })
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return { code: 0, stdout: '', stderr: '' }
|
|
217
|
+
}
|
|
218
|
+
|
|
119
219
|
async function installBeadsOnWindows() {
|
|
120
220
|
const shell = await resolveCommand('pwsh') || await resolveCommand('powershell')
|
|
121
221
|
if (!shell) {
|
|
@@ -135,6 +235,64 @@ async function installBeadsOnWindows() {
|
|
|
135
235
|
return result
|
|
136
236
|
}
|
|
137
237
|
|
|
238
|
+
async function installBeadsOnUnix() {
|
|
239
|
+
if (process.platform === 'linux') {
|
|
240
|
+
const depsResult = await installLinuxBeadsPrerequisites()
|
|
241
|
+
if (depsResult.code !== 0) {
|
|
242
|
+
return depsResult
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!(await commandExists('go'))) {
|
|
246
|
+
return { code: 1, stdout: '', stderr: 'Go is required to build a CGO-enabled Beads binary on Linux' }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const binDir = getLinuxBeadsBinDir()
|
|
250
|
+
await ensureDir(binDir)
|
|
251
|
+
|
|
252
|
+
log(`Installing Beads ${BEADS_VERSION} on Linux with CGO_ENABLED=1`)
|
|
253
|
+
const result = await runCommand('go', ['install', `github.com/steveyegge/beads/cmd/bd@v${BEADS_VERSION}`], {
|
|
254
|
+
stdio: 'inherit',
|
|
255
|
+
env: {
|
|
256
|
+
...process.env,
|
|
257
|
+
CGO_ENABLED: '1',
|
|
258
|
+
GOBIN: binDir,
|
|
259
|
+
},
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
await ensureUnixBeadsPath()
|
|
263
|
+
return result
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const managers = await detectPackageManagers()
|
|
267
|
+
if (managers.brew) {
|
|
268
|
+
log('Installing Beads via Homebrew')
|
|
269
|
+
const result = await runCommand('brew', ['install', 'beads'], { stdio: 'inherit' })
|
|
270
|
+
await ensureUnixBeadsPath()
|
|
271
|
+
return result
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const bashExecutable = await findBashExecutable()
|
|
275
|
+
if (!bashExecutable) {
|
|
276
|
+
return { code: 1, stdout: '', stderr: 'bash is required to run the Beads installer on this platform' }
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (await commandExists('curl')) {
|
|
280
|
+
log('Installing Beads with the upstream Unix install script')
|
|
281
|
+
const result = await runCommand(bashExecutable, ['-lc', 'curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash'], { stdio: 'inherit' })
|
|
282
|
+
await ensureUnixBeadsPath()
|
|
283
|
+
return result
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (await commandExists('wget')) {
|
|
287
|
+
log('Installing Beads with the upstream Unix install script')
|
|
288
|
+
const result = await runCommand(bashExecutable, ['-lc', 'wget -qO- https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash'], { stdio: 'inherit' })
|
|
289
|
+
await ensureUnixBeadsPath()
|
|
290
|
+
return result
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return { code: 1, stdout: '', stderr: 'curl or wget is required to install Beads on this platform' }
|
|
294
|
+
}
|
|
295
|
+
|
|
138
296
|
async function getCommandVersion(command, args = ['--version']) {
|
|
139
297
|
const exists = await commandExists(command)
|
|
140
298
|
if (!exists) return null
|
|
@@ -184,8 +342,7 @@ async function tryInstallCoreDependency(name) {
|
|
|
184
342
|
return installBeadsOnWindows()
|
|
185
343
|
}
|
|
186
344
|
|
|
187
|
-
|
|
188
|
-
return runCommand('npm', ['install', '-g', `@beads/bd@${BEADS_VERSION}`], { stdio: 'inherit' })
|
|
345
|
+
return installBeadsOnUnix()
|
|
189
346
|
}
|
|
190
347
|
|
|
191
348
|
const managers = await detectPackageManagers()
|
|
@@ -231,6 +388,8 @@ async function tryInstallCoreDependency(name) {
|
|
|
231
388
|
}
|
|
232
389
|
|
|
233
390
|
async function ensureDependency({ name, checkCommand, requiredVersion, installable }) {
|
|
391
|
+
let needsReinstall = false
|
|
392
|
+
|
|
234
393
|
if (name === 'python3') {
|
|
235
394
|
const python = await getPythonVersion()
|
|
236
395
|
if (python) {
|
|
@@ -263,24 +422,36 @@ async function ensureDependency({ name, checkCommand, requiredVersion, installab
|
|
|
263
422
|
if (checkCommand === 'bd') {
|
|
264
423
|
const beads = await getBeadsVersion()
|
|
265
424
|
if (beads) {
|
|
266
|
-
|
|
267
|
-
if (
|
|
268
|
-
warn(`${name}
|
|
425
|
+
const probe = await probeBeadsRuntime(beads.command)
|
|
426
|
+
if (!probe.ok) {
|
|
427
|
+
warn(`${name} was found at ${beads.command}, but it failed a runtime probe.${probe.output ? ` ${probe.output}` : ''}`)
|
|
428
|
+
needsReinstall = true
|
|
429
|
+
} else {
|
|
430
|
+
log(`${name} detected via ${beads.command}: ${beads.version}`)
|
|
431
|
+
if (requiredVersion && !beads.version.includes(requiredVersion)) {
|
|
432
|
+
warn(`${name} version differs from tested reference ${requiredVersion}`)
|
|
433
|
+
}
|
|
434
|
+
return
|
|
269
435
|
}
|
|
270
|
-
return
|
|
271
436
|
}
|
|
272
437
|
}
|
|
273
438
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
439
|
+
if (!needsReinstall) {
|
|
440
|
+
const version = await getCommandVersion(checkCommand)
|
|
441
|
+
if (version) {
|
|
442
|
+
log(`${name} detected: ${version}`)
|
|
443
|
+
if (requiredVersion && !version.includes(requiredVersion)) {
|
|
444
|
+
warn(`${name} version differs from tested reference ${requiredVersion}`)
|
|
445
|
+
}
|
|
446
|
+
return
|
|
279
447
|
}
|
|
280
|
-
return
|
|
281
448
|
}
|
|
282
449
|
|
|
283
|
-
|
|
450
|
+
if (needsReinstall) {
|
|
451
|
+
warn(`${name} needs to be reinstalled with a working runtime.`)
|
|
452
|
+
} else {
|
|
453
|
+
warn(`${name} is not installed or not available in PATH`)
|
|
454
|
+
}
|
|
284
455
|
|
|
285
456
|
if (!installable) {
|
|
286
457
|
return
|
|
@@ -288,7 +459,7 @@ async function ensureDependency({ name, checkCommand, requiredVersion, installab
|
|
|
288
459
|
|
|
289
460
|
const shouldInstall = await promptYesNo(`Attempt to install ${name} automatically?`, true)
|
|
290
461
|
if (!shouldInstall) {
|
|
291
|
-
|
|
462
|
+
warn(`Skipping ${name} installation. Please install it manually before using theslopmachine.`)
|
|
292
463
|
return
|
|
293
464
|
}
|
|
294
465
|
|
|
@@ -315,10 +486,16 @@ async function ensureDependency({ name, checkCommand, requiredVersion, installab
|
|
|
315
486
|
if (checkCommand === 'bd') {
|
|
316
487
|
const beads = await getBeadsVersion()
|
|
317
488
|
if (beads) {
|
|
318
|
-
|
|
319
|
-
if (
|
|
320
|
-
|
|
489
|
+
const probe = await probeBeadsRuntime(beads.command)
|
|
490
|
+
if (probe.ok) {
|
|
491
|
+
log(`Installed ${name} via ${beads.command}: ${beads.version}`)
|
|
492
|
+
if (process.platform === 'win32' && !(await commandExists('bd'))) {
|
|
493
|
+
warn('Beads was installed, but `bd` is not visible in this shell PATH yet. The installer added the common Windows Beads directories to PATH for future shells.')
|
|
494
|
+
}
|
|
495
|
+
return
|
|
321
496
|
}
|
|
497
|
+
|
|
498
|
+
warn(`${name} was installed at ${beads.command}, but it still failed a runtime probe.${probe.output ? ` ${probe.output}` : ''}`)
|
|
322
499
|
return
|
|
323
500
|
}
|
|
324
501
|
|
package/src/utils.js
CHANGED
|
@@ -155,12 +155,25 @@ async function getGlobalNpmPrefix() {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
export async function resolveCommand(command, options = {}) {
|
|
158
|
+
const candidates = []
|
|
159
|
+
|
|
160
|
+
if (Array.isArray(options.additionalCandidates)) {
|
|
161
|
+
candidates.push(...options.additionalCandidates)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (options.preferCandidates) {
|
|
165
|
+
for (const candidate of candidates) {
|
|
166
|
+
if (await pathExists(candidate)) {
|
|
167
|
+
return candidate
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
158
172
|
if (await commandExists(command)) {
|
|
159
173
|
return command
|
|
160
174
|
}
|
|
161
175
|
|
|
162
176
|
const prefix = await getGlobalNpmPrefix()
|
|
163
|
-
const candidates = []
|
|
164
177
|
|
|
165
178
|
if (prefix) {
|
|
166
179
|
candidates.push(...(process.platform === 'win32'
|
|
@@ -175,10 +188,6 @@ export async function resolveCommand(command, options = {}) {
|
|
|
175
188
|
]))
|
|
176
189
|
}
|
|
177
190
|
|
|
178
|
-
if (Array.isArray(options.additionalCandidates)) {
|
|
179
|
-
candidates.push(...options.additionalCandidates)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
191
|
for (const candidate of candidates) {
|
|
183
192
|
if (await pathExists(candidate)) {
|
|
184
193
|
return candidate
|