@llamaindex/semtools
Version:
Semantic search and document parsing tools for the command line (Rust-backed, npm-distributed)
181 lines (154 loc) • 5.71 kB
JavaScript
/*
Prefer zero-build: download prebuilt binaries from GitHub Releases.
Fallback: build Rust binaries on npm install and place them in dist/bin.
*/
const { spawnSync } = require('node:child_process');
const { existsSync, mkdirSync, copyFileSync, chmodSync, createWriteStream } = require('node:fs');
const { join } = require('node:path');
const https = require('node:https');
const os = require('node:os');
const tar = require('tar');
const unzipper = require('unzipper');
function fail(message) {
console.error(`@llamaindex/semtools install error: ${message}`);
process.exit(1);
}
function hasCargo() {
const result = spawnSync('cargo', ['--version'], { stdio: 'ignore' });
return result.status === 0;
}
function buildBinaries() {
const args = ['build', '--release', '--locked'];
const result = spawnSync('cargo', args, { stdio: 'inherit' });
if (result.status !== 0) {
fail('`cargo build --release` failed. Please ensure Rust and Cargo are installed. See https://www.rust-lang.org/tools/install');
}
}
function placeBinaries() {
const isWindows = process.platform === 'win32';
const exe = isWindows ? '.exe' : '';
const builtParse = join(__dirname, '..', 'target', 'release', `parse${exe}`);
const builtSearch = join(__dirname, '..', 'target', 'release', `search${exe}`);
const builtWorkspace = join(__dirname, '..', 'target', 'release', `workspace${exe}`);
const destDir = join(__dirname, '..', 'dist', 'bin');
mkdirSync(destDir, { recursive: true });
let anyPlaced = false;
if (existsSync(builtParse)) {
const dest = join(destDir, `parse${exe}`);
copyFileSync(builtParse, dest);
try { chmodSync(dest, 0o755); } catch {}
anyPlaced = true;
}
if (existsSync(builtSearch)) {
const dest = join(destDir, `search${exe}`);
copyFileSync(builtSearch, dest);
try { chmodSync(dest, 0o755); } catch {}
anyPlaced = true;
}
if (existsSync(builtWorkspace)) {
const dest = join(destDir, `workspace${exe}`);
copyFileSync(builtWorkspace, dest);
try { chmodSync(dest, 0o755); } catch {}
anyPlaced = true;
}
if (!anyPlaced) {
fail('No binaries were produced. Expected to find target/release/parse, target/release/search, and/or target/release/workspace.');
}
}
function detectMusl() {
try {
// Node >= 12 has process.report
if (typeof process.report?.getReport === 'function') {
const glibc = process.report.getReport().header?.glibcVersionRuntime;
return !glibc; // if no glibc, assume musl (e.g., Alpine)
}
} catch {}
return false;
}
function detectTargetTriple() {
const platform = os.platform();
const arch = os.arch();
if (platform === 'linux') {
const musl = detectMusl();
if (musl) {
// TODO: support musl
return null;
}
if (arch === 'x64') return 'x86_64-unknown-linux-gnu';
if (arch === 'arm64') return 'aarch64-unknown-linux-gnu';
} else if (platform === 'darwin') {
if (arch === 'x64') return 'x86_64-apple-darwin';
if (arch === 'arm64') return 'aarch64-apple-darwin';
} else if (platform === 'win32') {
if (arch === 'x64') return 'x86_64-pc-windows-msvc';
if (arch === 'arm64') return 'aarch64-pc-windows-msvc';
}
return null;
}
function download(url, destPath) {
return new Promise((resolve, reject) => {
const file = createWriteStream(destPath);
https.get(url, (res) => {
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
// Follow redirect
return resolve(download(res.headers.location, destPath));
}
if (res.statusCode !== 200) {
return reject(new Error(`HTTP ${res.statusCode}`));
}
res.pipe(file);
file.on('finish', () => file.close(resolve));
}).on('error', (err) => {
reject(err);
});
});
}
async function tryDownloadPrebuilt() {
const version = require('../package.json').version;
const target = detectTargetTriple();
if (!target) return false;
const isWindows = process.platform === 'win32';
const assetExt = isWindows ? 'zip' : 'tar.gz';
const assetName = `semtools-${target}.${assetExt}`;
const downloadUrl = `https://github.com/run-llama/semtools/releases/download/v${version}/${assetName}`;
const tmpDir = join(__dirname, '..', 'dist', 'tmp');
const distBin = join(__dirname, '..', 'dist', 'bin');
mkdirSync(tmpDir, { recursive: true });
mkdirSync(distBin, { recursive: true });
const archivePath = join(tmpDir, assetName);
try {
console.log(`@llamaindex/semtools: downloading prebuilt ${assetName} ...`);
await download(downloadUrl, archivePath);
} catch (e) {
console.warn(`@llamaindex/semtools: prebuilt download failed: ${e.message}`);
return false;
}
try {
if (isWindows) {
await unzipper.Open.file(archivePath).then(d => d.extract({ path: distBin, concurrency: 5 }));
} else {
await tar.x({ file: archivePath, cwd: distBin });
}
// Ensure executables
try { chmodSync(join(distBin, 'parse'), 0o755); } catch {}
try { chmodSync(join(distBin, 'search'), 0o755); } catch {}
try { chmodSync(join(distBin, 'workspace'), 0o755); } catch {}
return true;
} catch (e) {
console.warn(`@llamaindex/semtools: failed to extract prebuilt: ${e.message}`);
return false;
}
}
async function main() {
// Try prebuilt first
const ok = await tryDownloadPrebuilt();
if (ok) return;
// Fallback to local build
if (!hasCargo()) {
fail('No prebuilt binary available and Cargo was not found. Install Rust or use a supported platform/arch.');
}
buildBinaries();
placeBinaries();
}
main().catch(e => fail(e.message));