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 { await plugins.smartfile.fs.ensureDir(this.cacheDir); } async getManifest(): Promise { const exists = await plugins.smartfile.fs.fileExists(this.manifestPath); if (!exists) { return { version: this.cacheVersion, lastFormat: 0, files: [] }; } const content = await plugins.smartfile.fs.toStringSync(this.manifestPath); return JSON.parse(content); } async saveManifest(manifest: ICacheManifest): Promise { await plugins.smartfile.memory.toFs(JSON.stringify(manifest, null, 2), this.manifestPath); } async hasFileChanged(filePath: string): Promise { 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); const content = await 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 { 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); const content = await 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 { const changedFiles: string[] = []; for (const filePath of filePaths) { if (await this.hasFileChanged(filePath)) { changedFiles.push(filePath); } } return changedFiles; } async clean(): Promise { 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'); } }