mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-04-18 05:39:05 +02:00
🏎️ feat: Smart Reinstall with Turborepo Caching for Better DX (#11785)
* chore: Add Turborepo support and smart reinstall script - Updated .gitignore to include Turborepo cache directory. - Added Turbo as a dependency in package.json and package-lock.json. - Introduced turbo.json configuration for build tasks. - Created smart-reinstall.js script to optimize dependency installation and package builds using Turborepo caching. * fix: Address PR review feedback for smart reinstall - Fix Windows compatibility in hasTurbo() by checking for .cmd/.ps1 shims - Remove Unix-specific shell syntax (> /dev/null 2>&1) from cache clearing - Split try/catch blocks so daemon stop failure doesn't block cache clear - Add actionable tips in error output pointing to --force and --verbose
This commit is contained in:
parent
ccbf9dc093
commit
e50f59062f
5 changed files with 377 additions and 0 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -30,6 +30,9 @@ coverage
|
||||||
config/translations/stores/*
|
config/translations/stores/*
|
||||||
client/src/localization/languages/*_missing_keys.json
|
client/src/localization/languages/*_missing_keys.json
|
||||||
|
|
||||||
|
# Turborepo
|
||||||
|
.turbo
|
||||||
|
|
||||||
# Compiled Dirs (http://nodejs.org/api/addons.html)
|
# Compiled Dirs (http://nodejs.org/api/addons.html)
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
|
|
|
||||||
235
config/smart-reinstall.js
Normal file
235
config/smart-reinstall.js
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
#!/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, caches build
|
||||||
|
* outputs (dist/), and restores from cache when inputs match.
|
||||||
|
* Turbo v2 uses a global cache (~/.cache/turbo) that survives
|
||||||
|
* npm ci and is shared across worktrees.
|
||||||
|
*
|
||||||
|
* 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');
|
||||||
|
|
||||||
|
// Adds console.green, console.purple, etc.
|
||||||
|
require('./helpers');
|
||||||
|
|
||||||
|
// ─── Configuration ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
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'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Workspace directories whose node_modules should be cleaned during reinstall
|
||||||
|
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'),
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Dependency Installation ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Turbo Build ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fallback for when turbo is not installed (e.g., first run before npm ci).
|
||||||
|
* Runs the same sequential build as the original `npm run frontend`.
|
||||||
|
*/
|
||||||
|
function runFallbackBuild() {
|
||||||
|
console.orange(' turbo not found — using sequential fallback build\n');
|
||||||
|
|
||||||
|
const scripts = [
|
||||||
|
'build:data-provider',
|
||||||
|
'build:data-schemas',
|
||||||
|
'build:api',
|
||||||
|
'build:client-package',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!flags.skipClient) {
|
||||||
|
scripts.push('build:client');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const script of scripts) {
|
||||||
|
console.purple(` Running ${script}...`);
|
||||||
|
exec(`npm run ${script}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasTurbo() {
|
||||||
|
const binDir = path.join(ROOT_DIR, 'node_modules', '.bin');
|
||||||
|
return ['turbo', 'turbo.cmd', 'turbo.ps1'].some((name) => fs.existsSync(path.join(binDir, name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Main ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
console.green('\n Smart Reinstall — LibreChat');
|
||||||
|
console.green('─'.repeat(45));
|
||||||
|
|
||||||
|
// ── Handle --clean-cache ───────────────────────────────────────────────
|
||||||
|
if (flags.cleanCache) {
|
||||||
|
console.purple('Clearing Turborepo cache...');
|
||||||
|
if (hasTurbo()) {
|
||||||
|
try {
|
||||||
|
exec('npx turbo daemon stop', { stdio: 'pipe' });
|
||||||
|
} catch {
|
||||||
|
// ignore — daemon may not be running
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Clear local .turbo cache dir
|
||||||
|
const localTurboCache = path.join(ROOT_DIR, '.turbo');
|
||||||
|
if (fs.existsSync(localTurboCache)) {
|
||||||
|
fs.rmSync(localTurboCache, { recursive: true });
|
||||||
|
}
|
||||||
|
// Clear global turbo cache
|
||||||
|
if (hasTurbo()) {
|
||||||
|
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).');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.gray('turbo not installed — nothing to clear.');
|
||||||
|
}
|
||||||
|
|
||||||
|
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 packages ─────────────────────────────────────────────
|
||||||
|
console.purple('\n[2/2] Building packages...');
|
||||||
|
|
||||||
|
if (hasTurbo()) {
|
||||||
|
runTurboBuild();
|
||||||
|
} else {
|
||||||
|
runFallbackBuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Done ───────────────────────────────────────────────────────────────
|
||||||
|
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);
|
||||||
|
});
|
||||||
103
package-lock.json
generated
103
package-lock.json
generated
|
|
@ -40,6 +40,7 @@
|
||||||
"lint-staged": "^15.4.3",
|
"lint-staged": "^15.4.3",
|
||||||
"prettier": "^3.5.0",
|
"prettier": "^3.5.0",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
|
"turbo": "^2.8.7",
|
||||||
"typescript-eslint": "^8.24.0"
|
"typescript-eslint": "^8.24.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -39915,6 +39916,108 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/turbo": {
|
||||||
|
"version": "2.8.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/turbo/-/turbo-2.8.7.tgz",
|
||||||
|
"integrity": "sha512-RBLh5caMAu1kFdTK1jgH2gH/z+jFsvX5rGbhgJ9nlIAWXSvxlzwId05uDlBA1+pBd3wO/UaKYzaQZQBXDd7kcA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"turbo": "bin/turbo"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"turbo-darwin-64": "2.8.7",
|
||||||
|
"turbo-darwin-arm64": "2.8.7",
|
||||||
|
"turbo-linux-64": "2.8.7",
|
||||||
|
"turbo-linux-arm64": "2.8.7",
|
||||||
|
"turbo-windows-64": "2.8.7",
|
||||||
|
"turbo-windows-arm64": "2.8.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/turbo-darwin-64": {
|
||||||
|
"version": "2.8.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.8.7.tgz",
|
||||||
|
"integrity": "sha512-Xr4TO/oDDwoozbDtBvunb66g//WK8uHRygl72vUthuwzmiw48pil4IuoG/QbMHd9RE8aBnVmzC0WZEWk/WWt3A==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/turbo-darwin-arm64": {
|
||||||
|
"version": "2.8.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.8.7.tgz",
|
||||||
|
"integrity": "sha512-p8Xbmb9kZEY/NoshQUcFmQdO80s2PCGoLYj5DbpxjZr3diknipXxzOK7pcmT7l2gNHaMCpFVWLkiFY9nO3EU5w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/turbo-linux-64": {
|
||||||
|
"version": "2.8.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.8.7.tgz",
|
||||||
|
"integrity": "sha512-nwfEPAH3m5y/nJeYly3j1YJNYU2EG5+2ysZUxvBNM+VBV2LjQaLxB9CsEIpIOKuWKCjnFHKIADTSDPZ3D12J5Q==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/turbo-linux-arm64": {
|
||||||
|
"version": "2.8.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.8.7.tgz",
|
||||||
|
"integrity": "sha512-mgA/M6xiJzyxtXV70TtWGDPh+I6acOKmeQGtOzbFQZYEf794pu5jax26bCk5skAp1gqZu3vacPr6jhYHoHU9IQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/turbo-windows-64": {
|
||||||
|
"version": "2.8.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.8.7.tgz",
|
||||||
|
"integrity": "sha512-sHTYMaXuCcyHnGUQgfUUt7S8407TWoP14zc/4N2tsM0wZNK6V9h4H2t5jQPtqKEb6Fg8313kygdDgEwuM4vsHg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/turbo-windows-arm64": {
|
||||||
|
"version": "2.8.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.8.7.tgz",
|
||||||
|
"integrity": "sha512-WyGiOI2Zp3AhuzVagzQN+T+iq0fWx0oGxDfAWT3ZiLEd4U0cDUkwUZDKVGb3rKqPjDL6lWnuxKKu73ge5xtovQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/type": {
|
"node_modules/type": {
|
||||||
"version": "2.7.3",
|
"version": "2.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
"name": "LibreChat",
|
"name": "LibreChat",
|
||||||
"version": "v0.8.2",
|
"version": "v0.8.2",
|
||||||
"description": "",
|
"description": "",
|
||||||
|
"packageManager": "npm@11.10.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"api",
|
"api",
|
||||||
"client",
|
"client",
|
||||||
|
|
@ -15,6 +16,7 @@
|
||||||
"user-stats": "node config/user-stats.js",
|
"user-stats": "node config/user-stats.js",
|
||||||
"rebuild:package-lock": "node config/packages",
|
"rebuild:package-lock": "node config/packages",
|
||||||
"reinstall": "node config/update.js -l -g",
|
"reinstall": "node config/update.js -l -g",
|
||||||
|
"smart-reinstall": "node config/smart-reinstall.js",
|
||||||
"b:reinstall": "bun config/update.js -b -l -g",
|
"b:reinstall": "bun config/update.js -b -l -g",
|
||||||
"reinstall:docker": "node config/update.js -d -g",
|
"reinstall:docker": "node config/update.js -d -g",
|
||||||
"update:local": "node config/update.js -l",
|
"update:local": "node config/update.js -l",
|
||||||
|
|
@ -128,6 +130,7 @@
|
||||||
"lint-staged": "^15.4.3",
|
"lint-staged": "^15.4.3",
|
||||||
"prettier": "^3.5.0",
|
"prettier": "^3.5.0",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
|
"turbo": "^2.8.7",
|
||||||
"typescript-eslint": "^8.24.0"
|
"typescript-eslint": "^8.24.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
|
|
|
||||||
33
turbo.json
Normal file
33
turbo.json
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://turbo.build/schema.json",
|
||||||
|
"globalDependencies": ["package-lock.json"],
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"inputs": [
|
||||||
|
"src/**",
|
||||||
|
"!src/**/__tests__/**",
|
||||||
|
"!src/**/__mocks__/**",
|
||||||
|
"!src/**/*.test.*",
|
||||||
|
"!src/**/*.spec.*",
|
||||||
|
"scripts/**",
|
||||||
|
"rollup.config.js",
|
||||||
|
"server-rollup.config.js",
|
||||||
|
"tsconfig.json",
|
||||||
|
"tsconfig.build.json",
|
||||||
|
"vite.config.ts",
|
||||||
|
"index.html",
|
||||||
|
"postcss.config.*",
|
||||||
|
"tailwind.config.*",
|
||||||
|
"package.json"
|
||||||
|
],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
},
|
||||||
|
"@librechat/data-schemas#build": {
|
||||||
|
"dependsOn": ["^build", "librechat-data-provider#build"]
|
||||||
|
},
|
||||||
|
"@librechat/api#build": {
|
||||||
|
"dependsOn": ["^build", "librechat-data-provider#build", "@librechat/data-schemas#build"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue