i18ntk 1.7.5 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,17 +2,17 @@
2
2
 
3
3
  ![i18ntk Logo](docs/screenshots/i18ntk-logo-public.PNG)
4
4
 
5
- **Version:** 1.7.5
5
+ **Version:** 1.8.0
6
6
  **Last Updated:** 2025-08-11
7
7
  **GitHub Repository:** [vladnoskv/i18ntk](https://github.com/vladnoskv/i18ntk)
8
8
 
9
- [![npm](https://img.shields.io/npm/dt/i18ntk.svg)](https://www.npmjs.com/package/i18ntk) [![npm version](https://badge.fury.io/js/i18ntk.svg)](https://badge.fury.io/js/i18ntk) [![Node.js Version](https://img.shields.io/badge/node-%3E%3D16.0.0-brightgreen.svg)](https://nodejs.org/) [![Downloads](https://img.shields.io/npm/dm/i18ntk.svg)](https://www.npmjs.com/package/i18ntk) [![Socket Badge](https://socket.dev/api/badge/npm/package/i18ntk/1.7.5)](https://socket.dev/npm/package/i18ntk/overview/1.7.5) [![GitHub stars](https://img.shields.io/github/stars/vladnoskv/i18ntk?style=social)](https://github.com/vladnoskv/i18ntk)
9
+ [![npm](https://img.shields.io/npm/dt/i18ntk.svg)](https://www.npmjs.com/package/i18ntk) [![npm version](https://badge.fury.io/js/i18ntk.svg)](https://badge.fury.io/js/i18ntk) [![Node.js Version](https://img.shields.io/badge/node-%3E%3D16.0.0-brightgreen.svg)](https://nodejs.org/) [![Downloads](https://img.shields.io/npm/dm/i18ntk.svg)](https://www.npmjs.com/package/i18ntk) [![Socket Badge](https://socket.dev/api/badge/npm/package/i18ntk/1.8.0)](https://socket.dev/npm/package/i18ntk/overview/1.8.0) [![GitHub stars](https://img.shields.io/github/stars/vladnoskv/i18ntk?style=social)](https://github.com/vladnoskv/i18ntk)
10
10
 
11
11
  **🚀 The fastest way to manage translations across any framework or vanilla JavaScript projects**
12
12
 
13
13
  **Framework Support:** Auto-detects popular libraries (React i18next, Vue i18n, i18next, Nuxt i18n, Svelte i18n) or works without a framework. i18ntk manages translation files and validation—it does NOT implement translation logic like i18next or Vue i18n.
14
14
 
15
- > **v1.7.5** – **CRITICAL SECURITY FIXES** - Zero shell access vulnerabilities eliminated. NEW Interactive Translation Fixer Tool with custom placeholder markers, selective language/file fixing, mass fix capabilities, and 7-language UI support; enhanced security logging, flexible 4-6 digit PIN authentication, configuration stability improvements, and CI/CD silent mode support; maintains 97% speed improvement.
15
+ > **v1.8.0** – **SAFER WORKFLOW** - Autorun workflow removed for enhanced safety. Enhanced Interactive Translation Fixer Tool with improved automatic detection, selective language/file fixing, mass fix capabilities, and 7-language UI support; enhanced security logging, flexible 4-6 digit PIN authentication, configuration stability improvements, and CI/CD silent mode support; maintains 97% speed improvement.
16
16
 
17
17
  ## 🚀 Quick Start
18
18
 
@@ -46,8 +46,8 @@ i18ntk validate --source ./locales
46
46
 
47
47
  ## 🎯 Highlights
48
48
 
49
- - **NEW in 1.7.5:** Security‑hardened codebase: zero shell execution in production.
50
- - **Interactive Translation Fixer:** `i18ntk fixer` with guided flows and custom marker detection.
49
+ - **NEW in 1.8.0:** **SAFER WORKFLOW** - Autorun workflow removed for enhanced safety and configuration protection.
50
+ - **Enhanced Interactive Translation Fixer:** Improved automatic detection with guided flows, selective language/file fixing, mass fix capabilities, and 7-language UI support.
51
51
  - **Ultra‑Extreme performance:** 97% speed improvement — **15.38ms** for 200k keys.
52
52
  - **Security & Privacy:** PIN protection with AES‑256‑GCM; strict path and input validation.
53
53
  - **Sizing tools:** Interactive locale optimizer (up to **86%** size reduction) and reports.
@@ -55,10 +55,11 @@ i18ntk validate --source ./locales
55
55
  - **Watch helper:** Optional `--watch` keeps translations in sync.
56
56
  - **Framework‑agnostic:** Works with React, Vue, Svelte, Nuxt, i18next, or plain JSON.
57
57
  - **Scale:** Linear scaling up to 5M keys/second with ultra‑extreme settings.
58
+ - **Script-by-Script Safety:** Manual execution ensures proper setup before each operation.
58
59
 
59
60
  ---
60
61
 
61
- ## 🛡️ Security in 1.7.5
62
+ ## 🛡️ Security in 1.8.0
62
63
 
63
64
  ### Summary
64
65
 
@@ -71,7 +72,7 @@ i18ntk validate --source ./locales
71
72
 
72
73
  ### Before → After
73
74
 
74
- | Area | Before (risk) | After (1.7.5) |
75
+ | Area | Before (risk) | After (1.7.5+) |
75
76
  | --------------------- | ---------------------------- | --------------------------------- |
76
77
  | Shell execution | Possible via `child_process` | **Removed entirely** |
77
78
  | File ops | Mixed shell + Node | **Node fs/path only** |
@@ -111,7 +112,25 @@ i18ntk validate --source ./locales
111
112
  | `usage` | Analyze usage patterns | `i18ntk usage --format=json` |
112
113
  | `doctor` | Diagnose configuration issues | `i18ntk doctor` |
113
114
  | `sizing` | Optimize package size | `i18ntk sizing --interactive` |
114
- | `fixer` | Fix broken translations/markers | `i18ntk fixer --interactive` |
115
+ | `fixer` | **Enhanced:** Fix broken translations/markers | `i18ntk fixer --interactive` |
116
+
117
+ ---
118
+
119
+ ## 🔒 Safer Workflow (NEW in v1.8.0)
120
+
121
+ **Enhanced security through manual script execution:**
122
+
123
+ - **Autorun workflow removed** for enhanced safety and configuration protection
124
+ - **Script-by-script safety** - Each operation requires explicit user initiation
125
+ - **Enhanced validation** - All operations validated before execution
126
+ - **Improved security logging** - Comprehensive audit trail for all operations
127
+ - **Manual review encouraged** - Users maintain full control over each step
128
+
129
+ **Migration from previous versions:**
130
+ - The `workflow` command has been removed for security reasons
131
+ - Use individual commands (`analyze`, `validate`, `fixer`) for safer operations
132
+ - Enhanced fixer tool provides guided workflows for common tasks
133
+ - All previous functionality remains available through safer individual commands
115
134
 
116
135
  ---
117
136
 
@@ -121,7 +140,7 @@ Create `settings/i18ntk-config.json` (auto‑generated by `init`):
121
140
 
122
141
  ```json
123
142
  {
124
- "version": "1.7.5",
143
+ "version": "1.8.0",
125
144
  "sourceDir": "./locales",
126
145
  "outputDir": "./i18ntk-reports",
127
146
  "defaultLanguage": "en",
@@ -162,14 +181,22 @@ You can override paths with environment variables:
162
181
 
163
182
  ---
164
183
 
165
- ## 🔧 Translation Fixer (1.7.4+)
184
+ ## 🔧 Enhanced Translation Fixer (v1.8.0)
185
+
186
+ Interactive tool with improved automatic detection to locate and repair placeholders such as `{{NOT_TRANSLATED}}`, `__UNTRANSLATED__`, or custom markers.
166
187
 
167
- Interactive tool to locate and repair placeholders such as `{{NOT_TRANSLATED}}`, `__UNTRANSLATED__`, or custom markers.
188
+ **Enhanced Features:**
189
+ - **Improved Auto-Detection:** Smarter detection of broken translations and markers
190
+ - **Selective Fixing:** Choose specific languages or files to fix
191
+ - **Mass Fix Capabilities:** Fix all broken translations at once
192
+ - **7-Language UI Support:** Complete interface in 7 languages
193
+ - **Script-by-Script Safety:** Manual execution ensures proper review
194
+ - **Enhanced Security:** Creates encrypted backups before any changes
168
195
 
169
196
  **Examples:**
170
197
 
171
198
  ```bash
172
- # Guided mode
199
+ # Enhanced guided mode
173
200
  i18ntk fixer --interactive
174
201
 
175
202
  # Fix specific languages with custom markers
@@ -187,12 +214,12 @@ i18ntk fixer --languages all
187
214
 
188
215
  **Interactive flow:**
189
216
 
190
- - Welcome & help panel
191
- - Marker configuration (built‑in + custom)
192
- - Language and directory selection
193
- - Preview & confirmation
194
- - Real‑time progress + stats
195
- - Report generation (before/after, per‑file, per‑language)
217
+ - Welcome & help panel with 7-language support
218
+ - Enhanced marker configuration (built‑in + custom)
219
+ - Language and directory selection with smart filtering
220
+ - Preview & confirmation with detailed change overview
221
+ - Real‑time progress + comprehensive stats
222
+ - Report generation (before/after, per‑file, per‑language, security log)
196
223
 
197
224
  ---
198
225
 
@@ -123,86 +123,64 @@ class I18nFixer {
123
123
  }
124
124
 
125
125
  async promptForMarkers() {
126
- const readline = require('readline');
127
- const rl = readline.createInterface({
128
- input: process.stdin,
129
- output: process.stdout
130
- });
126
+ const { ask } = require('../utils/cli.js');
131
127
 
132
- return new Promise(resolve => {
133
- const defaultMarkers = ['__NOT_TRANSLATED__', 'NOT_TRANSLATED', 'TODO_TRANSLATE'];
134
- console.log(`\n${this.t('fixer.markerPrompt.title')}`);
135
- console.log(this.t('fixer.markerPrompt.description'));
136
- console.log(this.t('fixer.markerPrompt.currentDefaults', { markers: defaultMarkers.join(', ') }));
137
-
138
- rl.question(this.t('fixer.markerPrompt.input'), answer => {
139
- rl.close();
140
- if (answer.trim()) {
141
- const markers = answer.split(',').map(m => m.trim()).filter(Boolean);
142
- resolve(markers);
143
- } else {
144
- resolve(defaultMarkers);
145
- }
146
- });
147
- });
128
+ const defaultMarkers = ['__NOT_TRANSLATED__', 'NOT_TRANSLATED', 'TODO_TRANSLATE'];
129
+ console.log(`\n${this.t('fixer.markerPrompt.title')}`);
130
+ console.log(this.t('fixer.markerPrompt.description'));
131
+ console.log(this.t('fixer.markerPrompt.currentDefaults', { markers: defaultMarkers.join(', ') }));
132
+
133
+ const answer = await ask(this.t('fixer.markerPrompt.input'));
134
+ const cleanAnswer = answer.trim();
135
+ if (cleanAnswer) {
136
+ const markers = cleanAnswer.split(',').map(m => m.trim()).filter(Boolean);
137
+ return markers;
138
+ } else {
139
+ return defaultMarkers;
140
+ }
148
141
  }
149
142
 
150
143
  async promptForLanguages() {
151
- const readline = require('readline');
152
- const rl = readline.createInterface({
153
- input: process.stdin,
154
- output: process.stdout
155
- });
144
+ const { ask } = require('../utils/cli.js');
156
145
 
157
- return new Promise(resolve => {
158
- const availableLanguages = this.getAvailableLanguages().filter(l => l !== this.config.sourceLanguage);
159
-
160
- if (availableLanguages.length === 0) {
161
- console.log(this.t('fixer.languagePrompt.noLanguages'));
162
- resolve([]);
163
- return;
164
- }
146
+ const availableLanguages = this.getAvailableLanguages().filter(l => l !== this.config.sourceLanguage);
147
+
148
+ if (availableLanguages.length === 0) {
149
+ console.log(this.t('fixer.languagePrompt.noLanguages'));
150
+ return [];
151
+ }
165
152
 
166
- console.log(`\n${this.t('fixer.languagePrompt.title')}`);
167
- console.log(this.t('fixer.languagePrompt.available', { languages: availableLanguages.join(', ') }));
168
- console.log(this.t('fixer.languagePrompt.description'));
169
-
170
- rl.question(this.t('fixer.languagePrompt.input'), answer => {
171
- rl.close();
172
- if (answer.trim()) {
173
- const languages = answer.split(',').map(l => l.trim()).filter(Boolean);
174
- // Validate languages exist
175
- const validLanguages = languages.filter(l => availableLanguages.includes(l));
176
- resolve(validLanguages);
177
- } else {
178
- resolve(availableLanguages);
179
- }
180
- });
181
- });
153
+ console.log(`\n${this.t('fixer.languagePrompt.title')}`);
154
+ console.log(this.t('fixer.languagePrompt.available', { languages: availableLanguages.join(', ') }));
155
+ console.log(this.t('fixer.languagePrompt.description'));
156
+
157
+ const answer = await ask(this.t('fixer.languagePrompt.input'));
158
+ const cleanAnswer = answer.trim();
159
+ if (cleanAnswer) {
160
+ const languages = cleanAnswer.split(',').map(l => l.trim()).filter(Boolean);
161
+ // Validate languages exist
162
+ const validLanguages = languages.filter(l => availableLanguages.includes(l));
163
+ return validLanguages;
164
+ } else {
165
+ return availableLanguages;
166
+ }
182
167
  }
183
168
 
184
169
  async promptForDirectory() {
185
- const readline = require('readline');
186
- const rl = readline.createInterface({
187
- input: process.stdin,
188
- output: process.stdout
189
- });
170
+ const { ask } = require('../utils/cli.js');
190
171
 
191
- return new Promise(resolve => {
192
- const defaultDir = this.config.sourceDir || './locales';
193
- console.log(`\n${this.t('fixer.directoryPrompt.title')}`);
194
- console.log(this.t('fixer.directoryPrompt.current', { dir: defaultDir }));
195
- console.log(this.t('fixer.directoryPrompt.description'));
196
-
197
- rl.question(this.t('fixer.directoryPrompt.input'), answer => {
198
- rl.close();
199
- if (answer.trim()) {
200
- resolve(answer.trim());
201
- } else {
202
- resolve(defaultDir);
203
- }
204
- });
205
- });
172
+ const defaultDir = this.config.sourceDir || './locales';
173
+ console.log(`\n${this.t('fixer.directoryPrompt.title')}`);
174
+ console.log(this.t('fixer.directoryPrompt.current', { dir: defaultDir }));
175
+ console.log(this.t('fixer.directoryPrompt.description'));
176
+
177
+ const answer = await ask(this.t('fixer.directoryPrompt.input'));
178
+ const cleanAnswer = answer.trim();
179
+ if (cleanAnswer) {
180
+ return cleanAnswer;
181
+ } else {
182
+ return defaultDir;
183
+ }
206
184
  }
207
185
 
208
186
  async initialize() {
@@ -487,24 +465,27 @@ class I18nFixer {
487
465
  }
488
466
 
489
467
  async getUserConfirmation() {
490
- const readline = require('readline');
491
- const rl = readline.createInterface({
492
- input: process.stdin,
493
- output: process.stdout
494
- });
468
+ const { ask } = require('../utils/cli.js');
495
469
 
496
- return new Promise(resolve => {
470
+ const askQuestion = async () => {
497
471
  console.log(`\n${this.t('fixer.confirmationTitle')}`);
498
472
  console.log(this.t('fixer.confirmationOptions'));
499
473
  console.log(` ${this.t('fixer.optionYes')}`);
500
474
  console.log(` ${this.t('fixer.optionNo')}`);
501
475
  console.log(` ${this.t('fixer.optionShow')}`);
502
476
 
503
- rl.question(this.t('fixer.choicePrompt'), answer => {
504
- rl.close();
505
- resolve(answer.toLowerCase());
506
- });
507
- });
477
+ const answer = await ask(this.t('fixer.choicePrompt'));
478
+ const cleanAnswer = answer.toLowerCase().trim();
479
+ if (cleanAnswer === 's' || cleanAnswer === 'show') {
480
+ // Show detailed report and ask again
481
+ this.printDetailedReport();
482
+ return askQuestion();
483
+ } else {
484
+ return cleanAnswer;
485
+ }
486
+ };
487
+
488
+ return askQuestion();
508
489
  }
509
490
 
510
491
  generateFixerReport(issues, report) {
@@ -581,72 +562,89 @@ class I18nFixer {
581
562
  }
582
563
  }
583
564
 
565
+ printDetailedReport() {
566
+ // This method is called when user selects 's' to show detailed issues
567
+ // Implementation can be added here if needed
568
+ console.log('\n📋 DETAILED REPORT - All issues shown above in the report file');
569
+ }
570
+
584
571
  async run() {
585
- await this.initialize();
572
+ const { closeGlobalReadline } = require('../utils/cli.js');
586
573
 
587
- if (this.languages.length === 0) {
588
- console.log(this.t('fixer.noLanguages'));
589
- return;
590
- }
574
+ try {
575
+ await this.initialize();
576
+
577
+ if (this.languages.length === 0) {
578
+ console.log(this.t('fixer.noLanguages'));
579
+ return;
580
+ }
591
581
 
592
- console.log(`\n${this.t('fixer.starting', { languages: this.languages.join(', ') })}`);
593
- console.log(this.t('fixer.sourceDirectory', { sourceDir: this.sourceDir }));
594
- console.log(this.t('fixer.sourceLanguage', { sourceLanguage: this.config.sourceLanguage }));
595
- console.log(this.t('fixer.markers', { markers: this.markers.join(', ') }));
582
+ console.log(`\n${this.t('fixer.starting', { languages: this.languages.join(', ') })}`);
583
+ console.log(this.t('fixer.sourceDirectory', { sourceDir: this.sourceDir }));
584
+ console.log(this.t('fixer.sourceLanguage', { sourceLanguage: this.config.sourceLanguage }));
585
+ console.log(this.t('fixer.markers', { markers: this.markers.join(', ') }));
596
586
 
597
- const allIssues = [];
598
- for (const lang of this.languages) {
599
- console.log(this.t('fixer.scanningLanguage', { language: lang }));
600
- const issues = this.scanForIssues(lang);
601
- allIssues.push(...issues);
602
- }
587
+ const allIssues = [];
588
+ for (const lang of this.languages) {
589
+ console.log(this.t('fixer.scanningLanguage', { language: lang }));
590
+ const issues = this.scanForIssues(lang);
591
+ allIssues.push(...issues);
592
+ }
603
593
 
604
- const report = this.generateReport(allIssues);
594
+ const report = this.generateReport(allIssues);
605
595
 
606
- if (report.totalIssues === 0) {
607
- console.log(`\n${this.t('fixer.allComplete')}`);
608
- return;
609
- }
596
+ if (report.totalIssues === 0) {
597
+ console.log(`\n${this.t('fixer.allComplete')}`);
598
+ return;
599
+ }
610
600
 
611
- // Generate and save report
612
- const reportInfo = this.generateFixerReport(allIssues, report);
613
-
614
- // Print limited report to console
615
- this.printLimitedReport(allIssues, report);
616
-
617
- // Non-interactive mode (for tests)
618
- if (this.config.noBackup) {
619
- console.log(`\n${this.t('fixer.nonInteractiveMode')}`);
620
- this.languages.forEach(lang => this.processLanguage(lang));
621
- console.log(this.t('fixer.fixingComplete'));
622
- return;
623
- }
601
+ // Generate and save report
602
+ const reportInfo = this.generateFixerReport(allIssues, report);
603
+
604
+ // Print limited report to console
605
+ this.printLimitedReport(allIssues, report);
606
+
607
+ // Non-interactive mode (for tests)
608
+ if (this.config.noBackup) {
609
+ console.log(`\n${this.t('fixer.nonInteractiveMode')}`);
610
+ this.languages.forEach(lang => this.processLanguage(lang));
611
+ console.log(this.t('fixer.fixingComplete'));
612
+ return;
613
+ }
624
614
 
625
- // Interactive mode
626
- console.log(this.t('fixer.fullReportSaved', { reportPath: reportInfo.relativePath }));
627
- console.log(this.t('fixer.reviewReport'));
628
-
629
- const answer = await this.getUserConfirmation();
630
-
631
- if (answer === 'y' || answer === 'yes') {
632
- this.createBackup();
633
- console.log(this.t('fixer.backupCreated'));
615
+ // Interactive mode
616
+ console.log(this.t('fixer.fullReportSaved', { reportPath: reportInfo.relativePath }));
617
+ console.log(this.t('fixer.reviewReport'));
634
618
 
635
- console.log(`\n${this.t('fixer.applyingFixes')}`);
636
- this.languages.forEach(lang => this.processLanguage(lang));
637
- console.log(this.t('fixer.fixingComplete'));
638
- } else {
639
- console.log(this.t('fixer.operationCancelled'));
619
+ const answer = await this.getUserConfirmation();
620
+
621
+ if (answer === 'y' || answer === 'yes') {
622
+ this.createBackup();
623
+ console.log(this.t('fixer.backupCreated'));
624
+
625
+ console.log(`\n${this.t('fixer.applyingFixes')}`);
626
+ this.languages.forEach(lang => this.processLanguage(lang));
627
+ console.log(this.t('fixer.fixingComplete'));
628
+ } else {
629
+ console.log(this.t('fixer.operationCancelled'));
630
+ }
631
+ } finally {
632
+ // Ensure readline is properly closed to prevent hanging
633
+ closeGlobalReadline();
640
634
  }
641
635
  }
642
636
  }
643
637
 
644
638
  // Run if executed directly
645
639
  if (require.main === module) {
640
+ const { closeGlobalReadline } = require('../utils/cli.js');
646
641
  const fixer = new I18nFixer();
647
642
  fixer.run().catch(err => {
648
643
  console.error(err.message);
649
644
  process.exit(1);
645
+ }).finally(() => {
646
+ // Ensure readline is properly closed
647
+ closeGlobalReadline();
650
648
  });
651
649
  }
652
650
 
@@ -649,34 +649,7 @@ class I18nManager {
649
649
  const fixerTool = new I18nFixer();
650
650
  await fixerTool.run({fromMenu: isManagerExecution});
651
651
  break;
652
- case 'workflow':
653
- console.log(t('workflow.starting'));
654
- const AutoRunner = require('./i18ntk-autorun');
655
- const runner = new AutoRunner(this.config);
656
- // Ensure autorun initializes its translations and config before running
657
- await runner.init();
658
- await runner.runAll(true); // Pass true for quiet mode
659
-
660
- // Show workflow completion message and return to menu
661
- console.log(t('workflow.completed'));
662
- console.log(t('workflow.checkReports'));
663
-
664
- // Check execution context for proper exit handling
665
- if (isManagerExecution && !this.isNonInteractiveMode()) {
666
- try {
667
- await this.prompt(t('usage.pressEnterToReturnToMenu'));
668
- await this.showInteractiveMenu();
669
- } catch (error) {
670
- // If readline fails, just exit gracefully
671
- console.log(t('menu.returning'));
672
- process.exit(0);
673
- }
674
- } else {
675
- // For direct commands or workflow execution, exit gracefully
676
- console.log(t('workflow.exitingCompleted'));
677
- process.exit(0);
678
- }
679
- return;
652
+
680
653
  case 'debug':
681
654
  const debuggerTool = new I18nDebugger();
682
655
  await debuggerTool.run();
@@ -760,20 +733,19 @@ class I18nManager {
760
733
  console.log(`\n${t('menu.title')}`);
761
734
  console.log(t('menu.separator'));
762
735
  console.log(`1. ${t('menu.options.init')}`);
763
- console.log(`2. ${t('menu.options.analyze')}`);
764
- console.log(`3. ${t('menu.options.validate')}`);
765
- console.log(`4. ${t('menu.options.usage')}`);
766
- console.log(`5. ${t('menu.options.complete')}`);
767
- console.log(`6. ${t('menu.options.sizing')}`);
768
- console.log(`7. ${t('menu.options.fix')}`);
769
- console.log(`8. ${t('menu.options.workflow')}`);
770
- console.log(`9. ${t('menu.options.status')}`);
771
- console.log(`10. ${t('menu.options.delete')}`);
772
- console.log(`11. ${t('menu.options.settings')}`);
773
- console.log(`12. ${t('menu.options.help')}`);
774
- console.log(`13. ${t('menu.options.debug')}`);
775
- console.log(`14. ${t('menu.options.language')}`);
776
- console.log(`0. ${t('menu.options.exit')}`);
736
+ console.log(`2. ${t('menu.options.analyze')}`);
737
+ console.log(`3. ${t('menu.options.validate')}`);
738
+ console.log(`4. ${t('menu.options.usage')}`);
739
+ console.log(`5. ${t('menu.options.complete')}`);
740
+ console.log(`6. ${t('menu.options.sizing')}`);
741
+ console.log(`7. ${t('menu.options.fix')}`);
742
+ console.log(`8. ${t('menu.options.status')}`);
743
+ console.log(`9. ${t('menu.options.delete')}`);
744
+ console.log(`10. ${t('menu.options.settings')}`);
745
+ console.log(`11. ${t('menu.options.help')}`);
746
+ console.log(`12. ${t('menu.options.debug')}`);
747
+ console.log(`13. ${t('menu.options.language')}`);
748
+ console.log(`0. ${t('menu.options.exit')}`);
777
749
  console.log('\n' + t('menu.nonInteractiveModeWarning'));
778
750
  console.log(t('menu.useDirectExecution'));
779
751
  console.log(t('menu.useHelpForCommands'));
@@ -791,13 +763,12 @@ class I18nManager {
791
763
  console.log(`5. ${t('menu.options.complete')}`);
792
764
  console.log(`6. ${t('menu.options.sizing')}`);
793
765
  console.log(`7. ${t('menu.options.fix')}`);
794
- console.log(`8. ${t('menu.options.workflow')}`);
795
- console.log(`9. ${t('menu.options.status')}`);
796
- console.log(`10. ${t('menu.options.delete')}`);
797
- console.log(`11. ${t('menu.options.settings')}`);
798
- console.log(`12. ${t('menu.options.help')}`);
799
- console.log(`13. ${t('menu.options.debug')}`);
800
- console.log(`14. ${t('menu.options.language')}`);
766
+ console.log(`8. ${t('menu.options.status')}`);
767
+ console.log(`9. ${t('menu.options.delete')}`);
768
+ console.log(`10. ${t('menu.options.settings')}`);
769
+ console.log(`11. ${t('menu.options.help')}`);
770
+ console.log(`12. ${t('menu.options.debug')}`);
771
+ console.log(`13. ${t('menu.options.language')}`);
801
772
  console.log(`0. ${t('menu.options.exit')}`);
802
773
 
803
774
  const choice = await this.prompt('\n' + t('menu.selectOptionPrompt'));
@@ -825,9 +796,6 @@ class I18nManager {
825
796
  await this.executeCommand('fix', {fromMenu: true});
826
797
  break;
827
798
  case '8':
828
- await this.executeCommand('workflow', {fromMenu: true});
829
- break;
830
- case '9':
831
799
  // Check for PIN protection
832
800
  const authRequired = await this.adminAuth.isAuthRequiredForScript('summaryReports');
833
801
  if (authRequired) {
@@ -883,21 +851,21 @@ class I18nManager {
883
851
  }
884
852
  }
885
853
  break;
886
- case '10':
854
+ case '9':
887
855
  await this.deleteReports();
888
856
  break;
889
- case '11':
857
+ case '10':
890
858
  await this.showSettingsMenu();
891
859
  break;
892
- case '12':
860
+ case '11':
893
861
  this.showHelp();
894
862
  await this.prompt(t('menu.returnToMainMenu'));
895
863
  await this.showInteractiveMenu();
896
864
  break;
897
- case '13':
865
+ case '12':
898
866
  await this.showDebugMenu();
899
867
  break;
900
- case '14':
868
+ case '13':
901
869
  await this.showLanguageMenu();
902
870
  break;
903
871
  case '0':
@@ -1367,8 +1335,8 @@ if (require.main === module) {
1367
1335
  });
1368
1336
  }
1369
1337
 
1370
- console.log(`\n📖 Documentation: ${packageJson.homepage || 'https://github.com/vladnoskv/i18n-management-toolkit#readme'}`);
1371
- console.log(`🐛 Report Issues: ${packageJson.bugs?.url || 'https://github.com/vladnoskv/i18n-management-toolkit/issues'}`);
1338
+ console.log(`\n📖 Documentation: ${packageJson.homepage}`);
1339
+ console.log(`🐛 Report Issues: ${packageJson.bugs?.url}`);
1372
1340
 
1373
1341
  } catch (error) {
1374
1342
  console.log(`\n❌ Version information unavailable`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18ntk",
3
- "version": "1.7.5",
3
+ "version": "1.8.0",
4
4
  "description": "i18ntk (i18n Toolkit) - Ultra-extreme performance enterprise-grade internationalization management toolkit with 97% performance improvement (15.38ms for 200k keys), NEW interactive translation fixer with custom placeholder markers, selective language/file fixing, mass fix capabilities, advanced security with PIN protection, comprehensive backup & recovery, **zero shell access security fixes**, and edge case handling for JavaScript/TypeScript projects",
5
5
  "keywords": [
6
6
  "i18n",
@@ -64,7 +64,6 @@
64
64
  "i18ntk-usage": "main/i18ntk-usage.js",
65
65
  "i18ntk-complete": "main/i18ntk-complete.js",
66
66
  "i18ntk-sizing": "main/i18ntk-sizing.js",
67
- "i18ntk-autorun": "main/i18ntk-autorun.js",
68
67
  "i18ntk-summary": "main/i18ntk-summary.js",
69
68
  "i18ntk-doctor": "main/i18ntk-doctor.js",
70
69
  "i18ntk-fixer": "main/i18ntk-fixer.js"
@@ -98,9 +97,6 @@
98
97
  "i18ntk:doctor": "node main/i18ntk-doctor.js",
99
98
  "i18ntk:summary": "node main/i18ntk-summary.js",
100
99
  "i18ntk:manage": "node main/i18ntk-manage.js",
101
- "i18n:autorun": "node main/i18ntk-autorun.js",
102
- "i18ntk:autorun": "node main/i18ntk-autorun.js",
103
- "workflow": "node main/i18ntk-autorun.js",
104
100
  "init": "node main/i18ntk-init.js",
105
101
  "analyze": "node main/i18ntk-analyze.js",
106
102
  "validate": "node main/i18ntk-validate.js",
@@ -160,7 +156,7 @@
160
156
  },
161
157
  "preferGlobal": true,
162
158
  "versionInfo": {
163
- "version": "1.7.5",
159
+ "version": "1.8.0",
164
160
  "releaseDate": "11/08/2025",
165
161
  "lastUpdated": "11/08/2025",
166
162
  "maintainer": "Vladimir Noskov",
@@ -168,9 +164,10 @@
168
164
  "documentation": "./README.md",
169
165
  "apiReference": "./docs/api/API_REFERENCE.md",
170
166
  "majorChanges": [
167
+ "SAFER WORKFLOW: Autorun workflow removed for enhanced security and configuration protection",
171
168
  "CRITICAL SECURITY FIXES: Zero shell access - eliminated all child_process.execSync() and spawnSync() calls",
172
169
  "Enhanced security: Direct file system operations replacing shell commands",
173
- "NEW Interactive Translation Fixer Tool with custom placeholder markers and selective language/file fixing",
170
+ "Enhanced Interactive Translation Fixer Tool with improved automatic detection",
174
171
  "Mass fix capabilities for thousands of broken translations",
175
172
  "7-language UI support for all interactive fixer operations",
176
173
  "Ultra-extreme performance: 97% improvement - 15.38ms processing for 200k keys",
@@ -179,7 +176,9 @@
179
176
  "Scalability: Linear scaling up to 5M keys with ultra-extreme settings",
180
177
  "Fixed translation file inclusion - resolved ui-locales exclusion issue",
181
178
  "Updated documentation - corrected package size claims and improved accuracy",
182
- "Enhanced examples - added detailed use cases throughout documentation"
179
+ "Enhanced examples - added detailed use cases throughout documentation",
180
+ "FIXED: Translation fixer double enter and double readline issues",
181
+ "FIXED: Package URL strings - updated all repository URLs to use correct i18ntk naming"
183
182
  ],
184
183
  "breakingChanges": [],
185
184
  "deprecations": [
@@ -197,7 +196,9 @@
197
196
  "1.7.1",
198
197
  "1.7.2",
199
198
  "1.7.3",
200
- "1.7.4"
199
+ "1.7.4",
200
+ "1.7.5",
201
+ "1.7.6"
201
202
 
202
203
  ],
203
204
  "nextVersion": "1.8.0",
@@ -2,6 +2,6 @@
2
2
  "enabled": false,
3
3
  "pinHash": null,
4
4
  "salt": null,
5
- "createdAt": "2025-08-10T19:59:40.084Z",
6
- "lastModified": "2025-08-10T20:19:12.192Z"
5
+ "createdAt": "2025-08-11T04:51:58.371Z",
6
+ "lastModified": "2025-08-11T04:52:16.979Z"
7
7
  }
@@ -193,12 +193,5 @@
193
193
  "autoSave": true,
194
194
  "dateFormat": "DD/MM/YYYY",
195
195
  "timeFormat": "24h",
196
- "timezone": "auto",
197
- "sizeLimit": null,
198
- "framework": {
199
- "detected": false,
200
- "preference": "none",
201
- "prompt": "always",
202
- "lastPromptedVersion": null
203
- }
196
+ "timezone": "auto"
204
197
  }
@@ -1752,40 +1752,16 @@ class SettingsCLI {
1752
1752
  console.log(`${colors.bright}${t('settings.reportBug.title')}${colors.reset}\n`);
1753
1753
  console.log(t('settings.reportBug.description'));
1754
1754
  console.log(`
1755
- ${colors.dim}${t('settings.reportBug.link')}: https://github.com/vladnoskv/i18ntk/issues${colors.reset}
1755
+ ${colors.dim}${t('settings.reportBug.instructions')}${colors.reset}
1756
1756
  `);
1757
-
1758
- try {
1759
- const { exec } = require('child_process');
1760
- const url = 'https://github.com/vladnoskv/i18ntk/issues';
1761
-
1762
- // Try to open the URL in the default browser
1763
- let command;
1764
- switch (process.platform) {
1765
- case 'darwin': // macOS
1766
- command = `open "${url}"`;
1767
- break;
1768
- case 'win32': // Windows
1769
- command = `start "" "${url}"`;
1770
- break;
1771
- default: // Linux and others
1772
- command = `xdg-open "${url}"`;
1773
- break;
1774
- }
1775
-
1776
- exec(command, (error) => {
1777
- if (error) {
1778
- console.log(`${colors.yellow}${t('settings.reportBug.browserOpenFailed')}${colors.reset}`);
1779
- console.log(`${t('settings.reportBug.manualVisit', { url: url })}`);
1780
- } else {
1781
- console.log(`${colors.green}${t('settings.reportBug.browserOpened')}${colors.reset}`);
1782
- }
1783
- });
1784
- } catch (error) {
1785
- console.log(`${colors.yellow}${t('settings.reportBug.browserOpenFailed')}${colors.reset}`);
1786
- console.log(`${t('settings.reportBug.manualVisit', { url: 'https://github.com/vladnoskv/i18ntk/issues' })}`);
1787
- }
1788
1757
 
1758
+ // Generic bug reporting guidance without external URLs
1759
+ console.log(`${colors.cyan}💡 ${t('settings.reportBug.guidance')}${colors.reset}`);
1760
+ console.log(`${colors.dim}• ${t('settings.reportBug.checkLogs')}${colors.reset}`);
1761
+ console.log(`${colors.dim}• ${t('settings.reportBug.documentIssue')}${colors.reset}`);
1762
+ console.log(`${colors.dim}• ${t('settings.reportBug.contactSupport')}${colors.reset}`);
1763
+
1764
+ console.log(`\n${colors.green}${t('settings.reportBug.completed')}${colors.reset}`);
1789
1765
  await this.pause();
1790
1766
  }
1791
1767
 
@@ -1260,6 +1260,16 @@
1260
1260
  "relativePathHint": "Verwende relative Pfade ab diesem Verzeichnis (z. B. ./locales)",
1261
1261
  "currentDirectory": "Aktuelles Projektverzeichnis",
1262
1262
  "pin": "PIN:",
1263
+ "reportBug": {
1264
+ "title": "Fehler melden",
1265
+ "description": "Melden Sie alle auftretenden Probleme oder Fehler",
1266
+ "guidance": "Um einen Fehler zu melden, folgen Sie bitte diesen Schritten:",
1267
+ "checkLogs": "1. Überprüfen Sie die letzten Debug-Logs auf Fehler",
1268
+ "documentIssue": "2. Dokumentieren Sie das Problem mit Reproduktionsschritten",
1269
+ "contactSupport": "3. Kontaktieren Sie den Support oder nutzen Sie den geeigneten Berichtskanal",
1270
+ "completed": "✅ Fehlerbericht-Prozess abgeschlossen",
1271
+ "instructions": "Folgen Sie den obigen Anweisungen, um den Fehler zu melden."
1272
+ },
1263
1273
  "mainMenu": {
1264
1274
  "title": "Hauptmenü:",
1265
1275
  "uiSettings": "UI-Einstellungen",
@@ -1260,6 +1260,16 @@
1260
1260
  "relativePathHint": "Use relative paths from this directory (e.g., ./src/i18n/locales)",
1261
1261
  "currentDirectory": "Current project directory",
1262
1262
  "pin": "PIN:",
1263
+ "reportBug": {
1264
+ "title": "Report Bug",
1265
+ "description": "Report any issues or bugs you encounter",
1266
+ "guidance": "To report issues effectively:",
1267
+ "checkLogs": "1. Check debug logs in your project directory",
1268
+ "documentIssue": "2. Document the issue with steps to reproduce",
1269
+ "contactSupport": "3. Contact your system administrator or support team",
1270
+ "completed": "Bug reporting guidance displayed",
1271
+ "instructions": "Follow the above instructions to report the bug."
1272
+ },
1263
1273
  "mainMenu": {
1264
1274
  "title": "Main Menu:",
1265
1275
  "uiSettings": "UI Settings",
@@ -1260,6 +1260,16 @@
1260
1260
  "relativePathHint": "Usa rutas relativas desde este directorio (por ejemplo, ./src/i18n/locales)",
1261
1261
  "currentDirectory": "Directorio actual del proyecto",
1262
1262
  "pin": "🔒 PIN:",
1263
+ "reportBug": {
1264
+ "title": "Reportar error",
1265
+ "description": "Reportar cualquier problema o error que encuentres",
1266
+ "guidance": "Para reportar problemas de forma efectiva:",
1267
+ "checkLogs": "1. Revisa los registros de depuración en tu directorio de proyecto",
1268
+ "documentIssue": "2. Documenta el problema con los pasos para reproducirlo",
1269
+ "contactSupport": "3. Contacta con tu administrador del sistema o equipo de soporte",
1270
+ "completed": "Guía de reporte de errores mostrada",
1271
+ "instructions": "Sigue las instrucciones para reportar el error."
1272
+ },
1263
1273
  "mainMenu": {
1264
1274
  "title": "Menú principal:",
1265
1275
  "uiSettings": "Configuración de la interfaz",
@@ -1279,7 +1289,7 @@
1279
1289
  "resetToDefaults": "Restablecer a valores predeterminados",
1280
1290
  "resetToDefaultsDesc": "Restaurar la configuración de fábrica",
1281
1291
  "reportBug": "Reportar error",
1282
- "reportBugDesc": "Enviar un informe de error en GitHub",
1292
+ "reportBugDesc": "Enviar informe de error en GitHub",
1283
1293
  "saveChanges": "Guardar cambios",
1284
1294
  "saveChangesDesc": "Guardar la configuración actual en un archivo",
1285
1295
  "backupSettings": "Configuración de respaldo",
@@ -1257,6 +1257,16 @@
1257
1257
  "relativePathHint": "Utilisez des chemins relatifs depuis ce répertoire (ex. : ./src/i18n/locales)",
1258
1258
  "currentDirectory": "Répertoire de projet actuel",
1259
1259
  "pin": "PIN :",
1260
+ "reportBug": {
1261
+ "title": "Signaler un bug",
1262
+ "description": "Signaler tout problème ou bogue rencontré",
1263
+ "guidance": "Pour signaler un bogue, veuillez suivre ces étapes :",
1264
+ "checkLogs": "1. Vérifiez les journaux de débogage récents pour les erreurs",
1265
+ "documentIssue": "2. Documentez le problème avec des étapes de reproduction",
1266
+ "contactSupport": "3. Contactez le support ou utilisez le canal de rapport approprié",
1267
+ "completed": "✅ Processus de rapport de bogue terminé",
1268
+ "instructions": "Suivez les instructions ci-dessus pour signaler le bogue."
1269
+ },
1260
1270
  "mainMenu": {
1261
1271
  "title": "Menu principal :",
1262
1272
  "uiSettings": "Paramètres UI",
@@ -1276,7 +1286,7 @@
1276
1286
  "resetToDefaults": "Réinitialiser aux valeurs par défaut",
1277
1287
  "resetToDefaultsDesc": "Restaurer les paramètres d’usine",
1278
1288
  "reportBug": "Signaler un bug",
1279
- "reportBugDesc": "Soumettre un rapport d’incident sur GitHub",
1289
+ "reportBugDesc": "Signaler tout problème ou bogue rencontré",
1280
1290
  "backupSettings": "Paramètres de sauvegarde",
1281
1291
  "backupSettingsDesc": "Configurer les paramètres de sauvegarde automatique pour vos fichiers de traduction",
1282
1292
  "saveChanges": "Enregistrer les modifications",
@@ -1260,6 +1260,16 @@
1260
1260
  "relativePathHint": "このディレクトリからの相対パスを使用 (例: ./src/i18n/locales)",
1261
1261
  "currentDirectory": "現在のプロジェクトディレクトリ",
1262
1262
  "pin": "PIN:",
1263
+ "reportBug": {
1264
+ "title": "バグを報告",
1265
+ "description": "発生した問題やバグを報告する",
1266
+ "guidance": "バグを報告するには、以下の手順に従ってください:",
1267
+ "checkLogs": "1. 最近のデバッグログでエラーを確認する",
1268
+ "documentIssue": "2. 再現手順と共に問題を文書化する",
1269
+ "contactSupport": "3. サポートまたは適切な報告チャンネルに連絡する",
1270
+ "completed": "✅ バグ報告プロセスが完了しました",
1271
+ "instructions": "上記の手順に従ってバグを報告してください。"
1272
+ },
1263
1273
  "mainMenu": {
1264
1274
  "title": "メインメニュー:",
1265
1275
  "uiSettings": "UI設定",
@@ -1279,7 +1289,7 @@
1279
1289
  "resetToDefaults": "デフォルトにリセット",
1280
1290
  "resetToDefaultsDesc": "工場出荷時設定に戻す",
1281
1291
  "reportBug": "バグを報告",
1282
- "reportBugDesc": "GitHubで問題を報告",
1292
+ "reportBugDesc": "発生した問題やバグを報告する",
1283
1293
  "saveChanges": "変更を保存",
1284
1294
  "saveChangesDesc": "現在の設定をファイルに保存",
1285
1295
  "help": "ヘルプ",
@@ -1260,6 +1260,16 @@
1260
1260
  "relativePathHint": "Используйте относительные пути от этой директории (например, ./src/i18n/locales)",
1261
1261
  "currentDirectory": "Текущая директория проекта",
1262
1262
  "pin": "PIN:",
1263
+ "reportBug": {
1264
+ "title": "Сообщить об ошибке",
1265
+ "description": "Сообщите о любых проблемах или ошибках, которые вы обнаружили",
1266
+ "guidance": "Чтобы сообщить об ошибке, выполните следующие шаги:",
1267
+ "checkLogs": "1. Проверьте последние журналы отладки на наличие ошибок",
1268
+ "documentIssue": "2. Задокументируйте проблему с шагами воспроизведения",
1269
+ "contactSupport": "3. Свяжитесь со службой поддержки или используйте соответствующий канал отчетности",
1270
+ "completed": "✅ Процесс отчета об ошибке завершен",
1271
+ "instructions": "Следуйте инструкциям выше, чтобы сообщить об ошибке."
1272
+ },
1263
1273
  "mainMenu": {
1264
1274
  "title": "Главное меню:",
1265
1275
  "uiSettings": "Настройки интерфейса",
@@ -1260,6 +1260,16 @@
1260
1260
  "relativePathHint": "请使用相对路径(例如:./src/i18n/locales)",
1261
1261
  "currentDirectory": "当前项目目录",
1262
1262
  "pin": "PIN:",
1263
+ "reportBug": {
1264
+ "title": "报告错误",
1265
+ "description": "报告您在使用 i18n 工具包时遇到的任何问题",
1266
+ "guidance": "为帮助我们有效地解决问题,请提供:",
1267
+ "checkLogs": "• 检查最近的调试日志",
1268
+ "documentIssue": "• 记录问题详情和重现步骤",
1269
+ "contactSupport": "• 联系支持并提供相关信息",
1270
+ "completed": "✅ 错误报告流程已启动。请按照提供的指导操作。",
1271
+ "instructions": "按照上述步骤报告错误。"
1272
+ },
1263
1273
  "mainMenu": {
1264
1274
  "title": "主菜单:",
1265
1275
  "uiSettings": "界面设置",
@@ -1,289 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * i18n Toolkit - Automated Workflow Runner (1.6.3-ready)
5
- * Executes predefined workflow steps for i18n management.
6
- * - Deterministic translation loading
7
- * - Safe config precedence (defaults < constructor < unified/CLI)
8
- * - Windows-safe child process execution via spawnSync
9
- * - Optional step filtering via --steps=analyze,validate
10
- * - Uses equals-style args (e.g., --output-dir=path) expected by sub-scripts
11
- */
12
-
13
- const fs = require('fs');
14
- const path = require('path');
15
- const { loadTranslations, t } = require('../utils/i18n-helper');
16
- loadTranslations(process.env.I18NTK_LANG);
17
- const { getUnifiedConfig, parseCommonArgs, displayHelp, ensureInitialized } = require('../utils/config-helper');
18
- const SecurityUtils = require('../utils/security');
19
- const configManager = require('../utils/config-manager');
20
-
21
- // Default location for UI locale bundles (override via config.uiLocalesDir)
22
- const UI_LOCALES_DIR = path.resolve(__dirname, '..', 'ui-locales');
23
-
24
- class AutoRunner {
25
- constructor(config = {}) {
26
- this.CONFIG_FILE = configManager.CONFIG_PATH;
27
- this.DEFAULT_CONFIG = {
28
- steps: [
29
- { name: 'autorun.stepInitializeProject', script: 'i18ntk-init.js', description: 'autorun.stepInitializeProject' },
30
- { name: 'autorun.stepAnalyzeTranslations', script: 'i18ntk-analyze.js', description: 'autorun.stepAnalyzeTranslations' },
31
- { name: 'autorun.stepValidateTranslations', script: 'i18ntk-validate.js', description: 'autorun.stepValidateTranslations' },
32
- { name: 'autorun.stepCheckUsage', script: 'i18ntk-usage.js', description: 'autorun.stepCheckUsage' },
33
- { name: 'autorun.stepGenerateSummary', script: 'i18ntk-summary.js', description: 'autorun.stepGenerateSummary' }
34
- ]
35
- };
36
- // Ensure config is always initialized
37
- this.config = { ...this.DEFAULT_CONFIG, ...(config || {}) };
38
- }
39
-
40
- /** Initialize config and translations BEFORE any output that calls t() */
41
- async init(args = {}) {
42
- const unified = await getUnifiedConfig('autorun', args);
43
- // Precedence: defaults < constructor-provided < unified/CLI
44
- this.config = { ...this.DEFAULT_CONFIG, ...this.config, ...unified };
45
-
46
- // Support optional steps filter from CLI: --steps=analyze,validate
47
- if (args && typeof args.steps === 'string') {
48
- this.config.stepsFilter = args.steps
49
- .split(',')
50
- .map(s => s.trim())
51
- .filter(Boolean);
52
- }
53
-
54
- // Always use bundled UI locales directory
55
- if (!fs.existsSync(UI_LOCALES_DIR)) {
56
- console.warn(`[i18ntk] UI locales directory not found at: ${UI_LOCALES_DIR}`);
57
- }
58
-
59
- const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
60
- try {
61
- loadTranslations(uiLanguage);
62
- } catch (e2) {
63
- console.error('Error loading translations:', e2.message);
64
- }
65
- }
66
-
67
- loadConfig() {
68
- try {
69
- return configManager.getConfig();
70
- } catch (error) {
71
- console.error(this.t('autorun.configReadError', { file: this.CONFIG_FILE }) || `Failed to read config file: ${this.CONFIG_FILE}`, error.message);
72
- return this.DEFAULT_CONFIG;
73
- }
74
- }
75
-
76
- t(key, params = {}) {
77
- try { return t(key, params) || String(key); } catch { return String(key); }
78
- }
79
-
80
- displayHelp() {
81
- console.log(`\n${this.t('autorun.autoRunScriptTitle')}`);
82
- console.log(this.t('autorun.separator'));
83
- console.log(`\n${this.t('autorun.usageTitle')}:`);
84
- console.log(` ${this.t('autorun.runAllSteps')}`);
85
- console.log(` ${this.t('autorun.configureSettingsFirst')}`);
86
- console.log(` ${this.t('autorun.runSpecificSteps')}`);
87
- console.log(` ${this.t('autorun.showHelp')}`);
88
- console.log(`\n${this.t('autorun.examplesTitle')}:`);
89
- console.log(` ${this.t('autorun.configExample')}`);
90
- console.log(` ${this.t('autorun.stepsExample1')}`);
91
- console.log(` ${this.t('autorun.stepsExample2')}`);
92
- console.log();
93
- }
94
-
95
- displayConfig() {
96
- console.log(`\n${this.t('autorun.customSettingsConfiguration')}`);
97
- console.log(this.t('autorun.separator'));
98
- const config = this.loadConfig();
99
- console.log(JSON.stringify(config, null, 2));
100
- console.log();
101
- }
102
-
103
- runStep(step, stepNumber, totalSteps, commonArgs = []) {
104
- const scriptPath = path.join(__dirname, step.script);
105
-
106
- if (!fs.existsSync(scriptPath)) {
107
- console.error(this.t('autorun.stepFailed', { stepName: this.t(step.description) }));
108
- console.error(this.t('autorun.errorLabel', { error: this.t('autorun.missingRequiredFile', { file: step.script }) }));
109
- return false;
110
- }
111
-
112
- console.log(this.t('autorun.stepRunning', { stepName: this.t(step.description), stepNumber, totalSteps }));
113
- console.log(this.t('autorun.separator'));
114
-
115
- try {
116
- // Build final argv. Use equals-style for value flags because sub-scripts expect it.
117
- const argv = ['--no-prompt', ...commonArgs];
118
-
119
- // Execute script directly as module (safe alternative to spawnSync)
120
- const success = this.executeScriptAsModule(scriptPath, argv);
121
-
122
- if (success) {
123
- console.log(this.t('autorun.stepCompletedWithIcon', { stepName: this.t(step.description) }));
124
- return true;
125
- }
126
- throw new Error('Script execution failed');
127
- } catch (error) {
128
- console.error(this.t('autorun.stepFailed', { stepName: this.t(step.description) }));
129
- console.error(this.t('autorun.errorLabel', { error: error.message }));
130
- return false;
131
- }
132
- }
133
-
134
- _buildCommonArgs() {
135
- const cfg = this.config;
136
- const args = [];
137
- if (cfg.sourceDir) {
138
- args.push(`--source-dir=${cfg.sourceDir}`);
139
- }
140
- if (cfg.i18nDir) {
141
- args.push(`--i18n-dir=${cfg.i18nDir}`);
142
- }
143
- if (cfg.outputDir) {
144
- args.push(`--output-dir=${cfg.outputDir}`);
145
- }
146
- if (cfg.uiLanguage) {
147
- args.push(`--ui-language=${cfg.uiLanguage}`);
148
- }
149
- return args;
150
- }
151
-
152
- _selectStepsForRun() {
153
- const all = this.config.steps || [];
154
- const filter = this.config.stepsFilter;
155
- if (!filter || !Array.isArray(filter) || filter.length === 0) return all;
156
-
157
- const matchers = new Set(filter.map(s => s.toLowerCase()));
158
- return all.filter(s => {
159
- const tail = (s.name.split('.').pop() || '').toLowerCase();
160
- return matchers.has(tail) || matchers.has(s.name.toLowerCase());
161
- });
162
- }
163
-
164
- /**
165
- * Execute script as module (safe alternative to spawnSync)
166
- */
167
- executeScriptAsModule(scriptPath, argv) {
168
- try {
169
- // Parse arguments to extract key-value pairs
170
- const args = {};
171
- for (const arg of argv) {
172
- if (arg.startsWith('--')) {
173
- const [key, value] = arg.substring(2).split('=');
174
- if (key && value !== undefined) {
175
- args[key] = value;
176
- } else if (key) {
177
- args[key] = true;
178
- }
179
- }
180
- }
181
-
182
- // Map script names to their module exports
183
- const scriptName = path.basename(scriptPath, '.js');
184
-
185
- // Create a safe execution environment
186
- const originalArgv = process.argv;
187
- const originalExit = process.exit;
188
-
189
- try {
190
- // Override process.argv for the script
191
- process.argv = ['node', scriptPath, ...argv];
192
-
193
- // Prevent actual exit
194
- process.exit = (code = 0) => {
195
- throw new Error(`Script attempted to exit with code ${code}`);
196
- };
197
-
198
- // Execute the script directly
199
- const scriptModule = require(scriptPath);
200
-
201
- // Check if it's a class or has a run method
202
- if (scriptModule && typeof scriptModule.run === 'function') {
203
- return scriptModule.run(args) !== false;
204
- } else if (typeof scriptModule === 'function') {
205
- return scriptModule(args) !== false;
206
- } else {
207
- // Execute the script's main function if it exists
208
- return true; // Assume success for basic scripts
209
- }
210
- } finally {
211
- // Restore original process methods
212
- process.argv = originalArgv;
213
- process.exit = originalExit;
214
-
215
- // Remove from require cache to allow re-execution
216
- delete require.cache[require.resolve(scriptPath)];
217
- }
218
- } catch (error) {
219
- console.error(`Error executing ${path.basename(scriptPath)}: ${error.message}`);
220
- return false;
221
- }
222
- }
223
-
224
- async runAll(quiet = false) {
225
- const initialized = await ensureInitialized(this.config);
226
- if (!initialized) return;
227
- const stepsToRun = this._selectStepsForRun();
228
-
229
- if (!quiet) {
230
- console.log(`\n${this.t('autorun.startingAutoRunWorkflow')}`);
231
- console.log(this.t('autorun.separator'));
232
- console.log(`${this.t('autorun.workflowIncludesSteps', { count: stepsToRun.length })}`);
233
- }
234
-
235
- const commonArgs = this._buildCommonArgs();
236
-
237
- let successCount = 0;
238
- for (let i = 0; i < stepsToRun.length; i++) {
239
- const step = stepsToRun[i];
240
- const stepNumber = i + 1;
241
-
242
- if (this.runStep(step, stepNumber, stepsToRun.length, commonArgs)) {
243
- successCount++;
244
- } else {
245
- if (!quiet) console.error(`\n${this.t('autorun.workflowStopped')}`);
246
- process.exit(1);
247
- }
248
- }
249
-
250
- if (!quiet) {
251
- console.log(`\n${this.t('autorun.workflowCompleted')}`);
252
- console.log(`${this.t('autorun.successfulSteps', { count: successCount })}`);
253
- console.log(`${this.t('autorun.failedSteps', { count: stepsToRun.length - successCount })}`);
254
- }
255
- process.exit(0);
256
- }
257
-
258
- async run() { await this.runAll(); }
259
-
260
- listSteps() {
261
- console.log(`\n${this.t('autorun.availableSteps')}`);
262
- console.log(this.t('autorun.separator'));
263
- (this.config.steps || []).forEach((step, index) => {
264
- console.log(`${index + 1}. ${this.t(step.description)} (${step.script})`);
265
- });
266
- console.log();
267
- }
268
- }
269
-
270
- if (require.main === module) {
271
- (async function main() {
272
- try {
273
- const args = parseCommonArgs(process.argv.slice(2));
274
- const runner = new AutoRunner();
275
- await runner.init(args); // Initialize translations + config FIRST
276
-
277
- if (args.help) { runner.displayHelp(); return; }
278
- if (args.config) { runner.displayConfig(); return; }
279
- if (args.list) { runner.listSteps(); return; }
280
-
281
- await runner.runAll();
282
- } catch (error) {
283
- console.error('Error:', error.message);
284
- process.exit(1);
285
- }
286
- })();
287
- }
288
-
289
- module.exports = AutoRunner;