2 Commits
v1.2.0 ... main

10 changed files with 219 additions and 44 deletions

View File

@@ -1,5 +1,16 @@
# Changelog # Changelog
## 2026-02-09 - 1.3.0 - feat(toolchain)
add automatic bundled Rust toolchain fallback and integrate with CLI/CargoRunner
- Introduce ToolchainManager to download and install a minimal Rust toolchain to /tmp/tsrust_toolchain/ when system cargo is absent
- Add getEnvPrefix() and installation/verification logic to ToolchainManager; supports Linux and macOS (x64, arm64)
- Make CargoRunner accept an envPrefix and prepend it to cargo/rustup commands so bundled toolchain can be used transparently
- Update CLI to resolve toolchain at runtime (use system cargo if available; otherwise auto-install bundled toolchain) and pass envPrefix to CargoRunner for builds and clean
- Update exports to include mod_toolchain and add new ts/mod_toolchain module files
- Document the automatic toolchain behavior in readme.md and update usage description
- Bump dependencies: @push.rocks/smartcli ^4.0.20 and @types/node ^25.2.2
## 2026-02-09 - 1.2.0 - feat(cli) ## 2026-02-09 - 1.2.0 - feat(cli)
support default cross-compilation targets from npmextra.json support default cross-compilation targets from npmextra.json

View File

