i18ntk 1.10.2 → 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.
Files changed (108) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +141 -1191
  3. package/main/i18ntk-analyze.js +65 -84
  4. package/main/i18ntk-backup-class.js +420 -0
  5. package/main/i18ntk-backup.js +3 -3
  6. package/main/i18ntk-complete.js +90 -65
  7. package/main/i18ntk-doctor.js +123 -103
  8. package/main/i18ntk-fixer.js +61 -725
  9. package/main/i18ntk-go.js +14 -15
  10. package/main/i18ntk-init.js +77 -26
  11. package/main/i18ntk-java.js +27 -32
  12. package/main/i18ntk-js.js +70 -68
  13. package/main/i18ntk-manage.js +129 -30
  14. package/main/i18ntk-php.js +75 -75
  15. package/main/i18ntk-py.js +55 -56
  16. package/main/i18ntk-scanner.js +59 -57
  17. package/main/i18ntk-setup.js +9 -404
  18. package/main/i18ntk-sizing.js +6 -6
  19. package/main/i18ntk-summary.js +21 -18
  20. package/main/i18ntk-ui.js +11 -10
  21. package/main/i18ntk-usage.js +54 -18
  22. package/main/i18ntk-validate.js +13 -13
  23. package/main/manage/commands/AnalyzeCommand.js +1124 -0
  24. package/main/manage/commands/BackupCommand.js +62 -0
  25. package/main/manage/commands/CommandRouter.js +295 -0
  26. package/main/manage/commands/CompleteCommand.js +61 -0
  27. package/main/manage/commands/DoctorCommand.js +60 -0
  28. package/main/manage/commands/FixerCommand.js +624 -0
  29. package/main/manage/commands/InitCommand.js +62 -0
  30. package/main/manage/commands/ScannerCommand.js +654 -0
  31. package/main/manage/commands/SizingCommand.js +60 -0
  32. package/main/manage/commands/SummaryCommand.js +61 -0
  33. package/main/manage/commands/UsageCommand.js +60 -0
  34. package/main/manage/commands/ValidateCommand.js +978 -0
  35. package/main/manage/index-fixed.js +1447 -0
  36. package/main/manage/index.js +1462 -0
  37. package/main/manage/managers/DebugMenu.js +140 -0
  38. package/main/manage/managers/InteractiveMenu.js +177 -0
  39. package/main/manage/managers/LanguageMenu.js +62 -0
  40. package/main/manage/managers/SettingsMenu.js +53 -0
  41. package/main/manage/services/AuthenticationService.js +263 -0
  42. package/main/manage/services/ConfigurationService-fixed.js +449 -0
  43. package/main/manage/services/ConfigurationService.js +449 -0
  44. package/main/manage/services/FileManagementService.js +368 -0
  45. package/main/manage/services/FrameworkDetectionService.js +458 -0
  46. package/main/manage/services/InitService.js +1051 -0
  47. package/main/manage/services/SetupService.js +462 -0
  48. package/main/manage/services/SummaryService.js +450 -0
  49. package/main/manage/services/UsageService.js +1502 -0
  50. package/package.json +32 -29
  51. package/runtime/enhanced.d.ts +221 -221
  52. package/runtime/index.d.ts +29 -29
  53. package/runtime/index.full.d.ts +331 -331
  54. package/runtime/index.js +7 -6
  55. package/scripts/build-lite.js +17 -17
  56. package/scripts/deprecate-versions.js +23 -6
  57. package/scripts/export-translations.js +5 -5
  58. package/scripts/fix-all-i18n.js +3 -3
  59. package/scripts/fix-and-purify-i18n.js +3 -2
  60. package/scripts/fix-locale-control-chars.js +110 -0
  61. package/scripts/lint-locales.js +80 -0
  62. package/scripts/locale-optimizer.js +8 -8
  63. package/scripts/prepublish.js +21 -21
  64. package/scripts/security-check.js +117 -117
  65. package/scripts/sync-translations.js +4 -4
  66. package/scripts/sync-ui-locales.js +9 -8
  67. package/scripts/validate-all-translations.js +8 -7
  68. package/scripts/verify-deprecations.js +157 -161
  69. package/scripts/verify-translations.js +6 -5
  70. package/settings/i18ntk-config.json +282 -282
  71. package/settings/language-config.json +5 -5
  72. package/settings/settings-cli.js +9 -9
  73. package/settings/settings-manager.js +18 -18
  74. package/ui-locales/de.json +2417 -2348
  75. package/ui-locales/en.json +2415 -2352
  76. package/ui-locales/es.json +2425 -2353
  77. package/ui-locales/fr.json +2418 -2348
  78. package/ui-locales/ja.json +2463 -2361
  79. package/ui-locales/ru.json +2463 -2359
  80. package/ui-locales/zh.json +2418 -2351
  81. package/utils/admin-auth.js +2 -2
  82. package/utils/admin-cli.js +297 -297
  83. package/utils/admin-pin.js +9 -9
  84. package/utils/cli-helper.js +9 -9
  85. package/utils/config-helper.js +73 -104
  86. package/utils/config-manager.js +204 -171
  87. package/utils/config.js +5 -4
  88. package/utils/env-manager.js +249 -263
  89. package/utils/framework-detector.js +27 -24
  90. package/utils/i18n-helper.js +85 -41
  91. package/utils/init-helper.js +152 -94
  92. package/utils/json-output.js +98 -98
  93. package/utils/mini-commander.js +179 -0
  94. package/utils/missing-key-validator.js +5 -5
  95. package/utils/plugin-loader.js +40 -29
  96. package/utils/prompt.js +14 -44
  97. package/utils/safe-json.js +40 -0
  98. package/utils/secure-errors.js +3 -3
  99. package/utils/security-check-improved.js +390 -0
  100. package/utils/security-config.js +5 -5
  101. package/utils/security-fixed.js +607 -0
  102. package/utils/security.js +652 -602
  103. package/utils/setup-enforcer.js +136 -44
  104. package/utils/setup-validator.js +33 -32
  105. package/utils/ultra-performance-optimizer.js +11 -9
  106. package/utils/watch-locales.js +2 -1
  107. package/utils/prompt-fixed.js +0 -55
  108. package/utils/security-check.js +0 -454
