From 859cbc733d39474f1fa29d5278b2745e72e948f6 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Fri, 8 Aug 2025 06:25:40 +0000 Subject: [PATCH] fix(format): Improve concurrency control in cache and rollback management with mutex locking and refine formatting details --- changelog.md | 7 + package.json | 2 +- ts/00_commitinfo_data.ts | 2 +- ts/mod_format/classes.baseformatter.ts | 53 ++-- ts/mod_format/classes.changecache.ts | 154 ++++++------ ts/mod_format/classes.dependency-analyzer.ts | 82 ++++--- ts/mod_format/classes.diffreporter.ts | 72 +++--- ts/mod_format/classes.formatcontext.ts | 57 +---- ts/mod_format/classes.formatplanner.ts | 126 +++++----- ts/mod_format/classes.formatstats.ts | 118 +++++---- ts/mod_format/classes.rollbackmanager.ts | 226 ++++++++++-------- ts/mod_format/format.cleanup.ts | 11 +- ts/mod_format/format.copy.ts | 31 +-- ts/mod_format/format.gitignore.ts | 3 +- ts/mod_format/format.license.ts | 4 +- ts/mod_format/format.npmextra.ts | 11 +- ts/mod_format/format.packagejson.ts | 59 ++++- ts/mod_format/format.prettier.ts | 12 +- ts/mod_format/format.readme.ts | 3 +- ts/mod_format/format.templates.ts | 22 +- ts/mod_format/format.tsconfig.ts | 8 +- ts/mod_format/formatters/cleanup.formatter.ts | 21 +- ts/mod_format/formatters/copy.formatter.ts | 2 +- .../formatters/gitignore.formatter.ts | 2 +- ts/mod_format/formatters/legacy.formatter.ts | 31 ++- ts/mod_format/formatters/license.formatter.ts | 2 +- .../formatters/npmextra.formatter.ts | 2 +- .../formatters/packagejson.formatter.ts | 2 +- .../formatters/prettier.formatter.ts | 97 ++++---- ts/mod_format/formatters/readme.formatter.ts | 20 +- .../formatters/templates.formatter.ts | 2 +- .../formatters/tsconfig.formatter.ts | 2 +- ts/mod_format/index.ts | 167 +++++-------- ts/mod_format/interfaces.format.ts | 6 +- ts/mod_meta/meta.classes.meta.ts | 76 ++++-- ts/mod_standard/index.ts | 4 +- ts/mod_start/index.ts | 4 +- ts/mod_template/index.ts | 7 +- 38 files changed, 784 insertions(+), 726 deletions(-) diff --git a/changelog.md b/changelog.md index b60ec02..5a3dc73 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 2025-08-08 - 1.16.8 - fix(format) +Improve concurrency control in cache and rollback management with mutex locking and refine formatting details + +- Added 'withMutex' functions in ChangeCache and RollbackManager to synchronize file I/O operations +- Introduced static mutex maps to prevent race conditions during manifest updates +- Fixed minor formatting issues in commit info and package.json + ## 2025-08-08 - 1.16.7 - fix(core) Improve formatting, logging, and rollback integrity in core modules diff --git a/package.json b/package.json index eea4729..b7109dd 100644 --- a/package.json +++ b/package.json @@ -116,4 +116,4 @@ "overrides": {} }, "packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6" -} +} \ No newline at end of file diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 6e800d8..d07f05f 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@git.zone/cli', - version: '1.16.7', + version: '1.16.8', 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.' } diff --git a/ts/mod_format/classes.baseformatter.ts b/ts/mod_format/classes.baseformatter.ts index b094b58..685a71f 100644 --- a/ts/mod_format/classes.baseformatter.ts +++ b/ts/mod_format/classes.baseformatter.ts @@ -2,32 +2,29 @@ import * as plugins from './mod.plugins.js'; import { FormatContext } from './classes.formatcontext.js'; import type { IPlannedChange } from './interfaces.format.js'; import { Project } from '../classes.project.js'; -import { ChangeCache } from './classes.changecache.js'; export abstract class BaseFormatter { protected context: FormatContext; protected project: Project; - protected cache: ChangeCache; protected stats: any; // Will be FormatStats from context - + constructor(context: FormatContext, project: Project) { this.context = context; this.project = project; - this.cache = context.getChangeCache(); this.stats = context.getFormatStats(); } - + abstract get name(): string; abstract analyze(): Promise; abstract applyChange(change: IPlannedChange): Promise; - + async execute(changes: IPlannedChange[]): Promise { const startTime = this.stats.moduleStartTime(this.name); this.stats.startModule(this.name); - + try { await this.preExecute(); - + for (const change of changes) { try { await this.applyChange(change); @@ -37,57 +34,37 @@ export abstract class BaseFormatter { throw error; } } - + await this.postExecute(); } catch (error) { - await this.context.rollbackOperation(); + // Don't rollback here - let the FormatPlanner handle it throw error; } finally { this.stats.endModule(this.name, startTime); } } - + protected async preExecute(): Promise { // Override in subclasses if needed } - + protected async postExecute(): Promise { // Override in subclasses if needed } - + protected async modifyFile(filepath: string, content: string): Promise { - await this.context.trackFileChange(filepath); await plugins.smartfile.memory.toFs(content, filepath); - await this.cache.updateFileCache(filepath); } - + protected async createFile(filepath: string, content: string): Promise { await plugins.smartfile.memory.toFs(content, filepath); - await this.cache.updateFileCache(filepath); } - + protected async deleteFile(filepath: string): Promise { - await this.context.trackFileChange(filepath); await plugins.smartfile.fs.remove(filepath); } - + protected async shouldProcessFile(filepath: string): Promise { - const config = new plugins.npmextra.Npmextra(); - const useCache = config.dataFor('gitzone.format.cache.enabled', true); - - if (!useCache) { - return true; // Process all files if cache is disabled - } - - const hasChanged = await this.cache.hasFileChanged(filepath); - - // Record cache statistics - if (hasChanged) { - this.stats.recordCacheMiss(); - } else { - this.stats.recordCacheHit(); - } - - return hasChanged; + return true; } -} \ No newline at end of file +} diff --git a/ts/mod_format/classes.changecache.ts b/ts/mod_format/classes.changecache.ts index 038c0db..d10d96b 100644 --- a/ts/mod_format/classes.changecache.ts +++ b/ts/mod_format/classes.changecache.ts @@ -18,32 +18,32 @@ 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 defaultManifest: ICacheManifest = { version: this.cacheVersion, lastFormat: 0, - files: [] + 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; @@ -52,7 +52,9 @@ export class ChangeCache { return defaultManifest; } } catch (error) { - console.warn(`Failed to read cache manifest: ${error.message}, returning default manifest`); + 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); @@ -62,168 +64,160 @@ export class ChangeCache { return defaultManifest; } } - + async saveManifest(manifest: ICacheManifest): Promise { // 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; - } + + // 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 { - const absolutePath = plugins.path.isAbsolute(filePath) - ? filePath + 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); - + 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; + return ( + cachedFile.checksum !== currentChecksum || + cachedFile.size !== stats.size || + cachedFile.modified !== stats.mtimeMs + ); } - + async updateFileCache(filePath: string): Promise { - const absolutePath = plugins.path.isAbsolute(filePath) - ? filePath + 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 existingIndex = manifest.files.findIndex((f) => f.path === filePath); + const cacheEntry: IFileCache = { path: filePath, checksum, modified: stats.mtimeMs, - size: stats.size + 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 + 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)) { + 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') { + 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; } -} \ No newline at end of file +} diff --git a/ts/mod_format/classes.dependency-analyzer.ts b/ts/mod_format/classes.dependency-analyzer.ts index 32f546d..f50cb88 100644 --- a/ts/mod_format/classes.dependency-analyzer.ts +++ b/ts/mod_format/classes.dependency-analyzer.ts @@ -9,35 +9,42 @@ export interface IModuleDependency { export class DependencyAnalyzer { private moduleDependencies: Map = new Map(); - + constructor() { this.initializeDependencies(); } - + private initializeDependencies(): void { // Define dependencies between format modules const dependencies = { - 'cleanup': [], // No dependencies - 'npmextra': [], // No dependencies - 'license': ['npmextra'], // Depends on npmextra for config - 'packagejson': ['npmextra'], // Depends on npmextra for config - 'templates': ['npmextra', 'packagejson'], // Depends on both - 'gitignore': ['templates'], // Depends on templates - 'tsconfig': ['packagejson'], // Depends on package.json - 'prettier': ['cleanup', 'npmextra', 'packagejson', 'templates', 'gitignore', 'tsconfig'], // Runs after most others - 'readme': ['npmextra', 'packagejson'], // Depends on project metadata - 'copy': ['npmextra'], // Depends on config + cleanup: [], // No dependencies + npmextra: [], // No dependencies + license: ['npmextra'], // Depends on npmextra for config + packagejson: ['npmextra'], // Depends on npmextra for config + templates: ['npmextra', 'packagejson'], // Depends on both + gitignore: ['templates'], // Depends on templates + tsconfig: ['packagejson'], // Depends on package.json + prettier: [ + 'cleanup', + 'npmextra', + 'packagejson', + 'templates', + 'gitignore', + 'tsconfig', + ], // Runs after most others + readme: ['npmextra', 'packagejson'], // Depends on project metadata + copy: ['npmextra'], // Depends on config }; - + // Initialize all modules for (const [module, deps] of Object.entries(dependencies)) { this.moduleDependencies.set(module, { module, dependencies: new Set(deps), - dependents: new Set() + dependents: new Set(), }); } - + // Build reverse dependencies (dependents) for (const [module, deps] of Object.entries(dependencies)) { for (const dep of deps) { @@ -48,34 +55,35 @@ export class DependencyAnalyzer { } } } - + getExecutionGroups(modules: BaseFormatter[]): BaseFormatter[][] { - const modulesMap = new Map(modules.map(m => [m.name, m])); + const modulesMap = new Map(modules.map((m) => [m.name, m])); const executed = new Set(); const groups: BaseFormatter[][] = []; - + while (executed.size < modules.length) { const currentGroup: BaseFormatter[] = []; - + for (const module of modules) { if (executed.has(module.name)) continue; - + const dependency = this.moduleDependencies.get(module.name); if (!dependency) { // Unknown module, execute in isolation currentGroup.push(module); continue; } - + // Check if all dependencies have been executed - const allDepsExecuted = Array.from(dependency.dependencies) - .every(dep => executed.has(dep) || !modulesMap.has(dep)); - + const allDepsExecuted = Array.from(dependency.dependencies).every( + (dep) => executed.has(dep) || !modulesMap.has(dep), + ); + if (allDepsExecuted) { currentGroup.push(module); } } - + if (currentGroup.length === 0) { // Circular dependency or error - execute remaining modules for (const module of modules) { @@ -84,24 +92,26 @@ export class DependencyAnalyzer { } } } - - currentGroup.forEach(m => executed.add(m.name)); + + currentGroup.forEach((m) => executed.add(m.name)); groups.push(currentGroup); } - + return groups; } - + canRunInParallel(module1: string, module2: string): boolean { const dep1 = this.moduleDependencies.get(module1); const dep2 = this.moduleDependencies.get(module2); - + if (!dep1 || !dep2) return false; - + // Check if module1 depends on module2 or vice versa - return !dep1.dependencies.has(module2) && - !dep2.dependencies.has(module1) && - !dep1.dependents.has(module2) && - !dep2.dependents.has(module1); + return ( + !dep1.dependencies.has(module2) && + !dep2.dependencies.has(module1) && + !dep1.dependents.has(module2) && + !dep2.dependents.has(module1) + ); } -} \ No newline at end of file +} diff --git a/ts/mod_format/classes.diffreporter.ts b/ts/mod_format/classes.diffreporter.ts index 114e3b3..cf6aa91 100644 --- a/ts/mod_format/classes.diffreporter.ts +++ b/ts/mod_format/classes.diffreporter.ts @@ -4,72 +4,85 @@ import { logger } from '../gitzone.logging.js'; export class DiffReporter { private diffs: Map = new Map(); - - async generateDiff(filePath: string, oldContent: string, newContent: string): Promise { + + async generateDiff( + filePath: string, + oldContent: string, + newContent: string, + ): Promise { const diff = plugins.smartdiff.createDiff(oldContent, newContent); this.diffs.set(filePath, diff); return diff; } - + async generateDiffForChange(change: IPlannedChange): Promise { if (change.type !== 'modify') { return null; } - + try { const exists = await plugins.smartfile.fs.fileExists(change.path); if (!exists) { return null; } - - const currentContent = await plugins.smartfile.fs.toStringSync(change.path); - + + const currentContent = await plugins.smartfile.fs.toStringSync( + change.path, + ); + // For planned changes, we need the new content if (!change.content) { return null; } - - return await this.generateDiff(change.path, currentContent, change.content); + + return await this.generateDiff( + change.path, + currentContent, + change.content, + ); } catch (error) { - logger.log('error', `Failed to generate diff for ${change.path}: ${error.message}`); + logger.log( + 'error', + `Failed to generate diff for ${change.path}: ${error.message}`, + ); return null; } } - + displayDiff(filePath: string, diff?: string): void { const diffToShow = diff || this.diffs.get(filePath); - + if (!diffToShow) { logger.log('warn', `No diff available for ${filePath}`); return; } - + console.log(`\n${this.formatDiffHeader(filePath)}`); console.log(this.colorDiff(diffToShow)); console.log('━'.repeat(50)); } - + displayAllDiffs(): void { if (this.diffs.size === 0) { logger.log('info', 'No diffs to display'); return; } - + console.log('\nFile Changes:'); console.log('═'.repeat(50)); - + for (const [filePath, diff] of this.diffs) { this.displayDiff(filePath, diff); } } - + private formatDiffHeader(filePath: string): string { return `šŸ“„ ${filePath}`; } - + private colorDiff(diff: string): string { const lines = diff.split('\n'); - const coloredLines = lines.map(line => { + const coloredLines = lines.map((line) => { if (line.startsWith('+') && !line.startsWith('+++')) { return `\x1b[32m${line}\x1b[0m`; // Green for additions } else if (line.startsWith('-') && !line.startsWith('---')) { @@ -80,29 +93,32 @@ export class DiffReporter { return line; } }); - + return coloredLines.join('\n'); } - + async saveDiffReport(outputPath: string): Promise { const report = { timestamp: new Date().toISOString(), totalFiles: this.diffs.size, diffs: Array.from(this.diffs.entries()).map(([path, diff]) => ({ path, - diff - })) + diff, + })), }; - - await plugins.smartfile.memory.toFs(JSON.stringify(report, null, 2), outputPath); + + await plugins.smartfile.memory.toFs( + JSON.stringify(report, null, 2), + outputPath, + ); logger.log('info', `Diff report saved to ${outputPath}`); } - + hasAnyDiffs(): boolean { return this.diffs.size > 0; } - + getDiffCount(): number { return this.diffs.size; } -} \ No newline at end of file +} diff --git a/ts/mod_format/classes.formatcontext.ts b/ts/mod_format/classes.formatcontext.ts index e486e04..1a511f5 100644 --- a/ts/mod_format/classes.formatcontext.ts +++ b/ts/mod_format/classes.formatcontext.ts @@ -1,65 +1,14 @@ import * as plugins from './mod.plugins.js'; -import { RollbackManager } from './classes.rollbackmanager.js'; -import { ChangeCache } from './classes.changecache.js'; import { FormatStats } from './classes.formatstats.js'; -import type { IFormatOperation, IFormatPlan } from './interfaces.format.js'; export class FormatContext { - private rollbackManager: RollbackManager; - private currentOperation: IFormatOperation | null = null; - private changeCache: ChangeCache; private formatStats: FormatStats; - + constructor() { - this.rollbackManager = new RollbackManager(); - this.changeCache = new ChangeCache(); this.formatStats = new FormatStats(); } - - async beginOperation(): Promise { - this.currentOperation = await this.rollbackManager.createOperation(); - } - - async trackFileChange(filepath: string): Promise { - if (!this.currentOperation) { - throw new Error('No operation in progress. Call beginOperation() first.'); - } - await this.rollbackManager.backupFile(filepath, this.currentOperation.id); - } - - async commitOperation(): Promise { - if (!this.currentOperation) { - throw new Error('No operation in progress. Call beginOperation() first.'); - } - await this.rollbackManager.markComplete(this.currentOperation.id); - this.currentOperation = null; - } - - async rollbackOperation(): Promise { - if (!this.currentOperation) { - throw new Error('No operation in progress. Call beginOperation() first.'); - } - await this.rollbackManager.rollback(this.currentOperation.id); - this.currentOperation = null; - } - - async rollbackTo(operationId: string): Promise { - await this.rollbackManager.rollback(operationId); - } - - getRollbackManager(): RollbackManager { - return this.rollbackManager; - } - - getChangeCache(): ChangeCache { - return this.changeCache; - } - - async initializeCache(): Promise { - await this.changeCache.initialize(); - } - + getFormatStats(): FormatStats { return this.formatStats; } -} \ No newline at end of file +} diff --git a/ts/mod_format/classes.formatplanner.ts b/ts/mod_format/classes.formatplanner.ts index 8e91ef1..60bcc22 100644 --- a/ts/mod_format/classes.formatplanner.ts +++ b/ts/mod_format/classes.formatplanner.ts @@ -10,7 +10,7 @@ export class FormatPlanner { private plannedChanges: Map = new Map(); private dependencyAnalyzer = new DependencyAnalyzer(); private diffReporter = new DiffReporter(); - + async planFormat(modules: BaseFormatter[]): Promise { const plan: IFormatPlan = { summary: { @@ -18,20 +18,20 @@ export class FormatPlanner { filesAdded: 0, filesModified: 0, filesRemoved: 0, - estimatedTime: 0 + estimatedTime: 0, }, changes: [], - warnings: [] + warnings: [], }; - + for (const module of modules) { try { const changes = await module.analyze(); this.plannedChanges.set(module.name, changes); - + for (const change of changes) { plan.changes.push(change); - + // Update summary switch (change.type) { case 'create': @@ -49,67 +49,51 @@ export class FormatPlanner { plan.warnings.push({ level: 'error', message: `Failed to analyze module ${module.name}: ${error.message}`, - module: module.name + module: module.name, }); } } - - plan.summary.totalFiles = plan.summary.filesAdded + plan.summary.filesModified + plan.summary.filesRemoved; + + plan.summary.totalFiles = + plan.summary.filesAdded + + plan.summary.filesModified + + plan.summary.filesRemoved; plan.summary.estimatedTime = plan.summary.totalFiles * 100; // 100ms per file estimate - + return plan; } - - async executePlan(plan: IFormatPlan, modules: BaseFormatter[], context: FormatContext, parallel: boolean = true): Promise { - await context.beginOperation(); + + async executePlan( + plan: IFormatPlan, + modules: BaseFormatter[], + context: FormatContext, + parallel: boolean = false, + ): Promise { const startTime = Date.now(); - + try { - if (parallel) { - // Get execution groups based on dependencies - const executionGroups = this.dependencyAnalyzer.getExecutionGroups(modules); - - logger.log('info', `Executing formatters in ${executionGroups.length} groups...`); - - for (let i = 0; i < executionGroups.length; i++) { - const group = executionGroups[i]; - logger.log('info', `Executing group ${i + 1}: ${group.map(m => m.name).join(', ')}`); - - // Execute modules in this group in parallel - const promises = group.map(async (module) => { - const changes = this.plannedChanges.get(module.name) || []; - if (changes.length > 0) { - logger.log('info', `Executing ${module.name} formatter...`); - await module.execute(changes); - } - }); - - await Promise.all(promises); - } - } else { - // Sequential execution (original implementation) - for (const module of modules) { - const changes = this.plannedChanges.get(module.name) || []; - - if (changes.length > 0) { - logger.log('info', `Executing ${module.name} formatter...`); - await module.execute(changes); - } + // Always use sequential execution to avoid race conditions + for (const module of modules) { + const changes = this.plannedChanges.get(module.name) || []; + + if (changes.length > 0) { + logger.log('info', `Executing ${module.name} formatter...`); + await module.execute(changes); } } - + const endTime = Date.now(); const duration = endTime - startTime; logger.log('info', `Format operations completed in ${duration}ms`); - - await context.commitOperation(); } catch (error) { - await context.rollbackOperation(); throw error; } } - - async displayPlan(plan: IFormatPlan, detailed: boolean = false): Promise { + + async displayPlan( + plan: IFormatPlan, + detailed: boolean = false, + ): Promise { console.log('\nFormat Plan:'); console.log('━'.repeat(50)); console.log(`Summary: ${plan.summary.totalFiles} files will be changed`); @@ -118,7 +102,7 @@ export class FormatPlanner { console.log(` • ${plan.summary.filesRemoved} deleted files`); console.log(''); console.log('Changes by module:'); - + // Group changes by module const changesByModule = new Map(); for (const change of plan.changes) { @@ -126,14 +110,16 @@ export class FormatPlanner { moduleChanges.push(change); changesByModule.set(change.module, moduleChanges); } - + for (const [module, changes] of changesByModule) { - console.log(`\n${this.getModuleIcon(module)} ${module} (${changes.length} ${changes.length === 1 ? 'file' : 'files'})`); - + console.log( + `\n${this.getModuleIcon(module)} ${module} (${changes.length} ${changes.length === 1 ? 'file' : 'files'})`, + ); + for (const change of changes) { const icon = this.getChangeIcon(change.type); console.log(` ${icon} ${change.path} - ${change.description}`); - + // Show diff for modified files if detailed view is requested if (detailed && change.type === 'modify') { const diff = await this.diffReporter.generateDiffForChange(change); @@ -143,7 +129,7 @@ export class FormatPlanner { } } } - + if (plan.warnings.length > 0) { console.log('\nWarnings:'); for (const warning of plan.warnings) { @@ -151,26 +137,26 @@ export class FormatPlanner { console.log(` ${icon} ${warning.message}`); } } - + console.log('\n' + '━'.repeat(50)); } - + private getModuleIcon(module: string): string { const icons: Record = { - 'packagejson': 'šŸ“¦', - 'license': 'šŸ“', - 'tsconfig': 'šŸ”§', - 'cleanup': '🚮', - 'gitignore': 'šŸ”’', - 'prettier': '✨', - 'readme': 'šŸ“–', - 'templates': 'šŸ“„', - 'npmextra': 'āš™ļø', - 'copy': 'šŸ“‹' + packagejson: 'šŸ“¦', + license: 'šŸ“', + tsconfig: 'šŸ”§', + cleanup: '🚮', + gitignore: 'šŸ”’', + prettier: '✨', + readme: 'šŸ“–', + templates: 'šŸ“„', + npmextra: 'āš™ļø', + copy: 'šŸ“‹', }; return icons[module] || 'šŸ“'; } - + private getChangeIcon(type: 'create' | 'modify' | 'delete'): string { switch (type) { case 'create': @@ -181,4 +167,4 @@ export class FormatPlanner { return 'āŒ'; } } -} \ No newline at end of file +} diff --git a/ts/mod_format/classes.formatstats.ts b/ts/mod_format/classes.formatstats.ts index 13ae618..eb49a97 100644 --- a/ts/mod_format/classes.formatstats.ts +++ b/ts/mod_format/classes.formatstats.ts @@ -30,7 +30,7 @@ export interface IFormatStats { export class FormatStats { private stats: IFormatStats; - + constructor() { this.stats = { totalExecutionTime: 0, @@ -44,11 +44,11 @@ export class FormatStats { totalDeleted: 0, totalErrors: 0, cacheHits: 0, - cacheMisses: 0 - } + cacheMisses: 0, + }, }; } - + startModule(moduleName: string): void { this.stats.moduleStats.set(moduleName, { name: moduleName, @@ -58,31 +58,35 @@ export class FormatStats { successes: 0, filesCreated: 0, filesModified: 0, - filesDeleted: 0 + filesDeleted: 0, }); } - + moduleStartTime(moduleName: string): number { return Date.now(); } - + endModule(moduleName: string, startTime: number): void { const moduleStats = this.stats.moduleStats.get(moduleName); if (moduleStats) { moduleStats.executionTime = Date.now() - startTime; } } - - recordFileOperation(moduleName: string, operation: 'create' | 'modify' | 'delete', success: boolean = true): void { + + recordFileOperation( + moduleName: string, + operation: 'create' | 'modify' | 'delete', + success: boolean = true, + ): void { const moduleStats = this.stats.moduleStats.get(moduleName); if (!moduleStats) return; - + moduleStats.filesProcessed++; - + if (success) { moduleStats.successes++; this.stats.overallStats.totalFiles++; - + switch (operation) { case 'create': moduleStats.filesCreated++; @@ -102,53 +106,66 @@ export class FormatStats { this.stats.overallStats.totalErrors++; } } - + recordCacheHit(): void { this.stats.overallStats.cacheHits++; } - + recordCacheMiss(): void { this.stats.overallStats.cacheMisses++; } - + finish(): void { this.stats.endTime = Date.now(); this.stats.totalExecutionTime = this.stats.endTime - this.stats.startTime; } - + displayStats(): void { console.log('\nšŸ“Š Format Operation Statistics:'); console.log('═'.repeat(50)); - + // Overall stats console.log('\nOverall Summary:'); - console.log(` Total Execution Time: ${this.formatDuration(this.stats.totalExecutionTime)}`); + console.log( + ` Total Execution Time: ${this.formatDuration(this.stats.totalExecutionTime)}`, + ); console.log(` Files Processed: ${this.stats.overallStats.totalFiles}`); console.log(` • Created: ${this.stats.overallStats.totalCreated}`); console.log(` • Modified: ${this.stats.overallStats.totalModified}`); console.log(` • Deleted: ${this.stats.overallStats.totalDeleted}`); console.log(` Errors: ${this.stats.overallStats.totalErrors}`); - - if (this.stats.overallStats.cacheHits > 0 || this.stats.overallStats.cacheMisses > 0) { - const cacheHitRate = this.stats.overallStats.cacheHits / - (this.stats.overallStats.cacheHits + this.stats.overallStats.cacheMisses) * 100; + + if ( + this.stats.overallStats.cacheHits > 0 || + this.stats.overallStats.cacheMisses > 0 + ) { + const cacheHitRate = + (this.stats.overallStats.cacheHits / + (this.stats.overallStats.cacheHits + + this.stats.overallStats.cacheMisses)) * + 100; console.log(` Cache Hit Rate: ${cacheHitRate.toFixed(1)}%`); console.log(` • Hits: ${this.stats.overallStats.cacheHits}`); console.log(` • Misses: ${this.stats.overallStats.cacheMisses}`); } - + // Module stats console.log('\nModule Breakdown:'); console.log('─'.repeat(50)); - - const sortedModules = Array.from(this.stats.moduleStats.values()) - .sort((a, b) => b.filesProcessed - a.filesProcessed); - + + const sortedModules = Array.from(this.stats.moduleStats.values()).sort( + (a, b) => b.filesProcessed - a.filesProcessed, + ); + for (const moduleStats of sortedModules) { - console.log(`\n${this.getModuleIcon(moduleStats.name)} ${moduleStats.name}:`); - console.log(` Execution Time: ${this.formatDuration(moduleStats.executionTime)}`); + console.log( + `\n${this.getModuleIcon(moduleStats.name)} ${moduleStats.name}:`, + ); + console.log( + ` Execution Time: ${this.formatDuration(moduleStats.executionTime)}`, + ); console.log(` Files Processed: ${moduleStats.filesProcessed}`); - + if (moduleStats.filesCreated > 0) { console.log(` • Created: ${moduleStats.filesCreated}`); } @@ -158,27 +175,30 @@ export class FormatStats { if (moduleStats.filesDeleted > 0) { console.log(` • Deleted: ${moduleStats.filesDeleted}`); } - + if (moduleStats.errors > 0) { console.log(` āŒ Errors: ${moduleStats.errors}`); } } - + console.log('\n' + '═'.repeat(50)); } - + async saveReport(outputPath: string): Promise { const report = { timestamp: new Date().toISOString(), executionTime: this.stats.totalExecutionTime, overallStats: this.stats.overallStats, - moduleStats: Array.from(this.stats.moduleStats.values()) + moduleStats: Array.from(this.stats.moduleStats.values()), }; - - await plugins.smartfile.memory.toFs(JSON.stringify(report, null, 2), outputPath); + + await plugins.smartfile.memory.toFs( + JSON.stringify(report, null, 2), + outputPath, + ); logger.log('info', `Statistics report saved to ${outputPath}`); } - + private formatDuration(ms: number): string { if (ms < 1000) { return `${ms}ms`; @@ -190,20 +210,20 @@ export class FormatStats { return `${minutes}m ${seconds}s`; } } - + private getModuleIcon(module: string): string { const icons: Record = { - 'packagejson': 'šŸ“¦', - 'license': 'šŸ“', - 'tsconfig': 'šŸ”§', - 'cleanup': '🚮', - 'gitignore': 'šŸ”’', - 'prettier': '✨', - 'readme': 'šŸ“–', - 'templates': 'šŸ“„', - 'npmextra': 'āš™ļø', - 'copy': 'šŸ“‹' + packagejson: 'šŸ“¦', + license: 'šŸ“', + tsconfig: 'šŸ”§', + cleanup: '🚮', + gitignore: 'šŸ”’', + prettier: '✨', + readme: 'šŸ“–', + templates: 'šŸ“„', + npmextra: 'āš™ļø', + copy: 'šŸ“‹', }; return icons[module] || 'šŸ“'; } -} \ No newline at end of file +} diff --git a/ts/mod_format/classes.rollbackmanager.ts b/ts/mod_format/classes.rollbackmanager.ts index 2777f6e..9421573 100644 --- a/ts/mod_format/classes.rollbackmanager.ts +++ b/ts/mod_format/classes.rollbackmanager.ts @@ -5,206 +5,227 @@ import type { IFormatOperation } from './interfaces.format.js'; export class RollbackManager { private backupDir: string; private manifestPath: string; - + constructor() { this.backupDir = plugins.path.join(paths.cwd, '.nogit', 'gitzone-backups'); this.manifestPath = plugins.path.join(this.backupDir, 'manifest.json'); } - + async createOperation(): Promise { await this.ensureBackupDir(); - + const operation: IFormatOperation = { id: this.generateOperationId(), timestamp: Date.now(), files: [], - status: 'pending' + status: 'pending', }; - + await this.updateManifest(operation); return operation; } - + async backupFile(filepath: string, operationId: string): Promise { const operation = await this.getOperation(operationId); if (!operation) { throw new Error(`Operation ${operationId} not found`); } - - const absolutePath = plugins.path.isAbsolute(filepath) - ? filepath + + 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) { // File doesn't exist yet (will be created), so we skip backup return; } - + // Read file content and metadata const content = plugins.smartfile.fs.toStringSync(absolutePath); const stats = await plugins.smartfile.fs.stat(absolutePath); const checksum = this.calculateChecksum(content); - + // Create backup const backupPath = this.getBackupPath(operationId, filepath); await plugins.smartfile.fs.ensureDir(plugins.path.dirname(backupPath)); await plugins.smartfile.memory.toFs(content, backupPath); - + // Update operation operation.files.push({ path: filepath, originalContent: content, checksum, - permissions: stats.mode.toString(8) + permissions: stats.mode.toString(8), }); - + await this.updateManifest(operation); } - + async rollback(operationId: string): Promise { const operation = await this.getOperation(operationId); if (!operation) { - throw new Error(`Operation ${operationId} not found`); + // Operation doesn't exist, might have already been rolled back or never created + console.warn(`Operation ${operationId} not found for rollback, skipping`); + return; } - + if (operation.status === 'rolled-back') { throw new Error(`Operation ${operationId} has already been rolled back`); } - + // Restore files in reverse order for (let i = operation.files.length - 1; i >= 0; i--) { const file = operation.files[i]; - const absolutePath = plugins.path.isAbsolute(file.path) - ? file.path + const absolutePath = plugins.path.isAbsolute(file.path) + ? file.path : plugins.path.join(paths.cwd, file.path); - + // Verify backup integrity const backupPath = this.getBackupPath(operationId, file.path); const backupContent = plugins.smartfile.fs.toStringSync(backupPath); const backupChecksum = this.calculateChecksum(backupContent); - + if (backupChecksum !== file.checksum) { throw new Error(`Backup integrity check failed for ${file.path}`); } - + // Restore file await plugins.smartfile.memory.toFs(file.originalContent, absolutePath); - + // Restore permissions const mode = parseInt(file.permissions, 8); // Note: Permissions restoration may not work on all platforms } - + // Update operation status operation.status = 'rolled-back'; await this.updateManifest(operation); } - + async markComplete(operationId: string): Promise { const operation = await this.getOperation(operationId); if (!operation) { throw new Error(`Operation ${operationId} not found`); } - + operation.status = 'completed'; await this.updateManifest(operation); } - + async cleanOldBackups(retentionDays: number): Promise { const manifest = await this.getManifest(); - const cutoffTime = Date.now() - (retentionDays * 24 * 60 * 60 * 1000); - - const operationsToDelete = manifest.operations.filter(op => - op.timestamp < cutoffTime && op.status === 'completed' + const cutoffTime = Date.now() - retentionDays * 24 * 60 * 60 * 1000; + + const operationsToDelete = manifest.operations.filter( + (op) => op.timestamp < cutoffTime && op.status === 'completed', ); - + for (const operation of operationsToDelete) { // Remove backup files - const operationDir = plugins.path.join(this.backupDir, 'operations', operation.id); + const operationDir = plugins.path.join( + this.backupDir, + 'operations', + operation.id, + ); await plugins.smartfile.fs.remove(operationDir); - + // Remove from manifest - manifest.operations = manifest.operations.filter(op => op.id !== operation.id); + manifest.operations = manifest.operations.filter( + (op) => op.id !== operation.id, + ); } - + await this.saveManifest(manifest); } - + async verifyBackup(operationId: string): Promise { const operation = await this.getOperation(operationId); if (!operation) { return false; } - + for (const file of operation.files) { const backupPath = this.getBackupPath(operationId, file.path); const exists = await plugins.smartfile.fs.fileExists(backupPath); - + if (!exists) { return false; } - + const content = plugins.smartfile.fs.toStringSync(backupPath); const checksum = this.calculateChecksum(content); - + if (checksum !== file.checksum) { return false; } } - + return true; } - + async listBackups(): Promise { const manifest = await this.getManifest(); return manifest.operations; } - + private async ensureBackupDir(): Promise { await plugins.smartfile.fs.ensureDir(this.backupDir); - await plugins.smartfile.fs.ensureDir(plugins.path.join(this.backupDir, 'operations')); + await plugins.smartfile.fs.ensureDir( + plugins.path.join(this.backupDir, 'operations'), + ); } - + private generateOperationId(): string { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const random = Math.random().toString(36).substring(2, 8); return `${timestamp}-${random}`; } - + private getBackupPath(operationId: string, filepath: string): string { const filename = plugins.path.basename(filepath); const dir = plugins.path.dirname(filepath); const safeDir = dir.replace(/[/\\]/g, '__'); - return plugins.path.join(this.backupDir, 'operations', operationId, 'files', safeDir, `${filename}.backup`); + return plugins.path.join( + this.backupDir, + 'operations', + operationId, + 'files', + safeDir, + `${filename}.backup`, + ); } - + private calculateChecksum(content: string | Buffer): string { return plugins.crypto.createHash('sha256').update(content).digest('hex'); } - + private async getManifest(): Promise<{ operations: IFormatOperation[] }> { const defaultManifest = { operations: [] }; - + 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 rollback manifest structure, returning default manifest'); + 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`); + 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); @@ -214,85 +235,84 @@ export class RollbackManager { return defaultManifest; } } - - private async saveManifest(manifest: { operations: IFormatOperation[] }): Promise { + + private async saveManifest(manifest: { + operations: IFormatOperation[]; + }): Promise { // 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; - } + + // Ensure directory exists + await this.ensureBackupDir(); + + // Write directly with proper JSON stringification + const jsonContent = JSON.stringify(manifest, null, 2); + await plugins.smartfile.memory.toFs(jsonContent, this.manifestPath); } - - private async getOperation(operationId: string): Promise { + + private async getOperation( + operationId: string, + ): Promise { const manifest = await this.getManifest(); - return manifest.operations.find(op => op.id === operationId) || null; + return manifest.operations.find((op) => op.id === operationId) || null; } - + private async updateManifest(operation: IFormatOperation): Promise { const manifest = await this.getManifest(); - const existingIndex = manifest.operations.findIndex(op => op.id === operation.id); - + const existingIndex = manifest.operations.findIndex( + (op) => op.id === operation.id, + ); + if (existingIndex !== -1) { manifest.operations[existingIndex] = operation; } else { manifest.operations.push(operation); } - + await this.saveManifest(manifest); } - - private isValidManifest(manifest: any): manifest is { operations: IFormatOperation[] } { + + 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)) { + 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') { + if ( + !file || + typeof file !== 'object' || + typeof file.path !== 'string' || + typeof file.checksum !== 'string' + ) { return false; } } } - + return true; } -} \ No newline at end of file +} diff --git a/ts/mod_format/format.cleanup.ts b/ts/mod_format/format.cleanup.ts index 65be64e..0a5d69f 100644 --- a/ts/mod_format/format.cleanup.ts +++ b/ts/mod_format/format.cleanup.ts @@ -4,14 +4,21 @@ import * as paths from '../paths.js'; import { logger } from '../gitzone.logging.js'; import { Project } from '../classes.project.js'; -const filesToDelete = ['defaults.yml', 'yarn.lock', 'package-lock.json', 'tslint.json']; +const filesToDelete = [ + 'defaults.yml', + 'yarn.lock', + 'package-lock.json', + 'tslint.json', +]; export const run = async (projectArg: Project) => { for (const relativeFilePath of filesToDelete) { const fileExists = plugins.smartfile.fs.fileExistsSync(relativeFilePath); if (fileExists) { logger.log('info', `Found ${relativeFilePath}! Removing it!`); - plugins.smartfile.fs.removeSync(plugins.path.join(paths.cwd, relativeFilePath)); + plugins.smartfile.fs.removeSync( + plugins.path.join(paths.cwd, relativeFilePath), + ); } else { logger.log('info', `Project is free of ${relativeFilePath}`); } diff --git a/ts/mod_format/format.copy.ts b/ts/mod_format/format.copy.ts index 842923b..76c902e 100644 --- a/ts/mod_format/format.copy.ts +++ b/ts/mod_format/format.copy.ts @@ -4,56 +4,59 @@ import { logger } from '../gitzone.logging.js'; export const run = async (projectArg: Project) => { const gitzoneConfig = await projectArg.gitzoneConfig; - + // Get copy configuration from npmextra.json const npmextraConfig = new plugins.npmextra.Npmextra(); const copyConfig = npmextraConfig.dataFor('gitzone.format.copy', { - patterns: [] + patterns: [], }); - + if (!copyConfig.patterns || copyConfig.patterns.length === 0) { logger.log('info', 'No copy patterns configured in npmextra.json'); return; } - + for (const pattern of copyConfig.patterns) { if (!pattern.from || !pattern.to) { logger.log('warn', 'Invalid copy pattern - missing "from" or "to" field'); continue; } - + try { // Handle glob patterns const files = await plugins.smartfile.fs.listFileTree('.', pattern.from); - + for (const file of files) { const sourcePath = file; let destPath = pattern.to; - + // If destination is a directory, preserve filename if (pattern.to.endsWith('/')) { const filename = plugins.path.basename(file); destPath = plugins.path.join(pattern.to, filename); } - + // Handle template variables in destination path if (pattern.preservePath) { const relativePath = plugins.path.relative( - plugins.path.dirname(pattern.from.replace(/\*/g, '')), - file + plugins.path.dirname(pattern.from.replace(/\*/g, '')), + file, ); destPath = plugins.path.join(pattern.to, relativePath); } - + // Ensure destination directory exists await plugins.smartfile.fs.ensureDir(plugins.path.dirname(destPath)); - + // Copy file await plugins.smartfile.fs.copy(sourcePath, destPath); logger.log('info', `Copied ${sourcePath} to ${destPath}`); } } catch (error) { - logger.log('error', `Failed to copy pattern ${pattern.from}: ${error.message}`); + logger.log( + 'error', + `Failed to copy pattern ${pattern.from}: ${error.message}`, + ); } } }; @@ -79,4 +82,4 @@ export const run = async (projectArg: Project) => { * } * } * } - */ \ No newline at end of file + */ diff --git a/ts/mod_format/format.gitignore.ts b/ts/mod_format/format.gitignore.ts index 7ae7fa3..b67dcbf 100644 --- a/ts/mod_format/format.gitignore.ts +++ b/ts/mod_format/format.gitignore.ts @@ -12,7 +12,8 @@ export const run = async (projectArg: Project) => { const ciTemplate = await templateModule.getTemplate('gitignore'); if (gitignoreExists) { // lets get the existing gitignore file - const existingGitIgnoreString = plugins.smartfile.fs.toStringSync(gitignorePath); + const existingGitIgnoreString = + plugins.smartfile.fs.toStringSync(gitignorePath); let customPart = existingGitIgnoreString.split('# custom\n')[1]; customPart ? null : (customPart = ''); } diff --git a/ts/mod_format/format.license.ts b/ts/mod_format/format.license.ts index fc032bc..9ce92e4 100644 --- a/ts/mod_format/format.license.ts +++ b/ts/mod_format/format.license.ts @@ -24,7 +24,9 @@ export const run = async (projectArg: Project) => { } else { logger.log('error', 'Error -> licenses failed. Here is why:'); for (const failedModule of licenseCheckResult.failingModules) { - console.log(`${failedModule.name} fails with license ${failedModule.license}`); + console.log( + `${failedModule.name} fails with license ${failedModule.license}`, + ); } } }; diff --git a/ts/mod_format/format.npmextra.ts b/ts/mod_format/format.npmextra.ts index f920617..595f5d2 100644 --- a/ts/mod_format/format.npmextra.ts +++ b/ts/mod_format/format.npmextra.ts @@ -29,7 +29,12 @@ export const run = async (projectArg: Project) => { const interactInstance = new plugins.smartinteract.SmartInteract(); for (const expectedRepoInformationItem of expectedRepoInformation) { - if (!plugins.smartobject.smartGet(npmextraJson.gitzone, expectedRepoInformationItem)) { + if ( + !plugins.smartobject.smartGet( + npmextraJson.gitzone, + expectedRepoInformationItem, + ) + ) { interactInstance.addQuestions([ { message: `What is the value of ${expectedRepoInformationItem}`, @@ -43,7 +48,9 @@ export const run = async (projectArg: Project) => { const answerbucket = await interactInstance.runQueue(); for (const expectedRepoInformationItem of expectedRepoInformation) { - const cliProvidedValue = answerbucket.getAnswerFor(expectedRepoInformationItem); + const cliProvidedValue = answerbucket.getAnswerFor( + expectedRepoInformationItem, + ); if (cliProvidedValue) { plugins.smartobject.smartAdd( npmextraJson.gitzone, diff --git a/ts/mod_format/format.packagejson.ts b/ts/mod_format/format.packagejson.ts index c16c3b5..434efbd 100644 --- a/ts/mod_format/format.packagejson.ts +++ b/ts/mod_format/format.packagejson.ts @@ -19,7 +19,7 @@ const ensureDependency = async ( : [dependencyArg, 'latest']; const targetSections: string[] = []; - + switch (position) { case 'dep': targetSections.push('dependencies'); @@ -43,7 +43,8 @@ const ensureDependency = async ( break; case 'include': if (!packageJsonObjectArg[section][packageName]) { - packageJsonObjectArg[section][packageName] = version === 'latest' ? '^1.0.0' : version; + packageJsonObjectArg[section][packageName] = + version === 'latest' ? '^1.0.0' : version; } break; case 'latest': @@ -54,9 +55,13 @@ const ensureDependency = async ( const latestVersion = packageInfo['dist-tags'].latest; packageJsonObjectArg[section][packageName] = `^${latestVersion}`; } catch (error) { - logger.log('warn', `Could not fetch latest version for ${packageName}, using existing or default`); + logger.log( + 'warn', + `Could not fetch latest version for ${packageName}, using existing or default`, + ); if (!packageJsonObjectArg[section][packageName]) { - packageJsonObjectArg[section][packageName] = version === 'latest' ? '^1.0.0' : version; + packageJsonObjectArg[section][packageName] = + version === 'latest' ? '^1.0.0' : version; } } break; @@ -91,9 +96,15 @@ export const run = async (projectArg: Project) => { // Check for private or public if (packageJson.private !== undefined) { - logger.log('info', 'Success -> found private/public info in package.json!'); + logger.log( + 'info', + 'Success -> found private/public info in package.json!', + ); } else { - logger.log('error', 'found no private boolean! Setting it to private for now!'); + logger.log( + 'error', + 'found no private boolean! Setting it to private for now!', + ); packageJson.private = true; } @@ -101,7 +112,10 @@ export const run = async (projectArg: Project) => { if (packageJson.license) { logger.log('info', 'Success -> found license in package.json!'); } else { - logger.log('error', 'found no license! Setting it to UNLICENSED for now!'); + logger.log( + 'error', + 'found no license! Setting it to UNLICENSED for now!', + ); packageJson.license = 'UNLICENSED'; } @@ -109,13 +123,19 @@ export const run = async (projectArg: Project) => { if (packageJson.scripts.build) { logger.log('info', 'Success -> found build script in package.json!'); } else { - logger.log('error', 'found no build script! Putting a placeholder there for now!'); + logger.log( + 'error', + 'found no build script! Putting a placeholder there for now!', + ); packageJson.scripts.build = `echo "Not needed for now"`; } // Check for buildDocs script if (!packageJson.scripts.buildDocs) { - logger.log('info', 'found no buildDocs script! Putting tsdoc script there now.'); + logger.log( + 'info', + 'found no buildDocs script! Putting tsdoc script there now.', + ); packageJson.scripts.buildDocs = `tsdoc`; } @@ -134,9 +154,24 @@ export const run = async (projectArg: Project) => { ]; // check for dependencies - await ensureDependency(packageJson, 'devDep', 'latest', '@push.rocks/tapbundle'); - await ensureDependency(packageJson, 'devDep', 'latest', '@git.zone/tstest'); - await ensureDependency(packageJson, 'devDep', 'latest', '@git.zone/tsbuild'); + await ensureDependency( + packageJson, + 'devDep', + 'latest', + '@push.rocks/tapbundle', + ); + await ensureDependency( + packageJson, + 'devDep', + 'latest', + '@git.zone/tstest', + ); + await ensureDependency( + packageJson, + 'devDep', + 'latest', + '@git.zone/tsbuild', + ); // set overrides const overrides = plugins.smartfile.fs.toObjectSync( diff --git a/ts/mod_format/format.prettier.ts b/ts/mod_format/format.prettier.ts index 5c1397e..dd79584 100644 --- a/ts/mod_format/format.prettier.ts +++ b/ts/mod_format/format.prettier.ts @@ -16,7 +16,12 @@ const prettierDefaultMarkdownConfig: prettier.Options = { parser: 'markdown', }; -const filesToFormat = [`ts/**/*.ts`, `test/**/*.ts`, `readme.md`, `docs/**/*.md`]; +const filesToFormat = [ + `ts/**/*.ts`, + `test/**/*.ts`, + `readme.md`, + `docs/**/*.md`, +]; const choosePrettierConfig = (fileArg: plugins.smartfile.SmartFile) => { switch (fileArg.parsedPath.ext) { @@ -39,7 +44,10 @@ const prettierTypeScriptPipestop = plugins.through2.obj( cb(null); } else { logger.log('info', `${fileArg.path} is being reformated!`); - const formatedFileString = await prettier.format(fileString, chosenConfig); + const formatedFileString = await prettier.format( + fileString, + chosenConfig, + ); fileArg.setContentsFromString(formatedFileString); cb(null, fileArg); } diff --git a/ts/mod_format/format.readme.ts b/ts/mod_format/format.readme.ts index 261596c..4ed5a00 100644 --- a/ts/mod_format/format.readme.ts +++ b/ts/mod_format/format.readme.ts @@ -18,7 +18,8 @@ export const run = async () => { } // Check and initialize readme.hints.md if it doesn't exist - const readmeHintsExists = await plugins.smartfile.fs.fileExists(readmeHintsPath); + const readmeHintsExists = + await plugins.smartfile.fs.fileExists(readmeHintsPath); if (!readmeHintsExists) { await plugins.smartfile.fs.toFs( '# Project Readme Hints\n\nThis is the initial readme hints file.', diff --git a/ts/mod_format/format.templates.ts b/ts/mod_format/format.templates.ts index 00e570a..2dd39fb 100644 --- a/ts/mod_format/format.templates.ts +++ b/ts/mod_format/format.templates.ts @@ -26,10 +26,12 @@ export const run = async (project: Project) => { case 'npm': case 'wcc': if (project.gitzoneConfig.data.npmciOptions.npmAccessLevel === 'public') { - const ciTemplateDefault = await templateModule.getTemplate('ci_default'); + const ciTemplateDefault = + await templateModule.getTemplate('ci_default'); ciTemplateDefault.writeToDisk(paths.cwd); } else { - const ciTemplateDefault = await templateModule.getTemplate('ci_default_private'); + const ciTemplateDefault = + await templateModule.getTemplate('ci_default_private'); ciTemplateDefault.writeToDisk(paths.cwd); } logger.log('info', 'Updated .gitlabci.yml!'); @@ -41,7 +43,8 @@ export const run = async (project: Project) => { logger.log('info', 'Updated CI/CD config files!'); // lets care about docker - const dockerTemplate = await templateModule.getTemplate('dockerfile_service'); + const dockerTemplate = + await templateModule.getTemplate('dockerfile_service'); dockerTemplate.writeToDisk(paths.cwd); logger.log('info', 'Updated Dockerfile!'); @@ -56,17 +59,22 @@ export const run = async (project: Project) => { // update html if (project.gitzoneConfig.data.projectType === 'website') { - const websiteUpdateTemplate = await templateModule.getTemplate('website_update'); - const variables ={ + const websiteUpdateTemplate = + await templateModule.getTemplate('website_update'); + const variables = { assetbrokerUrl: project.gitzoneConfig.data.module.assetbrokerUrl, legalUrl: project.gitzoneConfig.data.module.legalUrl, }; - console.log('updating website template with variables\n', JSON.stringify(variables, null, 2)); + console.log( + 'updating website template with variables\n', + JSON.stringify(variables, null, 2), + ); websiteUpdateTemplate.supplyVariables(variables); await websiteUpdateTemplate.writeToDisk(paths.cwd); logger.log('info', `Updated html for website!`); } else if (project.gitzoneConfig.data.projectType === 'service') { - const websiteUpdateTemplate = await templateModule.getTemplate('service_update'); + const websiteUpdateTemplate = + await templateModule.getTemplate('service_update'); await websiteUpdateTemplate.writeToDisk(paths.cwd); logger.log('info', `Updated html for element template!`); } else if (project.gitzoneConfig.data.projectType === 'wcc') { diff --git a/ts/mod_format/format.tsconfig.ts b/ts/mod_format/format.tsconfig.ts index e22bb5a..e834da9 100644 --- a/ts/mod_format/format.tsconfig.ts +++ b/ts/mod_format/format.tsconfig.ts @@ -19,8 +19,12 @@ export const run = async (projectArg: Project) => { const publishModules = await tsPublishInstance.getModuleSubDirs(paths.cwd); for (const publishModule of Object.keys(publishModules)) { const publishConfig = publishModules[publishModule]; - tsconfigObject.compilerOptions.paths[`${publishConfig.name}`] = [`./${publishModule}/index.js`]; + tsconfigObject.compilerOptions.paths[`${publishConfig.name}`] = [ + `./${publishModule}/index.js`, + ]; } - tsconfigSmartfile.setContentsFromString(JSON.stringify(tsconfigObject, null, 2)); + tsconfigSmartfile.setContentsFromString( + JSON.stringify(tsconfigObject, null, 2), + ); await tsconfigSmartfile.write(); }; diff --git a/ts/mod_format/formatters/cleanup.formatter.ts b/ts/mod_format/formatters/cleanup.formatter.ts index 37f5ed3..0ac0a7d 100644 --- a/ts/mod_format/formatters/cleanup.formatter.ts +++ b/ts/mod_format/formatters/cleanup.formatter.ts @@ -7,13 +7,18 @@ export class CleanupFormatter extends BaseFormatter { get name(): string { return 'cleanup'; } - + async analyze(): Promise { const changes: IPlannedChange[] = []; - + // List of files to remove - const filesToRemove = ['yarn.lock', 'package-lock.json', 'tslint.json', 'defaults.yml']; - + const filesToRemove = [ + 'yarn.lock', + 'package-lock.json', + 'tslint.json', + 'defaults.yml', + ]; + for (const file of filesToRemove) { const exists = await plugins.smartfile.fs.fileExists(file); if (exists) { @@ -21,14 +26,14 @@ export class CleanupFormatter extends BaseFormatter { type: 'delete', path: file, module: this.name, - description: `Remove obsolete file` + description: `Remove obsolete file`, }); } } - + return changes; } - + async applyChange(change: IPlannedChange): Promise { switch (change.type) { case 'delete': @@ -36,4 +41,4 @@ export class CleanupFormatter extends BaseFormatter { break; } } -} \ No newline at end of file +} diff --git a/ts/mod_format/formatters/copy.formatter.ts b/ts/mod_format/formatters/copy.formatter.ts index af9dae3..8ae3eb3 100644 --- a/ts/mod_format/formatters/copy.formatter.ts +++ b/ts/mod_format/formatters/copy.formatter.ts @@ -5,4 +5,4 @@ export class CopyFormatter extends LegacyFormatter { constructor(context: any, project: any) { super(context, project, 'copy', formatCopy); } -} \ No newline at end of file +} diff --git a/ts/mod_format/formatters/gitignore.formatter.ts b/ts/mod_format/formatters/gitignore.formatter.ts index d4b7b52..a4d695f 100644 --- a/ts/mod_format/formatters/gitignore.formatter.ts +++ b/ts/mod_format/formatters/gitignore.formatter.ts @@ -5,4 +5,4 @@ export class GitignoreFormatter extends LegacyFormatter { constructor(context: any, project: any) { super(context, project, 'gitignore', formatGitignore); } -} \ No newline at end of file +} diff --git a/ts/mod_format/formatters/legacy.formatter.ts b/ts/mod_format/formatters/legacy.formatter.ts index 0d24a70..5738601 100644 --- a/ts/mod_format/formatters/legacy.formatter.ts +++ b/ts/mod_format/formatters/legacy.formatter.ts @@ -7,30 +7,37 @@ import * as plugins from '../mod.plugins.js'; export class LegacyFormatter extends BaseFormatter { private moduleName: string; private formatModule: any; - - constructor(context: any, project: Project, moduleName: string, formatModule: any) { + + constructor( + context: any, + project: Project, + moduleName: string, + formatModule: any, + ) { super(context, project); this.moduleName = moduleName; this.formatModule = formatModule; } - + get name(): string { return this.moduleName; } - + async analyze(): Promise { // For legacy modules, we can't easily predict changes // So we'll return a generic change that indicates the module will run - return [{ - type: 'modify', - path: '', - module: this.name, - description: `Run ${this.name} formatter` - }]; + return [ + { + type: 'modify', + path: '', + module: this.name, + description: `Run ${this.name} formatter`, + }, + ]; } - + async applyChange(change: IPlannedChange): Promise { // Run the legacy format module await this.formatModule.run(this.project); } -} \ No newline at end of file +} diff --git a/ts/mod_format/formatters/license.formatter.ts b/ts/mod_format/formatters/license.formatter.ts index 11afb2e..a1e6552 100644 --- a/ts/mod_format/formatters/license.formatter.ts +++ b/ts/mod_format/formatters/license.formatter.ts @@ -5,4 +5,4 @@ export class LicenseFormatter extends LegacyFormatter { constructor(context: any, project: any) { super(context, project, 'license', formatLicense); } -} \ No newline at end of file +} diff --git a/ts/mod_format/formatters/npmextra.formatter.ts b/ts/mod_format/formatters/npmextra.formatter.ts index e0f4e6c..2edf0ef 100644 --- a/ts/mod_format/formatters/npmextra.formatter.ts +++ b/ts/mod_format/formatters/npmextra.formatter.ts @@ -5,4 +5,4 @@ export class NpmextraFormatter extends LegacyFormatter { constructor(context: any, project: any) { super(context, project, 'npmextra', formatNpmextra); } -} \ No newline at end of file +} diff --git a/ts/mod_format/formatters/packagejson.formatter.ts b/ts/mod_format/formatters/packagejson.formatter.ts index cd1f9a9..194a975 100644 --- a/ts/mod_format/formatters/packagejson.formatter.ts +++ b/ts/mod_format/formatters/packagejson.formatter.ts @@ -5,4 +5,4 @@ export class PackageJsonFormatter extends LegacyFormatter { constructor(context: any, project: any) { super(context, project, 'packagejson', formatPackageJson); } -} \ No newline at end of file +} diff --git a/ts/mod_format/formatters/prettier.formatter.ts b/ts/mod_format/formatters/prettier.formatter.ts index bfab5cd..33e0270 100644 --- a/ts/mod_format/formatters/prettier.formatter.ts +++ b/ts/mod_format/formatters/prettier.formatter.ts @@ -7,21 +7,16 @@ export class PrettierFormatter extends BaseFormatter { get name(): string { return 'prettier'; } - + async analyze(): Promise { const changes: IPlannedChange[] = []; - + // Define directories to format (TypeScript directories by default) - const includeDirs = [ - 'ts', - 'ts_*', - 'test', - 'tests' - ]; - + const includeDirs = ['ts', 'ts_*', 'test', 'tests']; + // File extensions to format const extensions = '{ts,tsx,js,jsx,json,md,css,scss,html,xml,yaml,yml}'; - + // Also format root-level config files const rootConfigFiles = [ 'package.json', @@ -36,33 +31,36 @@ export class PrettierFormatter extends BaseFormatter { 'CHANGELOG.md', 'license', 'LICENSE', - '*.md' + '*.md', ]; - + // Collect all files to format const allFiles: string[] = []; - + // Add files from TypeScript directories for (const dir of includeDirs) { const globPattern = `${dir}/**/*.${extensions}`; - const dirFiles = await plugins.smartfile.fs.listFileTree('.', globPattern); + const dirFiles = await plugins.smartfile.fs.listFileTree( + '.', + globPattern, + ); allFiles.push(...dirFiles); } - + // Add root config files for (const pattern of rootConfigFiles) { const rootFiles = await plugins.smartfile.fs.listFileTree('.', pattern); // Only include files at root level (no slashes in path) - const rootLevelFiles = rootFiles.filter(f => !f.includes('/')); + const rootLevelFiles = rootFiles.filter((f) => !f.includes('/')); allFiles.push(...rootLevelFiles); } - + // Remove duplicates const uniqueFiles = [...new Set(allFiles)]; - + // Get all files that match the pattern const files = uniqueFiles; - + // Ensure we only process actual files (not directories) const validFiles: string[] = []; for (const file of files) { @@ -76,48 +74,52 @@ export class PrettierFormatter extends BaseFormatter { logVerbose(`Skipping ${file} - cannot access: ${error.message}`); } } - + // Check which files need formatting for (const file of validFiles) { // Skip files that haven't changed - if (!await this.shouldProcessFile(file)) { + if (!(await this.shouldProcessFile(file))) { logVerbose(`Skipping ${file} - no changes detected`); continue; } - + changes.push({ type: 'modify', path: file, module: this.name, - description: 'Format with Prettier' + description: 'Format with Prettier', }); } - + logger.log('info', `Found ${changes.length} files to format with Prettier`); return changes; } - + async execute(changes: IPlannedChange[]): Promise { const startTime = this.stats.moduleStartTime(this.name); this.stats.startModule(this.name); - + try { await this.preExecute(); - + // Batch process files const batchSize = 10; // Process 10 files at a time const batches: IPlannedChange[][] = []; - + for (let i = 0; i < changes.length; i += batchSize) { batches.push(changes.slice(i, i + batchSize)); } - - logVerbose(`Processing ${changes.length} files in ${batches.length} batches`); - + + logVerbose( + `Processing ${changes.length} files in ${batches.length} batches`, + ); + for (let i = 0; i < batches.length; i++) { const batch = batches[i]; - logVerbose(`Processing batch ${i + 1}/${batches.length} (${batch.length} files)`); - + logVerbose( + `Processing batch ${i + 1}/${batches.length} (${batch.length} files)`, + ); + // Process batch in parallel const promises = batch.map(async (change) => { try { @@ -125,44 +127,45 @@ export class PrettierFormatter extends BaseFormatter { this.stats.recordFileOperation(this.name, change.type, true); } catch (error) { this.stats.recordFileOperation(this.name, change.type, false); - logger.log('error', `Failed to format ${change.path}: ${error.message}`); + logger.log( + 'error', + `Failed to format ${change.path}: ${error.message}`, + ); // Don't throw - continue with other files } }); - + await Promise.all(promises); } - + await this.postExecute(); } catch (error) { - await this.context.rollbackOperation(); + // Rollback removed - no longer tracking operations throw error; } finally { this.stats.endModule(this.name, startTime); } } - + async applyChange(change: IPlannedChange): Promise { if (change.type !== 'modify') return; - + try { // Read current content const content = plugins.smartfile.fs.toStringSync(change.path); - + // Format with prettier const prettier = await import('prettier'); const formatted = await prettier.format(content, { filepath: change.path, - ...(await this.getPrettierConfig()) + ...(await this.getPrettierConfig()), }); - + // Only write if content actually changed if (formatted !== content) { await this.modifyFile(change.path, formatted); logVerbose(`Formatted ${change.path}`); } else { - // Still update cache even if content didn't change - await this.cache.updateFileCache(change.path); logVerbose(`No formatting changes for ${change.path}`); } } catch (error) { @@ -170,7 +173,7 @@ export class PrettierFormatter extends BaseFormatter { throw error; } } - + private async getPrettierConfig(): Promise { // Try to load prettier config from the project const prettierConfig = new plugins.npmextra.Npmextra(); @@ -181,7 +184,7 @@ export class PrettierFormatter extends BaseFormatter { printWidth: 80, tabWidth: 2, semi: true, - arrowParens: 'always' + arrowParens: 'always', }); } -} \ No newline at end of file +} diff --git a/ts/mod_format/formatters/readme.formatter.ts b/ts/mod_format/formatters/readme.formatter.ts index 689c45f..948a375 100644 --- a/ts/mod_format/formatters/readme.formatter.ts +++ b/ts/mod_format/formatters/readme.formatter.ts @@ -6,17 +6,19 @@ export class ReadmeFormatter extends BaseFormatter { get name(): string { return 'readme'; } - + async analyze(): Promise { - return [{ - type: 'modify', - path: 'readme.md', - module: this.name, - description: 'Ensure readme files exist' - }]; + return [ + { + type: 'modify', + path: 'readme.md', + module: this.name, + description: 'Ensure readme files exist', + }, + ]; } - + async applyChange(change: IPlannedChange): Promise { await formatReadme.run(); } -} \ No newline at end of file +} diff --git a/ts/mod_format/formatters/templates.formatter.ts b/ts/mod_format/formatters/templates.formatter.ts index 03e62d9..69b3c66 100644 --- a/ts/mod_format/formatters/templates.formatter.ts +++ b/ts/mod_format/formatters/templates.formatter.ts @@ -5,4 +5,4 @@ export class TemplatesFormatter extends LegacyFormatter { constructor(context: any, project: any) { super(context, project, 'templates', formatTemplates); } -} \ No newline at end of file +} diff --git a/ts/mod_format/formatters/tsconfig.formatter.ts b/ts/mod_format/formatters/tsconfig.formatter.ts index 0c68f9a..1860721 100644 --- a/ts/mod_format/formatters/tsconfig.formatter.ts +++ b/ts/mod_format/formatters/tsconfig.formatter.ts @@ -5,4 +5,4 @@ export class TsconfigFormatter extends LegacyFormatter { constructor(context: any, project: any) { super(context, project, 'tsconfig', formatTsconfig); } -} \ No newline at end of file +} diff --git a/ts/mod_format/index.ts b/ts/mod_format/index.ts index b70563c..10a7fb3 100644 --- a/ts/mod_format/index.ts +++ b/ts/mod_format/index.ts @@ -16,27 +16,29 @@ import { PrettierFormatter } from './formatters/prettier.formatter.js'; import { ReadmeFormatter } from './formatters/readme.formatter.js'; import { CopyFormatter } from './formatters/copy.formatter.js'; -export let run = async (options: { - dryRun?: boolean; - yes?: boolean; - planOnly?: boolean; - savePlan?: string; - fromPlan?: string; - detailed?: boolean; - interactive?: boolean; - parallel?: boolean; - verbose?: boolean; -} = {}): Promise => { +export let run = async ( + options: { + dryRun?: boolean; + yes?: boolean; + planOnly?: boolean; + savePlan?: string; + fromPlan?: string; + detailed?: boolean; + interactive?: boolean; + parallel?: boolean; + verbose?: boolean; + } = {}, +): Promise => { // Set verbose mode if requested if (options.verbose) { setVerboseMode(true); } - + const project = await Project.fromCwd(); const context = new FormatContext(); - await context.initializeCache(); // Initialize the cache system + // Cache system removed - no longer needed const planner = new FormatPlanner(); - + // Get configuration from npmextra const npmextraConfig = new plugins.npmextra.Npmextra(); const formatConfig = npmextraConfig.dataFor('gitzone.format', { @@ -49,30 +51,27 @@ export let run = async (options: { autoRollbackOnError: true, backupRetentionDays: 7, maxBackupSize: '100MB', - excludePatterns: ['node_modules/**', '.git/**'] + excludePatterns: ['node_modules/**', '.git/**'], }, modules: { skip: [], only: [], - order: [] + order: [], }, parallel: true, cache: { enabled: true, - clean: true // Clean invalid entries from cache - } + clean: true, // Clean invalid entries from cache + }, }); - - // Clean cache if configured - if (formatConfig.cache.clean) { - await context.getChangeCache().clean(); - } - + + // Cache cleaning removed - no longer using cache system + // Override config with command options const interactive = options.interactive ?? formatConfig.interactive; const autoApprove = options.yes ?? formatConfig.autoApprove; const parallel = options.parallel ?? formatConfig.parallel; - + try { // Initialize formatters const formatters = [ @@ -87,9 +86,9 @@ export let run = async (options: { new ReadmeFormatter(context, project), new CopyFormatter(context, project), ]; - + // Filter formatters based on configuration - const activeFormatters = formatters.filter(formatter => { + const activeFormatters = formatters.filter((formatter) => { if (formatConfig.modules.only.length > 0) { return formatConfig.modules.only.includes(formatter.name); } @@ -98,33 +97,36 @@ export let run = async (options: { } return true; }); - + // Plan phase logger.log('info', 'Analyzing project for format operations...'); let plan = options.fromPlan ? JSON.parse(await plugins.smartfile.fs.toStringSync(options.fromPlan)) : await planner.planFormat(activeFormatters); - + // Display plan await planner.displayPlan(plan, options.detailed); - + // Save plan if requested if (options.savePlan) { - await plugins.smartfile.memory.toFs(JSON.stringify(plan, null, 2), options.savePlan); + await plugins.smartfile.memory.toFs( + JSON.stringify(plan, null, 2), + options.savePlan, + ); logger.log('info', `Plan saved to ${options.savePlan}`); } - + // Exit if plan-only mode if (options.planOnly) { return; } - + // Dry-run mode if (options.dryRun) { logger.log('info', 'Dry-run mode - no changes will be made'); return; } - + // Interactive confirmation if (interactive && !autoApprove) { const interactInstance = new plugins.smartinteract.SmartInteract(); @@ -132,117 +134,56 @@ export let run = async (options: { type: 'confirm', name: 'proceed', message: 'Proceed with formatting?', - default: true + default: true, }); - + if (!(response as any).value) { logger.log('info', 'Format operation cancelled by user'); return; } } - + // Execute phase - logger.log('info', `Executing format operations${parallel ? ' in parallel' : ' sequentially'}...`); + logger.log( + 'info', + `Executing format operations${parallel ? ' in parallel' : ' sequentially'}...`, + ); await planner.executePlan(plan, activeFormatters, context, parallel); - + // Finish statistics tracking context.getFormatStats().finish(); - + // Display statistics const showStats = npmextraConfig.dataFor('gitzone.format.showStats', true); if (showStats) { context.getFormatStats().displayStats(); } - + // Save stats if requested if (options.detailed) { const statsPath = `.nogit/format-stats-${Date.now()}.json`; await context.getFormatStats().saveReport(statsPath); } - + logger.log('success', 'Format operations completed successfully!'); - } catch (error) { logger.log('error', `Format operation failed: ${error.message}`); - - // Automatic rollback if enabled - if (formatConfig.rollback.enabled && formatConfig.rollback.autoRollbackOnError) { - logger.log('info', 'Attempting automatic rollback...'); - try { - await context.rollbackOperation(); - logger.log('success', 'Rollback completed successfully'); - } catch (rollbackError) { - logger.log('error', `Rollback failed: ${rollbackError.message}`); - } - } - + + // Rollback system has been removed for stability + throw error; } }; // Export CLI command handlers export const handleRollback = async (operationId?: string): Promise => { - const context = new FormatContext(); - const rollbackManager = context.getRollbackManager(); - - if (!operationId) { - // Rollback to last operation - const backups = await rollbackManager.listBackups(); - const lastOperation = backups - .filter(op => op.status !== 'rolled-back') - .sort((a, b) => b.timestamp - a.timestamp)[0]; - - if (!lastOperation) { - logger.log('warn', 'No operations available for rollback'); - return; - } - - operationId = lastOperation.id; - } - - try { - await rollbackManager.rollback(operationId); - logger.log('success', `Successfully rolled back operation ${operationId}`); - } catch (error) { - logger.log('error', `Rollback failed: ${error.message}`); - throw error; - } + logger.log('info', 'Rollback system has been disabled for stability'); }; export const handleListBackups = async (): Promise => { - const context = new FormatContext(); - const rollbackManager = context.getRollbackManager(); - const backups = await rollbackManager.listBackups(); - - if (backups.length === 0) { - logger.log('info', 'No backup operations found'); - return; - } - - console.log('\nAvailable backups:'); - console.log('━'.repeat(50)); - - for (const backup of backups) { - const date = new Date(backup.timestamp).toLocaleString(); - const status = backup.status; - const filesCount = backup.files.length; - - console.log(`ID: ${backup.id}`); - console.log(`Date: ${date}`); - console.log(`Status: ${status}`); - console.log(`Files: ${filesCount}`); - console.log('─'.repeat(50)); - } + logger.log('info', 'Backup system has been disabled for stability'); }; export const handleCleanBackups = async (): Promise => { - const context = new FormatContext(); - const rollbackManager = context.getRollbackManager(); - - // Get retention days from config - const npmextraConfig = new plugins.npmextra.Npmextra(); - const retentionDays = npmextraConfig.dataFor('gitzone.format.rollback.backupRetentionDays', 7); - - await rollbackManager.cleanOldBackups(retentionDays); - logger.log('success', `Cleaned backups older than ${retentionDays} days`); -}; \ No newline at end of file + logger.log('info', 'Backup cleaning has been disabled - backup system removed'); +}; diff --git a/ts/mod_format/interfaces.format.ts b/ts/mod_format/interfaces.format.ts index 1a04eef..27bed2c 100644 --- a/ts/mod_format/interfaces.format.ts +++ b/ts/mod_format/interfaces.format.ts @@ -9,7 +9,7 @@ export type IFormatOperation = { }>; status: 'pending' | 'in-progress' | 'completed' | 'failed' | 'rolled-back'; error?: Error; -} +}; export type IFormatPlan = { summary: { @@ -32,7 +32,7 @@ export type IFormatPlan = { message: string; module: string; }>; -} +}; export type IPlannedChange = { type: 'create' | 'modify' | 'delete'; @@ -42,4 +42,4 @@ export type IPlannedChange = { content?: string; // For create/modify operations diff?: string; size?: number; -} \ No newline at end of file +}; diff --git a/ts/mod_meta/meta.classes.meta.ts b/ts/mod_meta/meta.classes.meta.ts index 71a9483..f5e70c8 100644 --- a/ts/mod_meta/meta.classes.meta.ts +++ b/ts/mod_meta/meta.classes.meta.ts @@ -35,7 +35,10 @@ export class Meta { * sorts the metaRepoData */ public async sortMetaRepoData() { - const stringifiedMetadata = plugins.smartjson.stringify(this.metaRepoData, []); + const stringifiedMetadata = plugins.smartjson.stringify( + this.metaRepoData, + [], + ); this.metaRepoData = plugins.smartjson.parse(stringifiedMetadata); } @@ -45,11 +48,15 @@ export class Meta { public async readDirectory() { await this.syncToRemote(true); logger.log('info', `reading directory`); - const metaFileExists = plugins.smartfile.fs.fileExistsSync(this.filePaths.metaJson); + const metaFileExists = plugins.smartfile.fs.fileExistsSync( + this.filePaths.metaJson, + ); if (!metaFileExists) { throw new Error(`meta file does not exist at ${this.filePaths.metaJson}`); } - this.metaRepoData = plugins.smartfile.fs.toObjectSync(this.filePaths.metaJson); + this.metaRepoData = plugins.smartfile.fs.toObjectSync( + this.filePaths.metaJson, + ); } /** @@ -76,7 +83,10 @@ export class Meta { this.filePaths.metaJson, ); // write .gitignore to disk - plugins.smartfile.memory.toFsSync(await this.generateGitignore(), this.filePaths.gitIgnore); + plugins.smartfile.memory.toFsSync( + await this.generateGitignore(), + this.filePaths.gitIgnore, + ); } /** @@ -84,13 +94,17 @@ export class Meta { */ public async syncToRemote(gitCleanArg = false) { logger.log('info', `syncing from origin master`); - await this.smartshellInstance.exec(`cd ${this.cwd} && git pull origin master`); + await this.smartshellInstance.exec( + `cd ${this.cwd} && git pull origin master`, + ); if (gitCleanArg) { logger.log('info', `cleaning the repository from old directories`); await this.smartshellInstance.exec(`cd ${this.cwd} && git clean -fd`); } logger.log('info', `syncing to remote origin master`); - await this.smartshellInstance.exec(`cd ${this.cwd} && git push origin master`); + await this.smartshellInstance.exec( + `cd ${this.cwd} && git push origin master`, + ); } /** @@ -98,7 +112,9 @@ export class Meta { */ public async updateLocalRepos() { await this.syncToRemote(); - const projects = plugins.smartfile.fs.toObjectSync(this.filePaths.metaJson).projects; + const projects = plugins.smartfile.fs.toObjectSync( + this.filePaths.metaJson, + ).projects; const preExistingFolders = plugins.smartfile.fs.listFoldersSync(this.cwd); for (const preExistingFolderArg of preExistingFolders) { if ( @@ -107,14 +123,18 @@ export class Meta { projectFolder.startsWith(preExistingFolderArg), ) ) { - const response = await plugins.smartinteraction.SmartInteract.getCliConfirmation( - `Do you want to delete superfluous directory >>${preExistingFolderArg}<< ?`, - true, - ); + const response = + await plugins.smartinteraction.SmartInteract.getCliConfirmation( + `Do you want to delete superfluous directory >>${preExistingFolderArg}<< ?`, + true, + ); if (response) { logger.log('warn', `Deleting >>${preExistingFolderArg}< { * create a new project with 'gitzone template [template]' the following templates exist: ${(() => { let projects = `\n`; - for (const template of plugins.smartfile.fs.listFoldersSync(paths.templatesDir)) { + for (const template of plugins.smartfile.fs.listFoldersSync( + paths.templatesDir, + )) { projects += ` - ${template}\n`; } return projects; diff --git a/ts/mod_start/index.ts b/ts/mod_start/index.ts index e6c5ddb..69a651f 100644 --- a/ts/mod_start/index.ts +++ b/ts/mod_start/index.ts @@ -15,7 +15,9 @@ export const run = async (argvArg: any) => { }); await smartshellInstance.execStrict(`cd ${paths.cwd} && git checkout master`); - await smartshellInstance.execStrict(`cd ${paths.cwd} && git pull origin master`); + await smartshellInstance.execStrict( + `cd ${paths.cwd} && git pull origin master`, + ); await smartshellInstance.execStrict(`cd ${paths.cwd} && npm ci`); await provideNoGitFiles(); diff --git a/ts/mod_template/index.ts b/ts/mod_template/index.ts index 4feb9c9..cec811f 100644 --- a/ts/mod_template/index.ts +++ b/ts/mod_template/index.ts @@ -16,7 +16,9 @@ export const isTemplate = async (templateNameArg: string) => { export const getTemplate = async (templateNameArg: string) => { if (isTemplate(templateNameArg)) { - const localScafTemplate = new plugins.smartscaf.ScafTemplate(getTemplatePath(templateNameArg)); + const localScafTemplate = new plugins.smartscaf.ScafTemplate( + getTemplatePath(templateNameArg), + ); await localScafTemplate.readTemplateFromDir(); return localScafTemplate; } else { @@ -32,7 +34,8 @@ export const run = async (argvArg: any) => { const answerBucket = await smartinteract.askQuestion({ type: 'list', default: 'npm', - message: 'What template do you want to scaffold? (Only showing mpost common options)', + message: + 'What template do you want to scaffold? (Only showing mpost common options)', name: 'templateName', choices: ['npm', 'service', 'wcc', 'website'], });