fix(core): Improve formatting, logging, and rollback integrity in core modules
This commit is contained in:
		| @@ -1,5 +1,13 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 2025-08-08 - 1.16.7 - fix(core) | ||||
| Improve formatting, logging, and rollback integrity in core modules | ||||
|  | ||||
| - Add .claude/settings.local.json with defined permissions for allowed commands | ||||
| - Standardize formatting in package.json, commit info, and configuration files | ||||
| - Refactor rollback manager to use atomic manifest writes and validate manifest structure | ||||
| - Enhance logging messages and overall code clarity in CLI and commit modules | ||||
|  | ||||
| ## 2025-08-08 - 1.16.6 - fix(changecache) | ||||
| Improve cache manifest validation and atomic file writes; add local settings and overrides | ||||
|  | ||||
|   | ||||
| @@ -116,4 +116,4 @@ | ||||
|     "overrides": {} | ||||
|   }, | ||||
|   "packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6" | ||||
| } | ||||
| } | ||||
| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@git.zone/cli', | ||||
|   version: '1.16.6', | ||||
|   version: '1.16.7', | ||||
|   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.' | ||||
| } | ||||
|   | ||||
| @@ -40,7 +40,9 @@ export class GitzoneConfig { | ||||
|   public async readConfigFromCwd() { | ||||
|     const npmextraInstance = new plugins.npmextra.Npmextra(paths.cwd); | ||||
|     this.data = npmextraInstance.dataFor<IGitzoneConfigData>('gitzone', {}); | ||||
|     this.data.npmciOptions = npmextraInstance.dataFor<IGitzoneConfigData['npmciOptions']>('npmci', { | ||||
|     this.data.npmciOptions = npmextraInstance.dataFor< | ||||
|       IGitzoneConfigData['npmciOptions'] | ||||
|     >('npmci', { | ||||
|       npmAccessLevel: 'public', | ||||
|     }); | ||||
|   } | ||||
|   | ||||
| @@ -62,23 +62,23 @@ export let run = async () => { | ||||
|   gitzoneSmartcli.addCommand('format').subscribe(async (argvArg) => { | ||||
|     const config = GitzoneConfig.fromCwd(); | ||||
|     const modFormat = await import('./mod_format/index.js'); | ||||
|      | ||||
|  | ||||
|     // Handle rollback commands | ||||
|     if (argvArg.rollback) { | ||||
|       await modFormat.handleRollback(argvArg.rollback); | ||||
|       return; | ||||
|     } | ||||
|      | ||||
|  | ||||
|     if (argvArg['list-backups']) { | ||||
|       await modFormat.handleListBackups(); | ||||
|       return; | ||||
|     } | ||||
|      | ||||
|  | ||||
|     if (argvArg['clean-backups']) { | ||||
|       await modFormat.handleCleanBackups(); | ||||
|       return; | ||||
|     } | ||||
|      | ||||
|  | ||||
|     // Handle format with options | ||||
|     await modFormat.run({ | ||||
|       dryRun: argvArg['dry-run'], | ||||
| @@ -89,7 +89,7 @@ export let run = async () => { | ||||
|       detailed: argvArg.detailed, | ||||
|       interactive: argvArg.interactive !== false, | ||||
|       parallel: argvArg.parallel !== false, | ||||
|       verbose: argvArg.verbose | ||||
|       verbose: argvArg.verbose, | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,8 @@ import * as plugins from './plugins.js'; | ||||
| export const logger = plugins.smartlog.Smartlog.createForCommitinfo(commitinfo); | ||||
|  | ||||
| // Add console destination | ||||
| const consoleDestination = new plugins.smartlogDestinationLocal.DestinationLocal(); | ||||
| const consoleDestination = | ||||
|   new plugins.smartlogDestinationLocal.DestinationLocal(); | ||||
| logger.addLogDestination(consoleDestination); | ||||
|  | ||||
| // Verbose logging helper | ||||
|   | ||||
| @@ -10,20 +10,22 @@ export const run = async (argvArg: any) => { | ||||
|     await formatMod.run(); | ||||
|   } | ||||
|  | ||||
|  | ||||
|   logger.log('info', `gathering facts...`); | ||||
|   const aidoc = new plugins.tsdoc.AiDoc(); | ||||
|   await aidoc.start(); | ||||
|  | ||||
|   const nextCommitObject = await aidoc.buildNextCommitObject(paths.cwd); | ||||
|  | ||||
|   logger.log('info', `--------- | ||||
|   logger.log( | ||||
|     'info', | ||||
|     `--------- | ||||
|     Next recommended commit would be: | ||||
|     =========== | ||||
|     -> ${nextCommitObject.recommendedNextVersion}: | ||||
|     -> ${nextCommitObject.recommendedNextVersionLevel}(${nextCommitObject.recommendedNextVersionScope}): ${nextCommitObject.recommendedNextVersionMessage} | ||||
|     =========== | ||||
|   `); | ||||
|   `, | ||||
|   ); | ||||
|   const commitInteract = new plugins.smartinteract.SmartInteract(); | ||||
|   commitInteract.addQuestions([ | ||||
|     { | ||||
| @@ -72,32 +74,55 @@ export const run = async (argvArg: any) => { | ||||
|   }); | ||||
|  | ||||
|   logger.log('info', `Baking commitinfo into code ...`); | ||||
|   const commitInfo = new plugins.commitinfo.CommitInfo(paths.cwd, commitVersionType); | ||||
|   const commitInfo = new plugins.commitinfo.CommitInfo( | ||||
|     paths.cwd, | ||||
|     commitVersionType, | ||||
|   ); | ||||
|   await commitInfo.writeIntoPotentialDirs(); | ||||
|  | ||||
|   logger.log('info', `Writing changelog.md ...`); | ||||
|   let changelog = nextCommitObject.changelog; | ||||
|   changelog = changelog.replaceAll('{{nextVersion}}', (await commitInfo.getNextPlannedVersion()).versionString); | ||||
|   changelog = changelog.replaceAll('{{nextVersionScope}}', `${await answerBucket.getAnswerFor('commitType')}(${await answerBucket.getAnswerFor('commitScope')})`); | ||||
|   changelog = changelog.replaceAll('{{nextVersionMessage}}', nextCommitObject.recommendedNextVersionMessage); | ||||
|   changelog = changelog.replaceAll( | ||||
|     '{{nextVersion}}', | ||||
|     (await commitInfo.getNextPlannedVersion()).versionString, | ||||
|   ); | ||||
|   changelog = changelog.replaceAll( | ||||
|     '{{nextVersionScope}}', | ||||
|     `${await answerBucket.getAnswerFor('commitType')}(${await answerBucket.getAnswerFor('commitScope')})`, | ||||
|   ); | ||||
|   changelog = changelog.replaceAll( | ||||
|     '{{nextVersionMessage}}', | ||||
|     nextCommitObject.recommendedNextVersionMessage, | ||||
|   ); | ||||
|   if (nextCommitObject.recommendedNextVersionDetails?.length > 0) { | ||||
|     changelog = changelog.replaceAll('{{nextVersionDetails}}', '- ' + nextCommitObject.recommendedNextVersionDetails.join('\n- ')); | ||||
|     changelog = changelog.replaceAll( | ||||
|       '{{nextVersionDetails}}', | ||||
|       '- ' + nextCommitObject.recommendedNextVersionDetails.join('\n- '), | ||||
|     ); | ||||
|   } else { | ||||
|     changelog = changelog.replaceAll('\n{{nextVersionDetails}}', ''); | ||||
|   } | ||||
|  | ||||
|   await plugins.smartfile.memory.toFs(changelog, plugins.path.join(paths.cwd, `changelog.md`)); | ||||
|   await plugins.smartfile.memory.toFs( | ||||
|     changelog, | ||||
|     plugins.path.join(paths.cwd, `changelog.md`), | ||||
|   ); | ||||
|  | ||||
|   logger.log('info', `Staging files for commit:`); | ||||
|   await smartshellInstance.exec(`git add -A`); | ||||
|   await smartshellInstance.exec(`git commit -m "${commitString}"`); | ||||
|   await smartshellInstance.exec(`npm version ${commitVersionType}`); | ||||
|   if (answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true')) { | ||||
|   if ( | ||||
|     answerBucket.getAnswerFor('pushToOrigin') && | ||||
|     !(process.env.CI === 'true') | ||||
|   ) { | ||||
|     await smartshellInstance.exec(`git push origin master --follow-tags`); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const createCommitStringFromAnswerBucket = (answerBucket: plugins.smartinteract.AnswerBucket) => { | ||||
| const createCommitStringFromAnswerBucket = ( | ||||
|   answerBucket: plugins.smartinteract.AnswerBucket, | ||||
| ) => { | ||||
|   const commitType = answerBucket.getAnswerFor('commitType'); | ||||
|   const commitScope = answerBucket.getAnswerFor('commitScope'); | ||||
|   const commitDescription = answerBucket.getAnswerFor('commitDescription'); | ||||
|   | ||||
| @@ -36,7 +36,10 @@ export const run = async () => { | ||||
|   const registryUrls = answerBucket.getAnswerFor(`registryUrls`).split(','); | ||||
|   const oldPackageName = answerBucket.getAnswerFor(`oldPackageName`); | ||||
|   const newPackageName = answerBucket.getAnswerFor(`newPackageName`); | ||||
|   logger.log('info', `Deprecating package ${oldPackageName} in favour of ${newPackageName}`); | ||||
|   logger.log( | ||||
|     'info', | ||||
|     `Deprecating package ${oldPackageName} in favour of ${newPackageName}`, | ||||
|   ); | ||||
|   const smartshellInstance = new plugins.smartshell.Smartshell({ | ||||
|     executor: 'bash', | ||||
|   }); | ||||
|   | ||||
| @@ -43,7 +43,7 @@ export class RollbackManager { | ||||
|     } | ||||
|      | ||||
|     // Read file content and metadata | ||||
|     const content = await plugins.smartfile.fs.toStringSync(absolutePath); | ||||
|     const content = plugins.smartfile.fs.toStringSync(absolutePath); | ||||
|     const stats = await plugins.smartfile.fs.stat(absolutePath); | ||||
|     const checksum = this.calculateChecksum(content); | ||||
|      | ||||
| @@ -82,7 +82,7 @@ export class RollbackManager { | ||||
|        | ||||
|       // Verify backup integrity | ||||
|       const backupPath = this.getBackupPath(operationId, file.path); | ||||
|       const backupContent = await plugins.smartfile.fs.toStringSync(backupPath); | ||||
|       const backupContent = plugins.smartfile.fs.toStringSync(backupPath); | ||||
|       const backupChecksum = this.calculateChecksum(backupContent); | ||||
|        | ||||
|       if (backupChecksum !== file.checksum) { | ||||
| @@ -146,7 +146,7 @@ export class RollbackManager { | ||||
|         return false; | ||||
|       } | ||||
|        | ||||
|       const content = await plugins.smartfile.fs.toStringSync(backupPath); | ||||
|       const content = plugins.smartfile.fs.toStringSync(backupPath); | ||||
|       const checksum = this.calculateChecksum(content); | ||||
|        | ||||
|       if (checksum !== file.checksum) { | ||||
| @@ -185,17 +185,63 @@ export class RollbackManager { | ||||
|   } | ||||
|    | ||||
|   private async getManifest(): Promise<{ operations: IFormatOperation[] }> { | ||||
|     const defaultManifest = { operations: [] }; | ||||
|      | ||||
|     const exists = await plugins.smartfile.fs.fileExists(this.manifestPath); | ||||
|     if (!exists) { | ||||
|       return { operations: [] }; | ||||
|       return defaultManifest; | ||||
|     } | ||||
|      | ||||
|     const content = await 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 rollback manifest structure, returning default manifest'); | ||||
|         return defaultManifest; | ||||
|       } | ||||
|     } catch (error) { | ||||
|       console.warn(`Failed to read rollback 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; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   private async saveManifest(manifest: { operations: IFormatOperation[] }): 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 rollback 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; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   private async getOperation(operationId: string): Promise<IFormatOperation | null> { | ||||
| @@ -215,4 +261,38 @@ export class RollbackManager { | ||||
|      | ||||
|     await this.saveManifest(manifest); | ||||
|   } | ||||
|    | ||||
|   private isValidManifest(manifest: any): manifest is { operations: IFormatOperation[] } { | ||||
|     // Check if manifest has the required structure | ||||
|     if (!manifest || typeof manifest !== 'object') { | ||||
|       return false; | ||||
|     } | ||||
|      | ||||
|     // Check required fields | ||||
|     if (!Array.isArray(manifest.operations)) { | ||||
|       return false; | ||||
|     } | ||||
|      | ||||
|     // Check each operation entry | ||||
|     for (const operation of manifest.operations) { | ||||
|       if (!operation || typeof operation !== 'object' || | ||||
|           typeof operation.id !== 'string' || | ||||
|           typeof operation.timestamp !== 'number' || | ||||
|           typeof operation.status !== 'string' || | ||||
|           !Array.isArray(operation.files)) { | ||||
|         return false; | ||||
|       } | ||||
|        | ||||
|       // Check each file in the operation | ||||
|       for (const file of operation.files) { | ||||
|         if (!file || typeof file !== 'object' || | ||||
|             typeof file.path !== 'string' || | ||||
|             typeof file.checksum !== 'string') { | ||||
|           return false; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     return true; | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user