@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.
Files changed (79) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/classes.gitzoneconfig.js +1 -1
  3. package/dist_ts/gitzone.cli.js +1 -1
  4. package/dist_ts/gitzone.logging.js +1 -1
  5. package/dist_ts/mod_commit/index.js +3 -2
  6. package/dist_ts/mod_deprecate/index.js +1 -1
  7. package/dist_ts/mod_format/classes.baseformatter.d.ts +0 -2
  8. package/dist_ts/mod_format/classes.baseformatter.js +3 -22
  9. package/dist_ts/mod_format/classes.changecache.js +14 -29
  10. package/dist_ts/mod_format/classes.dependency-analyzer.js +24 -18
  11. package/dist_ts/mod_format/classes.diffreporter.js +4 -4
  12. package/dist_ts/mod_format/classes.formatcontext.d.ts +0 -13
  13. package/dist_ts/mod_format/classes.formatcontext.js +1 -41
  14. package/dist_ts/mod_format/classes.formatplanner.js +25 -45
  15. package/dist_ts/mod_format/classes.formatstats.js +22 -20
  16. package/dist_ts/mod_format/classes.rollbackmanager.d.ts +1 -0
  17. package/dist_ts/mod_format/classes.rollbackmanager.js +79 -16
  18. package/dist_ts/mod_format/format.cleanup.js +7 -2
  19. package/dist_ts/mod_format/format.copy.d.ts +1 -1
  20. package/dist_ts/mod_format/format.copy.js +3 -3
  21. package/dist_ts/mod_format/format.gitignore.js +1 -1
  22. package/dist_ts/mod_format/format.license.js +1 -1
  23. package/dist_ts/mod_format/format.npmextra.js +1 -1
  24. package/dist_ts/mod_format/format.packagejson.js +5 -3
  25. package/dist_ts/mod_format/format.prettier.js +7 -2
  26. package/dist_ts/mod_format/format.readme.js +1 -1
  27. package/dist_ts/mod_format/format.templates.js +1 -1
  28. package/dist_ts/mod_format/format.tsconfig.js +4 -2
  29. package/dist_ts/mod_format/formatters/cleanup.formatter.js +8 -3
  30. package/dist_ts/mod_format/formatters/legacy.formatter.js +6 -4
  31. package/dist_ts/mod_format/formatters/prettier.formatter.js +9 -16
  32. package/dist_ts/mod_format/formatters/readme.formatter.js +6 -4
  33. package/dist_ts/mod_format/index.js +13 -71
  34. package/dist_ts/mod_meta/meta.classes.meta.js +1 -1
  35. package/dist_ts/mod_standard/index.js +1 -1
  36. package/dist_ts/mod_start/index.js +1 -1
  37. package/dist_ts/mod_template/index.js +1 -1
  38. package/package.json +1 -1
  39. package/ts/00_commitinfo_data.ts +1 -1
  40. package/ts/classes.gitzoneconfig.ts +3 -1
  41. package/ts/gitzone.cli.ts +5 -5
  42. package/ts/gitzone.logging.ts +2 -1
  43. package/ts/mod_commit/index.ts +36 -11
  44. package/ts/mod_deprecate/index.ts +4 -1
  45. package/ts/mod_format/classes.baseformatter.ts +15 -38
  46. package/ts/mod_format/classes.changecache.ts +74 -80
  47. package/ts/mod_format/classes.dependency-analyzer.ts +46 -36
  48. package/ts/mod_format/classes.diffreporter.ts +44 -28
  49. package/ts/mod_format/classes.formatcontext.ts +3 -54
  50. package/ts/mod_format/classes.formatplanner.ts +56 -70
  51. package/ts/mod_format/classes.formatstats.ts +69 -49
  52. package/ts/mod_format/classes.rollbackmanager.ts +170 -70
  53. package/ts/mod_format/format.cleanup.ts +9 -2
  54. package/ts/mod_format/format.copy.ts +17 -14
  55. package/ts/mod_format/format.gitignore.ts +2 -1
  56. package/ts/mod_format/format.license.ts +3 -1
  57. package/ts/mod_format/format.npmextra.ts +9 -2
  58. package/ts/mod_format/format.packagejson.ts +47 -12
  59. package/ts/mod_format/format.prettier.ts +10 -2
  60. package/ts/mod_format/format.readme.ts +2 -1
  61. package/ts/mod_format/format.templates.ts +15 -7
  62. package/ts/mod_format/format.tsconfig.ts +6 -2
  63. package/ts/mod_format/formatters/cleanup.formatter.ts +13 -8
  64. package/ts/mod_format/formatters/copy.formatter.ts +1 -1
  65. package/ts/mod_format/formatters/gitignore.formatter.ts +1 -1
  66. package/ts/mod_format/formatters/legacy.formatter.ts +19 -12
  67. package/ts/mod_format/formatters/license.formatter.ts +1 -1
  68. package/ts/mod_format/formatters/npmextra.formatter.ts +1 -1
  69. package/ts/mod_format/formatters/packagejson.formatter.ts +1 -1
  70. package/ts/mod_format/formatters/prettier.formatter.ts +50 -47
  71. package/ts/mod_format/formatters/readme.formatter.ts +11 -9
  72. package/ts/mod_format/formatters/templates.formatter.ts +1 -1
  73. package/ts/mod_format/formatters/tsconfig.formatter.ts +1 -1
  74. package/ts/mod_format/index.ts +54 -113
  75. package/ts/mod_format/interfaces.format.ts +3 -3
  76. package/ts/mod_meta/meta.classes.meta.ts +57 -19
  77. package/ts/mod_standard/index.ts +3 -1
  78. package/ts/mod_start/index.ts +3 -1
  79. 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(moduleName: string, operation: 'create' | 'modify' | 'delete', success: boolean = true): void {
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(` Total Execution Time: ${this.formatDuration(this.stats.totalExecutionTime)}`);
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 (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;
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
- .sort((a, b) => b.filesProcessed - a.filesProcessed);
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(`\n${this.getModuleIcon(moduleStats.name)} ${moduleStats.name}:`);
149
- console.log(` Execution Time: ${this.formatDuration(moduleStats.executionTime)}`);
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(JSON.stringify(report, null, 2), outputPath);
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
- 'packagejson': '📦',
197
- 'license': '📝',
198
- 'tsconfig': '🔧',
199
- 'cleanup': '🚮',
200
- 'gitignore': '🔒',
201
- 'prettier': '✨',
202
- 'readme': '📖',
203
- 'templates': '📄',
204
- 'npmextra': '⚙️',
205
- 'copy': '📋'
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 = await plugins.smartfile.fs.toStringSync(absolutePath);
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
- throw new Error(`Operation ${operationId} not found`);
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 = await plugins.smartfile.fs.toStringSync(backupPath);
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() - (retentionDays * 24 * 60 * 60 * 1000);
118
-
119
- const operationsToDelete = manifest.operations.filter(op =>
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(this.backupDir, 'operations', operation.id);
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(op => op.id !== operation.id);
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 = await plugins.smartfile.fs.toStringSync(backupPath);
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(plugins.path.join(this.backupDir, 'operations'));
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(this.backupDir, 'operations', operationId, 'files', safeDir, `${filename}.backup`);
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 { operations: [] };
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: { operations: IFormatOperation[] }): Promise<void> {
198
- await plugins.smartfile.memory.toFs(JSON.stringify(manifest, null, 2), this.manifestPath);
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(operationId: string): Promise<IFormatOperation | null> {
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(op => op.id === operation.id);
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 = ['defaults.yml', 'yarn.lock', 'package-lock.json', 'tslint.json'];
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(plugins.path.join(paths.cwd, relativeFilePath));
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
  }