UNPKG

@git.zone/cli

Version:

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.

177 lines (153 loc) 5.35 kB
import * as plugins from './mod.plugins.js'; import { FormatContext } from './classes.formatcontext.js'; import type { IPlannedChange, ICheckResult } from './interfaces.format.js'; import { Project } from '../classes.project.js'; export abstract class BaseFormatter { protected context: FormatContext; protected project: Project; protected stats: any; // Will be FormatStats from context constructor(context: FormatContext, project: Project) { this.context = context; this.project = project; this.stats = context.getFormatStats(); } abstract get name(): string; abstract analyze(): Promise<IPlannedChange[]>; abstract applyChange(change: IPlannedChange): Promise<void>; async execute(changes: IPlannedChange[]): Promise<void> { 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); this.stats.recordFileOperation(this.name, change.type, true); } catch (error) { this.stats.recordFileOperation(this.name, change.type, false); throw error; } } await this.postExecute(); } catch (error) { // Don't rollback here - let the FormatPlanner handle it throw error; } finally { this.stats.endModule(this.name, startTime); } } protected async preExecute(): Promise<void> { // Override in subclasses if needed } protected async postExecute(): Promise<void> { // Override in subclasses if needed } protected async modifyFile(filepath: string, content: string): Promise<void> { // Validate filepath before writing if (!filepath || filepath.trim() === '') { throw new Error(`Invalid empty filepath in modifyFile`); } // Ensure we have a proper path with directory component // If the path has no directory component (e.g., "package.json"), prepend "./" let normalizedPath = filepath; if (!plugins.path.parse(filepath).dir) { normalizedPath = './' + filepath; } await plugins.smartfs.file(normalizedPath).encoding('utf8').write(content); } protected async createFile(filepath: string, content: string): Promise<void> { await plugins.smartfs.file(filepath).encoding('utf8').write(content); } protected async deleteFile(filepath: string): Promise<void> { await plugins.smartfs.file(filepath).delete(); } protected async shouldProcessFile(filepath: string): Promise<boolean> { return true; } /** * Check for diffs without applying changes * Returns information about what would change */ async check(): Promise<ICheckResult> { const changes = await this.analyze(); const diffs: ICheckResult['diffs'] = []; for (const change of changes) { // Skip generic changes that don't have actual content if (change.path === '<various files>') { continue; } if (change.type === 'modify' || change.type === 'create') { // Read current content if file exists let currentContent: string | undefined; try { currentContent = await plugins.smartfs.file(change.path).encoding('utf8').read() as string; } catch { // File doesn't exist yet currentContent = undefined; } const newContent = change.content; // Check if there's an actual diff if (currentContent !== newContent && newContent !== undefined) { diffs.push({ path: change.path, type: change.type, before: currentContent, after: newContent, }); } } else if (change.type === 'delete') { // Check if file exists before marking for deletion try { const currentContent = await plugins.smartfs.file(change.path).encoding('utf8').read() as string; diffs.push({ path: change.path, type: 'delete', before: currentContent, after: undefined, }); } catch { // File doesn't exist, nothing to delete } } } return { hasDiff: diffs.length > 0, diffs, }; } /** * Display a single diff using smartdiff */ displayDiff(diff: ICheckResult['diffs'][0]): void { console.log(`\n--- ${diff.path}`); if (diff.before && diff.after) { console.log(plugins.smartdiff.formatUnifiedDiffForConsole(diff.before, diff.after, { originalFileName: diff.path, revisedFileName: diff.path, context: 3, })); } else if (diff.after && !diff.before) { console.log(' (new file)'); // Show first few lines of new content const lines = diff.after.split('\n').slice(0, 10); lines.forEach(line => console.log(` + ${line}`)); if (diff.after.split('\n').length > 10) { console.log(' ... (truncated)'); } } else if (diff.before && !diff.after) { console.log(' (file will be deleted)'); } } /** * Display all diffs from a check result */ displayAllDiffs(result: ICheckResult): void { if (!result.hasDiff) { console.log(' No changes detected'); return; } for (const diff of result.diffs) { this.displayDiff(diff); } } }