i18ntk 2.3.7 → 2.4.0
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/README.md +9 -6
- package/main/i18ntk-backup-class.js +35 -423
- package/main/manage/commands/BackupCommand.js +62 -62
- package/main/manage/services/SetupService.js +444 -462
- package/package.json +12 -9
- package/utils/config-manager.js +84 -30
- package/utils/config.js +15 -14
- package/utils/i18n-helper.js +35 -20
- package/utils/logger.js +233 -64
- package/utils/npm-version-warning.js +12 -141
- package/utils/security.js +233 -150
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# i18ntk v2.
|
|
1
|
+
# i18ntk v2.4.0
|
|
2
2
|
|
|
3
3
|
Zero-dependency internationalization toolkit for setup, scanning, analysis, validation, usage tracking, and translation completion.
|
|
4
4
|
|
|
@@ -9,12 +9,12 @@ Zero-dependency internationalization toolkit for setup, scanning, analysis, vali
|
|
|
9
9
|
[](https://nodejs.org)
|
|
10
10
|
[](https://www.npmjs.com/package/i18ntk)
|
|
11
11
|
[](LICENSE)
|
|
12
|
-
[](https://socket.dev/npm/package/i18ntk/overview/2.4.0)
|
|
13
13
|
|
|
14
14
|
## Upgrade Notice
|
|
15
15
|
|
|
16
|
-
Versions earlier than `2.
|
|
17
|
-
They are considered unsupported for production use. Upgrade to `2.
|
|
16
|
+
Versions earlier than `2.4.0` may contain known stability and security issues.
|
|
17
|
+
They are considered unsupported for production use. Upgrade to `2.4.0` or newer.
|
|
18
18
|
|
|
19
19
|
## What i18ntk Does
|
|
20
20
|
|
|
@@ -104,6 +104,9 @@ i18ntk-fixer
|
|
|
104
104
|
i18ntk-backup
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
+
Note: `i18ntk --command=backup` in the manager flow is disabled in current builds.
|
|
108
|
+
Use the standalone `i18ntk-backup` executable when backup operations are required.
|
|
109
|
+
|
|
107
110
|
## Common Flags
|
|
108
111
|
|
|
109
112
|
- `--source-dir <path>`
|
|
@@ -151,7 +154,7 @@ Example `.i18ntk-config`:
|
|
|
151
154
|
|
|
152
155
|
```json
|
|
153
156
|
{
|
|
154
|
-
"version": "2.
|
|
157
|
+
"version": "2.4.0",
|
|
155
158
|
"sourceDir": "./locales",
|
|
156
159
|
"i18nDir": "./locales",
|
|
157
160
|
"outputDir": "./i18ntk-reports",
|
|
@@ -174,7 +177,7 @@ See [docs/api/CONFIGURATION.md](docs/api/CONFIGURATION.md) for the full configur
|
|
|
174
177
|
- [Runtime API Guide](docs/runtime.md)
|
|
175
178
|
- [Scanner Guide](docs/scanner-guide.md)
|
|
176
179
|
- [Environment Variables](docs/environment-variables.md)
|
|
177
|
-
- [Migration Guide v2.
|
|
180
|
+
- [Migration Guide v2.4.0](docs/migration-guide-v2.4.0.md)
|
|
178
181
|
- [Optimization Prompt](docs/development/package-optimization-prompt.md)
|
|
179
182
|
|
|
180
183
|
## Code of Conduct
|
|
@@ -1,430 +1,42 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
'use strict';
|
|
4
|
-
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
const fsp = fs.promises;
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const configManager = require('../utils/config-manager');
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
9
5
|
const { logger } = require('../utils/logger');
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* I18NTK BACKUP CLASS
|
|
9
|
+
*
|
|
10
|
+
* Backup operations are intentionally disabled in the manager command path
|
|
11
|
+
* to reduce filesystem scanner surface.
|
|
12
|
+
*/
|
|
18
13
|
class I18nBackup {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
this.maxBackups = Math.min(Math.max(parseInt(config.backup?.maxBackups, 10) || 1, 1), 3);
|
|
23
|
-
}
|
|
14
|
+
constructor(config = {}) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
}
|
|
24
17
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
return validated;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Main run method for the backup command
|
|
35
|
-
*/
|
|
36
|
-
async run(options = {}) {
|
|
37
|
-
const command = options.command || options._ && options._[0];
|
|
38
|
-
|
|
39
|
-
// Show help if no command provided
|
|
40
|
-
if (!command) {
|
|
41
|
-
this.showHelp();
|
|
42
|
-
return { success: true, command: 'help' };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
switch (command) {
|
|
47
|
-
case 'create':
|
|
48
|
-
return await this.handleCreate(options);
|
|
49
|
-
case 'restore':
|
|
50
|
-
return await this.handleRestore(options);
|
|
51
|
-
case 'list':
|
|
52
|
-
return await this.handleList();
|
|
53
|
-
case 'verify':
|
|
54
|
-
return await this.handleVerify(options);
|
|
55
|
-
case 'cleanup':
|
|
56
|
-
return await this.handleCleanup(options);
|
|
57
|
-
default:
|
|
58
|
-
logger.error(`Unknown command: ${command}`);
|
|
59
|
-
this.showHelp();
|
|
60
|
-
return { success: false, error: `Unknown command: ${command}` };
|
|
61
|
-
}
|
|
62
|
-
} catch (error) {
|
|
63
|
-
this.handleError(error);
|
|
64
|
-
return { success: false, error: error.message };
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
showHelp() {
|
|
69
|
-
console.log(`
|
|
70
|
-
i18ntk-backup - Secure backup and restore for i18n translation files
|
|
71
|
-
|
|
72
|
-
Usage:
|
|
73
|
-
i18ntk-backup <command> [options]
|
|
74
|
-
|
|
75
|
-
Commands:
|
|
76
|
-
create <dir> Create a backup of translation files
|
|
77
|
-
restore <file> Restore from a backup
|
|
78
|
-
list List available backups
|
|
79
|
-
verify <file> Verify the integrity of a backup file
|
|
80
|
-
cleanup Remove old backups
|
|
81
|
-
|
|
82
|
-
Options:
|
|
83
|
-
--output <path> Output directory for backup/restore
|
|
84
|
-
--force Overwrite existing files without prompting
|
|
85
|
-
--keep <number> Number of backups to keep (default: 10)
|
|
86
|
-
`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Command handlers
|
|
90
|
-
async handleCreate(options = {}) {
|
|
91
|
-
// Use absolute path for the locales directory
|
|
92
|
-
const requestedDir = (options._ && options._[1]) || options.dir || path.join(process.cwd(), 'locales');
|
|
93
|
-
const requestedOutputDir = options.output || this.backupDir;
|
|
94
|
-
const dir = this.validateInProject(path.resolve(process.cwd(), requestedDir), 'source directory');
|
|
95
|
-
const outputDir = this.validateInProject(path.resolve(process.cwd(), requestedOutputDir), 'backup directory');
|
|
96
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
97
|
-
const backupName = `backup-${timestamp}.json`;
|
|
98
|
-
const backupPath = this.validateInProject(path.join(outputDir, backupName), 'backup file');
|
|
99
|
-
|
|
100
|
-
// Log the paths for debugging
|
|
101
|
-
logger.debug(`Source directory: ${dir}`);
|
|
102
|
-
logger.debug(`Backup will be saved to: ${backupPath}`);
|
|
103
|
-
|
|
104
|
-
// Create backup directory if it doesn't exist
|
|
105
|
-
try {
|
|
106
|
-
await fsp.mkdir(outputDir, { recursive: true });
|
|
107
|
-
logger.debug(`Created backup directory: ${outputDir}`);
|
|
108
|
-
} catch (err) {
|
|
109
|
-
if (err.code !== 'EEXIST') {
|
|
110
|
-
logger.error(`Failed to create backup directory: ${err.message}`);
|
|
111
|
-
throw err;
|
|
112
|
-
}
|
|
113
|
-
logger.debug(`Using existing backup directory: ${outputDir}`);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Validate directory
|
|
117
|
-
const sourceDir = dir;
|
|
118
|
-
try {
|
|
119
|
-
const stats = await fsp.stat(sourceDir);
|
|
120
|
-
if (!stats.isDirectory()) {
|
|
121
|
-
throw new Error(`Path exists but is not a directory: ${sourceDir}`);
|
|
122
|
-
}
|
|
123
|
-
logger.debug(`Source directory exists: ${sourceDir}`);
|
|
124
|
-
} catch (err) {
|
|
125
|
-
if (err.code === 'ENOENT') {
|
|
126
|
-
throw new Error(`Directory not found: ${sourceDir}. Please specify a valid directory.`);
|
|
127
|
-
}
|
|
128
|
-
throw new Error(`Error accessing directory ${sourceDir}: ${err.message}`);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
logger.info('\nCreating backup...');
|
|
132
|
-
|
|
133
|
-
// Read all files in the directory
|
|
134
|
-
const files = (await fsp.readdir(sourceDir, { withFileTypes: true }))
|
|
135
|
-
.filter(dirent => dirent.isFile() && dirent.name.endsWith('.json'))
|
|
136
|
-
.map(dirent => dirent.name);
|
|
137
|
-
|
|
138
|
-
if (files.length === 0) {
|
|
139
|
-
logger.warn('No JSON files found in the specified directory');
|
|
140
|
-
return { success: true, message: 'No files to backup' };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Read all translation files
|
|
144
|
-
const translations = {};
|
|
145
|
-
for (const file of files) {
|
|
146
|
-
const filePath = path.join(sourceDir, file);
|
|
147
|
-
try {
|
|
148
|
-
const content = JSON.parse(await fsp.readFile(filePath, 'utf8'));
|
|
149
|
-
translations[file] = content;
|
|
150
|
-
} catch (error) {
|
|
151
|
-
logger.error(`Could not read file ${file}: ${error.message}`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Create the backup
|
|
156
|
-
await fsp.writeFile(backupPath, JSON.stringify(translations, null, 2));
|
|
157
|
-
const stats = await fsp.stat(backupPath);
|
|
158
|
-
|
|
159
|
-
logger.success('Backup created successfully');
|
|
160
|
-
logger.info(` Location: ${backupPath}`);
|
|
161
|
-
logger.info(` Size: ${(stats.size / 1024).toFixed(2)} KB`);
|
|
162
|
-
logger.info(` Timestamp: ${new Date().toLocaleString()}`);
|
|
163
|
-
|
|
164
|
-
// Clean up old backups
|
|
165
|
-
await this.cleanupOldBackups(outputDir);
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
success: true,
|
|
169
|
-
command: 'create',
|
|
170
|
-
backupPath,
|
|
171
|
-
size: stats.size,
|
|
172
|
-
fileCount: files.length
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async handleRestore(options = {}) {
|
|
177
|
-
const backupFile = options._ && options._[1];
|
|
178
|
-
if (!backupFile) {
|
|
179
|
-
throw new Error('Backup file path is required');
|
|
180
|
-
}
|
|
18
|
+
showHelp() {
|
|
19
|
+
logger.warn('Backup command is disabled in this build.');
|
|
20
|
+
logger.info('Use project-level source control or external backup tooling instead.');
|
|
21
|
+
}
|
|
181
22
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
// Validate backup file
|
|
189
|
-
if (!SecurityUtils.safeExistsSync(backupPath, process.cwd())) {
|
|
190
|
-
throw new Error(`Backup file not found: ${backupPath}`);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
logger.info('\nRestoring backup...');
|
|
194
|
-
|
|
195
|
-
try {
|
|
196
|
-
// Read the backup file
|
|
197
|
-
const backupData = await fsp.readFile(backupPath, 'utf8');
|
|
198
|
-
const translations = JSON.parse(backupData);
|
|
199
|
-
|
|
200
|
-
// Create output directory if it doesn't exist
|
|
201
|
-
try {
|
|
202
|
-
await fsp.mkdir(outputDir, { recursive: true });
|
|
203
|
-
} catch (err) {
|
|
204
|
-
if (err.code !== 'EEXIST') throw err;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Write the restored files
|
|
208
|
-
for (const [file, content] of Object.entries(translations)) {
|
|
209
|
-
const filePath = this.validateInProject(path.join(outputDir, file), 'restore file');
|
|
210
|
-
await fsp.writeFile(filePath, JSON.stringify(content, null, 2), 'utf8');
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
logger.success('Backup restored successfully');
|
|
214
|
-
logger.info(` Restored ${Object.keys(translations).length} files to: ${outputDir}`);
|
|
215
|
-
|
|
216
|
-
return {
|
|
217
|
-
success: true,
|
|
218
|
-
command: 'restore',
|
|
219
|
-
outputDir,
|
|
220
|
-
fileCount: Object.keys(translations).length
|
|
221
|
-
};
|
|
222
|
-
} catch (error) {
|
|
223
|
-
this.handleError(error);
|
|
224
|
-
throw error;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
async handleList() {
|
|
229
|
-
const backupDir = this.validateInProject(this.backupDir, 'backup directory');
|
|
230
|
-
try {
|
|
231
|
-
// Ensure backup directory exists
|
|
232
|
-
try {
|
|
233
|
-
await fsp.access(backupDir);
|
|
234
|
-
} catch (err) {
|
|
235
|
-
if (err.code === 'ENOENT') {
|
|
236
|
-
logger.warn('No backups found. The backup directory does not exist yet.');
|
|
237
|
-
} else {
|
|
238
|
-
logger.error(`Error accessing backup directory: ${err.message}`);
|
|
239
|
-
}
|
|
240
|
-
return { success: true, backups: [] };
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const files = await fsp.readdir(backupDir);
|
|
244
|
-
const backups = [];
|
|
245
|
-
|
|
246
|
-
for (const file of files) {
|
|
247
|
-
if (file.startsWith('backup-') && file.endsWith('.json')) {
|
|
248
|
-
try {
|
|
249
|
-
const filePath = this.validateInProject(path.join(backupDir, file), 'backup file');
|
|
250
|
-
const stats = await fsp.stat(filePath);
|
|
251
|
-
backups.push({
|
|
252
|
-
name: file,
|
|
253
|
-
path: filePath,
|
|
254
|
-
size: stats.size,
|
|
255
|
-
createdAt: stats.mtime
|
|
256
|
-
});
|
|
257
|
-
} catch (err) {
|
|
258
|
-
logger.warn(`Skipping invalid backup file ${file}: ${err.message}`);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (backups.length === 0) {
|
|
264
|
-
logger.warn('No valid backup files found in the backup directory.');
|
|
265
|
-
return { success: true, backups: [] };
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Sort by creation time (newest first)
|
|
269
|
-
backups.sort((a, b) => b.createdAt - a.createdAt);
|
|
270
|
-
|
|
271
|
-
logger.info('\n📋 Available Backups');
|
|
272
|
-
logger.info('='.repeat(50));
|
|
273
|
-
|
|
274
|
-
backups.forEach((backup, index) => {
|
|
275
|
-
const sizeKB = (backup.size / 1024).toFixed(2);
|
|
276
|
-
const formattedDate = backup.createdAt.toLocaleString();
|
|
277
|
-
|
|
278
|
-
logger.info(`🔹 ${index + 1}. ${backup.name}`);
|
|
279
|
-
logger.info(` 📏 Size: ${sizeKB} KB`);
|
|
280
|
-
logger.info(` 📅 Created: ${formattedDate}`);
|
|
281
|
-
logger.info(` 📂 Path: ${backup.path}\n`);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
logger.info(`Total backups: ${backups.length}`);
|
|
285
|
-
|
|
286
|
-
return { success: true, backups, count: backups.length };
|
|
287
|
-
} catch (err) {
|
|
288
|
-
if (err.code === 'ENOENT') {
|
|
289
|
-
logger.warn('No backups found.');
|
|
290
|
-
return { success: true, backups: [] };
|
|
291
|
-
} else {
|
|
292
|
-
throw err;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
async handleVerify(options = {}) {
|
|
298
|
-
const backupFile = options._ && options._[1];
|
|
299
|
-
if (!backupFile) {
|
|
300
|
-
throw new Error('Backup file path is required');
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const backupPath = this.validateInProject(path.resolve(process.cwd(), backupFile), 'backup file');
|
|
304
|
-
|
|
305
|
-
// Validate backup file
|
|
306
|
-
if (!SecurityUtils.safeExistsSync(backupPath, process.cwd())) {
|
|
307
|
-
throw new Error(`Backup file not found: ${backupPath}`);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
logger.info('\nVerifying backup...');
|
|
311
|
-
|
|
312
|
-
try {
|
|
313
|
-
const data = await fsp.readFile(backupPath, 'utf8');
|
|
314
|
-
const content = JSON.parse(data);
|
|
315
|
-
|
|
316
|
-
if (typeof content === 'object' && content !== null) {
|
|
317
|
-
const fileCount = Object.keys(content).length;
|
|
318
|
-
logger.success('Backup is valid');
|
|
319
|
-
logger.info(` Contains ${fileCount} translation files`);
|
|
320
|
-
logger.info(` Last modified: ${(await fsp.stat(backupPath)).mtime.toLocaleString()}`);
|
|
321
|
-
|
|
322
|
-
return {
|
|
323
|
-
success: true,
|
|
324
|
-
command: 'verify',
|
|
325
|
-
valid: true,
|
|
326
|
-
fileCount
|
|
327
|
-
};
|
|
328
|
-
} else {
|
|
329
|
-
throw new Error('Invalid backup format');
|
|
330
|
-
}
|
|
331
|
-
} catch (error) {
|
|
332
|
-
logger.error('Backup verification failed!');
|
|
333
|
-
logger.error(` Error: ${error.message}`);
|
|
334
|
-
return {
|
|
335
|
-
success: false,
|
|
336
|
-
command: 'verify',
|
|
337
|
-
valid: false,
|
|
338
|
-
error: error.message
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
async handleCleanup(options = {}) {
|
|
344
|
-
const keep = options.keep ? parseInt(options.keep, 10) : this.maxBackups;
|
|
345
|
-
const backupDir = this.validateInProject(this.backupDir, 'backup directory');
|
|
346
|
-
|
|
347
|
-
logger.info('\nCleaning up old backups...');
|
|
348
|
-
|
|
349
|
-
try {
|
|
350
|
-
const files = await fsp.readdir(backupDir);
|
|
351
|
-
const backupFiles = files
|
|
352
|
-
.filter(file => file.startsWith('backup-') && file.endsWith('.json'))
|
|
353
|
-
.map(file => ({
|
|
354
|
-
name: file,
|
|
355
|
-
path: this.validateInProject(path.join(backupDir, file), 'backup file'),
|
|
356
|
-
time: (SecurityUtils.safeStatSync(path.join(backupDir, file), process.cwd()) || { mtime: new Date(0) }).mtime.getTime()
|
|
357
|
-
}))
|
|
358
|
-
.sort((a, b) => b.time - a.time);
|
|
359
|
-
|
|
360
|
-
// Keep only the most recent 'keep' files
|
|
361
|
-
const toDelete = backupFiles.slice(keep);
|
|
362
|
-
|
|
363
|
-
if (toDelete.length === 0) {
|
|
364
|
-
logger.info('No old backups to delete.');
|
|
365
|
-
return { success: true, deleted: 0 };
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// Delete old backups
|
|
369
|
-
for (const file of toDelete) {
|
|
370
|
-
try {
|
|
371
|
-
await fsp.unlink(file.path);
|
|
372
|
-
logger.info(` - Deleted: ${file.name}`);
|
|
373
|
-
} catch (err) {
|
|
374
|
-
logger.error(` - Failed to delete ${file.name}: ${err.message}`);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
logger.info(`\nRemoved ${toDelete.length} old backups`);
|
|
379
|
-
logger.info(`Total backups kept: ${keep}`);
|
|
380
|
-
|
|
381
|
-
return {
|
|
382
|
-
success: true,
|
|
383
|
-
command: 'cleanup',
|
|
384
|
-
deleted: toDelete.length,
|
|
385
|
-
kept: keep
|
|
386
|
-
};
|
|
387
|
-
} catch (error) {
|
|
388
|
-
logger.error('Error cleaning up backups:');
|
|
389
|
-
logger.error(` ${error.message}`);
|
|
390
|
-
logger.debug(error.stack || error.message);
|
|
391
|
-
throw error;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
async cleanupOldBackups(outputDir) {
|
|
396
|
-
try {
|
|
397
|
-
const safeOutputDir = this.validateInProject(outputDir, 'backup output directory');
|
|
398
|
-
const files = await fsp.readdir(safeOutputDir);
|
|
399
|
-
const backupFiles = files
|
|
400
|
-
.filter(file => file.startsWith('backup-') && file.endsWith('.json'))
|
|
401
|
-
.map(file => ({
|
|
402
|
-
name: file,
|
|
403
|
-
path: this.validateInProject(path.join(safeOutputDir, file), 'backup file'),
|
|
404
|
-
time: (SecurityUtils.safeStatSync(path.join(safeOutputDir, file), process.cwd()) || { mtime: new Date(0) }).mtime.getTime()
|
|
405
|
-
}))
|
|
406
|
-
.sort((a, b) => b.time - a.time);
|
|
407
|
-
|
|
408
|
-
// Keep only the most recent files
|
|
409
|
-
const toDelete = backupFiles.slice(this.maxBackups);
|
|
410
|
-
|
|
411
|
-
for (const file of toDelete) {
|
|
412
|
-
try {
|
|
413
|
-
await fsp.unlink(file.path);
|
|
414
|
-
} catch (err) {
|
|
415
|
-
// Ignore cleanup errors
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
} catch (error) {
|
|
419
|
-
// Ignore cleanup errors
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
handleError(error) {
|
|
424
|
-
logger.error('Backup operation failed:');
|
|
425
|
-
logger.error(` ${error.message}`);
|
|
426
|
-
logger.debug(error.stack || error.message);
|
|
23
|
+
async run(options = {}) {
|
|
24
|
+
const command = options.command || (options._ && options._[0]);
|
|
25
|
+
if (!command || command === 'help') {
|
|
26
|
+
this.showHelp();
|
|
27
|
+
return { success: false, command: 'backup', disabled: true };
|
|
427
28
|
}
|
|
29
|
+
|
|
30
|
+
logger.warn(`Backup command '${command}' is disabled in this build.`);
|
|
31
|
+
logger.info('Use project-level source control or external backup tooling instead.');
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
success: false,
|
|
35
|
+
command,
|
|
36
|
+
disabled: true,
|
|
37
|
+
error: 'Backup command is disabled'
|
|
38
|
+
};
|
|
39
|
+
}
|
|
428
40
|
}
|
|
429
|
-
|
|
41
|
+
|
|
430
42
|
module.exports = I18nBackup;
|
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* I18NTK BACKUP COMMAND
|
|
5
|
-
*
|
|
6
|
-
* Handles backup operations for translation files and settings.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const I18nBackup = require('../../i18ntk-backup-class');
|
|
10
|
-
|
|
11
|
-
class BackupCommand {
|
|
12
|
-
constructor(config = {}, ui = null) {
|
|
13
|
-
this.config = config;
|
|
14
|
-
this.ui = ui;
|
|
15
|
-
this.prompt = null;
|
|
16
|
-
this.isNonInteractiveMode = false;
|
|
17
|
-
this.safeClose = null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Set runtime dependencies for interactive operations
|
|
22
|
-
*/
|
|
23
|
-
setRuntimeDependencies(prompt, isNonInteractiveMode, safeClose) {
|
|
24
|
-
this.prompt = prompt;
|
|
25
|
-
this.isNonInteractiveMode = isNonInteractiveMode;
|
|
26
|
-
this.safeClose = safeClose;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Execute the backup command
|
|
31
|
-
*/
|
|
32
|
-
async execute(options = {}) {
|
|
33
|
-
try {
|
|
34
|
-
const backup = new I18nBackup();
|
|
35
|
-
await backup.run(options);
|
|
36
|
-
return { success:
|
|
37
|
-
} catch (error) {
|
|
38
|
-
console.error(`Backup command failed: ${error.message}`);
|
|
39
|
-
throw error;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Get command metadata
|
|
45
|
-
*/
|
|
46
|
-
getMetadata() {
|
|
47
|
-
return {
|
|
48
|
-
name: 'backup',
|
|
49
|
-
description: 'Create backups of translation files and settings',
|
|
50
|
-
category: 'maintenance',
|
|
51
|
-
aliases: [],
|
|
52
|
-
usage: 'backup [options]',
|
|
53
|
-
examples: [
|
|
54
|
-
'backup',
|
|
55
|
-
'backup --output-dir=./backups',
|
|
56
|
-
'backup --include-settings'
|
|
57
|
-
]
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
module.exports = BackupCommand;
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* I18NTK BACKUP COMMAND
|
|
5
|
+
*
|
|
6
|
+
* Handles backup operations for translation files and settings.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const I18nBackup = require('../../i18ntk-backup-class');
|
|
10
|
+
|
|
11
|
+
class BackupCommand {
|
|
12
|
+
constructor(config = {}, ui = null) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.ui = ui;
|
|
15
|
+
this.prompt = null;
|
|
16
|
+
this.isNonInteractiveMode = false;
|
|
17
|
+
this.safeClose = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Set runtime dependencies for interactive operations
|
|
22
|
+
*/
|
|
23
|
+
setRuntimeDependencies(prompt, isNonInteractiveMode, safeClose) {
|
|
24
|
+
this.prompt = prompt;
|
|
25
|
+
this.isNonInteractiveMode = isNonInteractiveMode;
|
|
26
|
+
this.safeClose = safeClose;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Execute the backup command
|
|
31
|
+
*/
|
|
32
|
+
async execute(options = {}) {
|
|
33
|
+
try {
|
|
34
|
+
const backup = new I18nBackup();
|
|
35
|
+
const result = await backup.run(options);
|
|
36
|
+
return result || { success: false, command: 'backup', error: 'Backup command failed' };
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error(`Backup command failed: ${error.message}`);
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get command metadata
|
|
45
|
+
*/
|
|
46
|
+
getMetadata() {
|
|
47
|
+
return {
|
|
48
|
+
name: 'backup',
|
|
49
|
+
description: 'Create backups of translation files and settings',
|
|
50
|
+
category: 'maintenance',
|
|
51
|
+
aliases: [],
|
|
52
|
+
usage: 'backup [options]',
|
|
53
|
+
examples: [
|
|
54
|
+
'backup',
|
|
55
|
+
'backup --output-dir=./backups',
|
|
56
|
+
'backup --include-settings'
|
|
57
|
+
]
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = BackupCommand;
|