LibreChat/config/flush-cache.js
Danny Avila a955097faf
📁 feat: Integrate SharePoint File Picker and Download Workflow (#8651)
* feat(sharepoint): integrate SharePoint file picker and download workflow
Introduces end‑to‑end SharePoint import support:
* Token exchange with Microsoft Graph and scope management (`useSharePointToken`)
* Re‑usable hooks: `useSharePointPicker`, `useSharePointDownload`,
  `useSharePointFileHandling`
* FileSearch dropdown now offers **From Local Machine** / **From SharePoint**
  sources and gracefully falls back when SharePoint is disabled
* Agent upload model, `AttachFileMenu`, and `DropdownPopup` extended for
  SharePoint files and sub‑menus
* Blurry overlay with progress indicator and `maxSelectionCount` limit during
  downloads
* Cache‑flush utility (`config/flush-cache.js`) supporting Redis & filesystem,
  with dry‑run and npm script
* Updated `SharePointIcon` (uses `currentColor`) and new i18n keys
* Bug fixes: placeholder syntax in progress message, picker event‑listener
  cleanup
* Misc style and performance optimizations

* Fix ESLint warnings

---------

Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com>
2025-08-13 16:24:16 -04:00

264 lines
7.1 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* LibreChat Cache Flush Utility
*
* This script flushes the cache store used by LibreChat, whether it's
* Redis (if configured) or file-based cache.
*
* Usage:
* npm run flush-cache
* node config/flush-cache.js
* node config/flush-cache.js --help
*/
const path = require('path');
const fs = require('fs');
// Set up environment
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
const { USE_REDIS, REDIS_URI, REDIS_KEY_PREFIX } = process.env;
// Simple utility function
const isEnabled = (value) => value === 'true' || value === true;
async function showHelp() {
console.log(`
LibreChat Cache Flush Utility
DESCRIPTION:
Flushes the cache store used by LibreChat. Automatically detects
whether Redis or file-based cache is being used and flushes accordingly.
USAGE:
npm run flush-cache
node config/flush-cache.js [options]
OPTIONS:
--help, -h Show this help message
--dry-run Show what would be flushed without actually doing it
--verbose, -v Show detailed output
CACHE TYPES:
• Redis Cache: Flushes all keys with the configured Redis prefix
• File Cache: Removes ./data/logs.json and ./data/violations.json
WHAT GETS FLUSHED:
• User sessions and authentication tokens
• Configuration cache
• Model queries cache
• Rate limiting data
• Conversation titles cache
• File upload progress
• SharePoint tokens
• And more...
NOTE: This will log out all users and may require them to re-authenticate.
`);
}
async function flushRedisCache(dryRun = false, verbose = false) {
try {
console.log('🔍 Redis cache detected');
if (verbose) {
console.log(` URI: ${REDIS_URI ? REDIS_URI.replace(/\/\/.*@/, '//***:***@') : 'Not set'}`);
console.log(` Prefix: ${REDIS_KEY_PREFIX || 'None'}`);
}
// Create Redis client directly
const Redis = require('ioredis');
let redis;
// Handle cluster vs single Redis
if (process.env.USE_REDIS_CLUSTER === 'true') {
const hosts = REDIS_URI.split(',').map((uri) => {
const [host, port] = uri.split(':');
return { host, port: parseInt(port) || 6379 };
});
redis = new Redis.Cluster(hosts);
} else {
redis = new Redis(REDIS_URI);
}
if (dryRun) {
console.log('🔍 [DRY RUN] Would flush Redis cache');
try {
const keys = await redis.keys('*');
console.log(` Would delete ${keys.length} keys`);
if (verbose && keys.length > 0) {
console.log(
' Sample keys:',
keys.slice(0, 10).join(', ') + (keys.length > 10 ? '...' : ''),
);
}
} catch (error) {
console.log(' Could not fetch keys for preview:', error.message);
}
await redis.disconnect();
return true;
}
// Get key count before flushing
let keyCount = 0;
try {
const keys = await redis.keys('*');
keyCount = keys.length;
} catch (error) {
// Continue with flush even if we can't count keys
}
// Flush the Redis cache
await redis.flushdb();
console.log('✅ Redis cache flushed successfully');
if (keyCount > 0) {
console.log(` Deleted ${keyCount} keys`);
}
await redis.disconnect();
return true;
} catch (error) {
console.error('❌ Error flushing Redis cache:', error.message);
if (verbose) {
console.error(' Full error:', error);
}
return false;
}
}
async function flushFileCache(dryRun = false, verbose = false) {
const dataDir = path.join(__dirname, '..', 'data');
const filesToClear = [path.join(dataDir, 'logs.json'), path.join(dataDir, 'violations.json')];
console.log('🔍 Checking file-based cache');
if (dryRun) {
console.log('🔍 [DRY RUN] Would flush file cache');
for (const filePath of filesToClear) {
if (fs.existsSync(filePath)) {
const stats = fs.statSync(filePath);
console.log(
` Would delete: ${path.basename(filePath)} (${(stats.size / 1024).toFixed(1)} KB)`,
);
}
}
return true;
}
let deletedCount = 0;
let totalSize = 0;
for (const filePath of filesToClear) {
try {
if (fs.existsSync(filePath)) {
const stats = fs.statSync(filePath);
totalSize += stats.size;
fs.unlinkSync(filePath);
deletedCount++;
if (verbose) {
console.log(
` ✅ Deleted ${path.basename(filePath)} (${(stats.size / 1024).toFixed(1)} KB)`,
);
}
}
} catch (error) {
if (verbose) {
console.log(` ❌ Failed to delete ${path.basename(filePath)}: ${error.message}`);
}
}
}
if (deletedCount > 0) {
console.log('✅ File cache flushed successfully');
console.log(` Deleted ${deletedCount} cache files (${(totalSize / 1024).toFixed(1)} KB)`);
} else {
console.log(' No file cache to flush');
}
return true;
}
async function restartRecommendation() {
console.log('\n💡 RECOMMENDATION:');
console.log(' For complete cache clearing, especially for in-memory caches,');
console.log(' consider restarting the LibreChat backend:');
console.log('');
console.log(' npm run backend:stop');
console.log(' npm run backend:dev');
console.log('');
}
async function main() {
const args = process.argv.slice(2);
const dryRun = args.includes('--dry-run');
const verbose = args.includes('--verbose') || args.includes('-v');
const help = args.includes('--help') || args.includes('-h');
if (help) {
await showHelp();
return;
}
console.log('🧹 LibreChat Cache Flush Utility');
console.log('================================');
if (dryRun) {
console.log('🔍 DRY RUN MODE - No actual changes will be made\n');
}
let success = true;
const isRedisEnabled = isEnabled(USE_REDIS) && REDIS_URI;
// Flush the appropriate cache type
if (isRedisEnabled) {
success = (await flushRedisCache(dryRun, verbose)) && success;
} else {
console.log(' Redis not configured, using file-based cache only');
}
// Always check file cache
success = (await flushFileCache(dryRun, verbose)) && success;
console.log('\n' + '='.repeat(50));
if (success) {
if (dryRun) {
console.log('✅ Cache flush preview completed');
console.log(' Run without --dry-run to actually flush the cache');
} else {
console.log('✅ Cache flush completed successfully');
console.log('⚠️ Note: All users will need to re-authenticate');
}
if (!isRedisEnabled) {
await restartRecommendation();
}
} else {
console.log('❌ Cache flush completed with errors');
console.log(' Check the output above for details');
process.exit(1);
}
}
// Handle errors gracefully
process.on('unhandledRejection', (error) => {
console.error('❌ Unhandled error:', error);
process.exit(1);
});
process.on('uncaughtException', (error) => {
console.error('❌ Uncaught exception:', error);
process.exit(1);
});
// Run the main function
if (require.main === module) {
main().catch((error) => {
console.error('❌ Fatal error:', error);
process.exit(1);
});
}
module.exports = { flushRedisCache, flushFileCache };