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.
- package/LICENSE +1 -1
- package/README.md +141 -1191
- package/main/i18ntk-analyze.js +65 -84
- package/main/i18ntk-backup-class.js +420 -0
- package/main/i18ntk-backup.js +3 -3
- 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 +77 -26
- package/main/i18ntk-java.js +27 -32
- package/main/i18ntk-js.js +70 -68
- package/main/i18ntk-manage.js +129 -30
- 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 +9 -404
- package/main/i18ntk-sizing.js +6 -6
- package/main/i18ntk-summary.js +21 -18
- package/main/i18ntk-ui.js +11 -10
- package/main/i18ntk-usage.js +54 -18
- 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 -29
- 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 +117 -117
- 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 +157 -161
- 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 +18 -18
- 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 +73 -104
- package/utils/config-manager.js +204 -171
- package/utils/config.js +5 -4
- package/utils/env-manager.js +249 -263
- 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/mini-commander.js +179 -0
- package/utils/missing-key-validator.js +5 -5
- package/utils/plugin-loader.js +40 -29
- 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 +652 -602
- package/utils/setup-enforcer.js +136 -44
- package/utils/setup-validator.js +33 -32
- 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 -454
package/utils/config-manager.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const
|
|
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
|
|
11
|
-
//
|
|
12
|
-
const
|
|
13
|
-
const
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
301
|
-
|
|
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 (!
|
|
309
|
+
if (!SecurityUtils.safeExistsSync(filePath)) {
|
|
327
310
|
return null;
|
|
328
311
|
}
|
|
329
|
-
|
|
330
|
-
const data =
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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 (!
|
|
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).
|
|
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
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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 (!
|
|
429
|
+
if (!SecurityUtils.safeExistsSync(PROJECT_SETTINGS_DIR)) {
|
|
419
430
|
fs.mkdirSync(PROJECT_SETTINGS_DIR, { recursive: true });
|
|
420
431
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
|
|
433
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
currentConfig = config;
|
|
449
|
-
return resolvePaths(config);
|
|
454
|
+
if (currentConfig) {
|
|
455
|
+
resetRecursionGuard();
|
|
456
|
+
return resolvePaths(currentConfig);
|
|
450
457
|
}
|
|
451
458
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
|
506
|
-
|
|
507
|
-
|
|
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
|
-
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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 (!
|
|
50
|
+
if (!SecurityUtils.safeExistsSync(configPath)) {
|
|
50
51
|
return null;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
// Read file with explicit encoding
|
|
54
|
-
const raw =
|
|
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 (!
|
|
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
|
-
|
|
92
|
+
SecurityUtils.safeWriteFileSync(
|
|
92
93
|
configPath,
|
|
93
94
|
JSON.stringify(config, null, 2),
|
|
94
95
|
{ mode: 0o600, encoding: 'utf8' }
|