fix(core): Improve formatting, logging, and rollback integrity in core modules

This commit is contained in:
2025-08-08 05:48:41 +00:00
parent c48f48fc8b
commit fd90cfe895
9 changed files with 147 additions and 28 deletions

View File

@@ -1,5 +1,13 @@
# Changelog # 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) ## 2025-08-08 - 1.16.6 - fix(changecache)
Improve cache manifest validation and atomic file writes; add local settings and overrides Improve cache manifest validation and atomic file writes; add local settings and overrides

View File

@@ -116,4 +116,4 @@
"overrides": {} "overrides": {}
}, },
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6" "packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
} }

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@git.zone/cli', 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.' 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.'
} }

View File

@@ -40,7 +40,9 @@ export class GitzoneConfig {
public async readConfigFromCwd() { public async readConfigFromCwd() {
const npmextraInstance = new plugins.npmextra.Npmextra(paths.cwd); const npmextraInstance = new plugins.npmextra.Npmextra(paths.cwd);
this.data = npmextraInstance.dataFor<IGitzoneConfigData>('gitzone', {}); this.data = npmextraInstance.dataFor<IGitzoneConfigData>('gitzone', {});
this.data.npmciOptions = npmextraInstance.dataFor<IGitzoneConfigData['npmciOptions']>('npmci', { this.data.npmciOptions = npmextraInstance.dataFor<
IGitzoneConfigData['npmciOptions']
>('npmci', {
npmAccessLevel: 'public', npmAccessLevel: 'public',
}); });
} }

View File

@@ -62,23 +62,23 @@ export let run = async () => {
gitzoneSmartcli.addCommand('format').subscribe(async (argvArg) => { gitzoneSmartcli.addCommand('format').subscribe(async (argvArg) => {
const config = GitzoneConfig.fromCwd(); const config = GitzoneConfig.fromCwd();
const modFormat = await import('./mod_format/index.js'); const modFormat = await import('./mod_format/index.js');
// Handle rollback commands // Handle rollback commands
if (argvArg.rollback) { if (argvArg.rollback) {
await modFormat.handleRollback(argvArg.rollback); await modFormat.handleRollback(argvArg.rollback);
return; return;
} }
if (argvArg['list-backups']) { if (argvArg['list-backups']) {
await modFormat.handleListBackups(); await modFormat.handleListBackups();
return; return;
} }
if (argvArg['clean-backups']) { if (argvArg['clean-backups']) {
await modFormat.handleCleanBackups(); await modFormat.handleCleanBackups();
return; return;
} }
// Handle format with options // Handle format with options
await modFormat.run({ await modFormat.run({
dryRun: argvArg['dry-run'], dryRun: argvArg['dry-run'],
@@ -89,7 +89,7 @@ export let run = async () => {
detailed: argvArg.detailed, detailed: argvArg.detailed,
interactive: argvArg.interactive !== false, interactive: argvArg.interactive !== false,
parallel: argvArg.parallel !== false, parallel: argvArg.parallel !== false,
verbose: argvArg.verbose verbose: argvArg.verbose,
}); });
}); });

View File

@@ -5,7 +5,8 @@ import * as plugins from './plugins.js';
export const logger = plugins.smartlog.Smartlog.createForCommitinfo(commitinfo); export const logger = plugins.smartlog.Smartlog.createForCommitinfo(commitinfo);
// Add console destination // Add console destination
const consoleDestination = new plugins.smartlogDestinationLocal.DestinationLocal(); const consoleDestination =
new plugins.smartlogDestinationLocal.DestinationLocal();
logger.addLogDestination(consoleDestination); logger.addLogDestination(consoleDestination);
// Verbose logging helper // Verbose logging helper

View File

@@ -10,20 +10,22 @@ export const run = async (argvArg: any) => {
await formatMod.run(); await formatMod.run();
} }
logger.log('info', `gathering facts...`); logger.log('info', `gathering facts...`);
const aidoc = new plugins.tsdoc.AiDoc(); const aidoc = new plugins.tsdoc.AiDoc();
await aidoc.start(); await aidoc.start();
const nextCommitObject = await aidoc.buildNextCommitObject(paths.cwd); const nextCommitObject = await aidoc.buildNextCommitObject(paths.cwd);
logger.log('info', `--------- logger.log(
'info',
`---------
Next recommended commit would be: Next recommended commit would be:
=========== ===========
-> ${nextCommitObject.recommendedNextVersion}: -> ${nextCommitObject.recommendedNextVersion}:
-> ${nextCommitObject.recommendedNextVersionLevel}(${nextCommitObject.recommendedNextVersionScope}): ${nextCommitObject.recommendedNextVersionMessage} -> ${nextCommitObject.recommendedNextVersionLevel}(${nextCommitObject.recommendedNextVersionScope}): ${nextCommitObject.recommendedNextVersionMessage}
=========== ===========
`); `,
);
const commitInteract = new plugins.smartinteract.SmartInteract(); const commitInteract = new plugins.smartinteract.SmartInteract();
commitInteract.addQuestions([ commitInteract.addQuestions([
{ {
@@ -72,32 +74,55 @@ export const run = async (argvArg: any) => {
}); });
logger.log('info', `Baking commitinfo into code ...`); 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(); await commitInfo.writeIntoPotentialDirs();
logger.log('info', `Writing changelog.md ...`); logger.log('info', `Writing changelog.md ...`);
let changelog = nextCommitObject.changelog; let changelog = nextCommitObject.changelog;
changelog = changelog.replaceAll('{{nextVersion}}', (await commitInfo.getNextPlannedVersion()).versionString); changelog = changelog.replaceAll(
changelog = changelog.replaceAll('{{nextVersionScope}}', `${await answerBucket.getAnswerFor('commitType')}(${await answerBucket.getAnswerFor('commitScope')})`); '{{nextVersion}}',
changelog = changelog.replaceAll('{{nextVersionMessage}}', nextCommitObject.recommendedNextVersionMessage); (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) { if (nextCommitObject.recommendedNextVersionDetails?.length > 0) {
changelog = changelog.replaceAll('{{nextVersionDetails}}', '- ' + nextCommitObject.recommendedNextVersionDetails.join('\n- ')); changelog = changelog.replaceAll(
'{{nextVersionDetails}}',
'- ' + nextCommitObject.recommendedNextVersionDetails.join('\n- '),
);
} else { } else {
changelog = changelog.replaceAll('\n{{nextVersionDetails}}', ''); 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:`); logger.log('info', `Staging files for commit:`);
await smartshellInstance.exec(`git add -A`); await smartshellInstance.exec(`git add -A`);
await smartshellInstance.exec(`git commit -m "${commitString}"`); await smartshellInstance.exec(`git commit -m "${commitString}"`);
await smartshellInstance.exec(`npm version ${commitVersionType}`); 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`); 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 commitType = answerBucket.getAnswerFor('commitType');
const commitScope = answerBucket.getAnswerFor('commitScope'); const commitScope = answerBucket.getAnswerFor('commitScope');
const commitDescription = answerBucket.getAnswerFor('commitDescription'); const commitDescription = answerBucket.getAnswerFor('commitDescription');

View File

@@ -36,7 +36,10 @@ export const run = async () => {
const registryUrls = answerBucket.getAnswerFor(`registryUrls`).split(','); const registryUrls = answerBucket.getAnswerFor(`registryUrls`).split(',');
const oldPackageName = answerBucket.getAnswerFor(`oldPackageName`); const oldPackageName = answerBucket.getAnswerFor(`oldPackageName`);
const newPackageName = answerBucket.getAnswerFor(`newPackageName`); 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({ const smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash', executor: 'bash',
}); });

View File

@@ -43,7 +43,7 @@ export class RollbackManager {
} }
// Read file content and metadata // 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 stats = await plugins.smartfile.fs.stat(absolutePath);
const checksum = this.calculateChecksum(content); const checksum = this.calculateChecksum(content);
@@ -82,7 +82,7 @@ export class RollbackManager {
// Verify backup integrity // Verify backup integrity
const backupPath = this.getBackupPath(operationId, file.path); 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); const backupChecksum = this.calculateChecksum(backupContent);
if (backupChecksum !== file.checksum) { if (backupChecksum !== file.checksum) {
@@ -146,7 +146,7 @@ export class RollbackManager {
return false; return false;
} }
const content = await plugins.smartfile.fs.toStringSync(backupPath); const content = plugins.smartfile.fs.toStringSync(backupPath);
const checksum = this.calculateChecksum(content); const checksum = this.calculateChecksum(content);
if (checksum !== file.checksum) { if (checksum !== file.checksum) {
@@ -185,17 +185,63 @@ export class RollbackManager {
} }
private async getManifest(): Promise<{ operations: IFormatOperation[] }> { private async getManifest(): Promise<{ operations: IFormatOperation[] }> {
const defaultManifest = { operations: [] };
const exists = await plugins.smartfile.fs.fileExists(this.manifestPath); const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
if (!exists) { if (!exists) {
return { operations: [] }; return defaultManifest;
} }
const content = await plugins.smartfile.fs.toStringSync(this.manifestPath); try {
return JSON.parse(content); 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> { 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> { private async getOperation(operationId: string): Promise<IFormatOperation | null> {
@@ -215,4 +261,38 @@ export class RollbackManager {
await this.saveManifest(manifest); 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;
}
} }