i18ntk 1.10.1 → 2.0.2
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/LICENSE +1 -1
- package/README.md +141 -1185
- package/main/i18ntk-analyze.js +149 -133
- package/main/i18ntk-backup-class.js +420 -0
- package/main/i18ntk-backup.js +4 -4
- package/main/i18ntk-complete.js +90 -65
- package/main/i18ntk-doctor.js +123 -103
- package/main/i18ntk-fixer.js +61 -725
- package/main/i18ntk-go.js +14 -15
- package/main/i18ntk-init.js +76 -25
- package/main/i18ntk-java.js +27 -32
- package/main/i18ntk-js.js +70 -68
- package/main/i18ntk-manage.js +128 -29
- package/main/i18ntk-php.js +75 -75
- package/main/i18ntk-py.js +55 -56
- package/main/i18ntk-scanner.js +59 -57
- package/main/i18ntk-setup.js +10 -396
- package/main/i18ntk-sizing.js +46 -40
- package/main/i18ntk-summary.js +21 -18
- package/main/i18ntk-ui.js +11 -10
- package/main/i18ntk-usage.js +55 -19
- package/main/i18ntk-validate.js +13 -13
- package/main/manage/commands/AnalyzeCommand.js +1124 -0
- package/main/manage/commands/BackupCommand.js +62 -0
- package/main/manage/commands/CommandRouter.js +295 -0
- package/main/manage/commands/CompleteCommand.js +61 -0
- package/main/manage/commands/DoctorCommand.js +60 -0
- package/main/manage/commands/FixerCommand.js +624 -0
- package/main/manage/commands/InitCommand.js +62 -0
- package/main/manage/commands/ScannerCommand.js +654 -0
- package/main/manage/commands/SizingCommand.js +60 -0
- package/main/manage/commands/SummaryCommand.js +61 -0
- package/main/manage/commands/UsageCommand.js +60 -0
- package/main/manage/commands/ValidateCommand.js +978 -0
- package/main/manage/index-fixed.js +1447 -0
- package/main/manage/index.js +1462 -0
- package/main/manage/managers/DebugMenu.js +140 -0
- package/main/manage/managers/InteractiveMenu.js +177 -0
- package/main/manage/managers/LanguageMenu.js +62 -0
- package/main/manage/managers/SettingsMenu.js +53 -0
- package/main/manage/services/AuthenticationService.js +263 -0
- package/main/manage/services/ConfigurationService-fixed.js +449 -0
- package/main/manage/services/ConfigurationService.js +449 -0
- package/main/manage/services/FileManagementService.js +368 -0
- package/main/manage/services/FrameworkDetectionService.js +458 -0
- package/main/manage/services/InitService.js +1051 -0
- package/main/manage/services/SetupService.js +462 -0
- package/main/manage/services/SummaryService.js +450 -0
- package/main/manage/services/UsageService.js +1502 -0
- package/package.json +32 -30
- package/runtime/enhanced.d.ts +221 -221
- package/runtime/index.d.ts +29 -29
- package/runtime/index.full.d.ts +331 -331
- package/runtime/index.js +7 -6
- package/scripts/build-lite.js +17 -17
- package/scripts/deprecate-versions.js +23 -6
- package/scripts/export-translations.js +5 -5
- package/scripts/fix-all-i18n.js +3 -3
- package/scripts/fix-and-purify-i18n.js +3 -2
- package/scripts/fix-locale-control-chars.js +110 -0
- package/scripts/lint-locales.js +80 -0
- package/scripts/locale-optimizer.js +8 -8
- package/scripts/prepublish.js +21 -21
- package/scripts/security-check.js +13 -5
- package/scripts/sync-translations.js +4 -4
- package/scripts/sync-ui-locales.js +9 -8
- package/scripts/validate-all-translations.js +8 -7
- package/scripts/verify-deprecations.js +23 -15
- package/scripts/verify-translations.js +6 -5
- package/settings/i18ntk-config.json +282 -282
- package/settings/language-config.json +5 -5
- package/settings/settings-cli.js +9 -9
- package/settings/settings-manager.js +23 -20
- package/ui-locales/de.json +2417 -2348
- package/ui-locales/en.json +2415 -2352
- package/ui-locales/es.json +2425 -2353
- package/ui-locales/fr.json +2418 -2348
- package/ui-locales/ja.json +2463 -2361
- package/ui-locales/ru.json +2463 -2359
- package/ui-locales/zh.json +2418 -2351
- package/utils/admin-auth.js +2 -2
- package/utils/admin-cli.js +297 -297
- package/utils/admin-pin.js +9 -9
- package/utils/cli-helper.js +9 -9
- package/utils/config-helper.js +152 -103
- package/utils/config-manager.js +204 -164
- package/utils/config.js +5 -4
- package/utils/env-manager.js +256 -0
- package/utils/framework-detector.js +27 -24
- package/utils/i18n-helper.js +85 -41
- package/utils/init-helper.js +152 -94
- package/utils/json-output.js +98 -98
- package/utils/logger.js +6 -2
- package/utils/mini-commander.js +179 -0
- package/utils/missing-key-validator.js +5 -5
- package/utils/plugin-loader.js +29 -11
- package/utils/prompt.js +14 -44
- package/utils/safe-json.js +40 -0
- package/utils/secure-errors.js +3 -3
- package/utils/security-check-improved.js +390 -0
- package/utils/security-config.js +5 -5
- package/utils/security-fixed.js +607 -0
- package/utils/security.js +462 -248
- package/utils/setup-enforcer.js +136 -44
- package/utils/setup-validator.js +33 -32
- package/utils/terminal-icons.js +1 -1
- package/utils/ultra-performance-optimizer.js +11 -9
- package/utils/watch-locales.js +2 -1
- package/utils/prompt-fixed.js +0 -55
- package/utils/security-check.js +0 -450
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const fs = require('fs/promises');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { existsSync } = require('fs');
|
|
8
|
+
const configManager = require('../utils/config-manager');
|
|
9
|
+
const { logger } = require('../utils/logger');
|
|
10
|
+
const { colors } = require('../utils/logger');
|
|
11
|
+
const SecurityUtils = require('../utils/security');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* I18NTK BACKUP CLASS
|
|
15
|
+
*
|
|
16
|
+
* Class-based implementation of backup functionality for use with CommandRouter
|
|
17
|
+
*/
|
|
18
|
+
class I18nBackup {
|
|
19
|
+
constructor(config = {}) {
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.backupDir = path.join(process.cwd(), 'i18n-backups');
|
|
22
|
+
this.maxBackups = config.backup?.maxBackups || 10;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Main run method for the backup command
|
|
27
|
+
*/
|
|
28
|
+
async run(options = {}) {
|
|
29
|
+
const command = options.command || options._ && options._[0];
|
|
30
|
+
|
|
31
|
+
// Show help if no command provided
|
|
32
|
+
if (!command) {
|
|
33
|
+
this.showHelp();
|
|
34
|
+
return { success: true, command: 'help' };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
switch (command) {
|
|
39
|
+
case 'create':
|
|
40
|
+
return await this.handleCreate(options);
|
|
41
|
+
case 'restore':
|
|
42
|
+
return await this.handleRestore(options);
|
|
43
|
+
case 'list':
|
|
44
|
+
return await this.handleList();
|
|
45
|
+
case 'verify':
|
|
46
|
+
return await this.handleVerify(options);
|
|
47
|
+
case 'cleanup':
|
|
48
|
+
return await this.handleCleanup(options);
|
|
49
|
+
default:
|
|
50
|
+
logger.error(`Unknown command: ${command}`);
|
|
51
|
+
this.showHelp();
|
|
52
|
+
return { success: false, error: `Unknown command: ${command}` };
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
this.handleError(error);
|
|
56
|
+
return { success: false, error: error.message };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
showHelp() {
|
|
61
|
+
console.log(`
|
|
62
|
+
i18ntk-backup - Secure backup and restore for i18n translation files
|
|
63
|
+
|
|
64
|
+
Usage:
|
|
65
|
+
i18ntk-backup <command> [options]
|
|
66
|
+
|
|
67
|
+
Commands:
|
|
68
|
+
create <dir> Create a backup of translation files
|
|
69
|
+
restore <file> Restore from a backup
|
|
70
|
+
list List available backups
|
|
71
|
+
verify <file> Verify the integrity of a backup file
|
|
72
|
+
cleanup Remove old backups
|
|
73
|
+
|
|
74
|
+
Options:
|
|
75
|
+
--output <path> Output directory for backup/restore
|
|
76
|
+
--force Overwrite existing files without prompting
|
|
77
|
+
--keep <number> Number of backups to keep (default: 10)
|
|
78
|
+
`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Command handlers
|
|
82
|
+
async handleCreate(options = {}) {
|
|
83
|
+
// Use absolute path for the locales directory
|
|
84
|
+
const dir = (options._ && options._[1]) || options.dir || path.join(__dirname, '..', 'locales');
|
|
85
|
+
const outputDir = options.output || this.backupDir;
|
|
86
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
87
|
+
const backupName = `backup-${timestamp}.json`;
|
|
88
|
+
const backupPath = path.join(outputDir, backupName);
|
|
89
|
+
|
|
90
|
+
// Log the paths for debugging
|
|
91
|
+
logger.debug(`Source directory: ${dir}`);
|
|
92
|
+
logger.debug(`Backup will be saved to: ${backupPath}`);
|
|
93
|
+
|
|
94
|
+
// Create backup directory if it doesn't exist
|
|
95
|
+
try {
|
|
96
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
97
|
+
logger.debug(`Created backup directory: ${outputDir}`);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
if (err.code !== 'EEXIST') {
|
|
100
|
+
logger.error(`Failed to create backup directory: ${err.message}`);
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
logger.debug(`Using existing backup directory: ${outputDir}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Validate directory
|
|
107
|
+
const sourceDir = path.resolve(dir);
|
|
108
|
+
try {
|
|
109
|
+
const stats = await fs.stat(sourceDir);
|
|
110
|
+
if (!stats.isDirectory()) {
|
|
111
|
+
throw new Error(`Path exists but is not a directory: ${sourceDir}`);
|
|
112
|
+
}
|
|
113
|
+
logger.debug(`Source directory exists: ${sourceDir}`);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
if (err.code === 'ENOENT') {
|
|
116
|
+
throw new Error(`Directory not found: ${sourceDir}. Please specify a valid directory.`);
|
|
117
|
+
}
|
|
118
|
+
throw new Error(`Error accessing directory ${sourceDir}: ${err.message}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
logger.info('\nCreating backup...');
|
|
122
|
+
|
|
123
|
+
// Read all files in the directory
|
|
124
|
+
const files = (await fs.readdir(sourceDir, { withFileTypes: true }))
|
|
125
|
+
.filter(dirent => dirent.isFile() && dirent.name.endsWith('.json'))
|
|
126
|
+
.map(dirent => dirent.name);
|
|
127
|
+
|
|
128
|
+
if (files.length === 0) {
|
|
129
|
+
logger.warn('No JSON files found in the specified directory');
|
|
130
|
+
return { success: true, message: 'No files to backup' };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Read all translation files
|
|
134
|
+
const translations = {};
|
|
135
|
+
for (const file of files) {
|
|
136
|
+
const filePath = path.join(sourceDir, file);
|
|
137
|
+
try {
|
|
138
|
+
const content = JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
139
|
+
translations[file] = content;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
logger.error(`Could not read file ${file}: ${error.message}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Create the backup
|
|
146
|
+
await fs.writeFile(backupPath, JSON.stringify(translations, null, 2));
|
|
147
|
+
const stats = await fs.stat(backupPath);
|
|
148
|
+
|
|
149
|
+
logger.success('Backup created successfully');
|
|
150
|
+
logger.info(` Location: ${backupPath}`);
|
|
151
|
+
logger.info(` Size: ${(stats.size / 1024).toFixed(2)} KB`);
|
|
152
|
+
logger.info(` Timestamp: ${new Date().toLocaleString()}`);
|
|
153
|
+
|
|
154
|
+
// Clean up old backups
|
|
155
|
+
await this.cleanupOldBackups(outputDir);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
success: true,
|
|
159
|
+
command: 'create',
|
|
160
|
+
backupPath,
|
|
161
|
+
size: stats.size,
|
|
162
|
+
fileCount: files.length
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async handleRestore(options = {}) {
|
|
167
|
+
const backupFile = options._ && options._[1];
|
|
168
|
+
if (!backupFile) {
|
|
169
|
+
throw new Error('Backup file path is required');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const backupPath = path.resolve(process.cwd(), backupFile);
|
|
173
|
+
const outputDir = options.output
|
|
174
|
+
? path.resolve(process.cwd(), options.output)
|
|
175
|
+
: path.join(process.cwd(), 'restored');
|
|
176
|
+
|
|
177
|
+
// Validate backup file
|
|
178
|
+
if (!SecurityUtils.safeExistsSync(backupPath, process.cwd())) {
|
|
179
|
+
throw new Error(`Backup file not found: ${backupPath}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
logger.info('\nRestoring backup...');
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
// Read the backup file
|
|
186
|
+
const backupData = await fs.readFile(backupPath, 'utf8');
|
|
187
|
+
const translations = JSON.parse(backupData);
|
|
188
|
+
|
|
189
|
+
// Create output directory if it doesn't exist
|
|
190
|
+
try {
|
|
191
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
192
|
+
} catch (err) {
|
|
193
|
+
if (err.code !== 'EEXIST') throw err;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Write the restored files
|
|
197
|
+
for (const [file, content] of Object.entries(translations)) {
|
|
198
|
+
const filePath = path.join(outputDir, file);
|
|
199
|
+
await fs.writeFile(filePath, JSON.stringify(content, null, 2));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
logger.success('Backup restored successfully');
|
|
203
|
+
logger.info(` Restored ${Object.keys(translations).length} files to: ${outputDir}`);
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
success: true,
|
|
207
|
+
command: 'restore',
|
|
208
|
+
outputDir,
|
|
209
|
+
fileCount: Object.keys(translations).length
|
|
210
|
+
};
|
|
211
|
+
} catch (error) {
|
|
212
|
+
this.handleError(error);
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async handleList() {
|
|
218
|
+
try {
|
|
219
|
+
// Ensure backup directory exists
|
|
220
|
+
try {
|
|
221
|
+
await fs.access(this.backupDir);
|
|
222
|
+
} catch (err) {
|
|
223
|
+
if (err.code === 'ENOENT') {
|
|
224
|
+
logger.warn('No backups found. The backup directory does not exist yet.');
|
|
225
|
+
} else {
|
|
226
|
+
logger.error(`Error accessing backup directory: ${err.message}`);
|
|
227
|
+
}
|
|
228
|
+
return { success: true, backups: [] };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const files = await fs.readdir(this.backupDir);
|
|
232
|
+
const backups = [];
|
|
233
|
+
|
|
234
|
+
for (const file of files) {
|
|
235
|
+
if (file.startsWith('backup-') && file.endsWith('.json')) {
|
|
236
|
+
try {
|
|
237
|
+
const filePath = path.join(this.backupDir, file);
|
|
238
|
+
const stats = await fs.stat(filePath);
|
|
239
|
+
backups.push({
|
|
240
|
+
name: file,
|
|
241
|
+
path: filePath,
|
|
242
|
+
size: stats.size,
|
|
243
|
+
createdAt: stats.mtime
|
|
244
|
+
});
|
|
245
|
+
} catch (err) {
|
|
246
|
+
logger.warn(`Skipping invalid backup file ${file}: ${err.message}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (backups.length === 0) {
|
|
252
|
+
logger.warn('No valid backup files found in the backup directory.');
|
|
253
|
+
return { success: true, backups: [] };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Sort by creation time (newest first)
|
|
257
|
+
backups.sort((a, b) => b.createdAt - a.createdAt);
|
|
258
|
+
|
|
259
|
+
logger.info('\n📋 Available Backups');
|
|
260
|
+
logger.info('='.repeat(50));
|
|
261
|
+
|
|
262
|
+
backups.forEach((backup, index) => {
|
|
263
|
+
const sizeKB = (backup.size / 1024).toFixed(2);
|
|
264
|
+
const formattedDate = backup.createdAt.toLocaleString();
|
|
265
|
+
|
|
266
|
+
logger.info(`🔹 ${index + 1}. ${backup.name}`);
|
|
267
|
+
logger.info(` 📏 Size: ${sizeKB} KB`);
|
|
268
|
+
logger.info(` 📅 Created: ${formattedDate}`);
|
|
269
|
+
logger.info(` 📂 Path: ${backup.path}\n`);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
logger.info(`Total backups: ${backups.length}`);
|
|
273
|
+
|
|
274
|
+
return { success: true, backups, count: backups.length };
|
|
275
|
+
} catch (err) {
|
|
276
|
+
if (err.code === 'ENOENT') {
|
|
277
|
+
logger.warn('No backups found.');
|
|
278
|
+
return { success: true, backups: [] };
|
|
279
|
+
} else {
|
|
280
|
+
throw err;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async handleVerify(options = {}) {
|
|
286
|
+
const backupFile = options._ && options._[1];
|
|
287
|
+
if (!backupFile) {
|
|
288
|
+
throw new Error('Backup file path is required');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const backupPath = path.resolve(process.cwd(), backupFile);
|
|
292
|
+
|
|
293
|
+
// Validate backup file
|
|
294
|
+
if (!SecurityUtils.safeExistsSync(backupPath, process.cwd())) {
|
|
295
|
+
throw new Error(`Backup file not found: ${backupPath}`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
logger.info('\nVerifying backup...');
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
const data = await fs.readFile(backupPath, 'utf8');
|
|
302
|
+
const content = JSON.parse(data);
|
|
303
|
+
|
|
304
|
+
if (typeof content === 'object' && content !== null) {
|
|
305
|
+
const fileCount = Object.keys(content).length;
|
|
306
|
+
logger.success('Backup is valid');
|
|
307
|
+
logger.info(` Contains ${fileCount} translation files`);
|
|
308
|
+
logger.info(` Last modified: ${(await fs.stat(backupPath)).mtime.toLocaleString()}`);
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
success: true,
|
|
312
|
+
command: 'verify',
|
|
313
|
+
valid: true,
|
|
314
|
+
fileCount
|
|
315
|
+
};
|
|
316
|
+
} else {
|
|
317
|
+
throw new Error('Invalid backup format');
|
|
318
|
+
}
|
|
319
|
+
} catch (error) {
|
|
320
|
+
logger.error('Backup verification failed!');
|
|
321
|
+
logger.error(` Error: ${error.message}`);
|
|
322
|
+
return {
|
|
323
|
+
success: false,
|
|
324
|
+
command: 'verify',
|
|
325
|
+
valid: false,
|
|
326
|
+
error: error.message
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async handleCleanup(options = {}) {
|
|
332
|
+
const keep = options.keep ? parseInt(options.keep, 10) : this.maxBackups;
|
|
333
|
+
|
|
334
|
+
logger.info('\nCleaning up old backups...');
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
const files = await fs.readdir(this.backupDir);
|
|
338
|
+
const backupFiles = files
|
|
339
|
+
.filter(file => file.startsWith('backup-') && file.endsWith('.json'))
|
|
340
|
+
.map(file => ({
|
|
341
|
+
name: file,
|
|
342
|
+
path: path.join(this.backupDir, file),
|
|
343
|
+
time: fs.statSync(path.join(this.backupDir, file)).mtime.getTime()
|
|
344
|
+
}))
|
|
345
|
+
.sort((a, b) => b.time - a.time);
|
|
346
|
+
|
|
347
|
+
// Keep only the most recent 'keep' files
|
|
348
|
+
const toDelete = backupFiles.slice(keep);
|
|
349
|
+
|
|
350
|
+
if (toDelete.length === 0) {
|
|
351
|
+
logger.info('No old backups to delete.');
|
|
352
|
+
return { success: true, deleted: 0 };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Delete old backups
|
|
356
|
+
for (const file of toDelete) {
|
|
357
|
+
try {
|
|
358
|
+
await fs.unlink(file.path);
|
|
359
|
+
logger.info(` - Deleted: ${file.name}`);
|
|
360
|
+
} catch (err) {
|
|
361
|
+
logger.error(` - Failed to delete ${file.name}: ${err.message}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
logger.info(`\nRemoved ${toDelete.length} old backups`);
|
|
366
|
+
logger.info(`Total backups kept: ${keep}`);
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
success: true,
|
|
370
|
+
command: 'cleanup',
|
|
371
|
+
deleted: toDelete.length,
|
|
372
|
+
kept: keep
|
|
373
|
+
};
|
|
374
|
+
} catch (error) {
|
|
375
|
+
logger.error('Error cleaning up backups:');
|
|
376
|
+
logger.error(` ${error.message}`);
|
|
377
|
+
if (process.env.DEBUG) {
|
|
378
|
+
console.error(error);
|
|
379
|
+
}
|
|
380
|
+
throw error;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async cleanupOldBackups(outputDir) {
|
|
385
|
+
try {
|
|
386
|
+
const files = await fs.readdir(outputDir);
|
|
387
|
+
const backupFiles = files
|
|
388
|
+
.filter(file => file.startsWith('backup-') && file.endsWith('.json'))
|
|
389
|
+
.map(file => ({
|
|
390
|
+
name: file,
|
|
391
|
+
path: path.join(outputDir, file),
|
|
392
|
+
time: fs.statSync(path.join(outputDir, file)).mtime.getTime()
|
|
393
|
+
}))
|
|
394
|
+
.sort((a, b) => b.time - a.time);
|
|
395
|
+
|
|
396
|
+
// Keep only the most recent files
|
|
397
|
+
const toDelete = backupFiles.slice(this.maxBackups);
|
|
398
|
+
|
|
399
|
+
for (const file of toDelete) {
|
|
400
|
+
try {
|
|
401
|
+
await fs.unlink(file.path);
|
|
402
|
+
} catch (err) {
|
|
403
|
+
// Ignore cleanup errors
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
} catch (error) {
|
|
407
|
+
// Ignore cleanup errors
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
handleError(error) {
|
|
412
|
+
logger.error('Backup operation failed:');
|
|
413
|
+
logger.error(` ${error.message}`);
|
|
414
|
+
if (process.env.DEBUG) {
|
|
415
|
+
console.error(error);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
module.exports = I18nBackup;
|
package/main/i18ntk-backup.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
@@ -40,7 +40,7 @@ const { existsSync } = require('fs');
|
|
|
40
40
|
const configManager = require('../utils/config-manager');
|
|
41
41
|
const { logger } = require('../utils/logger');
|
|
42
42
|
const { colors } = require('../utils/logger');
|
|
43
|
-
const prompt = require('../utils/prompt
|
|
43
|
+
const prompt = require('../utils/prompt');
|
|
44
44
|
|
|
45
45
|
// Backup configuration
|
|
46
46
|
const config = configManager.getConfig();
|
|
@@ -195,7 +195,7 @@ async function handleRestore(args) {
|
|
|
195
195
|
: path.join(process.cwd(), 'restored');
|
|
196
196
|
|
|
197
197
|
// Validate backup file
|
|
198
|
-
if (!
|
|
198
|
+
if (!SecurityUtils.safeExistsSync(backupPath, process.cwd())) {
|
|
199
199
|
throw new Error(`Backup file not found: ${backupPath}`);
|
|
200
200
|
}
|
|
201
201
|
|
|
@@ -300,7 +300,7 @@ async function handleVerify(args) {
|
|
|
300
300
|
const backupPath = path.resolve(process.cwd(), backupFile);
|
|
301
301
|
|
|
302
302
|
// Validate backup file
|
|
303
|
-
if (!
|
|
303
|
+
if (!SecurityUtils.safeExistsSync(backupPath, process.cwd())) {
|
|
304
304
|
throw new Error(`Backup file not found: ${backupPath}`);
|
|
305
305
|
}
|
|
306
306
|
|