UNPKG

qraft

Version:

A powerful CLI tool to qraft structured project setups from GitHub template repositories

452 lines 16.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.CacheManager = void 0; const crypto = __importStar(require("crypto")); const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const manifestManager_1 = require("./manifestManager"); /** * CacheManager handles local caching of downloaded boxes for improved performance */ class CacheManager { constructor(config) { this.cacheDirectory = config?.directory || path.join(process.env.HOME || process.env.USERPROFILE || '/tmp', '.cache', 'qraft'); this.ttl = config?.ttl || 3600; // Default 1 hour this.enabled = config?.enabled !== false; // Default enabled this.manifestManager = new manifestManager_1.ManifestManager(); } /** * Generate a cache key for a box reference * @param boxRef Box reference * @returns string Cache key */ generateCacheKey(boxRef) { const keyString = `${boxRef.registry}/${boxRef.boxName}`; return crypto.createHash('sha256').update(keyString).digest('hex'); } /** * Get the cache file path for a box reference * @param boxRef Box reference * @returns string Path to cache file */ getCacheFilePath(boxRef) { const cacheKey = this.generateCacheKey(boxRef); return path.join(this.cacheDirectory, `${cacheKey}.json`); } /** * Get the cache directory path for a box reference * @param boxRef Box reference * @returns string Path to cache directory for box files */ getCacheDirectoryPath(boxRef) { const cacheKey = this.generateCacheKey(boxRef); return path.join(this.cacheDirectory, cacheKey); } /** * Check if a cache entry exists and is valid * @param boxRef Box reference * @returns Promise<boolean> True if valid cache exists */ async hasValidCache(boxRef) { if (!this.enabled) { return false; } try { const cacheFilePath = this.getCacheFilePath(boxRef); if (!(await fs.pathExists(cacheFilePath))) { return false; } const cacheContent = await fs.readFile(cacheFilePath, 'utf-8'); const cacheEntry = JSON.parse(cacheContent); // Check if cache has expired const now = Date.now(); if (now > cacheEntry.expiresAt) { // Cache expired, clean it up await this.removeCacheEntry(boxRef); return false; } // Check if cache directory exists const cacheDir = this.getCacheDirectoryPath(boxRef); if (!(await fs.pathExists(cacheDir))) { // Cache metadata exists but files are missing await this.removeCacheEntry(boxRef); return false; } return true; } catch (error) { // If there's any error reading cache, consider it invalid return false; } } /** * Get cached box information * @param boxRef Box reference * @returns Promise<CacheEntry | null> Cache entry or null if not found/invalid */ async getCacheEntry(boxRef) { if (!this.enabled || !(await this.hasValidCache(boxRef))) { return null; } try { const cacheFilePath = this.getCacheFilePath(boxRef); const cacheContent = await fs.readFile(cacheFilePath, 'utf-8'); return JSON.parse(cacheContent); } catch (error) { return null; } } /** * Store box information and files in cache * @param boxRef Box reference * @param manifest Box manifest * @param files Array of file paths * @param fileContents Map of file paths to their content * @returns Promise<void> */ async setCacheEntry(boxRef, manifest, files, fileContents) { if (!this.enabled) { return; } try { // Ensure cache directory exists await fs.ensureDir(this.cacheDirectory); const cacheDir = this.getCacheDirectoryPath(boxRef); await fs.ensureDir(cacheDir); // Store files for (const [filePath, content] of fileContents.entries()) { const fullFilePath = path.join(cacheDir, filePath); await fs.ensureDir(path.dirname(fullFilePath)); await fs.writeFile(fullFilePath, content); } // Create cache entry const now = Date.now(); const cacheEntry = { boxReference: boxRef.fullReference, manifest, files, timestamp: now, expiresAt: now + (this.ttl * 1000), localPath: cacheDir }; // Store cache metadata const cacheFilePath = this.getCacheFilePath(boxRef); await fs.writeFile(cacheFilePath, JSON.stringify(cacheEntry, null, 2), 'utf-8'); } catch (error) { // If caching fails, don't throw error - just log warning console.warn(`Warning: Failed to cache box '${boxRef.fullReference}': ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Get cached file content * @param boxRef Box reference * @param filePath Relative file path within the box * @returns Promise<Buffer | null> File content or null if not cached */ async getCachedFile(boxRef, filePath) { if (!this.enabled || !(await this.hasValidCache(boxRef))) { return null; } try { const cacheDir = this.getCacheDirectoryPath(boxRef); const fullFilePath = path.join(cacheDir, filePath); if (await fs.pathExists(fullFilePath)) { return await fs.readFile(fullFilePath); } return null; } catch (error) { return null; } } /** * Remove a specific cache entry * @param boxRef Box reference * @returns Promise<void> */ async removeCacheEntry(boxRef) { try { const cacheFilePath = this.getCacheFilePath(boxRef); const cacheDir = this.getCacheDirectoryPath(boxRef); // Remove cache metadata file if (await fs.pathExists(cacheFilePath)) { await fs.remove(cacheFilePath); } // Remove cache directory if (await fs.pathExists(cacheDir)) { await fs.remove(cacheDir); } } catch (error) { // Ignore errors when removing cache } } /** * Clear all cache entries * @returns Promise<void> */ async clearCache() { try { if (await fs.pathExists(this.cacheDirectory)) { await fs.remove(this.cacheDirectory); } } catch (error) { throw new Error(`Failed to clear cache: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Clean up expired cache entries * @returns Promise<number> Number of entries cleaned up */ async cleanupExpiredEntries() { if (!this.enabled) { return 0; } let cleanedCount = 0; try { if (!(await fs.pathExists(this.cacheDirectory))) { return 0; } const entries = await fs.readdir(this.cacheDirectory); const now = Date.now(); for (const entry of entries) { if (entry.endsWith('.json')) { try { const cacheFilePath = path.join(this.cacheDirectory, entry); const cacheContent = await fs.readFile(cacheFilePath, 'utf-8'); const cacheEntry = JSON.parse(cacheContent); if (now > cacheEntry.expiresAt) { // Parse box reference to remove cache const boxRef = { registry: '', boxName: '', fullReference: cacheEntry.boxReference }; // Parse the full reference const parts = cacheEntry.boxReference.split('/'); if (parts.length === 1) { boxRef.boxName = parts[0]; boxRef.registry = 'dasheck0'; // Default registry } else { boxRef.boxName = parts[parts.length - 1]; boxRef.registry = parts.slice(0, -1).join('/'); } await this.removeCacheEntry(boxRef); cleanedCount++; } } catch (error) { // If we can't parse a cache entry, remove it const cacheFilePath = path.join(this.cacheDirectory, entry); await fs.remove(cacheFilePath); cleanedCount++; } } } } catch (error) { // Ignore errors during cleanup } return cleanedCount; } /** * Get cache statistics * @returns Promise<{totalEntries: number, totalSize: number, cacheDirectory: string}> Cache statistics */ async getCacheStats() { const stats = { totalEntries: 0, totalSize: 0, cacheDirectory: this.cacheDirectory }; try { if (!(await fs.pathExists(this.cacheDirectory))) { return stats; } const calculateSize = async (dirPath) => { let size = 0; const entries = await fs.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); if (entry.isDirectory()) { size += await calculateSize(fullPath); } else { const fileStat = await fs.stat(fullPath); size += fileStat.size; } } return size; }; const entries = await fs.readdir(this.cacheDirectory); for (const entry of entries) { if (entry.endsWith('.json')) { stats.totalEntries++; } } stats.totalSize = await calculateSize(this.cacheDirectory); } catch (error) { // Return empty stats on error } return stats; } /** * Check if caching is enabled * @returns boolean True if caching is enabled */ isEnabled() { return this.enabled; } /** * Get cache directory path * @returns string Cache directory path */ getCacheDirectory() { return this.cacheDirectory; } /** * Get cache TTL in seconds * @returns number TTL in seconds */ getTTL() { return this.ttl; } /** * Store manifest in cache directory for a box * @param boxRef Box reference * @param manifest Box manifest * @param registry Registry identifier * @param boxReference Full box reference * @returns Promise<void> */ async storeCachedManifest(boxRef, manifest, registry, boxReference) { if (!this.enabled) { return; } try { const cacheDir = this.getCacheDirectoryPath(boxRef); // Only store manifest if cache directory exists (box is cached) if (await fs.pathExists(cacheDir)) { await this.manifestManager.storeLocalManifest(cacheDir, manifest, registry, boxReference); } } catch (error) { // Ignore manifest storage errors in cache } } /** * Get cached manifest for a box * @param boxRef Box reference * @returns Promise<any | null> Cached manifest or null */ async getCachedManifest(boxRef) { if (!this.enabled || !(await this.hasValidCache(boxRef))) { return null; } try { const cacheDir = this.getCacheDirectoryPath(boxRef); return await this.manifestManager.getLocalManifest(cacheDir); } catch (error) { return null; } } /** * Check if cached manifest exists for a box * @param boxRef Box reference * @returns Promise<boolean> True if cached manifest exists */ async hasCachedManifest(boxRef) { if (!this.enabled || !(await this.hasValidCache(boxRef))) { return false; } try { const cacheDir = this.getCacheDirectoryPath(boxRef); return await this.manifestManager.hasLocalManifest(cacheDir); } catch (error) { return false; } } /** * Sync cached manifest with remote manifest * @param boxRef Box reference * @param remoteManifest Remote manifest * @param registry Registry identifier * @param boxReference Full box reference * @returns Promise<boolean> True if sync was needed and performed */ async syncCachedManifest(boxRef, remoteManifest, registry, boxReference) { if (!this.enabled || !(await this.hasValidCache(boxRef))) { return false; } try { const cacheDir = this.getCacheDirectoryPath(boxRef); const localManifest = await this.manifestManager.getLocalManifest(cacheDir); if (!localManifest) { // No local manifest, store the remote one await this.manifestManager.storeLocalManifest(cacheDir, remoteManifest, registry, boxReference); return true; } // Compare manifests const comparison = this.manifestManager.compareManifests(localManifest.manifest, remoteManifest); if (!comparison.isIdentical) { // Manifests differ, update local copy await this.manifestManager.storeLocalManifest(cacheDir, remoteManifest, registry, boxReference, true // isUpdate ); return true; } return false; } catch (error) { return false; } } /** * Get manifest manager instance * @returns ManifestManager Manifest manager instance */ getManifestManager() { return this.manifestManager; } } exports.CacheManager = CacheManager; //# sourceMappingURL=cacheManager.js.map