@git.zone/cli 1.15.5 → 1.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/gitzone.cli.js +26 -2
- package/dist_ts/gitzone.logging.d.ts +3 -0
- package/dist_ts/gitzone.logging.js +20 -2
- package/dist_ts/mod_format/classes.baseformatter.d.ts +21 -0
- package/dist_ts/mod_format/classes.baseformatter.js +73 -0
- package/dist_ts/mod_format/classes.changecache.d.ts +25 -0
- package/dist_ts/mod_format/classes.changecache.js +105 -0
- package/dist_ts/mod_format/classes.dependency-analyzer.d.ts +13 -0
- package/dist_ts/mod_format/classes.dependency-analyzer.js +87 -0
- package/dist_ts/mod_format/classes.diffreporter.d.ts +13 -0
- package/dist_ts/mod_format/classes.diffreporter.js +94 -0
- package/dist_ts/mod_format/classes.formatcontext.d.ts +19 -0
- package/dist_ts/mod_format/classes.formatcontext.js +51 -0
- package/dist_ts/mod_format/classes.formatplanner.d.ts +13 -0
- package/dist_ts/mod_format/classes.formatplanner.js +164 -0
- package/dist_ts/mod_format/classes.formatstats.d.ts +40 -0
- package/dist_ts/mod_format/classes.formatstats.js +161 -0
- package/dist_ts/mod_format/classes.rollbackmanager.d.ts +21 -0
- package/dist_ts/mod_format/classes.rollbackmanager.js +170 -0
- package/dist_ts/mod_format/format.copy.d.ts +22 -0
- package/dist_ts/mod_format/format.copy.js +66 -2
- package/dist_ts/mod_format/format.packagejson.js +48 -2
- package/dist_ts/mod_format/formatters/cleanup.formatter.d.ts +7 -0
- package/dist_ts/mod_format/formatters/cleanup.formatter.js +33 -0
- package/dist_ts/mod_format/formatters/copy.formatter.d.ts +4 -0
- package/dist_ts/mod_format/formatters/copy.formatter.js +8 -0
- package/dist_ts/mod_format/formatters/gitignore.formatter.d.ts +4 -0
- package/dist_ts/mod_format/formatters/gitignore.formatter.js +8 -0
- package/dist_ts/mod_format/formatters/legacy.formatter.d.ts +11 -0
- package/dist_ts/mod_format/formatters/legacy.formatter.js +29 -0
- package/dist_ts/mod_format/formatters/license.formatter.d.ts +4 -0
- package/dist_ts/mod_format/formatters/license.formatter.js +8 -0
- package/dist_ts/mod_format/formatters/npmextra.formatter.d.ts +4 -0
- package/dist_ts/mod_format/formatters/npmextra.formatter.js +8 -0
- package/dist_ts/mod_format/formatters/packagejson.formatter.d.ts +4 -0
- package/dist_ts/mod_format/formatters/packagejson.formatter.js +8 -0
- package/dist_ts/mod_format/formatters/prettier.formatter.d.ts +9 -0
- package/dist_ts/mod_format/formatters/prettier.formatter.js +111 -0
- package/dist_ts/mod_format/formatters/readme.formatter.d.ts +7 -0
- package/dist_ts/mod_format/formatters/readme.formatter.js +19 -0
- package/dist_ts/mod_format/formatters/templates.formatter.d.ts +4 -0
- package/dist_ts/mod_format/formatters/templates.formatter.js +8 -0
- package/dist_ts/mod_format/formatters/tsconfig.formatter.d.ts +4 -0
- package/dist_ts/mod_format/formatters/tsconfig.formatter.js +8 -0
- package/dist_ts/mod_format/index.d.ts +14 -1
- package/dist_ts/mod_format/index.js +202 -28
- package/dist_ts/mod_format/interfaces.format.d.ts +43 -0
- package/dist_ts/mod_format/interfaces.format.js +2 -0
- package/dist_ts/mod_format/mod.plugins.d.ts +5 -1
- package/dist_ts/mod_format/mod.plugins.js +6 -2
- package/package.json +2 -1
- package/readme.hints.md +187 -0
- package/readme.plan.md +170 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/gitzone.cli.ts +29 -1
- package/ts/gitzone.logging.ts +23 -1
- package/ts/mod_format/classes.baseformatter.ts +93 -0
- package/ts/mod_format/classes.changecache.ts +144 -0
- package/ts/mod_format/classes.dependency-analyzer.ts +107 -0
- package/ts/mod_format/classes.diffreporter.ts +108 -0
- package/ts/mod_format/classes.formatcontext.ts +65 -0
- package/ts/mod_format/classes.formatplanner.ts +184 -0
- package/ts/mod_format/classes.formatstats.ts +209 -0
- package/ts/mod_format/classes.rollbackmanager.ts +218 -0
- package/ts/mod_format/format.copy.ts +77 -1
- package/ts/mod_format/format.packagejson.ts +50 -1
- package/ts/mod_format/formatters/cleanup.formatter.ts +39 -0
- package/ts/mod_format/formatters/copy.formatter.ts +8 -0
- package/ts/mod_format/formatters/gitignore.formatter.ts +8 -0
- package/ts/mod_format/formatters/legacy.formatter.ts +36 -0
- package/ts/mod_format/formatters/license.formatter.ts +8 -0
- package/ts/mod_format/formatters/npmextra.formatter.ts +8 -0
- package/ts/mod_format/formatters/packagejson.formatter.ts +8 -0
- package/ts/mod_format/formatters/prettier.formatter.ts +125 -0
- package/ts/mod_format/formatters/readme.formatter.ts +22 -0
- package/ts/mod_format/formatters/templates.formatter.ts +8 -0
- package/ts/mod_format/formatters/tsconfig.formatter.ts +8 -0
- package/ts/mod_format/index.ts +241 -33
- package/ts/mod_format/interfaces.format.ts +45 -0
- package/ts/mod_format/mod.plugins.ts +8 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as plugins from './mod.plugins.js';
|
|
2
|
+
import { RollbackManager } from './classes.rollbackmanager.js';
|
|
3
|
+
import { ChangeCache } from './classes.changecache.js';
|
|
4
|
+
import { FormatStats } from './classes.formatstats.js';
|
|
5
|
+
import type { IFormatOperation, IFormatPlan } from './interfaces.format.js';
|
|
6
|
+
|
|
7
|
+
export class FormatContext {
|
|
8
|
+
private rollbackManager: RollbackManager;
|
|
9
|
+
private currentOperation: IFormatOperation | null = null;
|
|
10
|
+
private changeCache: ChangeCache;
|
|
11
|
+
private formatStats: FormatStats;
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.rollbackManager = new RollbackManager();
|
|
15
|
+
this.changeCache = new ChangeCache();
|
|
16
|
+
this.formatStats = new FormatStats();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async beginOperation(): Promise<void> {
|
|
20
|
+
this.currentOperation = await this.rollbackManager.createOperation();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async trackFileChange(filepath: string): Promise<void> {
|
|
24
|
+
if (!this.currentOperation) {
|
|
25
|
+
throw new Error('No operation in progress. Call beginOperation() first.');
|
|
26
|
+
}
|
|
27
|
+
await this.rollbackManager.backupFile(filepath, this.currentOperation.id);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async commitOperation(): Promise<void> {
|
|
31
|
+
if (!this.currentOperation) {
|
|
32
|
+
throw new Error('No operation in progress. Call beginOperation() first.');
|
|
33
|
+
}
|
|
34
|
+
await this.rollbackManager.markComplete(this.currentOperation.id);
|
|
35
|
+
this.currentOperation = null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async rollbackOperation(): Promise<void> {
|
|
39
|
+
if (!this.currentOperation) {
|
|
40
|
+
throw new Error('No operation in progress. Call beginOperation() first.');
|
|
41
|
+
}
|
|
42
|
+
await this.rollbackManager.rollback(this.currentOperation.id);
|
|
43
|
+
this.currentOperation = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async rollbackTo(operationId: string): Promise<void> {
|
|
47
|
+
await this.rollbackManager.rollback(operationId);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getRollbackManager(): RollbackManager {
|
|
51
|
+
return this.rollbackManager;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getChangeCache(): ChangeCache {
|
|
55
|
+
return this.changeCache;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async initializeCache(): Promise<void> {
|
|
59
|
+
await this.changeCache.initialize();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getFormatStats(): FormatStats {
|
|
63
|
+
return this.formatStats;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import * as plugins from './mod.plugins.js';
|
|
2
|
+
import { FormatContext } from './classes.formatcontext.js';
|
|
3
|
+
import { BaseFormatter } from './classes.baseformatter.js';
|
|
4
|
+
import type { IFormatPlan, IPlannedChange } from './interfaces.format.js';
|
|
5
|
+
import { logger } from '../gitzone.logging.js';
|
|
6
|
+
import { DependencyAnalyzer } from './classes.dependency-analyzer.js';
|
|
7
|
+
import { DiffReporter } from './classes.diffreporter.js';
|
|
8
|
+
|
|
9
|
+
export class FormatPlanner {
|
|
10
|
+
private plannedChanges: Map<string, IPlannedChange[]> = new Map();
|
|
11
|
+
private dependencyAnalyzer = new DependencyAnalyzer();
|
|
12
|
+
private diffReporter = new DiffReporter();
|
|
13
|
+
|
|
14
|
+
async planFormat(modules: BaseFormatter[]): Promise<IFormatPlan> {
|
|
15
|
+
const plan: IFormatPlan = {
|
|
16
|
+
summary: {
|
|
17
|
+
totalFiles: 0,
|
|
18
|
+
filesAdded: 0,
|
|
19
|
+
filesModified: 0,
|
|
20
|
+
filesRemoved: 0,
|
|
21
|
+
estimatedTime: 0
|
|
22
|
+
},
|
|
23
|
+
changes: [],
|
|
24
|
+
warnings: []
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
for (const module of modules) {
|
|
28
|
+
try {
|
|
29
|
+
const changes = await module.analyze();
|
|
30
|
+
this.plannedChanges.set(module.name, changes);
|
|
31
|
+
|
|
32
|
+
for (const change of changes) {
|
|
33
|
+
plan.changes.push(change);
|
|
34
|
+
|
|
35
|
+
// Update summary
|
|
36
|
+
switch (change.type) {
|
|
37
|
+
case 'create':
|
|
38
|
+
plan.summary.filesAdded++;
|
|
39
|
+
break;
|
|
40
|
+
case 'modify':
|
|
41
|
+
plan.summary.filesModified++;
|
|
42
|
+
break;
|
|
43
|
+
case 'delete':
|
|
44
|
+
plan.summary.filesRemoved++;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
plan.warnings.push({
|
|
50
|
+
level: 'error',
|
|
51
|
+
message: `Failed to analyze module ${module.name}: ${error.message}`,
|
|
52
|
+
module: module.name
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
plan.summary.totalFiles = plan.summary.filesAdded + plan.summary.filesModified + plan.summary.filesRemoved;
|
|
58
|
+
plan.summary.estimatedTime = plan.summary.totalFiles * 100; // 100ms per file estimate
|
|
59
|
+
|
|
60
|
+
return plan;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async executePlan(plan: IFormatPlan, modules: BaseFormatter[], context: FormatContext, parallel: boolean = true): Promise<void> {
|
|
64
|
+
await context.beginOperation();
|
|
65
|
+
const startTime = Date.now();
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
if (parallel) {
|
|
69
|
+
// Get execution groups based on dependencies
|
|
70
|
+
const executionGroups = this.dependencyAnalyzer.getExecutionGroups(modules);
|
|
71
|
+
|
|
72
|
+
logger.log('info', `Executing formatters in ${executionGroups.length} groups...`);
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < executionGroups.length; i++) {
|
|
75
|
+
const group = executionGroups[i];
|
|
76
|
+
logger.log('info', `Executing group ${i + 1}: ${group.map(m => m.name).join(', ')}`);
|
|
77
|
+
|
|
78
|
+
// Execute modules in this group in parallel
|
|
79
|
+
const promises = group.map(async (module) => {
|
|
80
|
+
const changes = this.plannedChanges.get(module.name) || [];
|
|
81
|
+
if (changes.length > 0) {
|
|
82
|
+
logger.log('info', `Executing ${module.name} formatter...`);
|
|
83
|
+
await module.execute(changes);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await Promise.all(promises);
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
// Sequential execution (original implementation)
|
|
91
|
+
for (const module of modules) {
|
|
92
|
+
const changes = this.plannedChanges.get(module.name) || [];
|
|
93
|
+
|
|
94
|
+
if (changes.length > 0) {
|
|
95
|
+
logger.log('info', `Executing ${module.name} formatter...`);
|
|
96
|
+
await module.execute(changes);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const endTime = Date.now();
|
|
102
|
+
const duration = endTime - startTime;
|
|
103
|
+
logger.log('info', `Format operations completed in ${duration}ms`);
|
|
104
|
+
|
|
105
|
+
await context.commitOperation();
|
|
106
|
+
} catch (error) {
|
|
107
|
+
await context.rollbackOperation();
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async displayPlan(plan: IFormatPlan, detailed: boolean = false): Promise<void> {
|
|
113
|
+
console.log('\nFormat Plan:');
|
|
114
|
+
console.log('━'.repeat(50));
|
|
115
|
+
console.log(`Summary: ${plan.summary.totalFiles} files will be changed`);
|
|
116
|
+
console.log(` • ${plan.summary.filesAdded} new files`);
|
|
117
|
+
console.log(` • ${plan.summary.filesModified} modified files`);
|
|
118
|
+
console.log(` • ${plan.summary.filesRemoved} deleted files`);
|
|
119
|
+
console.log('');
|
|
120
|
+
console.log('Changes by module:');
|
|
121
|
+
|
|
122
|
+
// Group changes by module
|
|
123
|
+
const changesByModule = new Map<string, IPlannedChange[]>();
|
|
124
|
+
for (const change of plan.changes) {
|
|
125
|
+
const moduleChanges = changesByModule.get(change.module) || [];
|
|
126
|
+
moduleChanges.push(change);
|
|
127
|
+
changesByModule.set(change.module, moduleChanges);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const [module, changes] of changesByModule) {
|
|
131
|
+
console.log(`\n${this.getModuleIcon(module)} ${module} (${changes.length} ${changes.length === 1 ? 'file' : 'files'})`);
|
|
132
|
+
|
|
133
|
+
for (const change of changes) {
|
|
134
|
+
const icon = this.getChangeIcon(change.type);
|
|
135
|
+
console.log(` ${icon} ${change.path} - ${change.description}`);
|
|
136
|
+
|
|
137
|
+
// Show diff for modified files if detailed view is requested
|
|
138
|
+
if (detailed && change.type === 'modify') {
|
|
139
|
+
const diff = await this.diffReporter.generateDiffForChange(change);
|
|
140
|
+
if (diff) {
|
|
141
|
+
this.diffReporter.displayDiff(change.path, diff);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (plan.warnings.length > 0) {
|
|
148
|
+
console.log('\nWarnings:');
|
|
149
|
+
for (const warning of plan.warnings) {
|
|
150
|
+
const icon = warning.level === 'error' ? '❌' : '⚠️';
|
|
151
|
+
console.log(` ${icon} ${warning.message}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log('\n' + '━'.repeat(50));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private getModuleIcon(module: string): string {
|
|
159
|
+
const icons: Record<string, string> = {
|
|
160
|
+
'packagejson': '📦',
|
|
161
|
+
'license': '📝',
|
|
162
|
+
'tsconfig': '🔧',
|
|
163
|
+
'cleanup': '🚮',
|
|
164
|
+
'gitignore': '🔒',
|
|
165
|
+
'prettier': '✨',
|
|
166
|
+
'readme': '📖',
|
|
167
|
+
'templates': '📄',
|
|
168
|
+
'npmextra': '⚙️',
|
|
169
|
+
'copy': '📋'
|
|
170
|
+
};
|
|
171
|
+
return icons[module] || '📁';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private getChangeIcon(type: 'create' | 'modify' | 'delete'): string {
|
|
175
|
+
switch (type) {
|
|
176
|
+
case 'create':
|
|
177
|
+
return '✅';
|
|
178
|
+
case 'modify':
|
|
179
|
+
return '✏️';
|
|
180
|
+
case 'delete':
|
|
181
|
+
return '❌';
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import * as plugins from './mod.plugins.js';
|
|
2
|
+
import { logger } from '../gitzone.logging.js';
|
|
3
|
+
|
|
4
|
+
export interface IModuleStats {
|
|
5
|
+
name: string;
|
|
6
|
+
filesProcessed: number;
|
|
7
|
+
executionTime: number;
|
|
8
|
+
errors: number;
|
|
9
|
+
successes: number;
|
|
10
|
+
filesCreated: number;
|
|
11
|
+
filesModified: number;
|
|
12
|
+
filesDeleted: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface IFormatStats {
|
|
16
|
+
totalExecutionTime: number;
|
|
17
|
+
startTime: number;
|
|
18
|
+
endTime: number;
|
|
19
|
+
moduleStats: Map<string, IModuleStats>;
|
|
20
|
+
overallStats: {
|
|
21
|
+
totalFiles: number;
|
|
22
|
+
totalCreated: number;
|
|
23
|
+
totalModified: number;
|
|
24
|
+
totalDeleted: number;
|
|
25
|
+
totalErrors: number;
|
|
26
|
+
cacheHits: number;
|
|
27
|
+
cacheMisses: number;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class FormatStats {
|
|
32
|
+
private stats: IFormatStats;
|
|
33
|
+
|
|
34
|
+
constructor() {
|
|
35
|
+
this.stats = {
|
|
36
|
+
totalExecutionTime: 0,
|
|
37
|
+
startTime: Date.now(),
|
|
38
|
+
endTime: 0,
|
|
39
|
+
moduleStats: new Map(),
|
|
40
|
+
overallStats: {
|
|
41
|
+
totalFiles: 0,
|
|
42
|
+
totalCreated: 0,
|
|
43
|
+
totalModified: 0,
|
|
44
|
+
totalDeleted: 0,
|
|
45
|
+
totalErrors: 0,
|
|
46
|
+
cacheHits: 0,
|
|
47
|
+
cacheMisses: 0
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
startModule(moduleName: string): void {
|
|
53
|
+
this.stats.moduleStats.set(moduleName, {
|
|
54
|
+
name: moduleName,
|
|
55
|
+
filesProcessed: 0,
|
|
56
|
+
executionTime: 0,
|
|
57
|
+
errors: 0,
|
|
58
|
+
successes: 0,
|
|
59
|
+
filesCreated: 0,
|
|
60
|
+
filesModified: 0,
|
|
61
|
+
filesDeleted: 0
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
moduleStartTime(moduleName: string): number {
|
|
66
|
+
return Date.now();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
endModule(moduleName: string, startTime: number): void {
|
|
70
|
+
const moduleStats = this.stats.moduleStats.get(moduleName);
|
|
71
|
+
if (moduleStats) {
|
|
72
|
+
moduleStats.executionTime = Date.now() - startTime;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
recordFileOperation(moduleName: string, operation: 'create' | 'modify' | 'delete', success: boolean = true): void {
|
|
77
|
+
const moduleStats = this.stats.moduleStats.get(moduleName);
|
|
78
|
+
if (!moduleStats) return;
|
|
79
|
+
|
|
80
|
+
moduleStats.filesProcessed++;
|
|
81
|
+
|
|
82
|
+
if (success) {
|
|
83
|
+
moduleStats.successes++;
|
|
84
|
+
this.stats.overallStats.totalFiles++;
|
|
85
|
+
|
|
86
|
+
switch (operation) {
|
|
87
|
+
case 'create':
|
|
88
|
+
moduleStats.filesCreated++;
|
|
89
|
+
this.stats.overallStats.totalCreated++;
|
|
90
|
+
break;
|
|
91
|
+
case 'modify':
|
|
92
|
+
moduleStats.filesModified++;
|
|
93
|
+
this.stats.overallStats.totalModified++;
|
|
94
|
+
break;
|
|
95
|
+
case 'delete':
|
|
96
|
+
moduleStats.filesDeleted++;
|
|
97
|
+
this.stats.overallStats.totalDeleted++;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
moduleStats.errors++;
|
|
102
|
+
this.stats.overallStats.totalErrors++;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
recordCacheHit(): void {
|
|
107
|
+
this.stats.overallStats.cacheHits++;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
recordCacheMiss(): void {
|
|
111
|
+
this.stats.overallStats.cacheMisses++;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
finish(): void {
|
|
115
|
+
this.stats.endTime = Date.now();
|
|
116
|
+
this.stats.totalExecutionTime = this.stats.endTime - this.stats.startTime;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
displayStats(): void {
|
|
120
|
+
console.log('\n📊 Format Operation Statistics:');
|
|
121
|
+
console.log('═'.repeat(50));
|
|
122
|
+
|
|
123
|
+
// Overall stats
|
|
124
|
+
console.log('\nOverall Summary:');
|
|
125
|
+
console.log(` Total Execution Time: ${this.formatDuration(this.stats.totalExecutionTime)}`);
|
|
126
|
+
console.log(` Files Processed: ${this.stats.overallStats.totalFiles}`);
|
|
127
|
+
console.log(` • Created: ${this.stats.overallStats.totalCreated}`);
|
|
128
|
+
console.log(` • Modified: ${this.stats.overallStats.totalModified}`);
|
|
129
|
+
console.log(` • Deleted: ${this.stats.overallStats.totalDeleted}`);
|
|
130
|
+
console.log(` Errors: ${this.stats.overallStats.totalErrors}`);
|
|
131
|
+
|
|
132
|
+
if (this.stats.overallStats.cacheHits > 0 || this.stats.overallStats.cacheMisses > 0) {
|
|
133
|
+
const cacheHitRate = this.stats.overallStats.cacheHits /
|
|
134
|
+
(this.stats.overallStats.cacheHits + this.stats.overallStats.cacheMisses) * 100;
|
|
135
|
+
console.log(` Cache Hit Rate: ${cacheHitRate.toFixed(1)}%`);
|
|
136
|
+
console.log(` • Hits: ${this.stats.overallStats.cacheHits}`);
|
|
137
|
+
console.log(` • Misses: ${this.stats.overallStats.cacheMisses}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Module stats
|
|
141
|
+
console.log('\nModule Breakdown:');
|
|
142
|
+
console.log('─'.repeat(50));
|
|
143
|
+
|
|
144
|
+
const sortedModules = Array.from(this.stats.moduleStats.values())
|
|
145
|
+
.sort((a, b) => b.filesProcessed - a.filesProcessed);
|
|
146
|
+
|
|
147
|
+
for (const moduleStats of sortedModules) {
|
|
148
|
+
console.log(`\n${this.getModuleIcon(moduleStats.name)} ${moduleStats.name}:`);
|
|
149
|
+
console.log(` Execution Time: ${this.formatDuration(moduleStats.executionTime)}`);
|
|
150
|
+
console.log(` Files Processed: ${moduleStats.filesProcessed}`);
|
|
151
|
+
|
|
152
|
+
if (moduleStats.filesCreated > 0) {
|
|
153
|
+
console.log(` • Created: ${moduleStats.filesCreated}`);
|
|
154
|
+
}
|
|
155
|
+
if (moduleStats.filesModified > 0) {
|
|
156
|
+
console.log(` • Modified: ${moduleStats.filesModified}`);
|
|
157
|
+
}
|
|
158
|
+
if (moduleStats.filesDeleted > 0) {
|
|
159
|
+
console.log(` • Deleted: ${moduleStats.filesDeleted}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (moduleStats.errors > 0) {
|
|
163
|
+
console.log(` ❌ Errors: ${moduleStats.errors}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log('\n' + '═'.repeat(50));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async saveReport(outputPath: string): Promise<void> {
|
|
171
|
+
const report = {
|
|
172
|
+
timestamp: new Date().toISOString(),
|
|
173
|
+
executionTime: this.stats.totalExecutionTime,
|
|
174
|
+
overallStats: this.stats.overallStats,
|
|
175
|
+
moduleStats: Array.from(this.stats.moduleStats.values())
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
await plugins.smartfile.memory.toFs(JSON.stringify(report, null, 2), outputPath);
|
|
179
|
+
logger.log('info', `Statistics report saved to ${outputPath}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private formatDuration(ms: number): string {
|
|
183
|
+
if (ms < 1000) {
|
|
184
|
+
return `${ms}ms`;
|
|
185
|
+
} else if (ms < 60000) {
|
|
186
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
187
|
+
} else {
|
|
188
|
+
const minutes = Math.floor(ms / 60000);
|
|
189
|
+
const seconds = Math.floor((ms % 60000) / 1000);
|
|
190
|
+
return `${minutes}m ${seconds}s`;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private getModuleIcon(module: string): string {
|
|
195
|
+
const icons: Record<string, string> = {
|
|
196
|
+
'packagejson': '📦',
|
|
197
|
+
'license': '📝',
|
|
198
|
+
'tsconfig': '🔧',
|
|
199
|
+
'cleanup': '🚮',
|
|
200
|
+
'gitignore': '🔒',
|
|
201
|
+
'prettier': '✨',
|
|
202
|
+
'readme': '📖',
|
|
203
|
+
'templates': '📄',
|
|
204
|
+
'npmextra': '⚙️',
|
|
205
|
+
'copy': '📋'
|
|
206
|
+
};
|
|
207
|
+
return icons[module] || '📁';
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import * as plugins from './mod.plugins.js';
|
|
2
|
+
import * as paths from '../paths.js';
|
|
3
|
+
import type { IFormatOperation } from './interfaces.format.js';
|
|
4
|
+
|
|
5
|
+
export class RollbackManager {
|
|
6
|
+
private backupDir: string;
|
|
7
|
+
private manifestPath: string;
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
this.backupDir = plugins.path.join(paths.cwd, '.nogit', 'gitzone-backups');
|
|
11
|
+
this.manifestPath = plugins.path.join(this.backupDir, 'manifest.json');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async createOperation(): Promise<IFormatOperation> {
|
|
15
|
+
await this.ensureBackupDir();
|
|
16
|
+
|
|
17
|
+
const operation: IFormatOperation = {
|
|
18
|
+
id: this.generateOperationId(),
|
|
19
|
+
timestamp: Date.now(),
|
|
20
|
+
files: [],
|
|
21
|
+
status: 'pending'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
await this.updateManifest(operation);
|
|
25
|
+
return operation;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async backupFile(filepath: string, operationId: string): Promise<void> {
|
|
29
|
+
const operation = await this.getOperation(operationId);
|
|
30
|
+
if (!operation) {
|
|
31
|
+
throw new Error(`Operation ${operationId} not found`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const absolutePath = plugins.path.isAbsolute(filepath)
|
|
35
|
+
? filepath
|
|
36
|
+
: plugins.path.join(paths.cwd, filepath);
|
|
37
|
+
|
|
38
|
+
// Check if file exists
|
|
39
|
+
const exists = await plugins.smartfile.fs.fileExists(absolutePath);
|
|
40
|
+
if (!exists) {
|
|
41
|
+
// File doesn't exist yet (will be created), so we skip backup
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Read file content and metadata
|
|
46
|
+
const content = await plugins.smartfile.fs.toStringSync(absolutePath);
|
|
47
|
+
const stats = await plugins.smartfile.fs.stat(absolutePath);
|
|
48
|
+
const checksum = this.calculateChecksum(content);
|
|
49
|
+
|
|
50
|
+
// Create backup
|
|
51
|
+
const backupPath = this.getBackupPath(operationId, filepath);
|
|
52
|
+
await plugins.smartfile.fs.ensureDir(plugins.path.dirname(backupPath));
|
|
53
|
+
await plugins.smartfile.memory.toFs(content, backupPath);
|
|
54
|
+
|
|
55
|
+
// Update operation
|
|
56
|
+
operation.files.push({
|
|
57
|
+
path: filepath,
|
|
58
|
+
originalContent: content,
|
|
59
|
+
checksum,
|
|
60
|
+
permissions: stats.mode.toString(8)
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await this.updateManifest(operation);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async rollback(operationId: string): Promise<void> {
|
|
67
|
+
const operation = await this.getOperation(operationId);
|
|
68
|
+
if (!operation) {
|
|
69
|
+
throw new Error(`Operation ${operationId} not found`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (operation.status === 'rolled-back') {
|
|
73
|
+
throw new Error(`Operation ${operationId} has already been rolled back`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Restore files in reverse order
|
|
77
|
+
for (let i = operation.files.length - 1; i >= 0; i--) {
|
|
78
|
+
const file = operation.files[i];
|
|
79
|
+
const absolutePath = plugins.path.isAbsolute(file.path)
|
|
80
|
+
? file.path
|
|
81
|
+
: plugins.path.join(paths.cwd, file.path);
|
|
82
|
+
|
|
83
|
+
// Verify backup integrity
|
|
84
|
+
const backupPath = this.getBackupPath(operationId, file.path);
|
|
85
|
+
const backupContent = await plugins.smartfile.fs.toStringSync(backupPath);
|
|
86
|
+
const backupChecksum = this.calculateChecksum(backupContent);
|
|
87
|
+
|
|
88
|
+
if (backupChecksum !== file.checksum) {
|
|
89
|
+
throw new Error(`Backup integrity check failed for ${file.path}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Restore file
|
|
93
|
+
await plugins.smartfile.memory.toFs(file.originalContent, absolutePath);
|
|
94
|
+
|
|
95
|
+
// Restore permissions
|
|
96
|
+
const mode = parseInt(file.permissions, 8);
|
|
97
|
+
// Note: Permissions restoration may not work on all platforms
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Update operation status
|
|
101
|
+
operation.status = 'rolled-back';
|
|
102
|
+
await this.updateManifest(operation);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async markComplete(operationId: string): Promise<void> {
|
|
106
|
+
const operation = await this.getOperation(operationId);
|
|
107
|
+
if (!operation) {
|
|
108
|
+
throw new Error(`Operation ${operationId} not found`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
operation.status = 'completed';
|
|
112
|
+
await this.updateManifest(operation);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async cleanOldBackups(retentionDays: number): Promise<void> {
|
|
116
|
+
const manifest = await this.getManifest();
|
|
117
|
+
const cutoffTime = Date.now() - (retentionDays * 24 * 60 * 60 * 1000);
|
|
118
|
+
|
|
119
|
+
const operationsToDelete = manifest.operations.filter(op =>
|
|
120
|
+
op.timestamp < cutoffTime && op.status === 'completed'
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
for (const operation of operationsToDelete) {
|
|
124
|
+
// Remove backup files
|
|
125
|
+
const operationDir = plugins.path.join(this.backupDir, 'operations', operation.id);
|
|
126
|
+
await plugins.smartfile.fs.remove(operationDir);
|
|
127
|
+
|
|
128
|
+
// Remove from manifest
|
|
129
|
+
manifest.operations = manifest.operations.filter(op => op.id !== operation.id);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
await this.saveManifest(manifest);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async verifyBackup(operationId: string): Promise<boolean> {
|
|
136
|
+
const operation = await this.getOperation(operationId);
|
|
137
|
+
if (!operation) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (const file of operation.files) {
|
|
142
|
+
const backupPath = this.getBackupPath(operationId, file.path);
|
|
143
|
+
const exists = await plugins.smartfile.fs.fileExists(backupPath);
|
|
144
|
+
|
|
145
|
+
if (!exists) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const content = await plugins.smartfile.fs.toStringSync(backupPath);
|
|
150
|
+
const checksum = this.calculateChecksum(content);
|
|
151
|
+
|
|
152
|
+
if (checksum !== file.checksum) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async listBackups(): Promise<IFormatOperation[]> {
|
|
161
|
+
const manifest = await this.getManifest();
|
|
162
|
+
return manifest.operations;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private async ensureBackupDir(): Promise<void> {
|
|
166
|
+
await plugins.smartfile.fs.ensureDir(this.backupDir);
|
|
167
|
+
await plugins.smartfile.fs.ensureDir(plugins.path.join(this.backupDir, 'operations'));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private generateOperationId(): string {
|
|
171
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
172
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
173
|
+
return `${timestamp}-${random}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private getBackupPath(operationId: string, filepath: string): string {
|
|
177
|
+
const filename = plugins.path.basename(filepath);
|
|
178
|
+
const dir = plugins.path.dirname(filepath);
|
|
179
|
+
const safeDir = dir.replace(/[/\\]/g, '__');
|
|
180
|
+
return plugins.path.join(this.backupDir, 'operations', operationId, 'files', safeDir, `${filename}.backup`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private calculateChecksum(content: string | Buffer): string {
|
|
184
|
+
return plugins.crypto.createHash('sha256').update(content).digest('hex');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private async getManifest(): Promise<{ operations: IFormatOperation[] }> {
|
|
188
|
+
const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
|
|
189
|
+
if (!exists) {
|
|
190
|
+
return { operations: [] };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const content = await plugins.smartfile.fs.toStringSync(this.manifestPath);
|
|
194
|
+
return JSON.parse(content);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private async saveManifest(manifest: { operations: IFormatOperation[] }): Promise<void> {
|
|
198
|
+
await plugins.smartfile.memory.toFs(JSON.stringify(manifest, null, 2), this.manifestPath);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private async getOperation(operationId: string): Promise<IFormatOperation | null> {
|
|
202
|
+
const manifest = await this.getManifest();
|
|
203
|
+
return manifest.operations.find(op => op.id === operationId) || null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private async updateManifest(operation: IFormatOperation): Promise<void> {
|
|
207
|
+
const manifest = await this.getManifest();
|
|
208
|
+
const existingIndex = manifest.operations.findIndex(op => op.id === operation.id);
|
|
209
|
+
|
|
210
|
+
if (existingIndex !== -1) {
|
|
211
|
+
manifest.operations[existingIndex] = operation;
|
|
212
|
+
} else {
|
|
213
|
+
manifest.operations.push(operation);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
await this.saveManifest(manifest);
|
|
217
|
+
}
|
|
218
|
+
}
|