qraft
Version:
A powerful CLI tool to qraft structured project setups from GitHub template repositories
452 lines • 16.4 kB
JavaScript
"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