@@ -1,16 +1,16 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const os = require('os');
4
- const { envManager } = require('./env-manager');
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const SecurityUtils = require('./security');
5
5
 
6
6
  // Determine package directory and user project root
7
7
  const packageDir = path.resolve(__dirname, '..');
8
8
  const userProjectRoot = process.cwd();
9
9
 
10
- // Always use package's internal settings directory to avoid polluting user projects
11
- // The settings directory should be within the package, not in user space
12
- const PROJECT_SETTINGS_DIR = path.join(packageDir, 'settings');
13
- const PROJECT_CONFIG_PATH = path.join(PROJECT_SETTINGS_DIR, 'i18ntk-config.json');
10
+ // Always use current working directory for settings to support test environments
11
+ // This ensures config works correctly when tests change the working directory
12
+ const PROJECT_CONFIG_PATH = path.join(process.cwd(), '.i18ntk-config');
13
+ const PROJECT_SETTINGS_DIR = path.dirname(PROJECT_CONFIG_PATH);
14
14
 
15
15
  // Setup tracking file
16
16
  const SETUP_COMPLETED_FILE = path.join(PROJECT_SETTINGS_DIR, 'setup.json');
@@ -241,24 +241,26 @@ const DEFAULT_CONFIG = {
241
241
  "timezone": "auto"
242
242
  };
243
243
 
244
- // Mapping of supported environment variables to config keys
245
- const ENV_VAR_MAP = {
246
- I18NTK_PROJECT_ROOT: 'projectRoot',
247
- I18NTK_SOURCE_DIR: 'sourceDir',
248
- I18NTK_I18N_DIR: 'i18nDir',
249
- I18NTK_OUTPUT_DIR: 'outputDir',
250
- I18NTK_FRAMEWORK_PREFERENCE: 'framework.preference',
251
- I18NTK_FRAMEWORK_FALLBACK: 'framework.fallback',
252
- I18NTK_FRAMEWORK_DETECT: 'framework.detect',
253
- I18NTK_LOG_LEVEL: 'advanced.logLevel',
254
- I18NTK_LANG: 'uiLanguage',
255
- I18NTK_SILENT: 'processing.minimalLogging',
256
- };
244
+ // Environment variable support has been removed in favor of exclusive .i18ntk-config configuration
257
245
 
258
246
  let currentConfig = null;
247
+ let configLoadInProgress = false;
248
+ let recursionDepth = 0;
249
+ const MAX_RECURSION_DEPTH = 15; // Increased to handle legitimate sequential calls
259
250
 
