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.
@@ -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(targetObj, key, sourceValue)
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(targetObj, key, sourceValue)
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(targetObj, key, sourceValue)
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, process.cwd(), 'utf8');
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 (fs.existsSync(toolPath)) {
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 (fs.existsSync(logsDir)) {
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 = fs.readFileSync(path.join(logsDir, files[fileIndex]), 'utf8');
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
+ };