import * as path from 'path'; import * as plugins from '../plugins.js'; import { CargoConfig } from '../mod_cargo/index.js'; import { CargoRunner } from '../mod_cargo/index.js'; import { FsHelpers } from '../mod_fs/index.js'; export class TsRustCli { private cli: plugins.smartcli.Smartcli; private cwd: string; constructor(cwd: string = process.cwd()) { this.cwd = cwd; this.cli = new plugins.smartcli.Smartcli(); this.registerCommands(); } private registerCommands(): void { this.registerStandardCommand(); this.registerCleanCommand(); } private registerStandardCommand(): void { this.cli.standardCommand().subscribe(async (argvArg) => { const startTime = Date.now(); // Check cargo is installed const runner = new CargoRunner(this.cwd); // temporary, just for version check if (!(await runner.checkCargoInstalled())) { console.error('Error: cargo is not installed or not in PATH.'); console.error('Install Rust via https://rustup.rs/'); process.exit(1); } const cargoVersion = await runner.getCargoVersion(); console.log(`Using ${cargoVersion}`); // Detect rust directory const rustDir = await this.detectRustDir(); if (!rustDir) { console.error('Error: No rust/ or ts_rust/ directory found with a Cargo.toml.'); process.exit(1); } console.log(`Found Rust project at: ${path.relative(this.cwd, rustDir) || '.'}`); // Parse Cargo.toml const cargoConfig = new CargoConfig(rustDir); const workspaceInfo = await cargoConfig.parse(); if (workspaceInfo.isWorkspace) { console.log('Detected Cargo workspace'); } if (workspaceInfo.binTargets.length === 0) { console.error('Error: No binary targets found in Cargo.toml.'); process.exit(1); } console.log(`Binary targets: ${workspaceInfo.binTargets.join(', ')}`); // Build const isDebug = !!(argvArg as any).debug; const shouldClean = !!(argvArg as any).clean; const cargoRunner = new CargoRunner(rustDir); const buildResult = await cargoRunner.build({ debug: isDebug, clean: shouldClean }); if (!buildResult.success) { console.error(`Build failed with exit code ${buildResult.exitCode}`); process.exit(1); } // Copy binaries to dist_rust/ const profile = isDebug ? 'debug' : 'release'; const targetDir = path.join(rustDir, 'target', profile); const distDir = path.join(this.cwd, 'dist_rust'); await FsHelpers.ensureEmptyDir(distDir); for (const binName of workspaceInfo.binTargets) { const srcBinary = path.join(targetDir, binName); const destBinary = path.join(distDir, binName); if (!(await FsHelpers.fileExists(srcBinary))) { console.warn(`Warning: Expected binary not found: ${srcBinary}`); continue; } await FsHelpers.copyFile(srcBinary, destBinary); await FsHelpers.makeExecutable(destBinary); const size = await FsHelpers.getFileSize(destBinary); console.log(`Copied ${binName} (${FsHelpers.formatFileSize(size)}) -> dist_rust/${binName}`); } const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); console.log(`Done in ${elapsed}s`); }); } private registerCleanCommand(): void { this.cli.addCommand('clean').subscribe(async (_argvArg) => { // Clean cargo build const rustDir = await this.detectRustDir(); if (rustDir) { console.log('Running cargo clean...'); const runner = new CargoRunner(rustDir); await runner.clean(); console.log('Cargo clean complete.'); } // Remove dist_rust/ const distDir = path.join(this.cwd, 'dist_rust'); if (await FsHelpers.directoryExists(distDir)) { await FsHelpers.removeDirectory(distDir); console.log('Removed dist_rust/'); } console.log('Clean complete.'); }); } private async detectRustDir(): Promise { // Check rust/ first const rustDir = path.join(this.cwd, 'rust'); if (await FsHelpers.fileExists(path.join(rustDir, 'Cargo.toml'))) { return rustDir; } // Fallback to ts_rust/ const tsRustDir = path.join(this.cwd, 'ts_rust'); if (await FsHelpers.fileExists(path.join(tsRustDir, 'Cargo.toml'))) { return tsRustDir; } return null; } public run(): void { this.cli.startParse(); } } export const runCli = async (): Promise => { const cli = new TsRustCli(); cli.run(); };