@git.zone/cli 1.16.6 → 1.16.8
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/classes.gitzoneconfig.js +1 -1
- package/dist_ts/gitzone.cli.js +1 -1
- package/dist_ts/gitzone.logging.js +1 -1
- package/dist_ts/mod_commit/index.js +3 -2
- package/dist_ts/mod_deprecate/index.js +1 -1
- package/dist_ts/mod_format/classes.baseformatter.d.ts +0 -2
- package/dist_ts/mod_format/classes.baseformatter.js +3 -22
- package/dist_ts/mod_format/classes.changecache.js +14 -29
- package/dist_ts/mod_format/classes.dependency-analyzer.js +24 -18
- package/dist_ts/mod_format/classes.diffreporter.js +4 -4
- package/dist_ts/mod_format/classes.formatcontext.d.ts +0 -13
- package/dist_ts/mod_format/classes.formatcontext.js +1 -41
- package/dist_ts/mod_format/classes.formatplanner.js +25 -45
- package/dist_ts/mod_format/classes.formatstats.js +22 -20
- package/dist_ts/mod_format/classes.rollbackmanager.d.ts +1 -0
- package/dist_ts/mod_format/classes.rollbackmanager.js +79 -16
- package/dist_ts/mod_format/format.cleanup.js +7 -2
- package/dist_ts/mod_format/format.copy.d.ts +1 -1
- package/dist_ts/mod_format/format.copy.js +3 -3
- package/dist_ts/mod_format/format.gitignore.js +1 -1
- package/dist_ts/mod_format/format.license.js +1 -1
- package/dist_ts/mod_format/format.npmextra.js +1 -1
- package/dist_ts/mod_format/format.packagejson.js +5 -3
- package/dist_ts/mod_format/format.prettier.js +7 -2
- package/dist_ts/mod_format/format.readme.js +1 -1
- package/dist_ts/mod_format/format.templates.js +1 -1
- package/dist_ts/mod_format/format.tsconfig.js +4 -2
- package/dist_ts/mod_format/formatters/cleanup.formatter.js +8 -3
- package/dist_ts/mod_format/formatters/legacy.formatter.js +6 -4
- package/dist_ts/mod_format/formatters/prettier.formatter.js +9 -16
- package/dist_ts/mod_format/formatters/readme.formatter.js +6 -4
- package/dist_ts/mod_format/index.js +13 -71
- package/dist_ts/mod_meta/meta.classes.meta.js +1 -1
- package/dist_ts/mod_standard/index.js +1 -1
- package/dist_ts/mod_start/index.js +1 -1
- package/dist_ts/mod_template/index.js +1 -1
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.gitzoneconfig.ts +3 -1
- package/ts/gitzone.cli.ts +5 -5
- package/ts/gitzone.logging.ts +2 -1
- package/ts/mod_commit/index.ts +36 -11
- package/ts/mod_deprecate/index.ts +4 -1
- package/ts/mod_format/classes.baseformatter.ts +15 -38
- package/ts/mod_format/classes.changecache.ts +74 -80
- package/ts/mod_format/classes.dependency-analyzer.ts +46 -36
- package/ts/mod_format/classes.diffreporter.ts +44 -28
- package/ts/mod_format/classes.formatcontext.ts +3 -54
- package/ts/mod_format/classes.formatplanner.ts +56 -70
- package/ts/mod_format/classes.formatstats.ts +69 -49
- package/ts/mod_format/classes.rollbackmanager.ts +170 -70
- package/ts/mod_format/format.cleanup.ts +9 -2
- package/ts/mod_format/format.copy.ts +17 -14
- package/ts/mod_format/format.gitignore.ts +2 -1
- package/ts/mod_format/format.license.ts +3 -1
- package/ts/mod_format/format.npmextra.ts +9 -2
- package/ts/mod_format/format.packagejson.ts +47 -12
- package/ts/mod_format/format.prettier.ts +10 -2
- package/ts/mod_format/format.readme.ts +2 -1
- package/ts/mod_format/format.templates.ts +15 -7
- package/ts/mod_format/format.tsconfig.ts +6 -2
- package/ts/mod_format/formatters/cleanup.formatter.ts +13 -8
- package/ts/mod_format/formatters/copy.formatter.ts +1 -1
- package/ts/mod_format/formatters/gitignore.formatter.ts +1 -1
- package/ts/mod_format/formatters/legacy.formatter.ts +19 -12
- package/ts/mod_format/formatters/license.formatter.ts +1 -1
- package/ts/mod_format/formatters/npmextra.formatter.ts +1 -1
- package/ts/mod_format/formatters/packagejson.formatter.ts +1 -1
- package/ts/mod_format/formatters/prettier.formatter.ts +50 -47
- package/ts/mod_format/formatters/readme.formatter.ts +11 -9
- package/ts/mod_format/formatters/templates.formatter.ts +1 -1
- package/ts/mod_format/formatters/tsconfig.formatter.ts +1 -1
- package/ts/mod_format/index.ts +54 -113
- package/ts/mod_format/interfaces.format.ts +3 -3
- package/ts/mod_meta/meta.classes.meta.ts +57 -19
- package/ts/mod_standard/index.ts +3 -1
- package/ts/mod_start/index.ts +3 -1
- package/ts/mod_template/index.ts +5 -2
|
@@ -30,7 +30,7 @@ export interface IFormatStats {
|
|
|
30
30
|
|
|
31
31
|
export class FormatStats {
|
|
32
32
|
private stats: IFormatStats;
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
constructor() {
|
|
35
35
|
this.stats = {
|
|
36
36
|
totalExecutionTime: 0,
|
|
@@ -44,11 +44,11 @@ export class FormatStats {
|
|
|
44
44
|
totalDeleted: 0,
|
|
45
45
|
totalErrors: 0,
|
|
46
46
|
cacheHits: 0,
|
|
47
|
-
cacheMisses: 0
|
|
48
|
-
}
|
|
47
|
+
cacheMisses: 0,
|
|
48
|
+
},
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
startModule(moduleName: string): void {
|
|
53
53
|
this.stats.moduleStats.set(moduleName, {
|
|
54
54
|
name: moduleName,
|
|
@@ -58,31 +58,35 @@ export class FormatStats {
|
|
|
58
58
|
successes: 0,
|
|
59
59
|
filesCreated: 0,
|
|
60
60
|
filesModified: 0,
|
|
61
|
-
filesDeleted: 0
|
|
61
|
+
filesDeleted: 0,
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
moduleStartTime(moduleName: string): number {
|
|
66
66
|
return Date.now();
|
|
67
67
|
}
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
endModule(moduleName: string, startTime: number): void {
|
|
70
70
|
const moduleStats = this.stats.moduleStats.get(moduleName);
|
|
71
71
|
if (moduleStats) {
|
|
72
72
|
moduleStats.executionTime = Date.now() - startTime;
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
|
|
76
|
-
recordFileOperation(
|
|
75
|
+
|
|
76
|
+
recordFileOperation(
|
|
77
|
+
moduleName: string,
|
|
78
|
+
operation: 'create' | 'modify' | 'delete',
|
|
79
|
+
success: boolean = true,
|
|
80
|
+
): void {
|
|
77
81
|
const moduleStats = this.stats.moduleStats.get(moduleName);
|
|
78
82
|
if (!moduleStats) return;
|
|
79
|
-
|
|
83
|
+
|
|
80
84
|
moduleStats.filesProcessed++;
|
|
81
|
-
|
|
85
|
+
|
|
82
86
|
if (success) {
|
|
83
87
|
moduleStats.successes++;
|
|
84
88
|
this.stats.overallStats.totalFiles++;
|
|
85
|
-
|
|
89
|
+
|
|
86
90
|
switch (operation) {
|
|
87
91
|
case 'create':
|
|
88
92
|
moduleStats.filesCreated++;
|
|
@@ -102,53 +106,66 @@ export class FormatStats {
|
|
|
102
106
|
this.stats.overallStats.totalErrors++;
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
|
-
|
|
109
|
+
|
|
106
110
|
recordCacheHit(): void {
|
|
107
111
|
this.stats.overallStats.cacheHits++;
|
|
108
112
|
}
|
|
109
|
-
|
|
113
|
+
|
|
110
114
|
recordCacheMiss(): void {
|
|
111
115
|
this.stats.overallStats.cacheMisses++;
|
|
112
116
|
}
|
|
113
|
-
|
|
117
|
+
|
|
114
118
|
finish(): void {
|
|
115
119
|
this.stats.endTime = Date.now();
|
|
116
120
|
this.stats.totalExecutionTime = this.stats.endTime - this.stats.startTime;
|
|
117
121
|
}
|
|
118
|
-
|
|
122
|
+
|
|
119
123
|
displayStats(): void {
|
|
120
124
|
console.log('\n📊 Format Operation Statistics:');
|
|
121
125
|
console.log('═'.repeat(50));
|
|
122
|
-
|
|
126
|
+
|
|
123
127
|
// Overall stats
|
|
124
128
|
console.log('\nOverall Summary:');
|
|
125
|
-
console.log(
|
|
129
|
+
console.log(
|
|
130
|
+
` Total Execution Time: ${this.formatDuration(this.stats.totalExecutionTime)}`,
|
|
131
|
+
);
|
|
126
132
|
console.log(` Files Processed: ${this.stats.overallStats.totalFiles}`);
|
|
127
133
|
console.log(` • Created: ${this.stats.overallStats.totalCreated}`);
|
|
128
134
|
console.log(` • Modified: ${this.stats.overallStats.totalModified}`);
|
|
129
135
|
console.log(` • Deleted: ${this.stats.overallStats.totalDeleted}`);
|
|
130
136
|
console.log(` Errors: ${this.stats.overallStats.totalErrors}`);
|
|
131
|
-
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
|
|
138
|
+
if (
|
|
139
|
+
this.stats.overallStats.cacheHits > 0 ||
|
|
140
|
+
this.stats.overallStats.cacheMisses > 0
|
|
141
|
+
) {
|
|
142
|
+
const cacheHitRate =
|
|
143
|
+
(this.stats.overallStats.cacheHits /
|
|
144
|
+
(this.stats.overallStats.cacheHits +
|
|
145
|
+
this.stats.overallStats.cacheMisses)) *
|
|
146
|
+
100;
|
|
135
147
|
console.log(` Cache Hit Rate: ${cacheHitRate.toFixed(1)}%`);
|
|
136
148
|
console.log(` • Hits: ${this.stats.overallStats.cacheHits}`);
|
|
137
149
|
console.log(` • Misses: ${this.stats.overallStats.cacheMisses}`);
|
|
138
150
|
}
|
|
139
|
-
|
|
151
|
+
|
|
140
152
|
// Module stats
|
|
141
153
|
console.log('\nModule Breakdown:');
|
|
142
154
|
console.log('─'.repeat(50));
|
|
143
|
-
|
|
144
|
-
const sortedModules = Array.from(this.stats.moduleStats.values())
|
|
145
|
-
|
|
146
|
-
|
|
155
|
+
|
|
156
|
+
const sortedModules = Array.from(this.stats.moduleStats.values()).sort(
|
|
157
|
+
(a, b) => b.filesProcessed - a.filesProcessed,
|
|
158
|
+
);
|
|
159
|
+
|
|
147
160
|
for (const moduleStats of sortedModules) {
|
|
148
|
-
console.log(
|
|
149
|
-
|
|
161
|
+
console.log(
|
|
162
|
+
`\n${this.getModuleIcon(moduleStats.name)} ${moduleStats.name}:`,
|
|
163
|
+
);
|
|
164
|
+
console.log(
|
|
165
|
+
` Execution Time: ${this.formatDuration(moduleStats.executionTime)}`,
|
|
166
|
+
);
|
|
150
167
|
console.log(` Files Processed: ${moduleStats.filesProcessed}`);
|
|
151
|
-
|
|
168
|
+
|
|
152
169
|
if (moduleStats.filesCreated > 0) {
|
|
153
170
|
console.log(` • Created: ${moduleStats.filesCreated}`);
|
|
154
171
|
}
|
|
@@ -158,27 +175,30 @@ export class FormatStats {
|
|
|
158
175
|
if (moduleStats.filesDeleted > 0) {
|
|
159
176
|
console.log(` • Deleted: ${moduleStats.filesDeleted}`);
|
|
160
177
|
}
|
|
161
|
-
|
|
178
|
+
|
|
162
179
|
if (moduleStats.errors > 0) {
|
|
163
180
|
console.log(` ❌ Errors: ${moduleStats.errors}`);
|
|
164
181
|
}
|
|
165
182
|
}
|
|
166
|
-
|
|
183
|
+
|
|
167
184
|
console.log('\n' + '═'.repeat(50));
|
|
168
185
|
}
|
|
169
|
-
|
|
186
|
+
|
|
170
187
|
async saveReport(outputPath: string): Promise<void> {
|
|
171
188
|
const report = {
|
|
172
189
|
timestamp: new Date().toISOString(),
|
|
173
190
|
executionTime: this.stats.totalExecutionTime,
|
|
174
191
|
overallStats: this.stats.overallStats,
|
|
175
|
-
moduleStats: Array.from(this.stats.moduleStats.values())
|
|
192
|
+
moduleStats: Array.from(this.stats.moduleStats.values()),
|
|
176
193
|
};
|
|
177
|
-
|
|
178
|
-
await plugins.smartfile.memory.toFs(
|
|
194
|
+
|
|
195
|
+
await plugins.smartfile.memory.toFs(
|
|
196
|
+
JSON.stringify(report, null, 2),
|
|
197
|
+
outputPath,
|
|
198
|
+
);
|
|
179
199
|
logger.log('info', `Statistics report saved to ${outputPath}`);
|
|
180
200
|
}
|
|
181
|
-
|
|
201
|
+
|
|
182
202
|
private formatDuration(ms: number): string {
|
|
183
203
|
if (ms < 1000) {
|
|
184
204
|
return `${ms}ms`;
|
|
@@ -190,20 +210,20 @@ export class FormatStats {
|
|
|
190
210
|
return `${minutes}m ${seconds}s`;
|
|
191
211
|
}
|
|
192
212
|
}
|
|
193
|
-
|
|
213
|
+
|
|
194
214
|
private getModuleIcon(module: string): string {
|
|
195
215
|
const icons: Record<string, string> = {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
216
|
+
packagejson: '📦',
|
|
217
|
+
license: '📝',
|
|
218
|
+
tsconfig: '🔧',
|
|
219
|
+
cleanup: '🚮',
|
|
220
|
+
gitignore: '🔒',
|
|
221
|
+
prettier: '✨',
|
|
222
|
+
readme: '📖',
|
|
223
|
+
templates: '📄',
|
|
224
|
+
npmextra: '⚙️',
|
|
225
|
+
copy: '📋',
|
|
206
226
|
};
|
|
207
227
|
return icons[module] || '📁';
|
|
208
228
|
}
|
|
209
|
-
}
|
|
229
|
+
}
|
|
@@ -5,214 +5,314 @@ import type { IFormatOperation } from './interfaces.format.js';
|
|
|
5
5
|
export class RollbackManager {
|
|
6
6
|
private backupDir: string;
|
|
7
7
|
private manifestPath: string;
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
constructor() {
|
|
10
10
|
this.backupDir = plugins.path.join(paths.cwd, '.nogit', 'gitzone-backups');
|
|
11
11
|
this.manifestPath = plugins.path.join(this.backupDir, 'manifest.json');
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
async createOperation(): Promise<IFormatOperation> {
|
|
15
15
|
await this.ensureBackupDir();
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
const operation: IFormatOperation = {
|
|
18
18
|
id: this.generateOperationId(),
|
|
19
19
|
timestamp: Date.now(),
|
|
20
20
|
files: [],
|
|
21
|
-
status: 'pending'
|
|
21
|
+
status: 'pending',
|
|
22
22
|
};
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
await this.updateManifest(operation);
|
|
25
25
|
return operation;
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
async backupFile(filepath: string, operationId: string): Promise<void> {
|
|
29
29
|
const operation = await this.getOperation(operationId);
|
|
30
30
|
if (!operation) {
|
|
31
31
|
throw new Error(`Operation ${operationId} not found`);
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
const absolutePath = plugins.path.isAbsolute(filepath)
|
|
35
|
-
? filepath
|
|
33
|
+
|
|
34
|
+
const absolutePath = plugins.path.isAbsolute(filepath)
|
|
35
|
+
? filepath
|
|
36
36
|
: plugins.path.join(paths.cwd, filepath);
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
// Check if file exists
|
|
39
39
|
const exists = await plugins.smartfile.fs.fileExists(absolutePath);
|
|
40
40
|
if (!exists) {
|
|
41
41
|
// File doesn't exist yet (will be created), so we skip backup
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
// Read file content and metadata
|
|
46
|
-
const content =
|
|
46
|
+
const content = plugins.smartfile.fs.toStringSync(absolutePath);
|
|
47
47
|
const stats = await plugins.smartfile.fs.stat(absolutePath);
|
|
48
48
|
const checksum = this.calculateChecksum(content);
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
// Create backup
|
|
51
51
|
const backupPath = this.getBackupPath(operationId, filepath);
|
|
52
52
|
await plugins.smartfile.fs.ensureDir(plugins.path.dirname(backupPath));
|
|
53
53
|
await plugins.smartfile.memory.toFs(content, backupPath);
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
// Update operation
|
|
56
56
|
operation.files.push({
|
|
57
57
|
path: filepath,
|
|
58
58
|
originalContent: content,
|
|
59
59
|
checksum,
|
|
60
|
-
permissions: stats.mode.toString(8)
|
|
60
|
+
permissions: stats.mode.toString(8),
|
|
61
61
|
});
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
await this.updateManifest(operation);
|
|
64
64
|
}
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
async rollback(operationId: string): Promise<void> {
|
|
67
67
|
const operation = await this.getOperation(operationId);
|
|
68
68
|
if (!operation) {
|
|
69
|
-
|
|
69
|
+
// Operation doesn't exist, might have already been rolled back or never created
|
|
70
|
+
console.warn(`Operation ${operationId} not found for rollback, skipping`);
|
|
71
|
+
return;
|
|
70
72
|
}
|
|
71
|
-
|
|
73
|
+
|
|
72
74
|
if (operation.status === 'rolled-back') {
|
|
73
75
|
throw new Error(`Operation ${operationId} has already been rolled back`);
|
|
74
76
|
}
|
|
75
|
-
|
|
77
|
+
|
|
76
78
|
// Restore files in reverse order
|
|
77
79
|
for (let i = operation.files.length - 1; i >= 0; i--) {
|
|
78
80
|
const file = operation.files[i];
|
|
79
|
-
const absolutePath = plugins.path.isAbsolute(file.path)
|
|
80
|
-
? file.path
|
|
81
|
+
const absolutePath = plugins.path.isAbsolute(file.path)
|
|
82
|
+
? file.path
|
|
81
83
|
: plugins.path.join(paths.cwd, file.path);
|
|
82
|
-
|
|
84
|
+
|
|
83
85
|
// Verify backup integrity
|
|
84
86
|
const backupPath = this.getBackupPath(operationId, file.path);
|
|
85
|
-
const backupContent =
|
|
87
|
+
const backupContent = plugins.smartfile.fs.toStringSync(backupPath);
|
|
86
88
|
const backupChecksum = this.calculateChecksum(backupContent);
|
|
87
|
-
|
|
89
|
+
|
|
88
90
|
if (backupChecksum !== file.checksum) {
|
|
89
91
|
throw new Error(`Backup integrity check failed for ${file.path}`);
|
|
90
92
|
}
|
|
91
|
-
|
|
93
|
+
|
|
92
94
|
// Restore file
|
|
93
95
|
await plugins.smartfile.memory.toFs(file.originalContent, absolutePath);
|
|
94
|
-
|
|
96
|
+
|
|
95
97
|
// Restore permissions
|
|
96
98
|
const mode = parseInt(file.permissions, 8);
|
|
97
99
|
// Note: Permissions restoration may not work on all platforms
|
|
98
100
|
}
|
|
99
|
-
|
|
101
|
+
|
|
100
102
|
// Update operation status
|
|
101
103
|
operation.status = 'rolled-back';
|
|
102
104
|
await this.updateManifest(operation);
|
|
103
105
|
}
|
|
104
|
-
|
|
106
|
+
|
|
105
107
|
async markComplete(operationId: string): Promise<void> {
|
|
106
108
|
const operation = await this.getOperation(operationId);
|
|
107
109
|
if (!operation) {
|
|
108
110
|
throw new Error(`Operation ${operationId} not found`);
|
|
109
111
|
}
|
|
110
|
-
|
|
112
|
+
|
|
111
113
|
operation.status = 'completed';
|
|
112
114
|
await this.updateManifest(operation);
|
|
113
115
|
}
|
|
114
|
-
|
|
116
|
+
|
|
115
117
|
async cleanOldBackups(retentionDays: number): Promise<void> {
|
|
116
118
|
const manifest = await this.getManifest();
|
|
117
|
-
const cutoffTime = Date.now() -
|
|
118
|
-
|
|
119
|
-
const operationsToDelete = manifest.operations.filter(
|
|
120
|
-
op.timestamp < cutoffTime && op.status === 'completed'
|
|
119
|
+
const cutoffTime = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
|
|
120
|
+
|
|
121
|
+
const operationsToDelete = manifest.operations.filter(
|
|
122
|
+
(op) => op.timestamp < cutoffTime && op.status === 'completed',
|
|
121
123
|
);
|
|
122
|
-
|
|
124
|
+
|
|
123
125
|
for (const operation of operationsToDelete) {
|
|
124
126
|
// Remove backup files
|
|
125
|
-
const operationDir = plugins.path.join(
|
|
127
|
+
const operationDir = plugins.path.join(
|
|
128
|
+
this.backupDir,
|
|
129
|
+
'operations',
|
|
130
|
+
operation.id,
|
|
131
|
+
);
|
|
126
132
|
await plugins.smartfile.fs.remove(operationDir);
|
|
127
|
-
|
|
133
|
+
|
|
128
134
|
// Remove from manifest
|
|
129
|
-
manifest.operations = manifest.operations.filter(
|
|
135
|
+
manifest.operations = manifest.operations.filter(
|
|
136
|
+
(op) => op.id !== operation.id,
|
|
137
|
+
);
|
|
130
138
|
}
|
|
131
|
-
|
|
139
|
+
|
|
132
140
|
await this.saveManifest(manifest);
|
|
133
141
|
}
|
|
134
|
-
|
|
142
|
+
|
|
135
143
|
async verifyBackup(operationId: string): Promise<boolean> {
|
|
136
144
|
const operation = await this.getOperation(operationId);
|
|
137
145
|
if (!operation) {
|
|
138
146
|
return false;
|
|
139
147
|
}
|
|
140
|
-
|
|
148
|
+
|
|
141
149
|
for (const file of operation.files) {
|
|
142
150
|
const backupPath = this.getBackupPath(operationId, file.path);
|
|
143
151
|
const exists = await plugins.smartfile.fs.fileExists(backupPath);
|
|
144
|
-
|
|
152
|
+
|
|
145
153
|
if (!exists) {
|
|
146
154
|
return false;
|
|
147
155
|
}
|
|
148
|
-
|
|
149
|
-
const content =
|
|
156
|
+
|
|
157
|
+
const content = plugins.smartfile.fs.toStringSync(backupPath);
|
|
150
158
|
const checksum = this.calculateChecksum(content);
|
|
151
|
-
|
|
159
|
+
|
|
152
160
|
if (checksum !== file.checksum) {
|
|
153
161
|
return false;
|
|
154
162
|
}
|
|
155
163
|
}
|
|
156
|
-
|
|
164
|
+
|
|
157
165
|
return true;
|
|
158
166
|
}
|
|
159
|
-
|
|
167
|
+
|
|
160
168
|
async listBackups(): Promise<IFormatOperation[]> {
|
|
161
169
|
const manifest = await this.getManifest();
|
|
162
170
|
return manifest.operations;
|
|
163
171
|
}
|
|
164
|
-
|
|
172
|
+
|
|
165
173
|
private async ensureBackupDir(): Promise<void> {
|
|
166
174
|
await plugins.smartfile.fs.ensureDir(this.backupDir);
|
|
167
|
-
await plugins.smartfile.fs.ensureDir(
|
|
175
|
+
await plugins.smartfile.fs.ensureDir(
|
|
176
|
+
plugins.path.join(this.backupDir, 'operations'),
|
|
177
|
+
);
|
|
168
178
|
}
|
|
169
|
-
|
|
179
|
+
|
|
170
180
|
private generateOperationId(): string {
|
|
171
181
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
172
182
|
const random = Math.random().toString(36).substring(2, 8);
|
|
173
183
|
return `${timestamp}-${random}`;
|
|
174
184
|
}
|
|
175
|
-
|
|
185
|
+
|
|
176
186
|
private getBackupPath(operationId: string, filepath: string): string {
|
|
177
187
|
const filename = plugins.path.basename(filepath);
|
|
178
188
|
const dir = plugins.path.dirname(filepath);
|
|
179
189
|
const safeDir = dir.replace(/[/\\]/g, '__');
|
|
180
|
-
return plugins.path.join(
|
|
190
|
+
return plugins.path.join(
|
|
191
|
+
this.backupDir,
|
|
192
|
+
'operations',
|
|
193
|
+
operationId,
|
|
194
|
+
'files',
|
|
195
|
+
safeDir,
|
|
196
|
+
`${filename}.backup`,
|
|
197
|
+
);
|
|
181
198
|
}
|
|
182
|
-
|
|
199
|
+
|
|
183
200
|
private calculateChecksum(content: string | Buffer): string {
|
|
184
201
|
return plugins.crypto.createHash('sha256').update(content).digest('hex');
|
|
185
202
|
}
|
|
186
|
-
|
|
203
|
+
|
|
187
204
|
private async getManifest(): Promise<{ operations: IFormatOperation[] }> {
|
|
205
|
+
const defaultManifest = { operations: [] };
|
|
206
|
+
|
|
188
207
|
const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
|
|
189
208
|
if (!exists) {
|
|
190
|
-
return
|
|
209
|
+
return defaultManifest;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const content = plugins.smartfile.fs.toStringSync(this.manifestPath);
|
|
214
|
+
const manifest = JSON.parse(content);
|
|
215
|
+
|
|
216
|
+
// Validate the manifest structure
|
|
217
|
+
if (this.isValidManifest(manifest)) {
|
|
218
|
+
return manifest;
|
|
219
|
+
} else {
|
|
220
|
+
console.warn(
|
|
221
|
+
'Invalid rollback manifest structure, returning default manifest',
|
|
222
|
+
);
|
|
223
|
+
return defaultManifest;
|
|
224
|
+
}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.warn(
|
|
227
|
+
`Failed to read rollback manifest: ${error.message}, returning default manifest`,
|
|
228
|
+
);
|
|
229
|
+
// Try to delete the corrupted file
|
|
230
|
+
try {
|
|
231
|
+
await plugins.smartfile.fs.remove(this.manifestPath);
|
|
232
|
+
} catch (removeError) {
|
|
233
|
+
// Ignore removal errors
|
|
234
|
+
}
|
|
235
|
+
return defaultManifest;
|
|
191
236
|
}
|
|
192
|
-
|
|
193
|
-
const content = await plugins.smartfile.fs.toStringSync(this.manifestPath);
|
|
194
|
-
return JSON.parse(content);
|
|
195
237
|
}
|
|
196
|
-
|
|
197
|
-
private async saveManifest(manifest: {
|
|
198
|
-
|
|
238
|
+
|
|
239
|
+
private async saveManifest(manifest: {
|
|
240
|
+
operations: IFormatOperation[];
|
|
241
|
+
}): Promise<void> {
|
|
242
|
+
// Validate before saving
|
|
243
|
+
if (!this.isValidManifest(manifest)) {
|
|
244
|
+
throw new Error('Invalid rollback manifest structure, cannot save');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Ensure directory exists
|
|
248
|
+
await this.ensureBackupDir();
|
|
249
|
+
|
|
250
|
+
// Write directly with proper JSON stringification
|
|
251
|
+
const jsonContent = JSON.stringify(manifest, null, 2);
|
|
252
|
+
await plugins.smartfile.memory.toFs(jsonContent, this.manifestPath);
|
|
199
253
|
}
|
|
200
|
-
|
|
201
|
-
private async getOperation(
|
|
254
|
+
|
|
255
|
+
private async getOperation(
|
|
256
|
+
operationId: string,
|
|
257
|
+
): Promise<IFormatOperation | null> {
|
|
202
258
|
const manifest = await this.getManifest();
|
|
203
|
-
return manifest.operations.find(op => op.id === operationId) || null;
|
|
259
|
+
return manifest.operations.find((op) => op.id === operationId) || null;
|
|
204
260
|
}
|
|
205
|
-
|
|
261
|
+
|
|
206
262
|
private async updateManifest(operation: IFormatOperation): Promise<void> {
|
|
207
263
|
const manifest = await this.getManifest();
|
|
208
|
-
const existingIndex = manifest.operations.findIndex(
|
|
209
|
-
|
|
264
|
+
const existingIndex = manifest.operations.findIndex(
|
|
265
|
+
(op) => op.id === operation.id,
|
|
266
|
+
);
|
|
267
|
+
|
|
210
268
|
if (existingIndex !== -1) {
|
|
211
269
|
manifest.operations[existingIndex] = operation;
|
|
212
270
|
} else {
|
|
213
271
|
manifest.operations.push(operation);
|
|
214
272
|
}
|
|
215
|
-
|
|
273
|
+
|
|
216
274
|
await this.saveManifest(manifest);
|
|
217
275
|
}
|
|
218
|
-
|
|
276
|
+
|
|
277
|
+
private isValidManifest(
|
|
278
|
+
manifest: any,
|
|
279
|
+
): manifest is { operations: IFormatOperation[] } {
|
|
280
|
+
// Check if manifest has the required structure
|
|
281
|
+
if (!manifest || typeof manifest !== 'object') {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Check required fields
|
|
286
|
+
if (!Array.isArray(manifest.operations)) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Check each operation entry
|
|
291
|
+
for (const operation of manifest.operations) {
|
|
292
|
+
if (
|
|
293
|
+
!operation ||
|
|
294
|
+
typeof operation !== 'object' ||
|
|
295
|
+
typeof operation.id !== 'string' ||
|
|
296
|
+
typeof operation.timestamp !== 'number' ||
|
|
297
|
+
typeof operation.status !== 'string' ||
|
|
298
|
+
!Array.isArray(operation.files)
|
|
299
|
+
) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Check each file in the operation
|
|
304
|
+
for (const file of operation.files) {
|
|
305
|
+
if (
|
|
306
|
+
!file ||
|
|
307
|
+
typeof file !== 'object' ||
|
|
308
|
+
typeof file.path !== 'string' ||
|
|
309
|
+
typeof file.checksum !== 'string'
|
|
310
|
+
) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
@@ -4,14 +4,21 @@ import * as paths from '../paths.js';
|
|
|
4
4
|
import { logger } from '../gitzone.logging.js';
|
|
5
5
|
import { Project } from '../classes.project.js';
|
|
6
6
|
|
|
7
|
-
const filesToDelete = [
|
|
7
|
+
const filesToDelete = [
|
|
8
|
+
'defaults.yml',
|
|
9
|
+
'yarn.lock',
|
|
10
|
+
'package-lock.json',
|
|
11
|
+
'tslint.json',
|
|
12
|
+
];
|
|
8
13
|
|
|
9
14
|
export const run = async (projectArg: Project) => {
|
|
10
15
|
for (const relativeFilePath of filesToDelete) {
|
|
11
16
|
const fileExists = plugins.smartfile.fs.fileExistsSync(relativeFilePath);
|
|
12
17
|
if (fileExists) {
|
|
13
18
|
logger.log('info', `Found ${relativeFilePath}! Removing it!`);
|
|
14
|
-
plugins.smartfile.fs.removeSync(
|
|
19
|
+
plugins.smartfile.fs.removeSync(
|
|
20
|
+
plugins.path.join(paths.cwd, relativeFilePath),
|
|
21
|
+
);
|
|
15
22
|
} else {
|
|
16
23
|
logger.log('info', `Project is free of ${relativeFilePath}`);
|
|
17
24
|
}
|