mirror of
https://github.com/stablyai/orca.git
synced 2026-11-14 01:47:24 +00:00
106 lines
3.7 KiB
TypeScript
106 lines
3.7 KiB
TypeScript
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
import type * as NodeFs from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { delimiter, join } from 'node:path'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { resolveCliCommandAsync } from './node-cli-command-async-resolution'
|
|
import { resolveCliCommand } from './node-cli-command-resolution'
|
|
|
|
const syncFs = vi.hoisted(() => ({ forbidden: false, calls: new Array<string>() }))
|
|
|
|
// Throws from every sync read a CLI lookup could make while forbidden, proving the async lookup makes none.
|
|
vi.mock('node:fs', async (importOriginal) => {
|
|
const actual = await importOriginal<typeof NodeFs>()
|
|
const guard =
|
|
<A extends unknown[], R>(name: string, read: (...args: A) => R) =>
|
|
(...args: A): R => {
|
|
if (syncFs.forbidden) {
|
|
syncFs.calls.push(name)
|
|
throw new Error(`sync fs call: ${name}`)
|
|
}
|
|
return read(...args)
|
|
}
|
|
return {
|
|
...actual,
|
|
accessSync: guard('accessSync', actual.accessSync),
|
|
existsSync: guard('existsSync', actual.existsSync),
|
|
lstatSync: guard('lstatSync', actual.lstatSync),
|
|
readdirSync: guard('readdirSync', actual.readdirSync),
|
|
readFileSync: guard('readFileSync', actual.readFileSync),
|
|
realpathSync: guard('realpathSync', actual.realpathSync),
|
|
statSync: guard('statSync', actual.statSync)
|
|
}
|
|
})
|
|
|
|
let root: string
|
|
|
|
beforeEach(() => {
|
|
root = mkdtempSync(join(tmpdir(), 'orca-cli-resolve-async-'))
|
|
})
|
|
|
|
afterEach(() => {
|
|
syncFs.forbidden = false
|
|
syncFs.calls = []
|
|
rmSync(root, { recursive: true, force: true })
|
|
})
|
|
|
|
function stage(...relative: string[]): void {
|
|
for (const path of relative) {
|
|
mkdirSync(join(root, path, '..'), { recursive: true })
|
|
writeFileSync(join(root, path), '')
|
|
}
|
|
}
|
|
|
|
// Windows' spawn recheck uses the async resolver; it must pick what panes' sync resolver picks.
|
|
// platform: 'win32' skips the X_OK check, so these run on any host.
|
|
describe('resolveCliCommandAsync agrees with resolveCliCommand on Windows', () => {
|
|
const options = (...dirs: string[]) => ({
|
|
pathEnv: dirs.map((dir) => join(root, dir)).join(delimiter),
|
|
homePath: join(root, 'home'),
|
|
platform: 'win32' as const
|
|
})
|
|
|
|
it.each([
|
|
{
|
|
name: 'the .cmd shim beats .exe and .bat in one dir',
|
|
files: ['a/codex.exe', 'a/codex.bat', 'a/codex.cmd'],
|
|
expected: 'a/codex.cmd'
|
|
},
|
|
{ name: '.exe beats .bat', files: ['a/codex.bat', 'a/codex.exe'], expected: 'a/codex.exe' },
|
|
{
|
|
name: 'an earlier PATH dir beats a better extension later',
|
|
files: ['a/codex.bat', 'b/codex.cmd'],
|
|
expected: 'a/codex.bat'
|
|
},
|
|
{
|
|
name: 'a PATH miss falls back to the npm global dir',
|
|
files: ['home/AppData/Roaming/npm/codex.cmd'],
|
|
expected: 'home/AppData/Roaming/npm/codex.cmd'
|
|
},
|
|
{
|
|
name: 'a PATH copy beats an install-dir copy',
|
|
files: ['home/AppData/Roaming/npm/codex.cmd', 'b/codex.exe'],
|
|
expected: 'b/codex.exe'
|
|
},
|
|
{
|
|
name: "nvm's default alias outranks a newer nvm install",
|
|
files: [
|
|
'home/.nvm/alias/default',
|
|
'home/.nvm/versions/node/v22.0.0/bin/codex.cmd',
|
|
'home/.nvm/versions/node/v20.1.0/bin/codex.cmd'
|
|
],
|
|
alias: '20',
|
|
expected: 'home/.nvm/versions/node/v20.1.0/bin/codex.cmd'
|
|
},
|
|
{ name: 'a miss everywhere returns the bare name', files: [], expected: null }
|
|
])('$name', async ({ files, alias, expected }) => {
|
|
stage(...files)
|
|
if (alias) {
|
|
writeFileSync(join(root, 'home/.nvm/alias/default'), alias)
|
|
}
|
|
const sync = resolveCliCommand('codex', options('a', 'b'))
|
|
expect(sync).toBe(expected === null ? 'codex' : join(root, expected))
|
|
expect(await resolveCliCommandAsync('codex', options('a', 'b'))).toBe(sync)
|
|
})
|
|
})
|