Files
smartrust/ts/classes.rustbinarylocator.ts

142 lines
4.4 KiB
TypeScript
Raw Normal View History

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 {
const resolved = import.meta.resolve(`${packageName}/${binaryName}`);
const packagePath = plugins.url.fileURLToPath(resolved);
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 {
return false;
}
}
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;
}
}