fix(changecache): Improve cache manifest validation and atomic file writes; add local settings and overrides
This commit is contained in:
		
							
								
								
									
										1
									
								
								assets/overrides.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/overrides.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| {} | ||||
| @@ -1,5 +1,14 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 2025-08-08 - 1.16.6 - fix(changecache) | ||||
| Improve cache manifest validation and atomic file writes; add local settings and overrides | ||||
|  | ||||
| - Add manifest structure validation and default fallback in getManifest | ||||
| - Implement atomic write in saveManifest using a temporary file and rename strategy | ||||
| - Enhance error handling and cleanup for corrupted manifest files | ||||
| - Introduce new .claude/settings.local.json for project-specific permission configuration | ||||
| - Add an empty assets/overrides.json file for future overrides | ||||
|  | ||||
| ## 2025-08-08 - 1.16.5 - fix(prettier) | ||||
| Improve file selection in Prettier formatter, remove legacy package overrides, and update CI template indentation | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@git.zone/cli', | ||||
|   version: '1.16.5', | ||||
|   version: '1.16.6', | ||||
|   description: '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.' | ||||
| } | ||||
|   | ||||
| @@ -29,21 +29,67 @@ export class ChangeCache { | ||||
|   } | ||||
|    | ||||
|   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 { | ||||
|         version: this.cacheVersion, | ||||
|         lastFormat: 0, | ||||
|         files: [] | ||||
|       }; | ||||
|       return defaultManifest; | ||||
|     } | ||||
|      | ||||
|     const content = plugins.smartfile.fs.toStringSync(this.manifestPath); | ||||
|     return JSON.parse(content); | ||||
|     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> { | ||||
|     await plugins.smartfile.memory.toFs(JSON.stringify(manifest, null, 2), this.manifestPath); | ||||
|     // Validate before saving | ||||
|     if (!this.isValidManifest(manifest)) { | ||||
|       throw new Error('Invalid manifest structure, cannot save'); | ||||
|     } | ||||
|      | ||||
|     // Use atomic write: write to temp file, then move it | ||||
|     const tempPath = `${this.manifestPath}.tmp`; | ||||
|      | ||||
|     try { | ||||
|       // Write to temporary file | ||||
|       const jsonContent = JSON.stringify(manifest, null, 2); | ||||
|       await plugins.smartfile.memory.toFs(jsonContent, tempPath); | ||||
|        | ||||
|       // Move temp file to actual manifest (atomic-like operation) | ||||
|       // Since smartfile doesn't have rename, we copy and delete | ||||
|       await plugins.smartfile.fs.copy(tempPath, this.manifestPath); | ||||
|       await plugins.smartfile.fs.remove(tempPath); | ||||
|     } catch (error) { | ||||
|       // Clean up temp file if it exists | ||||
|       try { | ||||
|         await plugins.smartfile.fs.remove(tempPath); | ||||
|       } catch (removeError) { | ||||
|         // Ignore removal errors | ||||
|       } | ||||
|       throw error; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   async hasFileChanged(filePath: string): Promise<boolean> { | ||||
| @@ -95,7 +141,7 @@ export class ChangeCache { | ||||
|       return; // Don't cache directories | ||||
|     } | ||||
|      | ||||
|     const content = await plugins.smartfile.fs.toStringSync(absolutePath); | ||||
|     const content = plugins.smartfile.fs.toStringSync(absolutePath); | ||||
|     const checksum = this.calculateChecksum(content); | ||||
|      | ||||
|     // Update manifest | ||||
| @@ -153,4 +199,31 @@ export class ChangeCache { | ||||
|   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; | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user