i18ntk 2.1.0 → 2.2.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.
Files changed (53) hide show
  1. package/README.md +10 -6
  2. package/main/i18ntk-analyze.js +63 -63
  3. package/main/i18ntk-complete.js +75 -74
  4. package/main/i18ntk-fixer.js +3 -3
  5. package/main/i18ntk-init.js +5 -5
  6. package/main/i18ntk-scanner.js +2 -2
  7. package/main/i18ntk-sizing.js +35 -35
  8. package/main/i18ntk-summary.js +4 -4
  9. package/main/i18ntk-ui.js +95 -96
  10. package/main/i18ntk-usage.js +14 -14
  11. package/main/i18ntk-validate.js +6 -5
  12. package/main/manage/commands/AnalyzeCommand.js +67 -67
  13. package/main/manage/commands/FixerCommand.js +2 -2
  14. package/main/manage/commands/ScannerCommand.js +2 -2
  15. package/main/manage/commands/ValidateCommand.js +9 -9
  16. package/main/manage/index.js +7 -4
  17. package/main/manage/managers/LanguageMenu.js +7 -2
  18. package/main/manage/services/UsageService.js +7 -7
  19. package/package.json +20 -41
  20. package/settings/settings-cli.js +3 -3
  21. package/ui-locales/de.json +12 -11
  22. package/ui-locales/en.json +12 -11
  23. package/ui-locales/es.json +12 -11
  24. package/ui-locales/fr.json +12 -11
  25. package/ui-locales/ja.json +12 -11
  26. package/ui-locales/ru.json +12 -11
  27. package/ui-locales/zh.json +12 -11
  28. package/utils/i18n-helper.js +161 -166
  29. package/{scripts → utils}/locale-optimizer.js +61 -60
  30. package/main/i18ntk-go.js +0 -283
  31. package/main/i18ntk-java.js +0 -380
  32. package/main/i18ntk-js.js +0 -512
  33. package/main/i18ntk-manage.js +0 -1694
  34. package/main/i18ntk-php.js +0 -462
  35. package/main/i18ntk-py.js +0 -379
  36. package/main/i18ntk-settings.js +0 -23
  37. package/main/manage/index-fixed.js +0 -1447
  38. package/scripts/build-lite.js +0 -279
  39. package/scripts/deprecate-versions.js +0 -317
  40. package/scripts/export-translations.js +0 -84
  41. package/scripts/fix-all-i18n.js +0 -236
  42. package/scripts/fix-and-purify-i18n.js +0 -233
  43. package/scripts/fix-locale-control-chars.js +0 -110
  44. package/scripts/lint-locales.js +0 -80
  45. package/scripts/prepublish-dev.js +0 -221
  46. package/scripts/prepublish.js +0 -362
  47. package/scripts/security-check.js +0 -117
  48. package/scripts/sync-translations.js +0 -151
  49. package/scripts/sync-ui-locales.js +0 -20
  50. package/scripts/validate-all-translations.js +0 -195
  51. package/scripts/verify-deprecations.js +0 -157
  52. package/scripts/verify-translations.js +0 -63
  53. package/utils/security-fixed.js +0 -609
