#!/usr/bin/env node /** * Smart Reinstall for LibreChat * * Combines cached dependency installation with Turborepo-powered builds. * * Dependencies (npm ci): * Hashes package-lock.json and stores a marker in node_modules. * Skips npm ci entirely when the lockfile hasn't changed. * * Package builds (Turborepo): * Turbo hashes each package's source/config inputs (including the * lockfile), caches build outputs (dist/), and restores from cache * when inputs match. This script delegates entirely to turbo for builds. * * Usage: * npm run smart-reinstall # Smart cached mode * npm run smart-reinstall -- --force # Full clean reinstall, bust all caches * npm run smart-reinstall -- --skip-client # Skip frontend (Vite) build * npm run smart-reinstall -- --clean-cache # Wipe turbo build cache * npm run smart-reinstall -- --verbose # Turbo verbose output */ const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); require('./helpers'); const ROOT_DIR = path.resolve(__dirname, '..'); const DEPS_HASH_MARKER = path.join(ROOT_DIR, 'node_modules', '.librechat-deps-hash'); const flags = { force: process.argv.includes('--force'), cleanCache: process.argv.includes('--clean-cache'), skipClient: process.argv.includes('--skip-client'), verbose: process.argv.includes('--verbose'), }; const NODE_MODULES_DIRS = [ ROOT_DIR, path.join(ROOT_DIR, 'packages', 'data-provider'), path.join(ROOT_DIR, 'packages', 'data-schemas'), path.join(ROOT_DIR, 'packages', 'client'), path.join(ROOT_DIR, 'packages', 'api'), path.join(ROOT_DIR, 'client'), path.join(ROOT_DIR, 'api'), ]; function hashFile(filePath) { return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex').slice(0, 16); } function exec(cmd, opts = {}) { execSync(cmd, { cwd: ROOT_DIR, stdio: 'inherit', ...opts }); } function checkDeps() { const lockfile = path.join(ROOT_DIR, 'package-lock.json'); if (!fs.existsSync(lockfile)) { return { needsInstall: true, hash: 'missing' }; } const hash = hashFile(lockfile); if (!fs.existsSync(path.join(ROOT_DIR, 'node_modules'))) { return { needsInstall: true, hash }; } if (!fs.existsSync(DEPS_HASH_MARKER)) { return { needsInstall: true, hash }; } const stored = fs.readFileSync(DEPS_HASH_MARKER, 'utf-8').trim(); return { needsInstall: stored !== hash, hash }; } function installDeps(hash) { const { deleteNodeModules } = require('./helpers'); NODE_MODULES_DIRS.forEach(deleteNodeModules); console.purple('Cleaning npm cache...'); exec('npm cache clean --force'); console.purple('Installing dependencies (npm ci)...'); exec('npm ci'); fs.writeFileSync(DEPS_HASH_MARKER, hash, 'utf-8'); } function runTurboBuild() { const args = ['npx', 'turbo', 'run', 'build']; if (flags.skipClient) { args.push('--filter=!@librechat/frontend'); } if (flags.force) { args.push('--force'); } if (flags.verbose) { args.push('--verbosity=2'); } const cmd = args.join(' '); console.gray(` ${cmd}\n`); exec(cmd); } function cleanTurboCache() { console.purple('Clearing Turborepo cache...'); try { exec('npx turbo daemon stop', { stdio: 'pipe' }); } catch { // daemon may not be running } const localTurboCache = path.join(ROOT_DIR, '.turbo'); if (fs.existsSync(localTurboCache)) { fs.rmSync(localTurboCache, { recursive: true }); } try { exec('npx turbo clean', { stdio: 'pipe' }); console.green('Turbo cache cleared.'); } catch { console.gray('Could not clear global turbo cache (may not exist yet).'); } } (async () => { const startTime = Date.now(); console.green('\n Smart Reinstall — LibreChat'); console.green('─'.repeat(45)); if (flags.cleanCache) { cleanTurboCache(); if (!flags.force) { return; } } // Step 1: Dependencies console.purple('\n[1/2] Checking dependencies...'); if (flags.force) { console.orange(' Force mode — reinstalling all dependencies'); const lockfile = path.join(ROOT_DIR, 'package-lock.json'); const hash = fs.existsSync(lockfile) ? hashFile(lockfile) : 'none'; installDeps(hash); console.green(' Dependencies installed.'); } else { const { needsInstall, hash } = checkDeps(); if (needsInstall) { console.orange(' package-lock.json changed or node_modules missing'); installDeps(hash); console.green(' Dependencies installed.'); } else { console.green(' Dependencies up to date — skipping npm ci'); } } // Step 2: Build via Turborepo console.purple('\n[2/2] Building packages...'); runTurboBuild(); const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); console.log(''); console.green('─'.repeat(45)); console.green(` Done (${elapsed}s)`); console.green(' Start the app with: npm run backend'); console.green('─'.repeat(45)); })().catch((err) => { console.red(`\nError: ${err.message}`); if (flags.verbose) { console.red(err.stack); } console.gray(' Tip: run with --force to clean all caches and reinstall from scratch'); console.gray(' Tip: run with --verbose for detailed output'); process.exit(1); });