260
251
  function clone(obj) {
261
- return JSON.parse(JSON.stringify(obj));
252
+ return JSON.parse(JSON.stringify(obj));
253
+ }
254
+
255
+ function checkRecursionGuard() {
256
+ // Disabled recursion detection to prevent false positives on legitimate sequential calls
257
+ // TODO: Implement more sophisticated recursion detection in future versions
258
+ return null;
259
+ }
260
+
261
+ function resetRecursionGuard() {
262
+ recursionDepth = 0;
263
+ configLoadInProgress = false;
262
264
  }
263
265
 
264
266
  function ensureProjectSettingsDir() {
@@ -297,56 +299,38 @@ function deepMerge(target, source, basePath = '') {
297
299
  }
298
300
 
299
301
  function applyEnvOverrides(cfg) {
300
- for (const [envVar, keyPath] of Object.entries(ENV_VAR_MAP)) {
301
- const value = envManager.get(envVar);
302
- if (value === null || value === undefined) continue;
303
-
304
- const keys = keyPath.split('.');
305
- let current = cfg;
306
- for (let i = 0; i < keys.length - 1; i++) {
307
- const k = keys[i];
308
- if (!current[k] || typeof current[k] !== 'object') current[k] = {};
309
- current = current[k];
310
- }
311
- const leaf = keys[keys.length - 1];
312
-
313
- if (keyPath === 'framework.detect' || envVar === 'I18NTK_FRAMEWORK_DETECT') {
314
- current[leaf] = String(value).toLowerCase() !== 'false' && value !== '0';
315
- } else if (envVar === 'I18NTK_SILENT') {
316
- current[leaf] = String(value).toLowerCase() === 'true' || value === '1';
317
- } else {
318
- current[leaf] = normalizePathValue(keyPath, value);
319
- }
320
- }
302
+ // Environment variable support has been removed in favor of exclusive .i18ntk-config configuration
303
+ // This function is kept for backward compatibility but does nothing
321
304
  return cfg;
322
305
  }
323
306
 
324
307
  function tryReadJson(filePath) {
325
308
  try {
326
- if (!fs.existsSync(filePath)) {
309
+ if (!SecurityUtils.safeExistsSync(filePath)) {
327
310
  return null;
328
311
  }
329
-
330
- const data = fs.readFileSync(filePath, 'utf8');
312
+
313
+ const data = SecurityUtils.safeReadFileSync(filePath, path.dirname(filePath), 'utf8');
331
314
  if (!data || data.trim() === '') {
332
315
  console.warn(`[i18ntk] Warning: Empty or invalid JSON file at ${filePath}`);
333
316
  return null;
334
317
  }
335
-
336
- try {
337
- return JSON.parse(data);
338
- } catch (parseError) {
339
- console.error(`[i18ntk] Error parsing JSON from ${filePath}: ${parseError.message}`);
340
- // Create a backup of the corrupted file
341
- const backupPath = `${filePath}.corrupted-${Date.now()}.bak`;
342
- try {
343
- fs.writeFileSync(backupPath, data, 'utf8');
344
- console.warn(`[i18ntk] Created backup of corrupted config at ${backupPath}`);
345
- } catch (backupError) {
346
- console.error(`[i18ntk] Failed to create backup of corrupted config: ${backupError.message}`);
347
- }
348
- return null;
349
- }
318
+
319
+ const parsed = SecurityUtils.safeParseJSON(data);
320
+ if (parsed && typeof parsed === 'object') {
321
+ return parsed;
322
+ }
323
+
324
+ console.error(`[i18ntk] Error parsing JSON from ${filePath}: Invalid JSON content`);
325
+ // Create a backup of the corrupted file
326
+ const backupPath = `${filePath}.corrupted-${Date.now()}.bak`;
327
+ try {
328
+ SecurityUtils.safeWriteFileSync(backupPath, data, path.dirname(backupPath), 'utf8');
329
+ console.warn(`[i18ntk] Created backup of corrupted config at ${backupPath}`);
330
+ } catch (backupError) {
331
+ console.error(`[i18ntk] Failed to create backup of corrupted config: ${backupError.message}`);
332
+ }
333
+ return null;
350
334
  } catch (error) {
351
335
  console.error(`[i18ntk] Error reading config file at ${filePath}: ${error.message}`);
352
336
  return null;
@@ -355,7 +339,7 @@ function tryReadJson(filePath) {
355
339
 
356
340
  async function migrateLegacyIfNeeded(baseCfg) {
357
341
  // If project config does not exist but legacy exists, migrate once
358
- if (!fs.existsSync(PROJECT_CONFIG_PATH) && fs.existsSync(LEGACY_CONFIG_PATH)) {
342
+ if (!SecurityUtils.safeExistsSync(PROJECT_CONFIG_PATH) && SecurityUtils.safeExistsSync(LEGACY_CONFIG_PATH)) {
359
343
  const legacy = tryReadJson(LEGACY_CONFIG_PATH);
360
344
  if (legacy && typeof legacy === 'object') {
361
345
  const merged = deepMerge(clone(baseCfg), legacy);
@@ -367,61 +351,95 @@ async function migrateLegacyIfNeeded(baseCfg) {
367
351
  // Best-effort removal of legacy file to prevent future use
368
352
  try { fs.unlinkSync(LEGACY_CONFIG_PATH); } catch (_) {}
369
353
  // Deprecation notice
370
- console.warn('[i18ntk] Deprecated config location detected (~/.i18ntk). Your config has been migrated to settings/i18ntk-config.json. Please commit the settings/ directory to your project.');
371
- return merged;
372
- } catch (_) {
373
- // If write fails, fall back to in-memory config without deleting legacy
374
- console.warn('[i18ntk] Deprecated config location detected (~/.i18ntk). Using migrated settings in memory; failed to persist to settings/. Ensure the project has write permissions.');
375
- return merged;
376
- }
354
+ console.warn('[i18ntk] Deprecated config location detected (~/.i18ntk). Configuration was migrated to project .i18ntk-config.');
355
+ return merged;
356
+ } catch (_) {
357
+ // If write fails, fall back to in-memory config without deleting legacy
358
+ console.warn('[i18ntk] Deprecated config location detected (~/.i18ntk). Using migrated settings in memory; failed to persist to .i18ntk-config.');
359
+ return merged;
360
+ }
377
361
  }
378
362
  }
379
363
  return null;
380
364
  }
381
365
 
382
366
  function loadConfig() {
383
- if (currentConfig) return currentConfig;
384
- let cfg = clone(DEFAULT_CONFIG);
385
- // 1) Project config (primary)
386
- const projectCfg = tryReadJson(PROJECT_CONFIG_PATH);
387
- if (projectCfg) {
388
- cfg = deepMerge(clone(DEFAULT_CONFIG), projectCfg);
389
- } else {
390
- // 2) Package default (read-only)
391
- const pkgCfg = tryReadJson(PACKAGE_CONFIG_PATH);
392
- if (pkgCfg) {
393
- cfg = deepMerge(clone(DEFAULT_CONFIG), pkgCfg);
367
+ // Check for recursion
368
+ const recursionFallback = checkRecursionGuard();
369
+ if (recursionFallback) return recursionFallback;
370
+
371
+ // Return cached config if available
372
+ if (currentConfig) {
373
+ resetRecursionGuard();
374
+ return currentConfig;
394
375
  }
395
- // 3) Legacy migration (read-only source)
396
- if (!projectCfg) {
397
- const fromLegacy = tryReadJson(LEGACY_CONFIG_PATH);
398
- if (fromLegacy) {
399
- cfg = deepMerge(clone(DEFAULT_CONFIG), fromLegacy);
400
- // Attempt to migrate to project settings
401
- // Ignore migration errors; we still return merged cfg in memory
402
- // eslint-disable-next-line no-unused-vars
403
- console.warn('[i18ntk] Detected legacy config at ~/.i18ntk. Migrating to project settings directory...');
404
- const _ = (async () => { await migrateLegacyIfNeeded(DEFAULT_CONFIG); })();
405
- }
376
+
377
+ // Prevent concurrent loading
378
+ if (configLoadInProgress) {
379
+ console.warn('[i18ntk] Configuration loading already in progress, returning defaults');
380
+ resetRecursionGuard();
381
+ return clone(DEFAULT_CONFIG);
406
382
  }
407
- }
408
- applyEnvOverrides(cfg);
409
- currentConfig = cfg;
410
- return currentConfig;
383
+
384
+ configLoadInProgress = true;
385
+
386
+ try {
387
+ let cfg = clone(DEFAULT_CONFIG);
388
+ // 1) Project config (primary)
389
+ const projectCfg = tryReadJson(PROJECT_CONFIG_PATH);
390
+ if (projectCfg) {
391
+ cfg = deepMerge(clone(DEFAULT_CONFIG), projectCfg);
392
+ } else {
393
+ // 2) Package default (read-only)
394
+ const pkgCfg = tryReadJson(PACKAGE_CONFIG_PATH);
395
+ if (pkgCfg) {
396
+ cfg = deepMerge(clone(DEFAULT_CONFIG), pkgCfg);
397
+ }
398
+ // 3) Legacy migration (read-only source)
399
+ if (!projectCfg) {
400
+ const fromLegacy = tryReadJson(LEGACY_CONFIG_PATH);
401
+ if (fromLegacy) {
402
+ cfg = deepMerge(clone(DEFAULT_CONFIG), fromLegacy);
403
+ // Attempt to migrate to project settings
404
+ // Ignore migration errors; we still return merged cfg in memory
405
+ // eslint-disable-next-line no-unused-vars
406
+ console.warn('[i18ntk] Detected legacy config at ~/.i18ntk. Migrating to project settings directory...');
407
+ const _ = (async () => { await migrateLegacyIfNeeded(DEFAULT_CONFIG); })();
408
+ }
409
+ }
410
+ }
411
+ applyEnvOverrides(cfg);
412
+ currentConfig = cfg;
413
+ return currentConfig;
414
+ } catch (error) {
415
+ console.error('[i18ntk] Error in loadConfig:', error.message);
416
+ currentConfig = clone(DEFAULT_CONFIG);
417
+ return currentConfig;
418
+ } finally {
419
+ configLoadInProgress = false;
420
+ recursionDepth = Math.max(0, recursionDepth - 1);
421
+ }
411
422
  }
412
423
 
413
- async function saveConfig(cfg = currentConfig) {
414
- if (!cfg) return;
415
-
424
+ async function saveConfig(cfg = currentConfig) {
425
+ if (!cfg || typeof cfg !== 'object') return;
426
+
416
427
  try {
417
428
  // Ensure settings directory exists
418
- if (!fs.existsSync(PROJECT_SETTINGS_DIR)) {
429
+ if (!SecurityUtils.safeExistsSync(PROJECT_SETTINGS_DIR)) {
419
430
  fs.mkdirSync(PROJECT_SETTINGS_DIR, { recursive: true });
420
431
  }
421
-
422
- // Save configuration to the project settings directory
423
- await fs.promises.writeFile(PROJECT_CONFIG_PATH, JSON.stringify(cfg, null, 2), 'utf8');
424
- currentConfig = cfg;
432
+
433
+ const serialized = JSON.stringify(cfg, null, 2);
434
+ if (typeof serialized !== 'string' || serialized.length === 0) {
435
+ throw new Error('Cannot save empty configuration payload');
436
+ }
437
+
438
+ // Atomic write prevents partially-written/empty config files.
439
+ const tempPath = `${PROJECT_CONFIG_PATH}.tmp`;
440
+ await fs.promises.writeFile(tempPath, serialized, 'utf8');
441
+ await fs.promises.rename(tempPath, PROJECT_CONFIG_PATH);
442
+ currentConfig = cfg;
425
443
  } catch (error) {
426
444
  console.error('[i18ntk] Error saving configuration:', error.message);
427
445
  throw error;
@@ -429,58 +447,71 @@ async function saveConfig(cfg = currentConfig) {
429
447
  }
430
448
 
431
449
  function getConfig() {
432
- if (currentConfig) {
433
- return resolvePaths(currentConfig);
434
- }
435
-
436
- try {
437
- // Ensure settings directory exists
438
- if (!fs.existsSync(PROJECT_SETTINGS_DIR)) {
439
- fs.mkdirSync(PROJECT_SETTINGS_DIR, { recursive: true });
440
- }
441
-
442
- // Setup is now handled automatically by the unified config system
443
- // No need to check here - handled by getUnifiedConfig
450
+ // Check for recursion
451
+ const recursionFallback = checkRecursionGuard();
452
+ if (recursionFallback) return resolvePaths(recursionFallback);
444
453
 
445
- // Check if config file exists
446
- if (fs.existsSync(PROJECT_CONFIG_PATH)) {
447
- const config = JSON.parse(fs.readFileSync(PROJECT_CONFIG_PATH, 'utf8'));
448
- currentConfig = config;
449
- return resolvePaths(config);
454
+ if (currentConfig) {
455
+ resetRecursionGuard();
456
+ return resolvePaths(currentConfig);
450
457
  }
451
458
 
452
- // Check for legacy config for migration
453
- if (fs.existsSync(LEGACY_CONFIG_PATH)) {
454
- console.log('📦 Migrating legacy configuration...');
455
- const legacyConfig = JSON.parse(fs.readFileSync(LEGACY_CONFIG_PATH, 'utf8'));
456
- const migratedConfig = { ...DEFAULT_CONFIG, ...legacyConfig };
457
- saveConfig(migratedConfig);
458
- currentConfig = migratedConfig;
459
-
460
- // Clean up legacy config
461
- try {
462
- fs.unlinkSync(LEGACY_CONFIG_PATH);
463
- if (fs.readdirSync(LEGACY_CONFIG_DIR).length === 0) {
464
- fs.rmdirSync(LEGACY_CONFIG_DIR);
465
- }
466
- } catch (cleanupError) {
467
- // Ignore cleanup errors
468
- }
469
-
470
- return resolvePaths(migratedConfig);
471
- }
472
-
473
- // Use package defaults for new installation
474
- console.log('📦 Initializing with default configuration...');
475
- saveConfig(DEFAULT_CONFIG);
476
- currentConfig = DEFAULT_CONFIG;
477
- return resolvePaths(DEFAULT_CONFIG);
478
-
479
- } catch (error) {
480
- console.warn('⚠️ Error loading configuration, using defaults:', error.message);
481
- currentConfig = DEFAULT_CONFIG;
482
- return resolvePaths(DEFAULT_CONFIG);
483
- }
459
+ try {
460
+ // Ensure settings directory exists
461
+ if (!SecurityUtils.safeExistsSync(PROJECT_SETTINGS_DIR)) {
462
+ fs.mkdirSync(PROJECT_SETTINGS_DIR, { recursive: true });
463
+ }
464
+
465
+ // Setup is now handled automatically by the unified config system
466
+ // No need to check here - handled by getUnifiedConfig
467
+
468
+ // Check if config file exists
469
+ if (SecurityUtils.safeExistsSync(PROJECT_CONFIG_PATH)) {
470
+ const rawConfig = SecurityUtils.safeReadFileSync(PROJECT_CONFIG_PATH, path.dirname(PROJECT_CONFIG_PATH), 'utf8');
471
+ const config = SecurityUtils.safeParseJSON(rawConfig);
472
+ if (config && typeof config === 'object') {
473
+ currentConfig = config;
474
+ return resolvePaths(config);
475
+ }
476
+ throw new Error('Invalid project configuration JSON');
477
+ }
478
+
479
+ // Check for legacy config for migration
480
+ if (SecurityUtils.safeExistsSync(LEGACY_CONFIG_PATH)) {
481
+ console.log('📦 Migrating legacy configuration...');
482
+ const legacyRaw = SecurityUtils.safeReadFileSync(LEGACY_CONFIG_PATH, path.dirname(LEGACY_CONFIG_PATH), 'utf8');
483
+ const legacyConfig = SecurityUtils.safeParseJSON(legacyRaw);
484
+ if (!legacyConfig || typeof legacyConfig !== 'object') {
485
+ throw new Error('Invalid legacy configuration JSON');
486
+ }
487
+ const migratedConfig = { ...DEFAULT_CONFIG, ...legacyConfig };
488
+ saveConfig(migratedConfig);
489
+ currentConfig = migratedConfig;
490
+
491
+ // Clean up legacy config
492
+ try {
493
+ fs.unlinkSync(LEGACY_CONFIG_PATH);
494
+ if (fs.readdirSync(LEGACY_CONFIG_DIR).length === 0) {
495
+ fs.rmdirSync(LEGACY_CONFIG_DIR);
496
+ }
497
+ } catch (cleanupError) {
498
+ // Ignore cleanup errors
499
+ }
500
+
501
+ return resolvePaths(migratedConfig);
502
+ }
503
+
504
+ // Use package defaults for new installation
505
+ console.log('📦 Initializing with default configuration...');
506
+ saveConfig(DEFAULT_CONFIG);
507
+ currentConfig = DEFAULT_CONFIG;
508
+ return resolvePaths(DEFAULT_CONFIG);
509
+
510
+ } catch (error) {
511
+ console.warn('⚠️ Error loading configuration, using defaults:', error.message);
512
+ currentConfig = DEFAULT_CONFIG;
513
+ return resolvePaths(DEFAULT_CONFIG);
514
+ }
484
515
  }
485
516
 
486
517
  async function setConfig(cfg) {
@@ -502,27 +533,30 @@ async function resetToDefaults() {
502
533
  return currentConfig;
503
534
  }
504
535
 
505
- function resolvePaths(cfg = getConfig()) {
506
- const root = path.resolve(projectRoot, cfg.projectRoot || '.');
507
- const resolved = clone(cfg);
508
- resolved.projectRoot = root;
509
- ['sourceDir', 'i18nDir', 'outputDir'].forEach(key => {
510
- if (resolved[key]) resolved[key] = path.resolve(root, resolved[key]);
511
- });
512
- if (resolved.scriptDirectories) {
513
- resolved.scriptDirectories = { ...resolved.scriptDirectories };
514
- for (const [k, v] of Object.entries(resolved.scriptDirectories)) {
515
- if (v) resolved.scriptDirectories[k] = path.resolve(root, v);
536
+ function resolvePaths(cfg) {
537
+ if (!cfg) {
538
+ cfg = clone(DEFAULT_CONFIG);
516
539
  }
517
- }
518
- return resolved;
540
+ const root = path.resolve(projectRoot, cfg.projectRoot || '.');
541
+ const resolved = clone(cfg);
542
+ resolved.projectRoot = root;
543
+ ['sourceDir', 'i18nDir', 'outputDir'].forEach(key => {
544
+ if (resolved[key]) resolved[key] = path.resolve(root, resolved[key]);
545
+ });
546
+ if (resolved.scriptDirectories) {
547
+ resolved.scriptDirectories = { ...resolved.scriptDirectories };
548
+ for (const [k, v] of Object.entries(resolved.scriptDirectories)) {
549
+ if (v) resolved.scriptDirectories[k] = path.resolve(root, v);
550
+ }
551
+ }
552
+ return resolved;
519
553
  }
520
554
 
521
555
  function toRelative(absPath) {
522
- if (!absPath) return absPath;
523
- const rel = path.relative(projectRoot, absPath);
524
- const normalized = rel ? `./${rel.replace(/\\/g, '/')}` : '.';
525
- return normalized;
556
+ if (!absPath) return absPath;
557
+ const rel = path.relative(projectRoot, absPath);
558
+ const normalized = rel ? `./${rel.replace(/\\/g, '/')}` : '.';
559
+ return normalized;
526
560
  }
527
561
 
528
562
 
@@ -539,5 +573,4 @@ module.exports = {
539
573
  resolvePaths,
540
574
  toRelative,
541
575
  normalizePathValue,
542
- }
543
-
576
+ }
package/utils/config.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const SecurityUtils = require('./security');
3
4
 
4
5
  const settingsManager = require('../settings/settings-manager');
5
6
  const CONFIG_FILE = 'i18ntk-config.json';
@@ -46,12 +47,12 @@ function loadConfig(cwd = settingsManager.configDir) {
46
47
  const configPath = getConfigPath(cwd);
47
48
 
48
49
  // Check if file exists and is accessible
49
- if (!fs.existsSync(configPath)) {
50
+ if (!SecurityUtils.safeExistsSync(configPath)) {
50
51
  return null;
51
52
  }
52
53
 
53
54
  // Read file with explicit encoding
54
- const raw = fs.readFileSync(configPath, { encoding: 'utf8', flag: 'r' });
55
+ const raw = SecurityUtils.safeReadFileSync(configPath, settingsManager.configDir, 'utf8');
55
56
 
56
57
  // Basic validation of file content
57
58
  if (!raw || typeof raw !== 'string') {
@@ -83,12 +84,12 @@ function saveConfig(config, cwd = settingsManager.configDir) {
83
84
  const dir = path.dirname(configPath);
84
85
 
85
86
  // Ensure directory exists
86
- if (!fs.existsSync(dir)) {
87
+ if (!SecurityUtils.safeExistsSync(dir)) {
87
88
  fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
88
89
  }
89
90
 
90
91
  // Write file with secure permissions (read/write for owner only)
91
- fs.writeFileSync(
92
+ SecurityUtils.safeWriteFileSync(
92
93
  configPath,
93
94
  JSON.stringify(config, null, 2),
94
95
  { mode: 0o600, encoding: 'utf8' }