UNPKG

@git.zone/cli

Version:

A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.

224 lines (183 loc) 6.13 kB
import * as plugins from './mod.plugins.js'; import * as paths from '../paths.js'; export interface IFileCache { path: string; checksum: string; modified: number; size: number; } export interface ICacheManifest { version: string; lastFormat: number; files: IFileCache[]; } export class ChangeCache { private cacheDir: string; private manifestPath: string; private cacheVersion = '1.0.0'; constructor() { this.cacheDir = plugins.path.join(paths.cwd, '.nogit', 'gitzone-cache'); this.manifestPath = plugins.path.join(this.cacheDir, 'manifest.json'); } async initialize(): Promise<void> { await plugins.smartfile.fs.ensureDir(this.cacheDir); } async getManifest(): Promise<ICacheManifest> { const defaultManifest: ICacheManifest = { version: this.cacheVersion, lastFormat: 0, files: [], }; const exists = await plugins.smartfile.fs.fileExists(this.manifestPath); if (!exists) { return defaultManifest; } try { const content = plugins.smartfile.fs.toStringSync(this.manifestPath); const manifest = JSON.parse(content); // Validate the manifest structure if (this.isValidManifest(manifest)) { return manifest; } else { console.warn('Invalid manifest structure, returning default manifest'); return defaultManifest; } } catch (error) { console.warn( `Failed to read cache manifest: ${error.message}, returning default manifest`, ); // Try to delete the corrupted file try { await plugins.smartfile.fs.remove(this.manifestPath); } catch (removeError) { // Ignore removal errors } return defaultManifest; } } async saveManifest(manifest: ICacheManifest): Promise<void> { // Validate before saving if (!this.isValidManifest(manifest)) { throw new Error('Invalid manifest structure, cannot save'); } // Ensure directory exists await plugins.smartfile.fs.ensureDir(this.cacheDir); // Write directly with proper JSON stringification const jsonContent = JSON.stringify(manifest, null, 2); await plugins.smartfile.memory.toFs(jsonContent, this.manifestPath); } async hasFileChanged(filePath: string): Promise<boolean> { const absolutePath = plugins.path.isAbsolute(filePath) ? filePath : plugins.path.join(paths.cwd, filePath); // Check if file exists const exists = await plugins.smartfile.fs.fileExists(absolutePath); if (!exists) { return true; // File doesn't exist, so it's "changed" (will be created) } // Get current file stats const stats = await plugins.smartfile.fs.stat(absolutePath); // Skip directories if (stats.isDirectory()) { return false; // Directories are not processed } const content = plugins.smartfile.fs.toStringSync(absolutePath); const currentChecksum = this.calculateChecksum(content); // Get cached info const manifest = await this.getManifest(); const cachedFile = manifest.files.find((f) => f.path === filePath); if (!cachedFile) { return true; // Not in cache, so it's changed } // Compare checksums return ( cachedFile.checksum !== currentChecksum || cachedFile.size !== stats.size || cachedFile.modified !== stats.mtimeMs ); } async updateFileCache(filePath: string): Promise<void> { const absolutePath = plugins.path.isAbsolute(filePath) ? filePath : plugins.path.join(paths.cwd, filePath); // Get current file stats const stats = await plugins.smartfile.fs.stat(absolutePath); // Skip directories if (stats.isDirectory()) { return; // Don't cache directories } const content = plugins.smartfile.fs.toStringSync(absolutePath); const checksum = this.calculateChecksum(content); // Update manifest const manifest = await this.getManifest(); const existingIndex = manifest.files.findIndex((f) => f.path === filePath); const cacheEntry: IFileCache = { path: filePath, checksum, modified: stats.mtimeMs, size: stats.size, }; if (existingIndex !== -1) { manifest.files[existingIndex] = cacheEntry; } else { manifest.files.push(cacheEntry); } manifest.lastFormat = Date.now(); await this.saveManifest(manifest); } async getChangedFiles(filePaths: string[]): Promise<string[]> { const changedFiles: string[] = []; for (const filePath of filePaths) { if (await this.hasFileChanged(filePath)) { changedFiles.push(filePath); } } return changedFiles; } async clean(): Promise<void> { const manifest = await this.getManifest(); const validFiles: IFileCache[] = []; // Remove entries for files that no longer exist for (const file of manifest.files) { const absolutePath = plugins.path.isAbsolute(file.path) ? file.path : plugins.path.join(paths.cwd, file.path); if (await plugins.smartfile.fs.fileExists(absolutePath)) { validFiles.push(file); } } manifest.files = validFiles; await this.saveManifest(manifest); } private calculateChecksum(content: string | Buffer): string { return plugins.crypto.createHash('sha256').update(content).digest('hex'); } private isValidManifest(manifest: any): manifest is ICacheManifest { // Check if manifest has the required structure if (!manifest || typeof manifest !== 'object') { return false; } // Check required fields if ( typeof manifest.version !== 'string' || typeof manifest.lastFormat !== 'number' || !Array.isArray(manifest.files) ) { return false; } // Check each file entry for (const file of manifest.files) { if ( !file || typeof file !== 'object' || typeof file.path !== 'string' || typeof file.checksum !== 'string' || typeof file.modified !== 'number' || typeof file.size !== 'number' ) { return false; } } return true; } }