i18ntk 4.2.2 ā 4.3.1
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/CHANGELOG.md +13 -1
- package/README.md +17 -8
- package/main/i18ntk-fixer.js +16 -11
- package/main/i18ntk-go.js +283 -0
- package/main/i18ntk-java.js +380 -0
- package/main/i18ntk-js.js +512 -0
- package/main/i18ntk-php.js +462 -0
- package/main/i18ntk-py.js +379 -0
- package/main/i18ntk-translate.js +29 -3
- package/main/manage/commands/CommandRouter.js +7 -4
- package/main/manage/commands/FixerCommand.js +144 -55
- package/main/manage/commands/TranslateCommand.js +36 -24
- package/package.json +16 -6
- package/utils/english-placeholder-checker.js +162 -0
- package/utils/mini-commander.js +179 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* i18ntk Python Command
|
|
5
|
+
* Specialized command for Python i18n management
|
|
6
|
+
*
|
|
7
|
+
* Usage: i18ntk-py [options]
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const SecurityUtils = require('../utils/security');
|
|
14
|
+
const { getConfig, saveConfig } = require('../utils/config-helper');
|
|
15
|
+
const I18nHelper = require('../utils/i18n-helper');
|
|
16
|
+
const SetupEnforcer = require('../utils/setup-enforcer');
|
|
17
|
+
const { program } = require('../utils/mini-commander');
|
|
18
|
+
|
|
19
|
+
(async () => {
|
|
20
|
+
try {
|
|
21
|
+
await SetupEnforcer.checkSetupCompleteAsync();
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error('Setup check failed:', error.message);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
})();
|
|
27
|
+
|
|
28
|
+
class I18ntkPythonCommand {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.config = null;
|
|
31
|
+
this.sourceDir = './locales';
|
|
32
|
+
this.pythonPatterns = [
|
|
33
|
+
/_(?:gettext)?\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/g,
|
|
34
|
+
/gettext\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/g,
|
|
35
|
+
/gettext_lazy\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/g,
|
|
36
|
+
/ngettext\s*\(\s*["'`]([^"'`]+)["'`]\s*,/g,
|
|
37
|
+
/lazy_gettext\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/g,
|
|
38
|
+
/ugettext\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/g
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async init() {
|
|
43
|
+
console.log('š§ Initializing i18ntk Python command...');
|
|
44
|
+
|
|
45
|
+
program
|
|
46
|
+
.name('i18ntk-py')
|
|
47
|
+
.description('i18ntk specialized for Python applications')
|
|
48
|
+
.version('1.10.1')
|
|
49
|
+
.option('-s, --source-dir <dir>', 'Source directory to scan', './')
|
|
50
|
+
.option('-l, --locales-dir <dir>', 'Locales directory', './locales')
|
|
51
|
+
.option('--framework <type>', 'Python framework type', 'auto')
|
|
52
|
+
.option('--dry-run', 'Show what would be done without making changes')
|
|
53
|
+
.option('--debug', 'Enable debug output')
|
|
54
|
+
.option('--extract-only', 'Only extract translations, don\'t analyze')
|
|
55
|
+
.option('--django', 'Force Django mode')
|
|
56
|
+
.option('--flask', 'Force Flask mode')
|
|
57
|
+
.option('--generic', 'Force generic Python mode')
|
|
58
|
+
.parse();
|
|
59
|
+
|
|
60
|
+
this.options = program.opts();
|
|
61
|
+
this.sourceDir = path.resolve(this.options.sourceDir);
|
|
62
|
+
this.localesDir = path.resolve(this.options.localesDir);
|
|
63
|
+
|
|
64
|
+
await this.validateSourceDir();
|
|
65
|
+
await this.loadConfig();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async validateSourceDir() {
|
|
69
|
+
if (!SecurityUtils.safeExistsSync(this.sourceDir, path.dirname(this.sourceDir))) {
|
|
70
|
+
console.error(`ā Source directory not found: ${this.sourceDir}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const stats = fs.statSync(this.sourceDir);
|
|
75
|
+
if (!stats.isDirectory()) {
|
|
76
|
+
console.error(`ā Source path is not a directory: ${this.sourceDir}`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async loadConfig() {
|
|
82
|
+
try {
|
|
83
|
+
this.config = await getConfig();
|
|
84
|
+
this.config.python = this.config.python || {};
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.warn('ā ļø Could not load config, using defaults');
|
|
87
|
+
this.config = { python: {} };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async detectFramework() {
|
|
92
|
+
if (this.options.django) return 'django';
|
|
93
|
+
if (this.options.flask) return 'flask';
|
|
94
|
+
if (this.options.generic) return 'generic';
|
|
95
|
+
|
|
96
|
+
console.log('š Detecting Python framework...');
|
|
97
|
+
|
|
98
|
+
// Check for Django
|
|
99
|
+
const djangoIndicators = [
|
|
100
|
+
'manage.py',
|
|
101
|
+
'settings.py',
|
|
102
|
+
'requirements.txt',
|
|
103
|
+
'django'
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
// Check for Flask
|
|
107
|
+
const flaskIndicators = [
|
|
108
|
+
'app.py',
|
|
109
|
+
'requirements.txt',
|
|
110
|
+
'flask'
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
let framework = 'generic';
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
// Check requirements.txt
|
|
117
|
+
const requirementsPath = path.join(this.sourceDir, 'requirements.txt');
|
|
118
|
+
if (SecurityUtils.safeExistsSync(requirementsPath, this.sourceDir)) {
|
|
119
|
+
const requirements = SecurityUtils.safeReadFileSync(requirementsPath, this.sourceDir, 'utf8');
|
|
120
|
+
if (requirements.includes('Django')) framework = 'django';
|
|
121
|
+
else if (requirements.includes('Flask')) framework = 'flask';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check for Django files
|
|
125
|
+
for (const indicator of djangoIndicators) {
|
|
126
|
+
if (this.findFiles(indicator).length > 0) {
|
|
127
|
+
framework = 'django';
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check for Flask files
|
|
133
|
+
for (const indicator of flaskIndicators) {
|
|
134
|
+
if (this.findFiles(indicator).length > 0) {
|
|
135
|
+
framework = 'flask';
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (this.options.debug) {
|
|
142
|
+
console.error('Debug: Framework detection error:', error.message);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
console.log(`ā
Detected framework: ${framework}`);
|
|
147
|
+
return framework;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
findFiles(pattern) {
|
|
151
|
+
const results = [];
|
|
152
|
+
|
|
153
|
+
function scanDir(dir) {
|
|
154
|
+
if (!SecurityUtils.safeExistsSync(dir, path.dirname(dir))) return;
|
|
155
|
+
|
|
156
|
+
const items = fs.readdirSync(dir);
|
|
157
|
+
for (const item of items) {
|
|
158
|
+
const fullPath = path.join(dir, item);
|
|
159
|
+
const stat = fs.statSync(fullPath);
|
|
160
|
+
|
|
161
|
+
if (stat.isDirectory() && !item.startsWith('.') && item !== 'node_modules') {
|
|
162
|
+
scanDir(fullPath);
|
|
163
|
+
} else if (item.includes(pattern) || fullPath.includes(pattern)) {
|
|
164
|
+
results.push(fullPath);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
scanDir(this.sourceDir);
|
|
170
|
+
return results;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async extractTranslations() {
|
|
174
|
+
console.log('š¦ Extracting Python translations...');
|
|
175
|
+
|
|
176
|
+
const pythonFiles = this.findFiles('.py');
|
|
177
|
+
const translations = new Set();
|
|
178
|
+
|
|
179
|
+
for (const file of pythonFiles) {
|
|
180
|
+
try {
|
|
181
|
+
const content = SecurityUtils.safeReadFileSync(file, path.dirname(file), 'utf8');
|
|
182
|
+
|
|
183
|
+
for (const pattern of this.pythonPatterns) {
|
|
184
|
+
let match;
|
|
185
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
186
|
+
translations.add(match[1]);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Reset regex state for next file
|
|
191
|
+
for (const pattern of this.pythonPatterns) {
|
|
192
|
+
pattern.lastIndex = 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
} catch (error) {
|
|
196
|
+
if (this.options.debug) {
|
|
197
|
+
console.error(`Error reading ${file}:`, error.message);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
console.log(`š Found ${translations.size} unique translation keys`);
|
|
203
|
+
return Array.from(translations);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async createLocaleStructure() {
|
|
207
|
+
if (!SecurityUtils.safeExistsSync(this.localesDir, path.dirname(this.localesDir))) {
|
|
208
|
+
if (this.options.dryRun) {
|
|
209
|
+
console.log(`š Would create directory: ${this.localesDir}`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
fs.mkdirSync(this.localesDir, { recursive: true });
|
|
214
|
+
console.log(`š Created locales directory: ${this.localesDir}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const languages = ['en', 'es', 'fr', 'de', 'ja', 'ru', 'zh'];
|
|
218
|
+
|
|
219
|
+
for (const lang of languages) {
|
|
220
|
+
const langDir = path.join(this.localesDir, lang);
|
|
221
|
+
if (!SecurityUtils.safeExistsSync(langDir, this.localesDir)) {
|
|
222
|
+
if (this.options.dryRun) {
|
|
223
|
+
console.log(`š Would create directory: ${langDir}`);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
fs.mkdirSync(langDir, { recursive: true });
|
|
228
|
+
|
|
229
|
+
// Create basic translation files
|
|
230
|
+
const commonFile = path.join(langDir, 'common.json');
|
|
231
|
+
const initialContent = {
|
|
232
|
+
"python": {
|
|
233
|
+
"welcome": `Welcome to Python (${lang})`,
|
|
234
|
+
"framework_detected": "Framework detected: {framework}",
|
|
235
|
+
"files_processed": "Processed {count} files"
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
SecurityUtils.safeWriteFileSync(commonFile, JSON.stringify(initialContent, null, 2), this.localesDir);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async analyzeFramework(framework) {
|
|
245
|
+
console.log(`š Analyzing ${framework} project...`);
|
|
246
|
+
|
|
247
|
+
const analysis = {
|
|
248
|
+
framework,
|
|
249
|
+
files: {
|
|
250
|
+
total: 0,
|
|
251
|
+
python: 0,
|
|
252
|
+
templates: 0,
|
|
253
|
+
config: 0
|
|
254
|
+
},
|
|
255
|
+
patterns: {
|
|
256
|
+
gettext: 0,
|
|
257
|
+
gettext_lazy: 0,
|
|
258
|
+
ngettext: 0,
|
|
259
|
+
django: 0,
|
|
260
|
+
flask: 0
|
|
261
|
+
},
|
|
262
|
+
recommendations: []
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// Count Python files
|
|
266
|
+
const pythonFiles = this.findFiles('.py');
|
|
267
|
+
analysis.files.python = pythonFiles.length;
|
|
268
|
+
analysis.files.total += pythonFiles.length;
|
|
269
|
+
|
|
270
|
+
// Count template files
|
|
271
|
+
const templateExtensions = ['.html', '.jinja', '.j2'];
|
|
272
|
+
let templateFiles = [];
|
|
273
|
+
for (const ext of templateExtensions) {
|
|
274
|
+
templateFiles = templateFiles.concat(this.findFiles(ext));
|
|
275
|
+
}
|
|
276
|
+
analysis.files.templates = templateFiles.length;
|
|
277
|
+
analysis.files.total += templateFiles.length;
|
|
278
|
+
|
|
279
|
+
// Analyze patterns
|
|
280
|
+
for (const file of pythonFiles) {
|
|
281
|
+
try {
|
|
282
|
+
const content = SecurityUtils.safeReadFileSync(file, path.dirname(file), 'utf8');
|
|
283
|
+
|
|
284
|
+
if (content.includes('gettext(')) analysis.patterns.gettext++;
|
|
285
|
+
if (content.includes('gettext_lazy(')) analysis.patterns.gettext_lazy++;
|
|
286
|
+
if (content.includes('ngettext(')) analysis.patterns.ngettext++;
|
|
287
|
+
if (content.includes('django')) analysis.patterns.django++;
|
|
288
|
+
if (content.includes('flask')) analysis.patterns.flask++;
|
|
289
|
+
|
|
290
|
+
} catch (error) {
|
|
291
|
+
// Skip unreadable files
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Generate recommendations
|
|
296
|
+
if (framework === 'django' && analysis.patterns.gettext === 0) {
|
|
297
|
+
analysis.recommendations.push('Consider adding Django gettext for i18n support');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (framework === 'flask' && analysis.patterns.gettext === 0) {
|
|
301
|
+
analysis.recommendations.push('Consider adding Flask-Babel for i18n support');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return analysis;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async generateReport(analysis, translations) {
|
|
308
|
+
const report = {
|
|
309
|
+
timestamp: new Date().toISOString(),
|
|
310
|
+
framework: analysis.framework,
|
|
311
|
+
summary: {
|
|
312
|
+
totalFiles: analysis.files.total,
|
|
313
|
+
pythonFiles: analysis.files.python,
|
|
314
|
+
templateFiles: analysis.files.templates,
|
|
315
|
+
translationKeys: translations.length
|
|
316
|
+
},
|
|
317
|
+
patterns: analysis.patterns,
|
|
318
|
+
recommendations: analysis.recommendations,
|
|
319
|
+
files: {
|
|
320
|
+
python: this.findFiles('.py'),
|
|
321
|
+
templates: this.findFiles('.html').concat(this.findFiles('.jinja')).concat(this.findFiles('.j2'))
|
|
322
|
+
},
|
|
323
|
+
translations: translations.sort()
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const reportPath = path.join(this.sourceDir, 'i18ntk-py-report.json');
|
|
327
|
+
|
|
328
|
+
if (this.options.dryRun) {
|
|
329
|
+
console.log(`š Would create report: ${reportPath}`);
|
|
330
|
+
console.log('š Report contents:', JSON.stringify(report, null, 2));
|
|
331
|
+
} else {
|
|
332
|
+
SecurityUtils.safeWriteFileSync(reportPath, JSON.stringify(report, null, 2), this.sourceDir);
|
|
333
|
+
console.log(`š Report saved: ${reportPath}`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return report;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async run() {
|
|
340
|
+
try {
|
|
341
|
+
console.log('š i18ntk Python Command v1.10.1');
|
|
342
|
+
console.log('='.repeat(50));
|
|
343
|
+
|
|
344
|
+
await this.init();
|
|
345
|
+
|
|
346
|
+
const framework = await this.detectFramework();
|
|
347
|
+
const translations = await this.extractTranslations();
|
|
348
|
+
|
|
349
|
+
if (!this.options.extractOnly) {
|
|
350
|
+
await this.createLocaleStructure();
|
|
351
|
+
const analysis = await this.analyzeFramework(framework);
|
|
352
|
+
const report = await this.generateReport(analysis, translations);
|
|
353
|
+
|
|
354
|
+
console.log('\nā
Analysis complete!');
|
|
355
|
+
console.log(`š Framework: ${framework}`);
|
|
356
|
+
console.log(`š Python files: ${analysis.files.python}`);
|
|
357
|
+
console.log(`šÆ Translation keys: ${translations.length}`);
|
|
358
|
+
console.log(`š Report: ${this.options.dryRun ? 'Not saved (dry-run)' : 'Saved to i18ntk-py-report.json'}`);
|
|
359
|
+
} else {
|
|
360
|
+
console.log(`š¦ Extracted ${translations.length} translation keys`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
} catch (error) {
|
|
364
|
+
console.error('ā Error:', error.message);
|
|
365
|
+
if (this.options.debug) {
|
|
366
|
+
console.error(error.stack);
|
|
367
|
+
}
|
|
368
|
+
process.exit(1);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Run if called directly
|
|
374
|
+
if (require.main === module) {
|
|
375
|
+
const cmd = new I18ntkPythonCommand();
|
|
376
|
+
cmd.run();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
module.exports = I18ntkPythonCommand;
|
package/main/i18ntk-translate.js
CHANGED
|
@@ -344,7 +344,7 @@ function normalizeLanguageCode(language) {
|
|
|
344
344
|
|
|
345
345
|
function parseLanguagePrefix(value) {
|
|
346
346
|
if (typeof value !== 'string') return null;
|
|
347
|
-
const match = value.match(/^\s*\[([A-Z]{2,3}(?:[-_][A-Z0-9]{2,4})?)\]\s*(.+)$/);
|
|
347
|
+
const match = value.match(/^\s*\[([A-Z]{2,3}(?:[-_][A-Z0-9]{2,4})?)\]\s*(.+)$/i);
|
|
348
348
|
if (!match) return null;
|
|
349
349
|
return {
|
|
350
350
|
raw: match[1],
|
|
@@ -368,6 +368,28 @@ function isLanguagePrefixedEnglish(value, args = {}) {
|
|
|
368
368
|
return hasLatinWordContent(prefix.text, 1) || isLikelyEnglish(prefix.text, args);
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
+
function isSafeUnchangedSourceCopy(value, args = {}) {
|
|
372
|
+
const text = String(value ?? '').trim();
|
|
373
|
+
if (!text) return false;
|
|
374
|
+
|
|
375
|
+
const normalized = text.toLowerCase();
|
|
376
|
+
const allowedTerms = new Set(['api']);
|
|
377
|
+
if (Array.isArray(args.allowedEnglishTerms)) {
|
|
378
|
+
args.allowedEnglishTerms
|
|
379
|
+
.filter(term => typeof term === 'string' && term.trim())
|
|
380
|
+
.forEach(term => allowedTerms.add(term.trim().toLowerCase()));
|
|
381
|
+
}
|
|
382
|
+
if (allowedTerms.has(normalized)) return true;
|
|
383
|
+
|
|
384
|
+
const tokens = text.match(/[A-Za-z0-9]+/g) || [];
|
|
385
|
+
if (tokens.length === 0) return true;
|
|
386
|
+
|
|
387
|
+
return tokens.every((token) => {
|
|
388
|
+
if (!/[A-Za-z]/.test(token)) return true;
|
|
389
|
+
return token.length <= 8 && /^[A-Z0-9]{2,}$/.test(token);
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
371
393
|
function isBrokenTranslationValue(value) {
|
|
372
394
|
if (typeof value !== 'string') return false;
|
|
373
395
|
const text = value.trim();
|
|
@@ -399,7 +421,9 @@ function shouldTranslateTargetValue(sourceValue, targetValue, args) {
|
|
|
399
421
|
if (isUntranslatedMarker(targetValue)) return true;
|
|
400
422
|
if (isBrokenTranslationValue(targetValue)) return true;
|
|
401
423
|
if (isLanguagePrefixedEnglish(targetValue, args)) return true;
|
|
402
|
-
if (targetValue.trim() === String(sourceValue ?? '').trim())
|
|
424
|
+
if (targetValue.trim() === String(sourceValue ?? '').trim()) {
|
|
425
|
+
return !isSafeUnchangedSourceCopy(targetValue, args);
|
|
426
|
+
}
|
|
403
427
|
if (args.onlyMissingOrEnglish !== false && isLikelyEnglish(targetValue, args)) return true;
|
|
404
428
|
return args.onlyMissingOrEnglish === false;
|
|
405
429
|
}
|
|
@@ -410,7 +434,9 @@ function getResidualUntranslatedReason(sourceValue, targetValue, args) {
|
|
|
410
434
|
if (isUntranslatedMarker(targetValue)) return 'marker';
|
|
411
435
|
if (isBrokenTranslationValue(targetValue)) return 'broken';
|
|
412
436
|
if (isLanguagePrefixedEnglish(targetValue, args)) return 'language_prefix';
|
|
413
|
-
if (targetValue.trim() === String(sourceValue ?? '').trim())
|
|
437
|
+
if (targetValue.trim() === String(sourceValue ?? '').trim()) {
|
|
438
|
+
return isSafeUnchangedSourceCopy(targetValue, args) ? null : 'source_copy';
|
|
439
|
+
}
|
|
414
440
|
return null;
|
|
415
441
|
}
|
|
416
442
|
|
|
@@ -157,10 +157,13 @@ class CommandRouter {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
try {
|
|
160
|
-
// Route command to appropriate handler
|
|
161
|
-
const result = await this.routeCommand(command, options, executionContext);
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
// Route command to appropriate handler
|
|
161
|
+
const result = await this.routeCommand(command, options, executionContext);
|
|
162
|
+
if (result && result.success === false) {
|
|
163
|
+
throw new Error(result.error || result.message || `${command} command failed`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Handle command completion based on execution context
|
|
164
167
|
console.log('\n' + t('operations.completed'));
|
|
165
168
|
|
|
166
169
|
if (isManagerExecution && !this.isNonInteractiveMode && this.prompt) {
|