2026-02-10 09:10:18 +00:00
import * as plugins from './plugins.js' ;
import type { IBinaryLocatorOptions , IRustBridgeLogger } from './interfaces/index.js' ;
const defaultLogger : IRustBridgeLogger = {
log() { } ,
} ;
/ * *
* Locates a Rust binary using a priority - ordered search strategy :
* 1 . Explicit binaryPath override
* 2 . Environment variable
* 3 . Platform - specific npm package
* 4 . Local development build paths
* 5 . System PATH
* /
export class RustBinaryLocator {
private options : IBinaryLocatorOptions ;
private logger : IRustBridgeLogger ;
private cachedPath : string | null = null ;
constructor ( options : IBinaryLocatorOptions , logger? : IRustBridgeLogger ) {
this . options = options ;
this . logger = logger || defaultLogger ;
}
/ * *
* Find the binary path .
* Returns null if no binary is available .
* /
public async findBinary ( ) : Promise < string | null > {
if ( this . cachedPath !== null ) {
return this . cachedPath ;
}
const path = await this . searchBinary ( ) ;
this . cachedPath = path ;
return path ;
}
/ * *
* Clear the cached binary path .
* /
public clearCache ( ) : void {
this . cachedPath = null ;
}
private async searchBinary ( ) : Promise < string | null > {
const { binaryName } = this . options ;
// 1. Explicit binary path override
if ( this . options . binaryPath ) {
if ( await this . isExecutable ( this . options . binaryPath ) ) {
this . logger . log ( 'info' , ` Binary found via explicit path: ${ this . options . binaryPath } ` ) ;
return this . options . binaryPath ;
}
this . logger . log ( 'warn' , ` Explicit binary path not executable: ${ this . options . binaryPath } ` ) ;
}
// 2. Environment variable override
if ( this . options . envVarName ) {
const envPath = process . env [ this . options . envVarName ] ;
if ( envPath ) {
if ( await this . isExecutable ( envPath ) ) {
this . logger . log ( 'info' , ` Binary found via ${ this . options . envVarName } : ${ envPath } ` ) ;
return envPath ;
}
this . logger . log ( 'warn' , ` ${ this . options . envVarName } set but not executable: ${ envPath } ` ) ;
}
}
// 3. Platform-specific npm package
if ( this . options . platformPackagePrefix ) {
const platformBinary = await this . findPlatformPackageBinary ( ) ;
if ( platformBinary ) {
this . logger . log ( 'info' , ` Binary found in platform package: ${ platformBinary } ` ) ;
return platformBinary ;
}
}
// 4. Local development build paths
const localPaths = this . options . localPaths || [
plugins . path . resolve ( process . cwd ( ) , ` rust/target/release/ ${ binaryName } ` ) ,
plugins . path . resolve ( process . cwd ( ) , ` rust/target/debug/ ${ binaryName } ` ) ,
] ;
for ( const localPath of localPaths ) {
if ( await this . isExecutable ( localPath ) ) {
this . logger . log ( 'info' , ` Binary found at local path: ${ localPath } ` ) ;
return localPath ;
}
}
// 5. System PATH
if ( this . options . searchSystemPath !== false ) {
const systemPath = await this . findInPath ( binaryName ) ;
if ( systemPath ) {
this . logger . log ( 'info' , ` Binary found in system PATH: ${ systemPath } ` ) ;
return systemPath ;
}
}
this . logger . log ( 'error' , ` No binary ' ${ binaryName } ' found. Provide an explicit path, set an env var, install the platform package, or build from source. ` ) ;
return null ;
}
private async findPlatformPackageBinary ( ) : Promise < string | null > {
const { binaryName , platformPackagePrefix } = this . options ;
const platform = process . platform ;
const arch = process . arch ;
const packageName = ` ${ platformPackagePrefix } - ${ platform } - ${ arch } ` ;
try {
2026-02-10 22:21:17 +00:00
const resolved = import . meta . resolve ( ` ${ packageName } / ${ binaryName } ` ) ;
const packagePath = plugins . url . fileURLToPath ( resolved ) ;
2026-02-10 09:10:18 +00:00
if ( await this . isExecutable ( packagePath ) ) {
return packagePath ;
}
} catch {
// Package not installed - expected for development
}
return null ;
}
private async isExecutable ( filePath : string ) : Promise < boolean > {
try {
await plugins . fs . promises . access ( filePath , plugins . fs . constants . X_OK ) ;
return true ;
} catch {
2026-02-12 21:25:11 +00:00
// File may exist but lack execute bit (common after npm/pnpm install).
// Try to make it executable.
try {
await plugins . fs . promises . access ( filePath , plugins . fs . constants . F_OK ) ;
await plugins . fs . promises . chmod ( filePath , 0 o755 ) ;
this . logger . log ( 'info' , ` Auto-fixed missing execute permission on: ${ filePath } ` ) ;
return true ;
} catch {
return false ;
}
2026-02-10 09:10:18 +00:00
}
}
private async findInPath ( binaryName : string ) : Promise < string | null > {
const pathDirs = ( process . env . PATH || '' ) . split ( plugins . path . delimiter ) ;
for ( const dir of pathDirs ) {
const fullPath = plugins . path . join ( dir , binaryName ) ;
if ( await this . isExecutable ( fullPath ) ) {
return fullPath ;
}
}
return null ;
}
}