@@ -1,6 +1,6 @@
{ {
"name": "@git.zone/tsrust", "name": "@git.zone/tsrust",
"version": "1.2.0", "version": "1.3.0",
"private": false, "private": false,
"description": "A tool for compiling Rust projects, detecting Cargo workspaces, building with cargo, and placing binaries in a conventional dist_rust directory.", "description": "A tool for compiling Rust projects, detecting Cargo workspaces, building with cargo, and placing binaries in a conventional dist_rust directory.",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",
@@ -35,7 +35,7 @@
"dependencies": { "dependencies": {
"@push.rocks/early": "^4.0.4", "@push.rocks/early": "^4.0.4",
"@push.rocks/npmextra": "^5.3.3", "@push.rocks/npmextra": "^5.3.3",
"@push.rocks/smartcli": "^4.0.19", "@push.rocks/smartcli": "^4.0.20",
"@push.rocks/smartfile": "^13.1.2", "@push.rocks/smartfile": "^13.1.2",
"@push.rocks/smartpath": "^6.0.0", "@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smartshell": "^3.0.6", "@push.rocks/smartshell": "^3.0.6",
@@ -45,7 +45,7 @@
"@git.zone/tsbuild": "^4.1.2", "@git.zone/tsbuild": "^4.1.2",
"@git.zone/tsrun": "^2.0.1", "@git.zone/tsrun": "^2.0.1",
"@git.zone/tstest": "^3.1.4", "@git.zone/tstest": "^3.1.4",
"@types/node": "^22.15.0" "@types/node": "^25.2.2"
}, },
"files": [ "files": [
"ts/**/*", "ts/**/*",

52
pnpm-lock.yaml generated
View File

@@ -40,8 +40,8 @@ importers:
specifier: ^3.1.4 specifier: ^3.1.4
version: 3.1.8(socks@2.8.7)(typescript@5.9.3) version: 3.1.8(socks@2.8.7)(typescript@5.9.3)
'@types/node': '@types/node':
specifier: ^22.15.0 specifier: ^25.2.2
version: 22.19.10 version: 25.2.2
packages: packages:
@@ -1398,6 +1398,9 @@ packages:
'@types/node@22.19.10': '@types/node@22.19.10':
resolution: {integrity: sha512-tF5VOugLS/EuDlTBijk0MqABfP8UxgYazTLo3uIn3b4yJgg26QRbVYJYsDtHrjdDUIRfP70+VfhTTc+CE1yskw==} resolution: {integrity: sha512-tF5VOugLS/EuDlTBijk0MqABfP8UxgYazTLo3uIn3b4yJgg26QRbVYJYsDtHrjdDUIRfP70+VfhTTc+CE1yskw==}
'@types/node@25.2.2':
resolution: {integrity: sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==}
'@types/ping@0.4.4': '@types/ping@0.4.4':
resolution: {integrity: sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==} resolution: {integrity: sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==}
@@ -3275,6 +3278,9 @@ packages:
undici-types@6.21.0: undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
unified@11.0.5: unified@11.0.5:
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
@@ -5846,27 +5852,27 @@ snapshots:
'@types/bn.js@5.2.0': '@types/bn.js@5.2.0':
dependencies: dependencies:
'@types/node': 22.19.10 '@types/node': 25.2.2
'@types/body-parser@1.19.6': '@types/body-parser@1.19.6':
dependencies: dependencies:
'@types/connect': 3.4.38 '@types/connect': 3.4.38
'@types/node': 22.19.10 '@types/node': 25.2.2
'@types/buffer-json@2.0.3': {} '@types/buffer-json@2.0.3': {}
'@types/clean-css@4.2.11': '@types/clean-css@4.2.11':
dependencies: dependencies:
'@types/node': 22.19.10 '@types/node': 25.2.2
source-map: 0.6.1 source-map: 0.6.1
'@types/connect@3.4.38': '@types/connect@3.4.38':
dependencies: dependencies:
'@types/node': 22.19.10 '@types/node': 25.2.2
'@types/cors@2.8.19': '@types/cors@2.8.19':
dependencies: dependencies:
'@types/node': 22.19.10 '@types/node': 25.2.2
'@types/debug@4.1.12': '@types/debug@4.1.12':
dependencies: dependencies:
@@ -5874,7 +5880,7 @@ snapshots:
'@types/dns-packet@5.6.5': '@types/dns-packet@5.6.5':
dependencies: dependencies:
'@types/node': 22.19.10 '@types/node': 25.2.2
'@types/elliptic@6.4.18': '@types/elliptic@6.4.18':
dependencies: dependencies:
@@ -5882,7 +5888,7 @@ snapshots:
'@types/express-serve-static-core@5.1.1': '@types/express-serve-static-core@5.1.1':
dependencies: dependencies:
'@types/node': 22.19.10 '@types/node': 25.2.2
'@types/qs': 6.14.0 '@types/qs': 6.14.0
'@types/range-parser': 1.2.7 '@types/range-parser': 1.2.7
'@types/send': 1.2.1 '@types/send': 1.2.1
@@ -5896,7 +5902,7 @@ snapshots:
'@types/fs-extra@11.0.4': '@types/fs-extra@11.0.4':
dependencies: dependencies:
'@types/jsonfile': 6.1.4 '@types/jsonfile': 6.1.4
'@types/node': 22.19.10 '@types/node': 25.2.2
'@types/hast@3.0.4': '@types/hast@3.0.4':
dependencies: dependencies:
@@ -5918,7 +5924,7 @@ snapshots:
'@types/jsonfile@6.1.4': '@types/jsonfile@6.1.4':
dependencies: dependencies:
'@types/node': 22.19.10 '@types/node': 25.2.2
'@types/mdast@4.0.4': '@types/mdast@4.0.4':
dependencies: dependencies:
@@ -5932,16 +5938,20 @@ snapshots:
'@types/mute-stream@0.0.4': '@types/mute-stream@0.0.4':
dependencies: dependencies:
'@types/node': 22.19.10 '@types/node': 25.2.2
'@types/node-forge@1.3.14': '@types/node-forge@1.3.14':
dependencies: dependencies:
'@types/node': 22.19.10 '@types/node': 25.2.2
'@types/node@22.19.10': '@types/node@22.19.10':
dependencies: dependencies:
undici-types: 6.21.0 undici-types: 6.21.0
'@types/node@25.2.2':
dependencies:
undici-types: 7.16.0
'@types/ping@0.4.4': {} '@types/ping@0.4.4': {}
'@types/qs@6.14.0': {} '@types/qs@6.14.0': {}
@@ -5956,22 +5966,22 @@ snapshots:
'@types/send@1.2.1': '@types/send@1.2.1':
dependencies: dependencies:
'@types/node': 22.19.10 '@types/node': 25.2.2
'@types/serve-static@2.2.0': '@types/serve-static@2.2.0':
dependencies: dependencies:
'@types/http-errors': 2.0.5 '@types/http-errors': 2.0.5
'@types/node': 22.19.10 '@types/node': 25.2.2
'@types/symbol-tree@3.2.5': {} '@types/symbol-tree@3.2.5': {}
'@types/tar-stream@3.1.4': '@types/tar-stream@3.1.4':
dependencies: dependencies:
'@types/node': 22.19.10 '@types/node': 25.2.2
'@types/through2@2.0.41': '@types/through2@2.0.41':
dependencies: dependencies:
'@types/node': 22.19.10 '@types/node': 25.2.2
'@types/trusted-types@2.0.7': {} '@types/trusted-types@2.0.7': {}
@@ -5997,11 +6007,11 @@ snapshots:
'@types/ws@8.18.1': '@types/ws@8.18.1':
dependencies: dependencies:
'@types/node': 22.19.10 '@types/node': 25.2.2
'@types/yauzl@2.10.3': '@types/yauzl@2.10.3':
dependencies: dependencies:
'@types/node': 22.19.10 '@types/node': 25.2.2
optional: true optional: true
'@ungap/structured-clone@1.3.0': {} '@ungap/structured-clone@1.3.0': {}
@@ -6406,7 +6416,7 @@ snapshots:
engine.io@6.6.4: engine.io@6.6.4:
dependencies: dependencies:
'@types/cors': 2.8.19 '@types/cors': 2.8.19
'@types/node': 22.19.10 '@types/node': 25.2.2
accepts: 1.3.8 accepts: 1.3.8
base64id: 2.0.0 base64id: 2.0.0
cookie: 0.7.2 cookie: 0.7.2
@@ -8192,6 +8202,8 @@ snapshots:
undici-types@6.21.0: {} undici-types@6.21.0: {}
undici-types@7.16.0: {}
unified@11.0.5: unified@11.0.5:
dependencies: dependencies:
'@types/unist': 3.0.3 '@types/unist': 3.0.3

View File

@@ -20,7 +20,7 @@ Or as a project-level dev dependency:
pnpm install --save-dev @git.zone/tsrust pnpm install --save-dev @git.zone/tsrust
``` ```
> ⚡ **Prerequisite:** You need a working Rust toolchain. Install via [rustup.rs](https://rustup.rs/) if you haven't already. > ⚡ **No Rust required!** If `cargo` isn't found on your system, `tsrust` automatically downloads and installs a minimal Rust toolchain to `/tmp/tsrust_toolchain/`. This gives a zero-setup experience. If you already have Rust installed, `tsrust` uses your system toolchain.
## The Convention ## The Convention
@@ -44,7 +44,7 @@ tsrust
``` ```
This will: This will:
1. Verify that `cargo` is available 1. Detect the Rust toolchain (system `cargo`, or auto-install to `/tmp/tsrust_toolchain/`)
2. Locate your `rust/` directory (containing `Cargo.toml`) 2. Locate your `rust/` directory (containing `Cargo.toml`)
3. Parse the workspace to discover all `[[bin]]` targets 3. Parse the workspace to discover all `[[bin]]` targets
4. Run `cargo build --release` with full streaming output 4. Run `cargo build --release` with full streaming output
@@ -65,6 +65,18 @@ Copied rustproxy (13.4 MB) -> dist_rust/rustproxy
Done in 29.2s Done in 29.2s
``` ```
### Automatic Rust Toolchain
`tsrust` provides a zero-setup experience through automatic toolchain management:
1. **System toolchain detected** → uses it as-is (no download, no overhead)
2. **No system toolchain** → checks `/tmp/tsrust_toolchain/` for a previously installed bundled toolchain
3. **No bundled toolchain** → downloads `rustup-init` and installs a minimal Rust toolchain (~70-90 MB download) to `/tmp/tsrust_toolchain/`
The bundled toolchain is stored in `/tmp/`, so it's cleaned up on reboot. Subsequent runs reuse the existing installation. This is ideal for CI environments and quick starts where you don't want to manage a system-wide Rust install.
Supported platforms for automatic install: Linux (x64, arm64) and macOS (x64, arm64).
### 🐛 Debug Build ### 🐛 Debug Build
Build with the debug profile instead of release: Build with the debug profile instead of release:

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@git.zone/tsrust', name: '@git.zone/tsrust',
version: '1.2.0', version: '1.3.0',
description: 'A tool for compiling Rust projects, detecting Cargo workspaces, building with cargo, and placing binaries in a conventional dist_rust directory.' description: 'A tool for compiling Rust projects, detecting Cargo workspaces, building with cargo, and placing binaries in a conventional dist_rust directory.'
} }

View File

@@ -4,5 +4,6 @@ plugins.early.start('@git.zone/tsrust');
export * from './mod_fs/index.js'; export * from './mod_fs/index.js';
export * from './mod_cargo/index.js'; export * from './mod_cargo/index.js';
export * from './mod_cli/index.js'; export * from './mod_cli/index.js';
export * from './mod_toolchain/index.js';
plugins.early.stop(); plugins.early.stop();

View File

@@ -9,21 +9,23 @@ export interface ICargoRunResult {
export class CargoRunner { export class CargoRunner {
private shell: plugins.smartshell.Smartshell; private shell: plugins.smartshell.Smartshell;
private rustDir: string; private rustDir: string;
private envPrefix: string;
constructor(rustDir: string) { constructor(rustDir: string, envPrefix: string = '') {
this.rustDir = rustDir; this.rustDir = rustDir;
this.envPrefix = envPrefix;
this.shell = new plugins.smartshell.Smartshell({ this.shell = new plugins.smartshell.Smartshell({
executor: 'bash', executor: 'bash',
}); });
} }
public async checkCargoInstalled(): Promise<boolean> { public async checkCargoInstalled(): Promise<boolean> {
const result = await this.shell.execSilent('cargo --version'); const result = await this.shell.execSilent(`${this.envPrefix}cargo --version`);
return result.exitCode === 0; return result.exitCode === 0;
} }
public async getCargoVersion(): Promise<string> { public async getCargoVersion(): Promise<string> {
const result = await this.shell.execSilent('cargo --version'); const result = await this.shell.execSilent(`${this.envPrefix}cargo --version`);
return result.stdout.trim(); return result.stdout.trim();
} }
@@ -39,7 +41,7 @@ export class CargoRunner {
const profile = options.debug ? '' : ' --release'; const profile = options.debug ? '' : ' --release';
const targetFlag = options.target ? ` --target ${options.target}` : ''; const targetFlag = options.target ? ` --target ${options.target}` : '';
const command = `cd ${this.rustDir} && cargo build${profile}${targetFlag}`; const command = `${this.envPrefix}cd ${this.rustDir} && cargo build${profile}${targetFlag}`;
console.log(`Running: cargo build${profile}${targetFlag}`); console.log(`Running: cargo build${profile}${targetFlag}`);
const result = await this.shell.exec(command); const result = await this.shell.exec(command);
@@ -55,20 +57,20 @@ export class CargoRunner {
* Ensures a rustup target is installed. If not present, installs it via `rustup target add`. * Ensures a rustup target is installed. If not present, installs it via `rustup target add`.
*/ */
public async ensureTarget(triple: string): Promise<void> { public async ensureTarget(triple: string): Promise<void> {
const listResult = await this.shell.execSilent('rustup target list --installed'); const listResult = await this.shell.execSilent(`${this.envPrefix}rustup target list --installed`);
const installedTargets = listResult.stdout.split('\n').map((l) => l.trim()); const installedTargets = listResult.stdout.split('\n').map((l) => l.trim());
if (installedTargets.includes(triple)) { if (installedTargets.includes(triple)) {
return; return;
} }
console.log(`Installing rustup target: ${triple}`); console.log(`Installing rustup target: ${triple}`);
const addResult = await this.shell.exec(`rustup target add ${triple}`); const addResult = await this.shell.exec(`${this.envPrefix}rustup target add ${triple}`);
if (addResult.exitCode !== 0) { if (addResult.exitCode !== 0) {
throw new Error(`Failed to install rustup target ${triple}`); throw new Error(`Failed to install rustup target ${triple}`);
} }
} }
public async clean(): Promise<ICargoRunResult> { public async clean(): Promise<ICargoRunResult> {
const command = `cd ${this.rustDir} && cargo clean`; const command = `${this.envPrefix}cd ${this.rustDir} && cargo clean`;
const result = await this.shell.exec(command); const result = await this.shell.exec(command);
return { return {

View File

@@ -3,6 +3,7 @@ import * as plugins from '../plugins.js';
import { CargoConfig } from '../mod_cargo/index.js'; import { CargoConfig } from '../mod_cargo/index.js';
import { CargoRunner } from '../mod_cargo/index.js'; import { CargoRunner } from '../mod_cargo/index.js';
import { FsHelpers } from '../mod_fs/index.js'; import { FsHelpers } from '../mod_fs/index.js';
import { ToolchainManager } from '../mod_toolchain/index.js';
/** Maps friendly target names to Rust target triples */ /** Maps friendly target names to Rust target triples */
const targetAliasMap: Record<string, string> = { const targetAliasMap: Record<string, string> = {
@@ -62,18 +63,33 @@ export class TsRustCli {
this.registerCleanCommand(); this.registerCleanCommand();
} }
/**
* Resolves the Rust toolchain to use. Tries system cargo first,
* then falls back to a bundled toolchain at /tmp/tsrust_toolchain/.
* Returns the envPrefix string to prepend to shell commands.
*/
private async resolveToolchain(): Promise<string> {
// 1. Try system cargo
const systemRunner = new CargoRunner(this.cwd);
if (await systemRunner.checkCargoInstalled()) {
return ''; // system toolchain, no prefix needed
}
// 2. Fall back to bundled toolchain
console.log('System cargo not found. Checking for bundled toolchain...');
const toolchain = new ToolchainManager();
await toolchain.ensureInstalled();
return toolchain.getEnvPrefix();
}
private registerStandardCommand(): void { private registerStandardCommand(): void {
this.cli.standardCommand().subscribe(async (argvArg) => { this.cli.standardCommand().subscribe(async (argvArg) => {
const startTime = Date.now(); const startTime = Date.now();
// Check cargo is installed // Resolve toolchain (system or bundled fallback)
const runner = new CargoRunner(this.cwd); // temporary, just for version check const envPrefix = await this.resolveToolchain();
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 runner = new CargoRunner(this.cwd, envPrefix);
const cargoVersion = await runner.getCargoVersion(); const cargoVersion = await runner.getCargoVersion();
console.log(`Using ${cargoVersion}`); console.log(`Using ${cargoVersion}`);
@@ -125,7 +141,7 @@ export class TsRustCli {
for (const { triple, friendly } of resolvedTargets) { for (const { triple, friendly } of resolvedTargets) {
console.log(`\n--- Building for ${friendly} (${triple}) ---`); console.log(`\n--- Building for ${friendly} (${triple}) ---`);
const cargoRunner = new CargoRunner(rustDir); const cargoRunner = new CargoRunner(rustDir, envPrefix);
const buildResult = await cargoRunner.build({ debug: isDebug, clean: shouldClean, target: triple }); const buildResult = await cargoRunner.build({ debug: isDebug, clean: shouldClean, target: triple });
if (!buildResult.success) { if (!buildResult.success) {
@@ -159,8 +175,8 @@ export class TsRustCli {
} }
} }
} else { } else {
// Native build (unchanged behavior) // Native build
const cargoRunner = new CargoRunner(rustDir); const cargoRunner = new CargoRunner(rustDir, envPrefix);
const buildResult = await cargoRunner.build({ debug: isDebug, clean: shouldClean }); const buildResult = await cargoRunner.build({ debug: isDebug, clean: shouldClean });
if (!buildResult.success) { if (!buildResult.success) {
@@ -199,8 +215,9 @@ export class TsRustCli {
// Clean cargo build // Clean cargo build
const rustDir = await this.detectRustDir(); const rustDir = await this.detectRustDir();
if (rustDir) { if (rustDir) {
const envPrefix = await this.resolveToolchain();
console.log('Running cargo clean...'); console.log('Running cargo clean...');
const runner = new CargoRunner(rustDir); const runner = new CargoRunner(rustDir, envPrefix);
await runner.clean(); await runner.clean();
console.log('Cargo clean complete.'); console.log('Cargo clean complete.');
} }

View File

@@ -0,0 +1,119 @@
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as plugins from '../plugins.js';
export class ToolchainManager {
public static TOOLCHAIN_DIR = '/tmp/tsrust_toolchain';
public static RUSTUP_HOME = '/tmp/tsrust_toolchain/rustup';
public static CARGO_HOME = '/tmp/tsrust_toolchain/cargo';
public static BIN_DIR = '/tmp/tsrust_toolchain/cargo/bin';
private shell: plugins.smartshell.Smartshell;
constructor() {
this.shell = new plugins.smartshell.Smartshell({
executor: 'bash',
});
}
/**
* Returns the Rust host triple for the current platform.
*/
public getHostTriple(): string {
const platform = os.platform();
const arch = os.arch();
if (platform === 'linux' && arch === 'x64') return 'x86_64-unknown-linux-gnu';
if (platform === 'linux' && arch === 'arm64') return 'aarch64-unknown-linux-gnu';
if (platform === 'darwin' && arch === 'x64') return 'x86_64-apple-darwin';
if (platform === 'darwin' && arch === 'arm64') return 'aarch64-apple-darwin';
throw new Error(
`Unsupported platform: ${platform}/${arch}. Please install Rust manually via https://rustup.rs/`
);
}
/**
* Checks if the bundled toolchain is already installed at TOOLCHAIN_DIR.
*/
public async isInstalled(): Promise<boolean> {
const cargoPath = path.join(ToolchainManager.BIN_DIR, 'cargo');
try {
const stat = await fs.promises.stat(cargoPath);
return stat.isFile();
} catch {
return false;
}
}
/**
* Downloads rustup-init and installs a minimal Rust toolchain to TOOLCHAIN_DIR.
*/
public async install(): Promise<void> {
const triple = this.getHostTriple();
const rustupInitUrl = `https://static.rust-lang.org/rustup/dist/${triple}/rustup-init`;
const rustupInitPath = path.join(ToolchainManager.TOOLCHAIN_DIR, 'rustup-init');
// Ensure toolchain directory exists
await fs.promises.mkdir(ToolchainManager.TOOLCHAIN_DIR, { recursive: true });
console.log(`Downloading rustup-init for ${triple}...`);
const downloadResult = await this.shell.exec(
`curl -sSf -o ${rustupInitPath} ${rustupInitUrl} && chmod +x ${rustupInitPath}`
);
if (downloadResult.exitCode !== 0) {
throw new Error(`Failed to download rustup-init: ${downloadResult.stdout}`);
}
console.log('Installing minimal Rust toolchain to /tmp/tsrust_toolchain/...');
const installCmd = [
`RUSTUP_HOME="${ToolchainManager.RUSTUP_HOME}"`,
`CARGO_HOME="${ToolchainManager.CARGO_HOME}"`,
rustupInitPath,
'-y',
'--default-toolchain stable',
'--profile minimal',
'--no-modify-path',
].join(' ');
const installResult = await this.shell.exec(installCmd);
if (installResult.exitCode !== 0) {
throw new Error(`Failed to install Rust toolchain: ${installResult.stdout}`);
}
// Verify installation
const verifyResult = await this.shell.execSilent(
`${this.getEnvPrefix()}cargo --version`
);
if (verifyResult.exitCode !== 0) {
throw new Error('Rust toolchain installation verification failed.');
}
console.log(`Installed: ${verifyResult.stdout.trim()}`);
// Clean up rustup-init binary
await fs.promises.unlink(rustupInitPath).catch(() => {});
}
/**
* Ensures the bundled toolchain is installed. Downloads if not present.
*/
public async ensureInstalled(): Promise<void> {
if (await this.isInstalled()) {
console.log('Using bundled Rust toolchain from /tmp/tsrust_toolchain/');
return;
}
await this.install();
}
/**
* Returns a shell prefix string that sets RUSTUP_HOME, CARGO_HOME, and PATH
* to point at the bundled toolchain. Prepend this to any shell command.
*/
public getEnvPrefix(): string {
return (
`export RUSTUP_HOME="${ToolchainManager.RUSTUP_HOME}" CARGO_HOME="${ToolchainManager.CARGO_HOME}" && ` +
`export PATH="${ToolchainManager.BIN_DIR}:$PATH" && `
);
}
}

View File

@@ -0,0 +1 @@
export * from './classes.toolchainmanager.js';