mirror of
https://github.com/stablyai/orca.git
synced 2026-11-06 12:07:58 +00:00
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { accessSync, constants, statSync } from 'node:fs'
|
||||
import { accessSync, constants, existsSync, readdirSync, statSync } from 'node:fs'
|
||||
import { homedir } from 'node:os'
|
||||
import { delimiter, dirname, join } from 'node:path'
|
||||
import { discoverNvmVersionBinDirectories } from './nvm-version-directory-discovery'
|
||||
|
||||
type ResolveCommandOptions = {
|
||||
pathEnv?: string | null
|
||||
@@ -28,6 +27,29 @@ function splitPath(pathEnv: string | null | undefined): string[] {
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
function parseVersionSegment(raw: string): number[] {
|
||||
return raw
|
||||
.replace(/^v/i, '')
|
||||
.split('.')
|
||||
.map((segment) => Number.parseInt(segment, 10))
|
||||
.map((segment) => (Number.isFinite(segment) ? segment : 0))
|
||||
}
|
||||
|
||||
function compareVersionDesc(left: string, right: string): number {
|
||||
const leftParts = parseVersionSegment(left)
|
||||
const rightParts = parseVersionSegment(right)
|
||||
const length = Math.max(leftParts.length, rightParts.length)
|
||||
|
||||
for (let index = 0; index < length; index += 1) {
|
||||
const delta = (rightParts[index] ?? 0) - (leftParts[index] ?? 0)
|
||||
if (delta !== 0) {
|
||||
return delta
|
||||
}
|
||||
}
|
||||
|
||||
return right.localeCompare(left)
|
||||
}
|
||||
|
||||
function findFirstExecutable(
|
||||
platform: NodeJS.Platform,
|
||||
directories: string[],
|
||||
@@ -101,6 +123,19 @@ function getBaseVersionManagerDirectories(platform: NodeJS.Platform, homePath: s
|
||||
return directories
|
||||
}
|
||||
|
||||
function getNvmVersionDirectories(homePath: string): string[] {
|
||||
const nvmVersionsDir = join(homePath, '.nvm', 'versions', 'node')
|
||||
if (!existsSync(nvmVersionsDir)) {
|
||||
return []
|
||||
}
|
||||
|
||||
return readdirSync(nvmVersionsDir, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => entry.name)
|
||||
.sort(compareVersionDesc)
|
||||
.map((entry) => join(nvmVersionsDir, entry, 'bin'))
|
||||
}
|
||||
|
||||
function getVersionManagerDirectories(
|
||||
platform: NodeJS.Platform,
|
||||
homePath: string,
|
||||
@@ -112,7 +147,7 @@ function getVersionManagerDirectories(
|
||||
// command resolution probes the newest installed Node versions explicitly.
|
||||
const firstNvmMatch = findFirstExecutable(
|
||||
platform,
|
||||
discoverNvmVersionBinDirectories(homePath),
|
||||
getNvmVersionDirectories(homePath),
|
||||
executableNames
|
||||
)
|
||||
if (firstNvmMatch) {
|
||||
@@ -154,7 +189,7 @@ export function resolveCliCommands(
|
||||
// Why: agent detection probes many CLIs at once; compute expensive install
|
||||
// directories, especially nvm versions, once per detection pass.
|
||||
const installDirectories = [
|
||||
...discoverNvmVersionBinDirectories(homePath),
|
||||
...getNvmVersionDirectories(homePath),
|
||||
...getBaseVersionManagerDirectories(platform, homePath)
|
||||
]
|
||||
const resolved = new Map<string, string>()
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
import { join } from 'node:path'
|
||||
import type * as NodeFs from 'node:fs'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const opendirSyncMock = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('node:fs', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof NodeFs>()),
|
||||
opendirSync: opendirSyncMock
|
||||
}))
|
||||
|
||||
import { discoverNvmVersionBinDirectories } from './nvm-version-directory-discovery'
|
||||
|
||||
type FakeEntry = { name: string; directory?: boolean }
|
||||
|
||||
function useEntries(entries: FakeEntry[]): { closeSync: ReturnType<typeof vi.fn> } {
|
||||
let index = 0
|
||||
const closeSync = vi.fn()
|
||||
opendirSyncMock.mockReturnValue({
|
||||
closeSync,
|
||||
readSync: vi.fn(() => {
|
||||
const entry = entries[index]
|
||||
index += 1
|
||||
return entry
|
||||
? {
|
||||
name: entry.name,
|
||||
isDirectory: () => entry.directory !== false
|
||||
}
|
||||
: null
|
||||
})
|
||||
})
|
||||
return { closeSync }
|
||||
}
|
||||
|
||||
describe('nvm version directory discovery', () => {
|
||||
beforeEach(() => {
|
||||
opendirSyncMock.mockReset()
|
||||
})
|
||||
|
||||
it('preserves newest-version ordering for an ordinary listing', () => {
|
||||
useEntries([
|
||||
{ name: 'v20.18.0' },
|
||||
{ name: 'README', directory: false },
|
||||
{ name: 'v24.2.0' },
|
||||
{ name: 'v22.14.0' }
|
||||
])
|
||||
|
||||
expect(discoverNvmVersionBinDirectories('/home/alice')).toEqual([
|
||||
join('/home/alice', '.nvm', 'versions', 'node', 'v24.2.0', 'bin'),
|
||||
join('/home/alice', '.nvm', 'versions', 'node', 'v22.14.0', 'bin'),
|
||||
join('/home/alice', '.nvm', 'versions', 'node', 'v20.18.0', 'bin')
|
||||
])
|
||||
})
|
||||
|
||||
it('accepts the exact entry and retained-name limits', () => {
|
||||
const { closeSync } = useEntries([{ name: 'v20' }, { name: 'v22' }])
|
||||
|
||||
expect(
|
||||
discoverNvmVersionBinDirectories('/home/alice', {
|
||||
maxEntries: 2,
|
||||
maxRetainedNameBytes: 6
|
||||
})
|
||||
).toHaveLength(2)
|
||||
expect(closeSync).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('fails closed and closes the stream on the first entry over the count limit', () => {
|
||||
const { closeSync } = useEntries([
|
||||
{ name: 'v20' },
|
||||
{ name: 'v22' },
|
||||
{ name: 'v24' },
|
||||
{ name: 'v26' }
|
||||
])
|
||||
|
||||
expect(
|
||||
discoverNvmVersionBinDirectories('/home/alice', {
|
||||
maxEntries: 2
|
||||
})
|
||||
).toEqual([])
|
||||
expect(closeSync).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('fails closed when retained directory names exceed the byte limit', () => {
|
||||
const { closeSync } = useEntries([{ name: 'v20' }, { name: 'v22' }])
|
||||
|
||||
expect(
|
||||
discoverNvmVersionBinDirectories('/home/alice', {
|
||||
maxRetainedNameBytes: 5
|
||||
})
|
||||
).toEqual([])
|
||||
expect(closeSync).toHaveBeenCalledOnce()
|
||||
})
|
||||
})
|
||||
@@ -1,100 +0,0 @@
|
||||
import { opendirSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
export const NVM_VERSION_DISCOVERY_MAX_ENTRIES = 4_096
|
||||
export const NVM_VERSION_DISCOVERY_MAX_RETAINED_NAME_BYTES = 1024 * 1024
|
||||
|
||||
export type NvmVersionDiscoveryLimits = {
|
||||
maxEntries: number
|
||||
maxRetainedNameBytes: number
|
||||
}
|
||||
|
||||
function parseVersionSegment(raw: string): number[] {
|
||||
return raw
|
||||
.replace(/^v/i, '')
|
||||
.split('.')
|
||||
.map((segment) => Number.parseInt(segment, 10))
|
||||
.map((segment) => (Number.isFinite(segment) ? segment : 0))
|
||||
}
|
||||
|
||||
function compareVersionDesc(left: string, right: string): number {
|
||||
const leftParts = parseVersionSegment(left)
|
||||
const rightParts = parseVersionSegment(right)
|
||||
const length = Math.max(leftParts.length, rightParts.length)
|
||||
|
||||
for (let index = 0; index < length; index += 1) {
|
||||
const delta = (rightParts[index] ?? 0) - (leftParts[index] ?? 0)
|
||||
if (delta !== 0) {
|
||||
return delta
|
||||
}
|
||||
}
|
||||
|
||||
return right.localeCompare(left)
|
||||
}
|
||||
|
||||
function resolveLimit(requested: number | undefined, maximum: number, name: string): number {
|
||||
if (requested === undefined) {
|
||||
return maximum
|
||||
}
|
||||
if (!Number.isSafeInteger(requested) || requested < 0) {
|
||||
throw new RangeError(`${name} must be a non-negative safe integer`)
|
||||
}
|
||||
return Math.min(requested, maximum)
|
||||
}
|
||||
|
||||
function closeNvmVersionsDirectory(directory: ReturnType<typeof opendirSync>): void {
|
||||
try {
|
||||
directory.closeSync()
|
||||
} catch {
|
||||
// The OS may already have closed a fully consumed directory stream.
|
||||
}
|
||||
}
|
||||
|
||||
export function discoverNvmVersionBinDirectories(
|
||||
homePath: string,
|
||||
requestedLimits: Partial<NvmVersionDiscoveryLimits> = {}
|
||||
): string[] {
|
||||
const maxEntries = resolveLimit(
|
||||
requestedLimits.maxEntries,
|
||||
NVM_VERSION_DISCOVERY_MAX_ENTRIES,
|
||||
'maxEntries'
|
||||
)
|
||||
const maxRetainedNameBytes = resolveLimit(
|
||||
requestedLimits.maxRetainedNameBytes,
|
||||
NVM_VERSION_DISCOVERY_MAX_RETAINED_NAME_BYTES,
|
||||
'maxRetainedNameBytes'
|
||||
)
|
||||
const versionsDir = join(homePath, '.nvm', 'versions', 'node')
|
||||
let directory: ReturnType<typeof opendirSync>
|
||||
try {
|
||||
directory = opendirSync(versionsDir, { bufferSize: 32 })
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
|
||||
const versionNames: string[] = []
|
||||
let scannedEntries = 0
|
||||
let retainedNameBytes = 0
|
||||
try {
|
||||
for (let entry = directory.readSync(); entry !== null; entry = directory.readSync()) {
|
||||
scannedEntries += 1
|
||||
if (scannedEntries > maxEntries) {
|
||||
return []
|
||||
}
|
||||
if (!entry.isDirectory()) {
|
||||
continue
|
||||
}
|
||||
retainedNameBytes += Buffer.byteLength(entry.name, 'utf8')
|
||||
if (retainedNameBytes > maxRetainedNameBytes) {
|
||||
return []
|
||||
}
|
||||
versionNames.push(entry.name)
|
||||
}
|
||||
} finally {
|
||||
closeNvmVersionsDirectory(directory)
|
||||
}
|
||||
|
||||
return versionNames
|
||||
.sort(compareVersionDesc)
|
||||
.map((versionName) => join(versionsDir, versionName, 'bin'))
|
||||
}
|
||||
Reference in New Issue
Block a user