i18ntk 2.3.8 → 2.5.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 +10 -6
- package/main/i18ntk-backup-class.js +35 -423
- package/main/manage/commands/BackupCommand.js +62 -62
- package/main/manage/commands/FixerCommand.js +97 -97
- package/main/manage/managers/DebugMenu.js +10 -9
- package/main/manage/services/SetupService.js +444 -462
- package/package.json +61 -32
- package/runtime/index.js +14 -8
- package/utils/admin-auth.js +594 -576
- package/utils/config-manager.js +72 -72
- package/utils/env-manager.js +117 -26
- package/utils/i18n-helper.js +50 -49
- package/utils/json-output.js +13 -12
- package/utils/logger.js +7 -6
- package/utils/npm-version-warning.js +12 -141
- package/utils/prompt-helper.js +44 -41
- package/utils/secure-errors.js +156 -154
- package/utils/security.js +235 -233
- package/utils/setup-enforcer.js +110 -109
- package/utils/terminal-icons.js +164 -163
- package/settings/i18ntk-config.json +0 -283
- package/utils/admin-pin.js +0 -520
- package/utils/arg-parser.js +0 -40
- package/utils/cli-args.js +0 -210
- package/utils/mini-commander.js +0 -179
- package/utils/missing-key-validator.js +0 -858
- package/utils/path-utils.js +0 -33
- package/utils/performance-optimizer.js +0 -246
- package/utils/prompt-new.js +0 -55
- package/utils/promptPin.js +0 -76
- package/utils/safe-json.js +0 -40
- package/utils/secure-backup.js +0 -340
- package/utils/security-check-improved.js +0 -393
- package/utils/security-config.js +0 -239
- package/utils/setup-validator.js +0 -717
- package/utils/ultra-performance-optimizer.js +0 -352
|
@@ -27,21 +27,21 @@ class FixerCommand {
|
|
|
27
27
|
// Initialize fixer properties
|
|
28
28
|
this.sourceDir = null;
|
|
29
29
|
this.outputDir = null;
|
|
30
|
-
this.backupDir = null;
|
|
31
|
-
this.dryRun = false;
|
|
32
|
-
this.force = false;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
isExcludedLanguageDirectory(name) {
|
|
36
|
-
if (!name || typeof name !== 'string') return true;
|
|
37
|
-
const lowered = name.toLowerCase();
|
|
38
|
-
return lowered.startsWith('backup-') ||
|
|
39
|
-
lowered === 'backup' ||
|
|
40
|
-
lowered === 'backups' ||
|
|
41
|
-
lowered === 'i18ntk-backups' ||
|
|
42
|
-
lowered === 'reports' ||
|
|
43
|
-
lowered === 'i18ntk-reports';
|
|
44
|
-
}
|
|
30
|
+
this.backupDir = null;
|
|
31
|
+
this.dryRun = false;
|
|
32
|
+
this.force = false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
isExcludedLanguageDirectory(name) {
|
|
36
|
+
if (!name || typeof name !== 'string') return true;
|
|
37
|
+
const lowered = name.toLowerCase();
|
|
38
|
+
return lowered.startsWith('backup-') ||
|
|
39
|
+
lowered === 'backup' ||
|
|
40
|
+
lowered === 'backups' ||
|
|
41
|
+
lowered === 'i18ntk-backups' ||
|
|
42
|
+
lowered === 'reports' ||
|
|
43
|
+
lowered === 'i18ntk-reports';
|
|
44
|
+
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* Set runtime dependencies for interactive operations
|
|
@@ -79,7 +79,7 @@ class FixerCommand {
|
|
|
79
79
|
|
|
80
80
|
this.sourceDir = this.config.sourceDir;
|
|
81
81
|
this.outputDir = this.config.outputDir;
|
|
82
|
-
this.backupDir = path.resolve(this.config.backup?.location || './i18ntk-backups', 'fixer');
|
|
82
|
+
this.backupDir = path.resolve(this.config.backup?.location || './i18ntk-backups', 'fixer');
|
|
83
83
|
|
|
84
84
|
// Validate source directory exists
|
|
85
85
|
const { validateSourceDir } = require('../../../utils/config-helper');
|
|
@@ -138,15 +138,15 @@ class FixerCommand {
|
|
|
138
138
|
const languages = [];
|
|
139
139
|
|
|
140
140
|
// Check for directory-based structure
|
|
141
|
-
const directories = items
|
|
142
|
-
.filter(item => item.isDirectory())
|
|
143
|
-
.map(item => item.name)
|
|
144
|
-
.filter(name =>
|
|
145
|
-
name !== 'node_modules' &&
|
|
146
|
-
!name.startsWith('.') &&
|
|
147
|
-
name !== this.config.sourceLanguage &&
|
|
148
|
-
!this.isExcludedLanguageDirectory(name)
|
|
149
|
-
);
|
|
141
|
+
const directories = items
|
|
142
|
+
.filter(item => item.isDirectory())
|
|
143
|
+
.map(item => item.name)
|
|
144
|
+
.filter(name =>
|
|
145
|
+
name !== 'node_modules' &&
|
|
146
|
+
!name.startsWith('.') &&
|
|
147
|
+
name !== this.config.sourceLanguage &&
|
|
148
|
+
!this.isExcludedLanguageDirectory(name)
|
|
149
|
+
);
|
|
150
150
|
|
|
151
151
|
// Check for monolith files (language.json files)
|
|
152
152
|
const files = items
|
|
@@ -158,13 +158,13 @@ class FixerCommand {
|
|
|
158
158
|
|
|
159
159
|
// Add monolith files as languages (without .json extension)
|
|
160
160
|
const monolithLanguages = files
|
|
161
|
-
.map(file => file.replace('.json', ''))
|
|
162
|
-
.filter(lang =>
|
|
163
|
-
!languages.includes(lang) &&
|
|
164
|
-
lang !== this.config.sourceLanguage &&
|
|
165
|
-
!this.isExcludedLanguageDirectory(lang)
|
|
166
|
-
);
|
|
167
|
-
languages.push(...monolithLanguages);
|
|
161
|
+
.map(file => file.replace('.json', ''))
|
|
162
|
+
.filter(lang =>
|
|
163
|
+
!languages.includes(lang) &&
|
|
164
|
+
lang !== this.config.sourceLanguage &&
|
|
165
|
+
!this.isExcludedLanguageDirectory(lang)
|
|
166
|
+
);
|
|
167
|
+
languages.push(...monolithLanguages);
|
|
168
168
|
|
|
169
169
|
return [...new Set(languages)].sort();
|
|
170
170
|
} catch (error) {
|
|
@@ -299,21 +299,21 @@ class FixerCommand {
|
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
// Create backup of translation files
|
|
302
|
-
async createBackup() {
|
|
303
|
-
if (this.dryRun) return;
|
|
304
|
-
|
|
305
|
-
try {
|
|
306
|
-
const backupEnabled = this.config?.backup?.enabled === true;
|
|
307
|
-
if (!backupEnabled) {
|
|
308
|
-
this.backupDir = null;
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
313
|
-
const backupRoot = path.resolve(this.config?.backup?.location || './i18ntk-backups');
|
|
314
|
-
this.backupDir = path.join(backupRoot, 'fixer', `backup-${timestamp}`);
|
|
315
|
-
|
|
316
|
-
console.log(t('fixer.creatingBackup', { dir: this.backupDir }));
|
|
302
|
+
async createBackup() {
|
|
303
|
+
if (this.dryRun) return;
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const backupEnabled = this.config?.backup?.enabled === true;
|
|
307
|
+
if (!backupEnabled) {
|
|
308
|
+
this.backupDir = null;
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
313
|
+
const backupRoot = path.resolve(this.config?.backup?.location || './i18ntk-backups');
|
|
314
|
+
this.backupDir = path.join(backupRoot, 'fixer', `backup-${timestamp}`);
|
|
315
|
+
|
|
316
|
+
console.log(t('fixer.creatingBackup', { dir: this.backupDir }));
|
|
317
317
|
|
|
318
318
|
// Ensure backup directory exists
|
|
319
319
|
const dirCreated = SecurityUtils.safeMkdirSync(this.backupDir, process.cwd(), { recursive: true });
|
|
@@ -326,8 +326,8 @@ class FixerCommand {
|
|
|
326
326
|
const languages = this.getAvailableLanguages();
|
|
327
327
|
languages.push(this.config.sourceLanguage); // Include source language
|
|
328
328
|
|
|
329
|
-
for (const language of languages) {
|
|
330
|
-
const languageFiles = this.getLanguageFiles(language);
|
|
329
|
+
for (const language of languages) {
|
|
330
|
+
const languageFiles = this.getLanguageFiles(language);
|
|
331
331
|
|
|
332
332
|
for (const fileName of languageFiles) {
|
|
333
333
|
const sourcePath = path.join(this.sourceDir, language, fileName);
|
|
@@ -343,42 +343,42 @@ class FixerCommand {
|
|
|
343
343
|
SecurityUtils.safeWriteFileSync(backupPath, content, process.cwd(), 'utf8');
|
|
344
344
|
}
|
|
345
345
|
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
this.cleanupOldBackups();
|
|
349
|
-
|
|
350
|
-
console.log(t('fixer.backupCreated'));
|
|
351
|
-
} catch (error) {
|
|
352
|
-
console.warn(`Failed to create backup: ${error.message}`);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
cleanupOldBackups() {
|
|
357
|
-
try {
|
|
358
|
-
const fixerBackupRoot = path.resolve(this.config?.backup?.location || './i18ntk-backups', 'fixer');
|
|
359
|
-
if (!SecurityUtils.safeExistsSync(fixerBackupRoot, process.cwd())) return;
|
|
360
|
-
|
|
361
|
-
const configuredKeep = parseInt(this.config?.backup?.maxBackups, 10);
|
|
362
|
-
const keepCount = Number.isInteger(configuredKeep) ? Math.min(Math.max(configuredKeep, 1), 3) : 1;
|
|
363
|
-
|
|
364
|
-
const backupDirs = SecurityUtils.safeReaddirSync(fixerBackupRoot, process.cwd(), { withFileTypes: true })
|
|
365
|
-
.filter(entry => entry.isDirectory() && entry.name.startsWith('backup-'))
|
|
366
|
-
.map(entry => {
|
|
367
|
-
const dirPath = path.join(fixerBackupRoot, entry.name);
|
|
368
|
-
const stat = SecurityUtils.safeStatSync(dirPath, process.cwd());
|
|
369
|
-
return { name: entry.name, path: dirPath, mtimeMs: stat ? stat.mtimeMs : 0 };
|
|
370
|
-
})
|
|
371
|
-
.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
372
|
-
|
|
373
|
-
if (backupDirs.length <= keepCount) return;
|
|
374
|
-
|
|
375
|
-
for (const staleDir of backupDirs.slice(keepCount)) {
|
|
376
|
-
fs.rmSync(staleDir.path, { recursive: true, force: true });
|
|
377
|
-
}
|
|
378
|
-
} catch (error) {
|
|
379
|
-
console.warn(`Failed to clean old backups: ${error.message}`);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
this.cleanupOldBackups();
|
|
349
|
+
|
|
350
|
+
console.log(t('fixer.backupCreated'));
|
|
351
|
+
} catch (error) {
|
|
352
|
+
console.warn(`Failed to create backup: ${error.message}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
cleanupOldBackups() {
|
|
357
|
+
try {
|
|
358
|
+
const fixerBackupRoot = path.resolve(this.config?.backup?.location || './i18ntk-backups', 'fixer');
|
|
359
|
+
if (!SecurityUtils.safeExistsSync(fixerBackupRoot, process.cwd())) return;
|
|
360
|
+
|
|
361
|
+
const configuredKeep = parseInt(this.config?.backup?.maxBackups, 10);
|
|
362
|
+
const keepCount = Number.isInteger(configuredKeep) ? Math.min(Math.max(configuredKeep, 1), 3) : 1;
|
|
363
|
+
|
|
364
|
+
const backupDirs = SecurityUtils.safeReaddirSync(fixerBackupRoot, process.cwd(), { withFileTypes: true })
|
|
365
|
+
.filter(entry => entry.isDirectory() && entry.name.startsWith('backup-'))
|
|
366
|
+
.map(entry => {
|
|
367
|
+
const dirPath = path.join(fixerBackupRoot, entry.name);
|
|
368
|
+
const stat = SecurityUtils.safeStatSync(dirPath, process.cwd());
|
|
369
|
+
return { name: entry.name, path: dirPath, mtimeMs: stat ? stat.mtimeMs : 0 };
|
|
370
|
+
})
|
|
371
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
372
|
+
|
|
373
|
+
if (backupDirs.length <= keepCount) return;
|
|
374
|
+
|
|
375
|
+
for (const staleDir of backupDirs.slice(keepCount)) {
|
|
376
|
+
fs.rmSync(staleDir.path, { recursive: true, force: true });
|
|
377
|
+
}
|
|
378
|
+
} catch (error) {
|
|
379
|
+
console.warn(`Failed to clean old backups: ${error.message}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
382
|
|
|
383
383
|
// Analyze translation issues for fixing
|
|
384
384
|
analyzeIssues(language, fileName) {
|
|
@@ -420,7 +420,7 @@ class FixerCommand {
|
|
|
420
420
|
type: 'missing_key',
|
|
421
421
|
key,
|
|
422
422
|
sourceValue,
|
|
423
|
-
fix: () => this.setValueByPath(
|
|
423
|
+
fix: (obj) => this.setValueByPath(obj, key, sourceValue)
|
|
424
424
|
});
|
|
425
425
|
} else if (targetValue === '') {
|
|
426
426
|
// Empty value
|
|
@@ -428,7 +428,7 @@ class FixerCommand {
|
|
|
428
428
|
type: 'empty_value',
|
|
429
429
|
key,
|
|
430
430
|
sourceValue,
|
|
431
|
-
fix: () => this.setValueByPath(
|
|
431
|
+
fix: (obj) => this.setValueByPath(obj, key, sourceValue)
|
|
432
432
|
});
|
|
433
433
|
} else {
|
|
434
434
|
const markers = this.config.notTranslatedMarkers || [this.config.notTranslatedMarker];
|
|
@@ -438,7 +438,7 @@ class FixerCommand {
|
|
|
438
438
|
type: 'untranslated_marker',
|
|
439
439
|
key,
|
|
440
440
|
sourceValue,
|
|
441
|
-
fix: () => this.setValueByPath(
|
|
441
|
+
fix: (obj) => this.setValueByPath(obj, key, sourceValue)
|
|
442
442
|
});
|
|
443
443
|
}
|
|
444
444
|
}
|
|
@@ -486,7 +486,7 @@ class FixerCommand {
|
|
|
486
486
|
|
|
487
487
|
for (const issue of issues) {
|
|
488
488
|
if (typeof issue.fix === 'function') {
|
|
489
|
-
issue.fix();
|
|
489
|
+
issue.fix(targetObj);
|
|
490
490
|
fixes.files[fileName].fixed++;
|
|
491
491
|
fixes.fixedIssues++;
|
|
492
492
|
}
|
|
@@ -494,7 +494,7 @@ class FixerCommand {
|
|
|
494
494
|
|
|
495
495
|
// Write back the fixed content
|
|
496
496
|
const fixedContent = JSON.stringify(targetObj, null, 2);
|
|
497
|
-
SecurityUtils.safeWriteFileSync(targetFilePath, fixedContent,
|
|
497
|
+
SecurityUtils.safeWriteFileSync(targetFilePath, fixedContent, this.sourceDir, 'utf8');
|
|
498
498
|
|
|
499
499
|
} catch (error) {
|
|
500
500
|
console.warn(`Error fixing ${language}/${fileName}: ${error.message}`);
|
|
@@ -527,9 +527,9 @@ class FixerCommand {
|
|
|
527
527
|
}
|
|
528
528
|
|
|
529
529
|
// Create backup unless disabled
|
|
530
|
-
if (!args.noBackup && !this.dryRun && this.config?.backup?.enabled === true) {
|
|
531
|
-
await this.createBackup();
|
|
532
|
-
}
|
|
530
|
+
if (!args.noBackup && !this.dryRun && this.config?.backup?.enabled === true) {
|
|
531
|
+
await this.createBackup();
|
|
532
|
+
}
|
|
533
533
|
|
|
534
534
|
const languages = this.getAvailableLanguages();
|
|
535
535
|
|
|
@@ -591,9 +591,9 @@ class FixerCommand {
|
|
|
591
591
|
console.log(t('fixer.totalIssues', { count: totalIssues }));
|
|
592
592
|
console.log(t('fixer.totalFixed', { count: totalFixed }));
|
|
593
593
|
|
|
594
|
-
if (this.backupDir && !args.noBackup && this.config?.backup?.enabled === true) {
|
|
595
|
-
console.log(t('fixer.backupLocation', { dir: this.backupDir }));
|
|
596
|
-
}
|
|
594
|
+
if (this.backupDir && !args.noBackup && this.config?.backup?.enabled === true) {
|
|
595
|
+
console.log(t('fixer.backupLocation', { dir: this.backupDir }));
|
|
596
|
+
}
|
|
597
597
|
|
|
598
598
|
console.log(t('fixer.completed'));
|
|
599
599
|
|
|
@@ -677,4 +677,4 @@ class FixerCommand {
|
|
|
677
677
|
}
|
|
678
678
|
}
|
|
679
679
|
|
|
680
|
-
module.exports = FixerCommand;
|
|
680
|
+
module.exports = FixerCommand;
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* @module managers/DebugMenu
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const path = require('path');
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const { t } = require('../../../utils/i18n-helper');
|
|
9
|
-
const cliHelper = require('../../../utils/cli-helper');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const { t } = require('../../../utils/i18n-helper');
|
|
9
|
+
const cliHelper = require('../../../utils/cli-helper');
|
|
10
|
+
const SecurityUtils = require('../../../utils/security');
|
|
10
11
|
|
|
11
12
|
module.exports = class DebugMenu {
|
|
12
13
|
constructor(manager) {
|
|
@@ -22,7 +23,7 @@ module.exports = class DebugMenu {
|
|
|
22
23
|
const authRequired = await this.adminAuth.isAuthRequiredForScript('debugMenu');
|
|
23
24
|
if (authRequired) {
|
|
24
25
|
console.log(`\n${t('adminPin.protectedAccess')}`);
|
|
25
|
-
const pin = await cliHelper.promptPin(t('adminPin.enterPin') + ': ');
|
|
26
|
+
const pin = await cliHelper.promptPin(t('adminPin.enterPin') + ': ');
|
|
26
27
|
const isValid = await this.adminAuth.verifyPin(pin);
|
|
27
28
|
|
|
28
29
|
if (!isValid) {
|
|
@@ -66,7 +67,7 @@ module.exports = class DebugMenu {
|
|
|
66
67
|
console.log(t('debug.runningDebugTool', { displayName }));
|
|
67
68
|
try {
|
|
68
69
|
const toolPath = path.join(__dirname, '..', '..', 'scripts', 'debug', toolName);
|
|
69
|
-
if (
|
|
70
|
+
if (SecurityUtils.safeExistsSync(toolPath, path.dirname(toolPath))) {
|
|
70
71
|
console.log(`Debug tool available: ${toolName}`);
|
|
71
72
|
console.log(`To run this tool manually: node "${toolPath}"`);
|
|
72
73
|
console.log(`Working directory: ${path.join(__dirname, '..', '..')}`);
|
|
@@ -90,7 +91,7 @@ module.exports = class DebugMenu {
|
|
|
90
91
|
|
|
91
92
|
try {
|
|
92
93
|
const logsDir = path.join(__dirname, '..', '..', 'scripts', 'debug', 'logs');
|
|
93
|
-
if (
|
|
94
|
+
if (SecurityUtils.safeExistsSync(logsDir, path.dirname(logsDir))) {
|
|
94
95
|
const files = fs.readdirSync(logsDir)
|
|
95
96
|
.filter(file => file.endsWith('.log') || file.endsWith('.txt'))
|
|
96
97
|
.sort((a, b) => {
|
|
@@ -111,7 +112,7 @@ module.exports = class DebugMenu {
|
|
|
111
112
|
const fileIndex = parseInt(choice) - 1;
|
|
112
113
|
|
|
113
114
|
if (fileIndex >= 0 && fileIndex < files.length) {
|
|
114
|
-
const logContent =
|
|
115
|
+
const logContent = SecurityUtils.safeReadFileSync(path.join(logsDir, files[fileIndex]), logsDir, 'utf8');
|
|
115
116
|
console.log(`\n${t('debug.contentOf', { filename: files[fileIndex] })}:`);
|
|
116
117
|
console.log('============================================================');
|
|
117
118
|
console.log(logContent.slice(-2000)); // Show last 2000 characters
|
|
@@ -137,4 +138,4 @@ module.exports = class DebugMenu {
|
|
|
137
138
|
async show() {
|
|
138
139
|
return this.showDebugMenu();
|
|
139
140
|
}
|
|
140
|
-
};
|
|
141
|
+
};
|