@@ -1,1447 +0,0 @@
1
- /**
2
- * I18NTK MANAGEMENT TOOLKIT - MAIN MANAGER
3
- *
4
- * This is the main entry point for all i18n operations.
5
- * It provides an interactive interface to manage translations.
6
- *
7
- * Usage:
8
- * npm run i18ntk:manage
9
- * npm run i18ntk:manage -- --command=init
10
- * npm run i18ntk:manage -- --command=analyze
11
- * npm run i18ntk:manage -- --command=validate
12
- * npm run i18ntk:manage -- --help
13
- */
14
-
15
- const path = require('path');
16
- const AdminAuth = require('../../utils/admin-auth');
17
- const SecurityUtils = require('../../utils/security');
18
- const AdminCLI = require('../../utils/admin-cli');
19
- const configManager = require('../../settings/settings-manager');
20
- const { showFrameworkWarningOnce } = require('../../utils/cli-helper');
21
- const { createPrompt, isInteractive } = require('../../utils/prompt-helper');
22
- const { loadTranslations, t, refreshLanguageFromSettings} = require('../../utils/i18n-helper');
23
- const cliHelper = require('../../utils/cli-helper');
24
- const { loadConfig, saveConfig, ensureConfigDefaults } = require('../../utils/config');
25
- const pkg = require('../../package.json');
26
- const SetupEnforcer = require('../../utils/setup-enforcer');
27
- const CommandRouter = require('./commands/CommandRouter');
28
-
29
- // Import services to replace circular dependencies
30
- const ConfigurationService = require('./services/ConfigurationService');
31
- const SummaryService = require('./services/SummaryService');
32
-
33
- // Preload translations early to avoid missing key warnings
34
- loadTranslations();
35
-
36
- class I18nManager {
37
- constructor(config = {}) {
38
- this.config = config;
39
- this.rl = null;
40
- this.isReadlineClosed = false;
41
- this.isAuthenticated = false;
42
- this.ui = null;
43
- this.adminAuth = new AdminAuth();
44
- this.commandRouter = null;
45
-
46
- // Initialize services
47
- this.configurationService = new ConfigurationService(config);
48
- this.summaryService = new SummaryService(config);
49
-
50
- // No longer create readline interface here - use CLI helpers
51
- }
52
-
53
- initializeReadline() {
54
- // Use centralized CLI helper instead of direct readline
55
- this.rl = null;
56
- this.isReadlineClosed = false;
57
- }
58
-
59
- // Initialize configuration using unified system
60
- async initialize() {
61
- try {
62
- // Parse args here for other initialization needs (but language is already loaded)
63
- const args = this.parseArgs();
64
- if (args.help) {
65
- this.showHelp();
66
- process.exit(0);
67
- }
68
-
69
- // Load translations for the UI language
70
- const settings = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
71
- const uiLanguage = args.uiLanguage || settings.uiLanguage || settings.language || this.config.uiLanguage || 'en';
72
- loadTranslations(uiLanguage);
73
-
74
- // Validate source directory exists
75
- const {validateSourceDir, displayPaths} = require('../../utils/config-helper');
76
- try {
77
- validateSourceDir(this.config.sourceDir, 'i18ntk-manage');
78
- } catch (err) {
79
- console.log(t('init.requiredTitle'));
80
- console.log(t('init.requiredBody'));
81
- const answer = await cliHelper.prompt(t('init.promptRunNow'));
82
- if (answer.trim().toLowerCase() === 'y') {
83
- // Note: Initialization should be handled by the calling code
84
- // to avoid circular dependencies
85
- console.log('Please run initialization manually or use the init command');
86
- } else {
87
- throw err;
88
- }
89
- }
90
-
91
- } catch (error) {
92
- throw error;
93
- }
94
- }
95
-
96
- // Auto-detect i18n directory from common locations only if not configured in settings
97
- detectI18nDirectory() {
98
- const settings = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
99
- const projectRoot = path.resolve(settings.projectRoot || this.config.projectRoot || '.');
100
-
101
- // Use per-script directory configuration if available, fallback to global sourceDir
102
- const sourceDir = settings.scriptDirectories?.manage || settings.sourceDir;
103
-
104
- if (sourceDir) {
105
- this.config.sourceDir = path.resolve(projectRoot, sourceDir);
106
- return;
107
- }
108
-
109
- // Define possible i18n paths for auto-detection
110
- const possibleI18nPaths = [
111
- './locales',
112
- './src/locales',
113
- './src/i18n',
114
- './src/i18n/locales',
115
- './app/locales',
116
- './app/i18n',
117
- './public/locales',
118
- './assets/locales',
119
- './translations',
120
- './lang'
121
- ];
122
-
123
- // Only auto-detect if no settings are configured
124
- for (const possiblePath of possibleI18nPaths) {
125
- const resolvedPath = path.resolve(projectRoot, possiblePath);
126
- if (SecurityUtils.safeExistsSync(resolvedPath)) {
127
- // Check if it contains language directories
128
- try {
129
- const items = require('fs').readdirSync(resolvedPath);
130
- const hasLanguageDirs = items.some(item => {
131
- const itemPath = path.join(resolvedPath, item);
132
- return require('fs').statSync(itemPath).isDirectory() &&
133
- ['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'].includes(item);
134
- });
135
-
136
- if (hasLanguageDirs) {
137
- this.config.sourceDir = possiblePath;
138
- t('init.autoDetectedI18nDirectory', { path: possiblePath });
139
- break;
140
- }
141
- } catch (error) {
142
- // Continue checking other paths
143
- }
144
- }
145
- }
146
- }
147
-
148
- // Check if i18n framework is installed - configuration-based check without prompts
149
- async checkI18nDependencies() {
150
- const packageJsonPath = path.resolve('./package.json');
151
-
152
- if (!SecurityUtils.safeExistsSync(packageJsonPath)) {
153
- console.log(this.ui ? this.ui.t('errors.noPackageJson') : 'No package.json found');
154
- return false; // Treat as no framework detected
155
- }
156
-
157
- try {
158
- const packageJson = JSON.parse(SecurityUtils.safeReadFileSync(packageJsonPath, path.dirname(packageJsonPath), 'utf8'));
159
- // Include peerDependencies in the check
160
- const dependencies = {
161
- ...packageJson.dependencies,
162
- ...packageJson.devDependencies,
163
- ...packageJson.peerDependencies
164
- };
165
-
166
- const i18nFrameworks = [
167
- 'react-i18next',
168
- 'vue-i18n',
169
- 'angular-i18n',
170
- 'i18next',
171
- 'next-i18next',
172
- 'svelte-i18n',
173
- '@nuxtjs/i18n',
174
- 'i18ntk-runtime'
175
- ];
176
-
177
- const installedFrameworks = i18nFrameworks.filter(framework => dependencies[framework]);
178
-
179
- if (installedFrameworks.length > 0) {
180
- if (this.ui && this.ui.t) {
181
- console.log(this.ui.t('init.detectedFrameworks', { frameworks: installedFrameworks.join(', ') }));
182
- } else {
183
- console.log(`Detected frameworks: ${installedFrameworks.join(', ')}`);
184
- }
185
- const cfg = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
186
- cfg.framework = cfg.framework || {};
187
- cfg.framework.detected = true;
188
- cfg.framework.installed = installedFrameworks;
189
- if (configManager.saveSettings) {
190
- configManager.saveSettings(cfg);
191
- } else if (configManager.saveConfig) {
192
- configManager.saveConfig(cfg);
193
- }
194
- return true;
195
- } else {
196
- const cfg = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
197
- if (cfg.framework) {
198
- cfg.framework.detected = false;
199
- if (configManager.saveSettings) {
200
- configManager.saveSettings(cfg);
201
- } else if (configManager.saveConfig) {
202
- configManager.saveConfig(cfg);
203
- }
204
- }
205
- // Always signal that frameworks were not detected
206
- return false;
207
- }
208
- } catch (error) {
209
- console.log(t('init.errors.packageJsonRead'));
210
- return false; // Treat as no framework detected on error
211
- }
212
- }
213
-
214
- // Parse command line arguments
215
- parseArgs() {
216
- const args = process.argv.slice(2);
217
- const parsed = {};
218
-
219
- args.forEach(arg => {
220
- if (arg.startsWith('--')) {
221
- const [key, value] = arg.substring(2).split('=');
222
- const sanitizedKey = key?.trim();
223
- const sanitizedValue = value !== undefined ? value.trim() : true;
224
-
225
- switch (sanitizedKey) {
226
- case 'source-dir':
227
- parsed.sourceDir = sanitizedValue;
228
- break;
229
- case 'i18n-dir':
230
- parsed.i18nDir = sanitizedValue;
231
- break;
232
- case 'output-dir':
233
- parsed.outputDir = sanitizedValue;
234
- break;
235
- case 'source-language':
236
- parsed.sourceLanguage = sanitizedValue;
237
- break;
238
- case 'ui-language':
239
- parsed.uiLanguage = sanitizedValue;
240
- break;
241
- case 'no-prompt':
242
- parsed.noPrompt = true;
243
- break;
244
- case 'admin-pin':
245
- parsed.adminPin = sanitizedValue || '';
246
- break;
247
- case 'help':
248
- case 'h':
249
- parsed.help = true;
250
- break;
251
- default:
252
- // Handle language shorthand flags like --de, --fr
253
- if (['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'].includes(sanitizedKey)) {
254
- parsed.uiLanguage = sanitizedKey;
255
- }
256
- break;
257
- }
258
- }
259
- });
260
-
261
- return parsed;
262
- }
263
-
264
- // Add this run method after the checkI18nDependencies method
265
- async run() {
266
- // Add timeout to prevent hanging
267
- const args = this.parseArgs();
268
-
269
- const timeout = setTimeout(() => {
270
- console.error('❌ CLI startup timeout - something is hanging');
271
- if (args.debug) {
272
- console.error('🔍 DEBUG: Last known execution point reached');
273
- }
274
- process.exit(1);
275
- }, 10000); // 10 second timeout
276
-
277
- if (args.debug) {
278
- console.log('🔍 DEBUG: Starting i18ntk-manage.js...');
279
- console.log('🔍 DEBUG: Process.argv:', process.argv);
280
- console.log('🔍 DEBUG: Parsed args:', args);
281
- console.log('🔍 DEBUG: About to call SetupEnforcer.checkSetupCompleteAsync()');
282
- }
283
-
284
- let prompt;
285
- try {
286
- // Ensure setup is complete before running any operations
287
- await SetupEnforcer.checkSetupCompleteAsync();
288
-
289
- prompt = createPrompt({ noPrompt: args.noPrompt || Boolean(args.adminPin) });
290
- const interactive = isInteractive({ noPrompt: args.noPrompt || Boolean(args.adminPin) });
291
-
292
- // Load settings and UI language
293
- const settings = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
294
- const uiLanguage = args.uiLanguage || settings.uiLanguage || settings.language || this.config.uiLanguage || 'en';
295
- loadTranslations(uiLanguage);
296
-
297
- // Initialize CommandRouter
298
- this.commandRouter = new CommandRouter(this.config, this.ui, this.adminAuth);
299
-
300
- if (args.adminPin) {
301
- this.adminAuth.verifyPin = async () => true;
302
- this.prompt = async () => '';
303
- }
304
-
305
- if (args.help) {
306
- this.showHelp();
307
- return;
308
- }
309
-
310
- let cfgAfterInitCheck = {};
311
- if (interactive) {
312
- cfgAfterInitCheck = await this.ensureInitializedOrExit(prompt);
313
- const frameworksDetected = await this.checkI18nDependencies();
314
- if (!frameworksDetected) {
315
- await this.maybePromptFramework(prompt, cfgAfterInitCheck, pkg.version);
316
- }
317
- }
318
-
319
- this.config = { ...this.config, ...cfgAfterInitCheck };
320
- await this.initialize();
321
-
322
- const rawArgs = process.argv.slice(2); // Preserve original CLI args array for positional checks
323
- let commandToExecute = null;
324
-
325
- // Define valid direct commands
326
- const directCommands = [
327
- 'init', 'analyze', 'validate', 'usage', 'scanner', 'sizing', 'complete', 'fix', 'summary', 'debug', 'workflow'
328
- ];
329
-
330
- // Handle help immediately without dependency checks
331
- if (args.help) {
332
- this.showHelp();
333
- this.safeClose();
334
- process.exit(0);
335
- }
336
-
337
- // Handle debug flag
338
- if (args.debug) {
339
- // Enable debug mode for this session
340
- const { blue } = require('../../utils/colors-new');
341
- console.log(blue('Debug mode enabled'));
342
- }
343
-
344
- // Check for --command= argument first
345
- const commandFlagArg = rawArgs.find(arg => arg.startsWith('--command='));
346
- if (commandFlagArg) {
347
- commandToExecute = commandFlagArg.split('=')[1];
348
- } else if (rawArgs.length > 0 && directCommands.includes(rawArgs[0])) {
349
- // If no --command=, check if the first argument is a direct command
350
- commandToExecute = rawArgs[0];
351
- }
352
-
353
- if (commandToExecute) {
354
- console.log(t('ui.executingCommand', { command: commandToExecute }));
355
- await this.executeCommand(commandToExecute);
356
- this.safeClose();
357
- return;
358
- }
359
-
360
- // If no command provided and --no-prompt is set, exit gracefully
361
- if (args.noPrompt) {
362
- this.safeClose();
363
- process.exit(0);
364
- }
365
-
366
- // Framework detection is now handled by maybePromptFramework above
367
- // Skip the redundant checkI18nDependencies prompt
368
-
369
- // Interactive mode - showInteractiveMenu will handle the title
370
- await this.showInteractiveMenu();
371
-
372
- } catch (error) {
373
- if (this.ui && this.ui.t) {
374
- console.error(t('common.genericError', { error: error.message }));
375
- } else {
376
- console.error(`Error: ${error.message}`);
377
- }
378
- process.exit(1);
379
- } finally {
380
- if (prompt && typeof prompt.close === 'function') {
381
- prompt.close();
382
- }
383
- this.safeClose();
384
- }
385
- }
386
-
387
- showHelp() {
388
- const localT = this.ui && this.ui.t ? this.ui.t.bind(this.ui) : (key) => {
389
- // Fallback help text when UI is not initialized
390
- const helpTexts = {
391
- 'help.usage': 'Usage: npm run i18ntk [command] [options]',
392
- 'help.interactiveMode': 'Run without arguments for interactive mode',
393
- 'help.initProject': ' init - Initialize i18n project structure',
394
- 'help.analyzeTranslations': ' analyze - Analyze translation files',
395
- 'help.validateTranslations': ' validate - Validate translations for errors',
396
- 'help.checkUsage': ' usage - Check translation usage in code',
397
- 'help.showHelp': ' help - Show this help message',
398
- 'help.availableCommands': '\nAvailable commands:',
399
- 'help.initCommand': ' --command=init Initialize i18n project',
400
- 'help.analyzeCommand': ' --command=analyze Analyze translations',
401
- 'help.validateCommand': ' --command=validate Validate translations',
402
- 'help.usageCommand': ' --command=usage Check translation usage',
403
- 'help.sizingCommand': ' --command=sizing Analyze translation sizing',
404
- 'help.completeCommand': ' --command=complete Run complete analysis',
405
- 'help.summaryCommand': ' --command=summary Generate summary report',
406
- 'help.debugCommand': ' --command=debug Run debug utilities',
407
- 'help.scannerCommand': ' --command=scanner Scan for translation keys'
408
- };
409
- return helpTexts[key] || key;
410
- };
411
-
412
- console.log(t('help.usage'));
413
- console.log(t('help.interactiveMode'));
414
- console.log(t('help.initProject'));
415
- console.log(t('help.analyzeTranslations'));
416
- console.log(t('help.validateTranslations'));
417
- console.log(t('help.checkUsage'));
418
- console.log(t('help.showHelp'));
419
- console.log(t('help.availableCommands'));
420
- console.log(t('help.initCommand'));
421
- console.log(t('help.analyzeCommand'));
422
- console.log(t('help.validateCommand'));
423
- console.log(t('help.usageCommand'));
424
- console.log(t('help.sizingCommand'));
425
- console.log(t('help.completeCommand'));
426
- console.log(t('help.summaryCommand'));
427
- console.log(t('help.debugCommand'));
428
- console.log(t('help.scannerCommand'));
429
-
430
- // Ensure proper exit for direct command execution
431
- if (process.argv.includes('--help') || process.argv.includes('-h')) {
432
- this.safeClose();
433
- process.exit(0);
434
- }
435
- }
436
-
437
- /**
438
- * Execute a command using the new CommandRouter system
439
- */
440
- async executeCommand(command, options = {}) {
441
- if (!this.commandRouter) {
442
- throw new Error('CommandRouter not initialized');
443
- }
444
-
445
- // Set runtime dependencies for the command router
446
- this.commandRouter.setRuntimeDependencies(
447
- this.prompt.bind(this),
448
- this.isNonInteractiveMode(),
449
- this.safeClose.bind(this)
450
- );
451
-
452
- return await this.commandRouter.executeCommand(command, options);
453
- }
454
-
455
- // ... existing code for ensureInitializedOrExit, maybePromptFramework, showInteractiveMenu, etc. ...
456
-
457
- async ensureInitializedOrExit(prompt) {
458
- const { checkInitialized } = require('../../utils/init-helper');
459
- const cliHelper = require('../../utils/cli-helper');
460
- const pkg = require('../../package.json');
461
-
462
- const { initialized, config } = await checkInitialized();
463
-
464
- if (!initialized) {
465
- console.log('\nThis project is not yet initialized with i18ntk.');
466
- const shouldInitialize = await cliHelper.confirm('Would you like to initialize it now?');
467
-
468
- if (!shouldInitialize) {
469
- console.log('Exiting. Please initialize the project first.');
470
- process.exit(1);
471
- }
472
-
473
- // The initialization will be handled by the init command
474
- return config;
475
- }
476
-
477
- // Check if we need to prompt for framework detection
478
- const settings = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
479
-
480
- // Ensure framework configuration exists with all required fields
481
- if (!settings.framework) {
482
- settings.framework = {
483
- detected: false,
484
- preference: null,
485
- prompt: 'always',
486
- lastPromptedVersion: null,
487
- installed: [],
488
- version: '1.0' // Schema version for future compatibility
489
- };
490
- }
491
-
492
- // Check if we need to prompt for framework detection
493
- if (!settings.framework.detected &&
494
- settings.framework.prompt !== 'suppress' &&
495
- settings.framework.lastPromptedVersion !== pkg.version) {
496
-
497
- console.log('\nWe noticed you haven\'t set up an i18n framework yet.');
498
- console.log('Would you like to detect your i18n framework automatically?');
499
-
500
- console.log('1. Detect automatically');
501
- console.log('2. I\'ll set it up manually');
502
- console.log('3. Don\'t show this again');
503
-
504
- const answer = await prompt.question('\nSelect an option (1-3): ');
505
- const choice = answer.trim();
506
-
507
- let action;
508
- if (choice === '1') action = 'detect';
509
- else if (choice === '2') action = 'manual';
510
- else if (choice === '3') action = 'dont-show';
511
- else action = 'manual'; // default fallback
512
-
513
- if (action === 'dont-show') {
514
- // Update settings to suppress future prompts for this version
515
- settings.framework.prompt = 'suppress';
516
- settings.framework.lastPromptedVersion = pkg.version;
517
-
518
- if (configManager.saveSettings) {
519
- await configManager.saveSettings(settings);
520
- } else if (configManager.saveConfig) {
521
- await configManager.saveConfig(settings);
522
- }
523
-
524
- console.log('Framework detection prompt will be suppressed for this version.');
525
- } else if (action === 'detect') {
526
- // Run framework detection
527
- const { detectedLanguage, detectedFramework } = await this.detectEnvironmentAndFramework();
528
-
529
- if (detectedFramework && detectedFramework !== 'generic') {
530
- console.log(`\nDetected framework: ${detectedFramework}`);
531
-
532
- // Update settings with detected framework
533
- settings.framework.detected = true;
534
- settings.framework.preference = detectedFramework;
535
- settings.framework.lastDetected = new Date().toISOString();
536
-
537
- if (configManager.saveSettings) {
538
- await configManager.saveSettings(settings);
539
- } else if (configManager.saveConfig) {
540
- await configManager.saveConfig(settings);
541
- }
542
-
543
- console.log(`Framework set to: ${detectedFramework}`);
544
- } else {
545
- console.log('\nCould not detect a specific i18n framework.');
546
- console.log('Please set up your i18n framework manually.');
547
- }
548
- }
549
- }
550
-
551
- return { ...config, ...settings };
552
- }
553
-
554
- async detectEnvironmentAndFramework() {
555
- // Defensive check to ensure SecurityUtils is available
556
- if (!SecurityUtils) {
557
- throw new Error('SecurityUtils is not available. This may indicate a module loading issue.');
558
- }
559
- const fs = require('fs');
560
- const path = require('path');
561
-
562
- const packageJsonPath = path.join(process.cwd(), 'package.json');
563
- const pyprojectPath = path.join(process.cwd(), 'pyproject.toml');
564
- const requirementsPath = path.join(process.cwd(), 'requirements.txt');
565
- const goModPath = path.join(process.cwd(), 'go.mod');
566
- const pomPath = path.join(process.cwd(), 'pom.xml');
567
- const composerPath = path.join(process.cwd(), 'composer.json');
568
-
569
- let detectedLanguage = 'generic';
570
- let detectedFramework = 'generic';
571
-
572
- if (SecurityUtils.safeExistsSync(packageJsonPath)) {
573
- detectedLanguage = 'javascript';
574
- try {
575
- const packageJson = JSON.parse(SecurityUtils.safeReadFileSync(packageJsonPath, path.dirname(packageJsonPath), 'utf8'));
576
- const deps = {
577
- ...(packageJson.dependencies || {}),
578
- ...(packageJson.devDependencies || {}),
579
- ...(packageJson.peerDependencies || {})
580
- };
581
-
582
- // Check for i18ntk-runtime first (check both package names)
583
- const hasI18nTkRuntime = deps['i18ntk-runtime'] || deps['i18ntk/runtime'];
584
-
585
- // Check for common i18n patterns in source code if not found in package.json
586
- if (!hasI18nTkRuntime) {
587
- const i18nPatterns = [
588
- /i18n\.t\(['\"`]/,
589
- /useI18n\(/,
590
- /from ['\"]i18ntk[\/\\]runtime['\"]/,
591
- /require\(['\"]i18ntk[\/\\]runtime['\"]\)/
592
- ];
593
-
594
- const sourceFiles = await this.customGlob(['src/**/*.{js,jsx,ts,tsx}'], {
595
- cwd: process.cwd(),
596
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**']
597
- });
598
-
599
- for (const file of sourceFiles) {
600
- try {
601
- const content = await fs.promises.readFile(path.join(process.cwd(), file), 'utf8');
602
- if (i18nPatterns.some(pattern => pattern.test(content))) {
603
- detectedFramework = 'i18ntk-runtime';
604
- break;
605
- }
606
- } catch (e) {
607
- // Skip files we can't read
608
- continue;
609
- }
610
- }
611
- } else {
612
- detectedFramework = 'i18ntk-runtime';
613
- }
614
-
615
- // Only check other frameworks if i18ntk-runtime wasn't detected
616
- if (detectedFramework !== 'i18ntk-runtime') {
617
- if (deps.react || deps['react-dom']) detectedFramework = 'react';
618
- else if (deps.vue || deps['vue-router']) detectedFramework = 'vue';
619
- else if (deps['@angular/core']) detectedFramework = 'angular';
620
- else if (deps.next) detectedFramework = 'nextjs';
621
- else if (deps.nuxt) detectedFramework = 'nuxt';
622
- else if (deps.svelte) detectedFramework = 'svelte';
623
- else detectedFramework = 'generic';
624
- }
625
- } catch (error) {
626
- detectedFramework = 'generic';
627
- }
628
- } else if (SecurityUtils.safeExistsSync(pyprojectPath) || SecurityUtils.safeExistsSync(requirementsPath)) {
629
- detectedLanguage = 'python';
630
- try {
631
- if (SecurityUtils.safeExistsSync(requirementsPath)) {
632
- const requirements = SecurityUtils.safeReadFileSync(requirementsPath, path.dirname(requirementsPath), 'utf8');
633
- if (requirements.includes('django')) detectedFramework = 'django';
634
- else if (requirements.includes('flask')) detectedFramework = 'flask';
635
- else if (requirements.includes('fastapi')) detectedFramework = 'fastapi';
636
- else detectedFramework = 'generic';
637
- }
638
- } catch (error) {
639
- detectedFramework = 'generic';
640
- }
641
- } else if (SecurityUtils.safeExistsSync(goModPath)) {
642
- detectedLanguage = 'go';
643
- detectedFramework = 'generic';
644
- } else if (SecurityUtils.safeExistsSync(pomPath)) {
645
- detectedLanguage = 'java';
646
- try {
647
- const pomContent = SecurityUtils.safeReadFileSync(pomPath, path.dirname(pomPath), 'utf8');
648
- if (pomContent.includes('spring-boot')) detectedFramework = 'spring-boot';
649
- else if (pomContent.includes('spring')) detectedFramework = 'spring';
650
- else if (pomContent.includes('quarkus')) detectedFramework = 'quarkus';
651
- else detectedFramework = 'generic';
652
- } catch (error) {
653
- detectedFramework = 'generic';
654
- }
655
- } else if (SecurityUtils.safeExistsSync(composerPath)) {
656
- detectedLanguage = 'php';
657
- try {
658
- const composer = JSON.parse(SecurityUtils.safeReadFileSync(composerPath, path.dirname(composerPath), 'utf8'));
659
- const deps = composer.require || {};
660
-
661
- if (deps['laravel/framework']) detectedFramework = 'laravel';
662
- else if (deps['symfony/framework-bundle']) detectedFramework = 'symfony';
663
- else if (deps['wordpress']) detectedFramework = 'wordpress';
664
- else detectedFramework = 'generic';
665
- } catch (error) {
666
- detectedFramework = 'generic';
667
- }
668
- }
669
-
670
- return { detectedLanguage, detectedFramework };
671
- }
672
-
673
- async customGlob(patterns, options = {}) {
674
- const fs = require('fs');
675
- const path = require('path');
676
- const cwd = options.cwd || process.cwd();
677
- const ignorePatterns = options.ignore || [];
678
-
679
- function matchesPattern(filename, pattern) {
680
- // Simple pattern matching for **/*.{js,jsx,ts,tsx} style patterns
681
- if (pattern.includes('**/*')) {
682
- const extensionPart = pattern.split('*.')[1];
683
- if (extensionPart) {
684
- const extensions = extensionPart.replace('{', '').replace('}', '').split(',');
685
- return extensions.some(ext => filename.endsWith('.' + ext.trim()));
686
- }
687
- }
688
- return filename.includes(pattern.replace('**/', ''));
689
- }
690
-
691
- function shouldIgnore(filePath) {
692
- return ignorePatterns.some(pattern => {
693
- if (pattern.includes('**/')) {
694
- const patternEnd = pattern.replace('**/', '');
695
- return filePath.includes('/' + patternEnd) || filePath.includes('\\' + patternEnd);
696
- }
697
- return filePath.includes(pattern);
698
- });
699
- }
700
-
701
- function findFiles(dir, results = []) {
702
- try {
703
- const items = fs.readdirSync(dir);
704
-
705
- for (const item of items) {
706
- const fullPath = path.join(dir, item);
707
- const relativePath = path.relative(cwd, fullPath);
708
-
709
- if (shouldIgnore(relativePath)) {
710
- continue;
711
- }
712
-
713
- try {
714
- const stat = fs.statSync(fullPath);
715
-
716
- if (stat.isDirectory()) {
717
- findFiles(fullPath, results);
718
- } else if (stat.isFile()) {
719
- // Check if file matches any of our patterns
720
- for (const pattern of patterns) {
721
- if (matchesPattern(item, pattern)) {
722
- results.push(relativePath);
723
- break;
724
- }
725
- }
726
- }
727
- } catch (error) {
728
- // Skip files we can't access
729
- continue;
730
- }
731
- }
732
- } catch (error) {
733
- // Skip directories we can't access
734
- }
735
-
736
- return results;
737
- }
738
-
739
- return findFiles(cwd);
740
- }
741
-
742
- async maybePromptFramework(prompt, cfg, currentVersion) {
743
- // Load current settings to check framework configuration
744
- let settings = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
745
-
746
- // Ensure framework configuration exists with all required fields
747
- if (!settings.framework) {
748
- settings.framework = {
749
- detected: false,
750
- preference: null,
751
- prompt: 'always',
752
- lastPromptedVersion: null,
753
- installed: [],
754
- version: '1.0' // Schema version for future compatibility
755
- };
756
-
757
- // Save the updated settings
758
- if (configManager.saveSettings) {
759
- await configManager.saveSettings(settings);
760
- } else if (configManager.saveConfig) {
761
- await configManager.saveConfig(settings);
762
- }
763
- }
764
-
765
- // Reload settings to ensure we have the latest framework detection results
766
- const freshSettings = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
767
- if (freshSettings.framework) {
768
- settings.framework = { ...settings.framework, ...freshSettings.framework };
769
- }
770
-
771
- // Check if framework is already detected or preference is explicitly set to none
772
- if (settings.framework.detected || settings.framework.preference === 'none') {
773
- return cfg;
774
- }
775
-
776
- // Check if DNR (Do Not Remind) is active for this version
777
- if (settings.framework.prompt === 'suppress' && settings.framework.lastPromptedVersion === currentVersion) {
778
- return cfg;
779
- }
780
-
781
- // Reset DNR if version changed
782
- if (settings.framework.prompt === 'suppress' && settings.framework.lastPromptedVersion !== currentVersion) {
783
- settings.framework.prompt = 'always';
784
- settings.framework.lastPromptedVersion = null;
785
-
786
- // Save the updated settings
787
- if (configManager.saveSettings) {
788
- await configManager.saveSettings(settings);
789
- } else if (configManager.saveConfig) {
790
- await configManager.saveConfig(settings);
791
- }
792
- }
793
-
794
- // This function is now handled by ensureInitializedOrExit for better flow control
795
-
796
- return cfg;
797
- }
798
-
799
- async showInteractiveMenu() {
800
- // Check if we're in non-interactive mode (like echo 0 | node script)
801
- if (this.isNonInteractiveMode()) {
802
- console.log(`\n${t('menu.title')}`);
803
- console.log(t('menu.separator'));
804
- console.log(`1. ${t('menu.options.init')}`);
805
- console.log(`2. ${t('menu.options.analyze')}`);
806
- console.log(`3. ${t('menu.options.validate')}`);
807
- console.log(`4. ${t('menu.options.usage')}`);
808
- console.log(`5. ${t('menu.options.complete')}`);
809
- console.log(`6. ${t('menu.options.sizing')}`);
810
- console.log(`7. ${t('menu.options.fix')}`);
811
- console.log(`8. ${t('menu.options.status')}`);
812
- console.log(`9. ${t('menu.options.delete')}`);
813
- console.log(`10. ${t('menu.options.settings')}`);
814
- console.log(`11. ${t('menu.options.help')}`);
815
- console.log(`12. ${t('menu.options.language')}`);
816
- console.log(`13. ${t('menu.options.scanner')}`);
817
- console.log(`0. ${t('menu.options.exit')}`);
818
-
819
- console.log('\n' + t('menu.nonInteractiveModeWarning'));
820
- console.log(t('menu.useDirectExecution'));
821
- console.log(t('menu.useHelpForCommands'));
822
- this.safeClose();
823
- process.exit(0);
824
- return;
825
- }
826
-
827
- console.log(`\n${t('menu.title')}`);
828
- console.log(t('menu.separator'));
829
- console.log(`1. ${t('menu.options.init')}`);
830
- console.log(`2. ${t('menu.options.analyze')}`);
831
- console.log(`3. ${t('menu.options.validate')}`);
832
- console.log(`4. ${t('menu.options.usage')}`);
833
- console.log(`5. ${t('menu.options.complete')}`);
834
- console.log(`6. ${t('menu.options.sizing')}`);
835
- console.log(`7. ${t('menu.options.fix')}`);
836
- console.log(`8. ${t('menu.options.status')}`);
837
- console.log(`9. ${t('menu.options.delete')}`);
838
- console.log(`10. ${t('menu.options.settings')}`);
839
- console.log(`11. ${t('menu.options.help')}`);
840
- console.log(`12. ${t('menu.options.language')}`);
841
- console.log(`13. ${t('menu.options.scanner')}`);
842
- console.log(`0. ${t('menu.options.exit')}`);
843
-
844
- const choice = await this.prompt('\n' + t('menu.selectOptionPrompt'));
845
-
846
- switch (choice.trim()) {
847
- case '1':
848
- await this.executeCommand('init', {fromMenu: true});
849
- break;
850
- case '2':
851
- await this.executeCommand('analyze', {fromMenu: true});
852
- break;
853
- case '3':
854
- await this.executeCommand('validate', {fromMenu: true});
855
- break;
856
- case '4':
857
- await this.executeCommand('usage', {fromMenu: true});
858
- break;
859
- case '5':
860
- await this.executeCommand('complete', {fromMenu: true});
861
- break;
862
- case '6':
863
- await this.executeCommand('sizing', {fromMenu: true});
864
- break;
865
- case '7':
866
- await this.executeCommand('fix', {fromMenu: true});
867
- break;
868
- case '8':
869
- // Check for PIN protection
870
- const authRequired = await this.adminAuth.isAuthRequiredForScript('summaryReports');
871
- if (authRequired) {
872
- console.log(`\n${t('adminCli.protectedAccess')}`);
873
- const cliHelper = require('../../utils/cli-helper');
874
- const pin = await cliHelper.promptPin(t('adminCli.enterPin') + ': ');
875
- const isValid = await this.adminAuth.verifyPin(pin);
876
-
877
- if (!isValid) {
878
- console.log(t('adminCli.invalidPin'));
879
- await this.prompt(t('menu.pressEnterToContinue'));
880
- await this.showInteractiveMenu();
881
- return;
882
- }
883
-
884
- console.log(t('adminCli.accessGranted'));
885
- }
886
-
887
- console.log(t('summary.status.generating'));
888
- try {
889
- // Use SummaryService instead of direct import
890
- this.summaryService.initialize(configManager);
891
- const report = await this.summaryService.run({ fromMenu: true });
892
- console.log(report);
893
- console.log(t('summary.status.completed'));
894
-
895
- // Check if we're in interactive mode before prompting
896
- if (!this.isNonInteractiveMode()) {
897
- try {
898
- await this.prompt('\n' + t('debug.pressEnterToContinue'));
899
- await this.showInteractiveMenu();
900
- } catch (error) {
901
- console.log(t('menu.returning'));
902
- process.exit(0);
903
- }
904
- } else {
905
- console.log(t('status.exitingCompleted'));
906
- process.exit(0);
907
- }
908
- } catch (error) {
909
- console.error(t('common.errorGeneratingStatusSummary', { error: error.message }));
910
-
911
- // Check if we're in interactive mode before prompting
912
- if (!this.isNonInteractiveMode()) {
913
- try {
914
- await this.prompt('\n' + t('debug.pressEnterToContinue'));
915
- await this.showInteractiveMenu();
916
- } catch (error) {
917
- console.log(t('menu.returning'));
918
- process.exit(0);
919
- }
920
- } else {
921
- console.log(t('common.errorExiting'));
922
- process.exit(1);
923
- }
924
- }
925
- break;
926
- case '9':
927
- await this.deleteReports();
928
- break;
929
- case '10':
930
- await this.showSettingsMenu();
931
- break;
932
- case '11':
933
- this.showHelp();
934
- await this.prompt(t('menu.returnToMainMenu'));
935
- await this.showInteractiveMenu();
936
- break;
937
- case '12':
938
- await this.showLanguageMenu();
939
- break;
940
- case '13':
941
- await this.executeCommand('scanner', {fromMenu: true});
942
- break;
943
- case '0':
944
- console.log(t('menu.goodbye'));
945
- this.safeClose();
946
- process.exit(0);
947
- default:
948
- console.log(t('menu.invalidChoice'));
949
- await this.showInteractiveMenu();
950
- }
951
- }
952
-
953
- // ... existing code for showLanguageMenu, showDebugMenu, deleteReports, showSettingsMenu, etc. ...
954
-
955
- async showLanguageMenu() {
956
- console.log(`\n${t('language.title')}`);
957
- console.log(t('language.separator'));
958
- console.log(t('language.current', { language: 'English' })); // Simplified since we don't have UIi18n
959
- console.log('\n' + t('language.available'));
960
-
961
- const languages = [
962
- { code: 'en', name: 'English' },
963
- { code: 'de', name: 'Deutsch' },
964
- { code: 'es', name: 'Español' },
965
- { code: 'fr', name: 'Français' },
966
- { code: 'ru', name: 'Русский' },
967
- { code: 'ja', name: '日本語' },
968
- { code: 'zh', name: '中文' }
969
- ];
970
-
971
- languages.forEach((lang, index) => {
972
- const current = 'en' === 'en' ? ' ✓' : '';
973
- console.log(t('language.languageOption', { index: index + 1, displayName: lang.name, current }));
974
- });
975
-
976
- console.log(`0. ${t('language.backToMainMenu')}`);
977
-
978
- const choice = await this.prompt('\n' + t('language.prompt'));
979
- const choiceNum = parseInt(choice);
980
-
981
- if (choiceNum === 0) {
982
- await this.showInteractiveMenu();
983
- return;
984
- } else if (choiceNum >= 1 && choiceNum <= languages.length) {
985
- const selectedLang = languages[choiceNum - 1];
986
- console.log(t('language.changed', { language: selectedLang.name }));
987
-
988
- // Force reload translations for the entire system
989
- const { loadTranslations } = require('../../utils/i18n-helper');
990
- loadTranslations(selectedLang.code);
991
-
992
- // Return to main menu with new language
993
- await this.prompt('\n' + t('language.pressEnterToContinue'));
994
- await this.showInteractiveMenu();
995
- } else {
996
- console.log(t('language.invalid'));
997
- await this.prompt('\n' + t('language.pressEnterToContinue'));
998
- await this.showLanguageMenu();
999
- }
1000
- }
1001
-
1002
- async showDebugMenu() {
1003
- // Check for PIN protection
1004
- const authRequired = await this.adminAuth.isAuthRequiredForScript('debugMenu');
1005
- if (authRequired) {
1006
- console.log(`\n${t('adminPin.protectedAccess')}`);
1007
- const cliHelper = require('../../utils/cli-helper');
1008
- const pin = await cliHelper.promptPin(t('adminPin.enterPin') + ': ');
1009
- const isValid = await this.adminAuth.verifyPin(pin);
1010
-
1011
- if (!isValid) {
1012
- console.log(t('adminPin.invalidPin'));
1013
- await this.prompt(t('menu.pressEnterToContinue'));
1014
- await this.showInteractiveMenu();
1015
- return;
1016
- }
1017
-
1018
- console.log(t('adminPin.accessGranted'));
1019
- }
1020
-
1021
- console.log(`\n${t('debug.title')}`);
1022
- console.log(t('debug.separator'));
1023
- console.log(t('debug.mainDebuggerSystemDiagnostics'));
1024
- console.log(t('debug.debugLogs'));
1025
- console.log(t('debug.backToMainMenu'));
1026
-
1027
- const choice = await this.prompt('\n' + t('debug.selectOption'));
1028
-
1029
- switch (choice.trim()) {
1030
- case '1':
1031
- await this.runDebugTool('debugger.js', 'Main Debugger');
1032
- break;
1033
- case '2':
1034
- await this.viewDebugLogs();
1035
- break;
1036
- case '0':
1037
- await this.showInteractiveMenu();
1038
- return;
1039
- default:
1040
- console.log(t('debug.invalidChoiceSelectRange'));
1041
- await this.showDebugMenu();
1042
- }
1043
- }
1044
-
1045
- async runDebugTool(toolName, displayName) {
1046
- console.log(t('debug.runningDebugTool', { displayName }));
1047
- try {
1048
- const toolPath = path.join(__dirname, '..', '..', 'scripts', 'debug', toolName);
1049
- if (SecurityUtils.safeExistsSync(toolPath)) {
1050
- console.log(`Debug tool available: ${toolName}`);
1051
- console.log(`To run this tool manually: node "${toolPath}"`);
1052
- console.log(`Working directory: ${path.join(__dirname, '..', '..')}`);
1053
- } else {
1054
- console.log(t('debug.debugToolNotFound', { toolName }));
1055
- }
1056
- } catch (error) {
1057
- console.error(t('debug.errorRunningDebugTool', { displayName, error: error.message }));
1058
- }
1059
-
1060
- await this.prompt('\n' + t('menu.pressEnterToContinue'));
1061
- await this.showDebugMenu();
1062
- }
1063
-
1064
- async viewDebugLogs() {
1065
- console.log(`\n${t('debug.recentDebugLogs')}`);
1066
- console.log('============================================================');
1067
-
1068
- try {
1069
- const logsDir = path.join(__dirname, '..', '..', 'scripts', 'debug', 'logs');
1070
- if (SecurityUtils.safeExistsSync(logsDir)) {
1071
- const files = require('fs').readdirSync(logsDir)
1072
- .filter(file => file.endsWith('.log') || file.endsWith('.txt'))
1073
- .sort((a, b) => {
1074
- const statA = require('fs').statSync(path.join(logsDir, a));
1075
- const statB = require('fs').statSync(path.join(logsDir, b));
1076
- return statB.mtime - statA.mtime;
1077
- })
1078
- .slice(0, 5);
1079
-
1080
- if (files.length > 0) {
1081
- files.forEach((file, index) => {
1082
- const filePath = path.join(logsDir, file);
1083
- const stats = require('fs').statSync(filePath);
1084
- console.log(`${index + 1}. ${file} (${stats.mtime.toLocaleString()})`);
1085
- });
1086
-
1087
- const choice = await this.prompt('\n' + t('debug.selectLogPrompt', { count: files.length }));
1088
- const fileIndex = parseInt(choice) - 1;
1089
-
1090
- if (fileIndex >= 0 && fileIndex < files.length) {
1091
- const logContent = SecurityUtils.safeReadFileSync(path.join(logsDir, files[fileIndex]), logsDir, 'utf8');
1092
- console.log(`\n${t('debug.contentOf', { filename: files[fileIndex] })}:`);
1093
- console.log('============================================================');
1094
- console.log(logContent.slice(-2000)); // Show last 2000 characters
1095
- console.log('============================================================');
1096
- }
1097
- } else {
1098
- console.log(t('debug.noDebugLogsFound'));
1099
- }
1100
- } else {
1101
- console.log(t('debug.debugLogsDirectoryNotFound'));
1102
- }
1103
- } catch (error) {
1104
- console.error(t('errors.errorReadingDebugLogs', { error: error.message }));
1105
- }
1106
-
1107
- await this.prompt('\n' + t('menu.pressEnterToContinue'));
1108
- await this.showInteractiveMenu();
1109
- }
1110
-
1111
- async deleteReports() {
1112
- // Check for PIN protection
1113
- const authRequired = await this.adminAuth.isAuthRequiredForScript('deleteReports');
1114
- if (authRequired) {
1115
- console.log(`\n${t('adminPin.protectedAccess')}`);
1116
- const cliHelper = require('../../utils/cli-helper');
1117
- const pin = await cliHelper.promptPin(t('adminPin.enterPin') + ': ');
1118
- const isValid = await this.adminAuth.verifyPin(pin);
1119
-
1120
- if (!isValid) {
1121
- console.log(t('adminPin.invalidPin'));
1122
- await this.prompt(t('menu.pressEnterToContinue'));
1123
- await this.showInteractiveMenu();
1124
- return;
1125
- }
1126
-
1127
- console.log(t('adminPin.accessGranted'));
1128
- }
1129
-
1130
- console.log(`\n${t('operations.deleteReportsTitle')}`);
1131
- console.log('============================================================');
1132
-
1133
- const targetDirs = [
1134
- { path: path.join(process.cwd(), 'i18ntk-reports'), name: 'Reports', type: 'reports' },
1135
- { path: path.join(process.cwd(), 'reports'), name: 'Legacy Reports', type: 'reports' },
1136
- { path: path.join(process.cwd(), 'reports', 'backups'), name: 'Reports Backups', type: 'backups' },
1137
- { path: path.join(process.cwd(), 'scripts', 'debug', 'logs'), name: 'Debug Logs', type: 'logs' },
1138
- { path: path.join(process.cwd(), 'scripts', 'debug', 'reports'), name: 'Debug Reports', type: 'reports' },
1139
- { path: path.join(process.cwd(), 'settings', 'backups'), name: 'Settings Backups', type: 'backups' },
1140
- { path: path.join(process.cwd(), 'utils', 'i18ntk-reports'), name: 'Utils Reports', type: 'reports' }
1141
- ].filter(dir => dir.path && typeof dir.path === 'string');
1142
-
1143
- try {
1144
- console.log(t('operations.scanningForFiles'));
1145
-
1146
- let availableDirs = [];
1147
-
1148
- // Check which directories exist and have files
1149
- for (const dir of targetDirs) {
1150
- if (SecurityUtils.safeExistsSync(dir.path)) {
1151
- const files = this.getAllReportFiles(dir.path);
1152
- if (files.length > 0) {
1153
- availableDirs.push({
1154
- ...dir,
1155
- files: files.map(file => ({ path: file, dir: dir.path })),
1156
- count: files.length
1157
- });
1158
- }
1159
- }
1160
- }
1161
-
1162
- if (availableDirs.length === 0) {
1163
- console.log(t('operations.noFilesFoundToDelete'));
1164
- await this.prompt(t('menu.pressEnterToContinue'));
1165
- await this.showInteractiveMenu();
1166
- return;
1167
- }
1168
-
1169
- // Show available directories
1170
- console.log(t('operations.availableDirectories'));
1171
- availableDirs.forEach((dir, index) => {
1172
- console.log(` ${index + 1}. ${dir.name} (${dir.count} files)`);
1173
- });
1174
- console.log(` ${availableDirs.length + 1}. ${t('operations.allDirectories')}`);
1175
- console.log(` 0. ${t('operations.cancelOption')}`);
1176
-
1177
- const dirChoice = await this.prompt(`\nSelect directory to clean (0-${availableDirs.length + 1}): `);
1178
- const dirIndex = parseInt(dirChoice) - 1;
1179
-
1180
- let selectedDirs = [];
1181
-
1182
- if (dirChoice.trim() === '0') {
1183
- console.log(t('operations.cancelled'));
1184
- await this.prompt(t('menu.pressEnterToContinue'));
1185
- await this.showInteractiveMenu();
1186
- return;
1187
- } else if (dirIndex === availableDirs.length) {
1188
- selectedDirs = availableDirs;
1189
- } else if (dirIndex >= 0 && dirIndex < availableDirs.length) {
1190
- selectedDirs = [availableDirs[dirIndex]];
1191
- } else {
1192
- console.log(t('operations.invalidSelection'));
1193
- await this.prompt(t('menu.pressEnterToContinue'));
1194
- await this.showInteractiveMenu();
1195
- return;
1196
- }
1197
-
1198
- // Collect all files from selected directories
1199
- let allFiles = [];
1200
- selectedDirs.forEach(dir => {
1201
- allFiles.push(...dir.files);
1202
- });
1203
-
1204
- console.log(t('operations.foundFilesInSelectedDirectories', { count: allFiles.length }));
1205
- selectedDirs.forEach(dir => {
1206
- console.log(` 📁 ${dir.name}: ${dir.count} files`);
1207
- });
1208
-
1209
- // Show deletion options
1210
- console.log(t('operations.deletionOptions'));
1211
- console.log(` 1. ${t('operations.deleteAllFiles')}`);
1212
- console.log(` 2. ${t('operations.keepLast3Files')}`);
1213
- console.log(` 3. ${t('operations.keepLast5Files')}`);
1214
- console.log(` 0. ${t('operations.cancelReportOption')}`);
1215
-
1216
- const option = await this.prompt('\nSelect option (0-3): ');
1217
-
1218
- let filesToDelete = [];
1219
-
1220
- switch (option.trim()) {
1221
- case '1':
1222
- filesToDelete = allFiles;
1223
- break;
1224
- case '2':
1225
- filesToDelete = this.getFilesToDeleteKeepLast(allFiles, 3);
1226
- break;
1227
- case '3':
1228
- filesToDelete = this.getFilesToDeleteKeepLast(allFiles, 5);
1229
- break;
1230
- case '0':
1231
- console.log(t('operations.cancelled'));
1232
- await this.prompt(t('menu.pressEnterToContinue'));
1233
- await this.showInteractiveMenu();
1234
- return;
1235
- default:
1236
- console.log(t('menu.invalidOption'));
1237
- await this.prompt(t('menu.pressEnterToContinue'));
1238
- await this.showInteractiveMenu();
1239
- return;
1240
- }
1241
-
1242
- if (filesToDelete.length === 0) {
1243
- console.log(t('operations.noFilesToDelete'));
1244
- await this.prompt(t('menu.pressEnterToContinue'));
1245
- await this.showInteractiveMenu();
1246
- return;
1247
- }
1248
-
1249
- console.log(t('operations.filesToDeleteCount', { count: filesToDelete.length }));
1250
- console.log(t('operations.filesToKeepCount', { count: allFiles.length - filesToDelete.length }));
1251
-
1252
- const confirm = await this.prompt(t('operations.confirmDeletion'));
1253
-
1254
- if (confirm.toLowerCase() === 'y' || confirm.toLowerCase() === 'yes') {
1255
- let deletedCount = 0;
1256
-
1257
- for (const fileInfo of filesToDelete) {
1258
- try {
1259
- require('fs').unlinkSync(fileInfo.path);
1260
- console.log(t('operations.deletedFile', { filename: path.basename(fileInfo.path) }));
1261
- deletedCount++;
1262
- } catch (error) {
1263
- console.log(t('operations.failedToDeleteFile', { filename: path.basename(fileInfo.path), error: error.message }));
1264
- }
1265
- }
1266
-
1267
- console.log(`\n🎉 Successfully deleted ${deletedCount} files!`);
1268
- } else {
1269
- console.log(t('operations.cancelled'));
1270
- }
1271
-
1272
- } catch (error) {
1273
- console.error(`❌ Error during deletion process: ${error.message}`);
1274
- }
1275
-
1276
- await this.prompt(t('menu.pressEnterToContinue'));
1277
- await this.showInteractiveMenu();
1278
- }
1279
-
1280
- getAllReportFiles(dir) {
1281
- if (!dir || typeof dir !== 'string') {
1282
- return [];
1283
- }
1284
-
1285
- let files = [];
1286
-
1287
- try {
1288
- if (!SecurityUtils.safeExistsSync(dir)) {
1289
- return [];
1290
- }
1291
-
1292
- const items = require('fs').readdirSync(dir);
1293
- for (const item of items) {
1294
- const fullPath = path.join(dir, item);
1295
-
1296
- try {
1297
- const stat = require('fs').statSync(fullPath);
1298
-
1299
- if (stat.isDirectory()) {
1300
- files.push(...this.getAllReportFiles(fullPath));
1301
- } else if (
1302
- // Common report file extensions
1303
- item.endsWith('.json') ||
1304
- item.endsWith('.html') ||
1305
- item.endsWith('.txt') ||
1306
- item.endsWith('.log') ||
1307
- item.endsWith('.csv') ||
1308
- item.endsWith('.md') ||
1309
- // Specific report filename patterns
1310
- item.includes('-report.') ||
1311
- item.includes('_report.') ||
1312
- item.includes('report-') ||
1313
- item.includes('report_') ||
1314
- item.includes('analysis-') ||
1315
- item.includes('validation-')
1316
- ) {
1317
- files.push(fullPath);
1318
- }
1319
- } catch (error) {
1320
- // Skip individual files that can't be accessed
1321
- continue;
1322
- }
1323
- }
1324
- } catch (error) {
1325
- // Silent fail for inaccessible directories
1326
- console.log(`⚠️ Could not access directory: ${dir}`);
1327
- }
1328
-
1329
- return files;
1330
- }
1331
-
1332
- getFilesToDeleteKeepLast(allFiles, keepCount = 3) {
1333
- // Sort files by modification time (newest first)
1334
- const sortedFiles = allFiles.sort((a, b) => {
1335
- try {
1336
- const statA = require('fs').statSync(a.path || a);
1337
- const statB = require('fs').statSync(b.path || b);
1338
- return statB.mtime.getTime() - statA.mtime.getTime();
1339
- } catch (error) {
1340
- // If stat fails, sort by filename as fallback
1341
- const pathA = a.path || a;
1342
- const pathB = b.path || b;
1343
- return pathB.localeCompare(pathA);
1344
- }
1345
- });
1346
-
1347
- // Keep the N newest files, delete the rest
1348
- return sortedFiles.slice(keepCount);
1349
- }
1350
-
1351
- async showSettingsMenu() {
1352
- try {
1353
- // Check for PIN protection
1354
- const authRequired = await this.adminAuth.isAuthRequiredForScript('settingsMenu');
1355
- if (authRequired) {
1356
- console.log(`\n${t('adminPin.protectedAccess')}`);
1357
- const cliHelper = require('../../utils/cli-helper');
1358
- const pin = await cliHelper.promptPin(t('adminPin.enterPin') + ': ');
1359
- const isValid = await this.adminAuth.verifyPin(pin);
1360
-
1361
- if (!isValid) {
1362
- console.log(t('adminPin.invalidPin'));
1363
- await this.prompt(t('menu.pressEnterToContinue'));
1364
- await this.showInteractiveMenu();
1365
- return;
1366
- }
1367
-
1368
- console.log(t('adminPin.accessGranted'));
1369
- }
1370
-
1371
- const SettingsCLI = require('../../settings/settings-cli');
1372
- const settingsCLI = new SettingsCLI();
1373
- await settingsCLI.run();
1374
- } catch (error) {
1375
- console.error('❌ Error opening settings:', error.message);
1376
- await this.prompt(t('menu.pressEnterToContinue'));
1377
- }
1378
- await this.showInteractiveMenu();
1379
- }
1380
-
1381
- prompt(question) {
1382
- const cliHelper = require('../../utils/cli-helper');
1383
- // If interactive not available, return empty string to avoid hangs
1384
- if (!process.stdin.isTTY || process.stdin.destroyed) {
1385
- console.log('\n⚠️ Interactive input not available, using default response.');
1386
- return Promise.resolve('');
1387
- }
1388
- return cliHelper.prompt(`${question} `);
1389
- }
1390
-
1391
- // Safe method to check if we're in non-interactive mode
1392
- isNonInteractiveMode() {
1393
- return !process.stdin.isTTY || process.stdin.destroyed || this.isReadlineClosed;
1394
- }
1395
-
1396
- safeClose() {
1397
- if (this.rl && !this.isReadlineClosed) {
1398
- try {
1399
- this.rl.close();
1400
- this.isReadlineClosed = true;
1401
- } catch (error) {
1402
- // Ignore close errors
1403
- }
1404
- }
1405
- }
1406
- }
1407
-
1408
- // Run if called directly
1409
- if (require.main === module) {
1410
- // Handle version and help immediately before any initialization
1411
- const args = process.argv.slice(2);
1412
-
1413
- if (args.includes('--version') || args.includes('-v')) {
1414
- try {
1415
- const packageJsonPath = path.resolve(__dirname, '..', '..', 'package.json');
1416
- const packageJson = JSON.parse(SecurityUtils.safeReadFileSync(packageJsonPath, path.dirname(packageJsonPath), 'utf8'));
1417
- const versionInfo = packageJson.versionInfo || {};
1418
-
1419
- console.log(`\n🌍 i18n Toolkit (i18ntk)`);
1420
- console.log(`Version: ${packageJson.version}`);
1421
- console.log(`Release Date: ${versionInfo.releaseDate || 'N/A'}`);
1422
- console.log(`Maintainer: ${versionInfo.maintainer || packageJson.author}`);
1423
- console.log(`Node.js: ${versionInfo.supportedNodeVersions || packageJson.engines?.node || '>=16.0.0'}`);
1424
- console.log(`License: ${packageJson.license}`);
1425
-
1426
- if (versionInfo.majorChanges && versionInfo.majorChanges.length > 0) {
1427
- console.log(`\n✨ What's New in ${packageJson.version}:`);
1428
- versionInfo.majorChanges.forEach(change => {
1429
- console.log(` • ${change}`);
1430
- });
1431
- }
1432
-
1433
- console.log(`\n📖 Documentation: ${packageJson.homepage}`);
1434
- console.log(`🐛 Report Issues: ${packageJson.bugs?.url}`);
1435
-
1436
- } catch (error) {
1437
- console.log(`\n❌ Version information unavailable`);
1438
- console.log(`Error: ${error.message}`);
1439
- }
1440
- process.exit(0);
1441
- }
1442
-
1443
- const manager = new I18nManager();
1444
- manager.run();
1445
- }
1446
-
1447
- module.exports = I18nManager;