i18ntk 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -1
- package/README.md +1 -1
- package/package.json +4 -2
- package/settings/admin-config.json +7 -0
- package/settings/settings-cli.js +1021 -0
- package/settings/settings-manager.js +596 -0
- package/settings/user-config.json +97 -0
|
@@ -0,0 +1,1021 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings CLI Interface
|
|
3
|
+
* Interactive terminal-based settings management for i18n toolkit
|
|
4
|
+
* No external dependencies - uses Node.js built-in readline
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const readline = require('readline');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const settingsManager = require('./settings-manager');
|
|
11
|
+
const UIi18n = require('../main/ui-i18n');
|
|
12
|
+
const AdminPinManager = require('../utils/admin-pin');
|
|
13
|
+
const uiI18n = new UIi18n();
|
|
14
|
+
|
|
15
|
+
// ANSI color codes for terminal output
|
|
16
|
+
const colors = {
|
|
17
|
+
reset: '\x1b[0m',
|
|
18
|
+
bright: '\x1b[1m',
|
|
19
|
+
dim: '\x1b[2m',
|
|
20
|
+
red: '\x1b[31m',
|
|
21
|
+
green: '\x1b[32m',
|
|
22
|
+
yellow: '\x1b[33m',
|
|
23
|
+
blue: '\x1b[34m',
|
|
24
|
+
magenta: '\x1b[35m',
|
|
25
|
+
cyan: '\x1b[36m',
|
|
26
|
+
white: '\x1b[37m',
|
|
27
|
+
bgRed: '\x1b[41m',
|
|
28
|
+
bgGreen: '\x1b[42m',
|
|
29
|
+
bgYellow: '\x1b[43m'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
class SettingsCLI {
|
|
33
|
+
constructor() {
|
|
34
|
+
// Check if there's already an active readline interface
|
|
35
|
+
if (global.activeReadlineInterface) {
|
|
36
|
+
this.rl = global.activeReadlineInterface;
|
|
37
|
+
this.shouldCloseRL = false;
|
|
38
|
+
} else {
|
|
39
|
+
this.rl = readline.createInterface({
|
|
40
|
+
input: process.stdin,
|
|
41
|
+
output: process.stdout,
|
|
42
|
+
terminal: true,
|
|
43
|
+
historySize: 0
|
|
44
|
+
});
|
|
45
|
+
this.shouldCloseRL = true;
|
|
46
|
+
global.activeReadlineInterface = this.rl;
|
|
47
|
+
}
|
|
48
|
+
this.settings = null;
|
|
49
|
+
this.schema = null;
|
|
50
|
+
this.modified = false;
|
|
51
|
+
this.adminPin = new AdminPinManager();
|
|
52
|
+
this.adminAuthenticated = false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Initialize the CLI interface
|
|
57
|
+
*/
|
|
58
|
+
async init() {
|
|
59
|
+
try {
|
|
60
|
+
this.settings = settingsManager.getSettings();
|
|
61
|
+
this.schema = settingsManager.getSettingsSchema();
|
|
62
|
+
return true;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
this.error(`Failed to initialize settings: ${error.message}`);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Start the interactive settings interface
|
|
71
|
+
*/
|
|
72
|
+
async start() {
|
|
73
|
+
if (!(await this.init())) {
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.clearScreen();
|
|
78
|
+
this.showHeader();
|
|
79
|
+
await this.showMainMenu();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Run the settings interface (alias for start)
|
|
84
|
+
*/
|
|
85
|
+
async run() {
|
|
86
|
+
await this.start();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Clear the terminal screen
|
|
91
|
+
*/
|
|
92
|
+
clearScreen() {
|
|
93
|
+
process.stdout.write('\x1b[2J\x1b[0f');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Show the main header
|
|
98
|
+
*/
|
|
99
|
+
showHeader() {
|
|
100
|
+
const title = uiI18n.t('operations.settings.title') || 'Settings Management';
|
|
101
|
+
const separator = uiI18n.t('operations.settings.separator') || '============================================================';
|
|
102
|
+
|
|
103
|
+
console.log(`${colors.cyan}${colors.bright}`);
|
|
104
|
+
console.log(`${title}`);
|
|
105
|
+
console.log(separator);
|
|
106
|
+
console.log(colors.reset);
|
|
107
|
+
|
|
108
|
+
if (this.modified) {
|
|
109
|
+
console.log(`${colors.yellow}⚠️ You have unsaved changes${colors.reset}\n`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Show the main menu
|
|
115
|
+
*/
|
|
116
|
+
async showMainMenu() {
|
|
117
|
+
const options = [
|
|
118
|
+
{ key: '1', label: 'UI Settings', description: 'Language, theme, and interface options' },
|
|
119
|
+
{ key: '2', label: 'Directory Settings', description: 'Source and output directory paths' },
|
|
120
|
+
{ key: '3', label: 'Processing Settings', description: 'Batch size, concurrency, and performance' },
|
|
121
|
+
{ key: '4', label: 'Advanced Settings', description: 'Validation, logging, and expert options' },
|
|
122
|
+
{ key: '5', label: 'View All Settings', description: 'Display current configuration' },
|
|
123
|
+
{ key: '6', label: 'Import/Export', description: 'Backup and restore settings' },
|
|
124
|
+
{ key: '7', label: 'Reset to Defaults', description: 'Restore factory settings' },
|
|
125
|
+
{ key: '8', label: 'Report Bug', description: 'Submit an issue report on GitHub' },
|
|
126
|
+
{ key: 's', label: 'Save Changes', description: 'Save current settings to file' },
|
|
127
|
+
{ key: 'h', label: 'Help', description: 'Show detailed help information' },
|
|
128
|
+
{ key: 'q', label: 'Quit', description: 'Exit settings (with save prompt if needed)' }
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
console.log(`${colors.bright}Main Menu:${colors.reset}\n`);
|
|
132
|
+
|
|
133
|
+
options.forEach(option => {
|
|
134
|
+
const keyColor = option.key.match(/[0-9]/) ? colors.cyan : colors.yellow;
|
|
135
|
+
console.log(` ${keyColor}${option.key}${colors.reset}) ${colors.bright}${option.label}${colors.reset}`);
|
|
136
|
+
console.log(` ${colors.dim}${option.description}${colors.reset}`);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
console.log();
|
|
140
|
+
const choice = await this.prompt('Select an option: ');
|
|
141
|
+
await this.handleMainMenuChoice(choice.toLowerCase());
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Handle main menu choice
|
|
146
|
+
*/
|
|
147
|
+
async handleMainMenuChoice(choice) {
|
|
148
|
+
switch (choice) {
|
|
149
|
+
case '1':
|
|
150
|
+
await this.showUISettings();
|
|
151
|
+
break;
|
|
152
|
+
case '2':
|
|
153
|
+
await this.showDirectorySettings();
|
|
154
|
+
break;
|
|
155
|
+
case '3':
|
|
156
|
+
await this.showProcessingSettings();
|
|
157
|
+
break;
|
|
158
|
+
case '4':
|
|
159
|
+
await this.showAdvancedSettings();
|
|
160
|
+
break;
|
|
161
|
+
case '5':
|
|
162
|
+
await this.showAllSettings();
|
|
163
|
+
break;
|
|
164
|
+
case '6':
|
|
165
|
+
await this.showImportExport();
|
|
166
|
+
break;
|
|
167
|
+
case '7':
|
|
168
|
+
await this.resetToDefaults();
|
|
169
|
+
break;
|
|
170
|
+
case '8':
|
|
171
|
+
await this.reportBug();
|
|
172
|
+
break;
|
|
173
|
+
case 's':
|
|
174
|
+
await this.saveSettings();
|
|
175
|
+
break;
|
|
176
|
+
case 'h':
|
|
177
|
+
await this.showHelp();
|
|
178
|
+
break;
|
|
179
|
+
case 'q':
|
|
180
|
+
await this.quit();
|
|
181
|
+
return;
|
|
182
|
+
default:
|
|
183
|
+
this.error('Invalid option. Please try again.');
|
|
184
|
+
await this.pause();
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.clearScreen();
|
|
189
|
+
this.showHeader();
|
|
190
|
+
await this.showMainMenu();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Show UI settings menu
|
|
195
|
+
*/
|
|
196
|
+
async showUISettings() {
|
|
197
|
+
this.clearScreen();
|
|
198
|
+
this.showHeader();
|
|
199
|
+
console.log(`${colors.bright}UI Settings${colors.reset}\n`);
|
|
200
|
+
|
|
201
|
+
const uiSettings = {
|
|
202
|
+
'language': 'Interface Language', // Fix: use root language instead of ui.language
|
|
203
|
+
'theme': 'Color Theme',
|
|
204
|
+
'dateFormat': 'Date Format',
|
|
205
|
+
'notifications.enabled': 'Show Notifications'
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
await this.showSettingsCategory(uiSettings);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Show directory settings menu
|
|
213
|
+
*/
|
|
214
|
+
async showDirectorySettings() {
|
|
215
|
+
this.clearScreen();
|
|
216
|
+
this.showHeader();
|
|
217
|
+
console.log(`${colors.bright}Directory Settings${colors.reset}\n`);
|
|
218
|
+
|
|
219
|
+
const dirSettings = {
|
|
220
|
+
'directories.locales': 'Locales Directory',
|
|
221
|
+
'directories.reports': 'Reports Directory',
|
|
222
|
+
'directories.backup': 'Backup Directory'
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
await this.showSettingsCategory(dirSettings);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Show processing settings menu
|
|
230
|
+
*/
|
|
231
|
+
async showProcessingSettings() {
|
|
232
|
+
this.clearScreen();
|
|
233
|
+
this.showHeader();
|
|
234
|
+
console.log(`${colors.bright}Processing Settings${colors.reset}\n`);
|
|
235
|
+
|
|
236
|
+
const processSettings = {
|
|
237
|
+
'processing.batchSize': 'Batch Size',
|
|
238
|
+
'processing.concurrentFiles': 'Concurrent Files',
|
|
239
|
+
'processing.sizingThreshold': 'Sizing Threshold (KB)',
|
|
240
|
+
'processing.autoSave': 'Auto-save Interval (minutes)'
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
await this.showSettingsCategory(processSettings);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Show advanced settings menu
|
|
248
|
+
*/
|
|
249
|
+
async showAdvancedSettings() {
|
|
250
|
+
this.clearScreen();
|
|
251
|
+
this.showHeader();
|
|
252
|
+
console.log(`${colors.bright}Advanced Settings${colors.reset}\n`);
|
|
253
|
+
|
|
254
|
+
const advancedSettings = {
|
|
255
|
+
'advanced.strictMode': 'Strict Validation Mode',
|
|
256
|
+
'debug.enabled': 'Debug Mode',
|
|
257
|
+
'debug.verboseLogging': 'Verbose Logging',
|
|
258
|
+
'security.adminPinEnabled': 'Admin PIN Protection',
|
|
259
|
+
'security.sessionTimeout': 'Session Timeout (minutes)',
|
|
260
|
+
'advanced.backupBeforeChanges': 'Auto Backup',
|
|
261
|
+
'_setupPin': 'Setup/Change Admin PIN'
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
await this.showSettingsCategory(advancedSettings);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Show settings category with edit options
|
|
269
|
+
*/
|
|
270
|
+
async showSettingsCategory(categorySettings) {
|
|
271
|
+
const keys = Object.keys(categorySettings);
|
|
272
|
+
|
|
273
|
+
// Display current values
|
|
274
|
+
keys.forEach((key, index) => {
|
|
275
|
+
const value = this.getNestedValue(this.settings, key);
|
|
276
|
+
const displayValue = this.formatValue(value);
|
|
277
|
+
console.log(` ${colors.cyan}${index + 1}${colors.reset}) ${categorySettings[key]}`);
|
|
278
|
+
console.log(` ${colors.dim}Current: ${colors.reset}${displayValue}`);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
console.log(`\n ${colors.yellow}b${colors.reset}) Back to main menu`);
|
|
282
|
+
console.log();
|
|
283
|
+
|
|
284
|
+
const choice = await this.prompt('Select setting to edit (or b for back): ');
|
|
285
|
+
|
|
286
|
+
if (choice.toLowerCase() === 'b') {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const index = parseInt(choice) - 1;
|
|
291
|
+
if (index >= 0 && index < keys.length) {
|
|
292
|
+
const key = keys[index];
|
|
293
|
+
await this.editSetting(key, categorySettings[key]);
|
|
294
|
+
await this.showSettingsCategory(categorySettings);
|
|
295
|
+
} else {
|
|
296
|
+
this.error('Invalid option.');
|
|
297
|
+
await this.pause();
|
|
298
|
+
await this.showSettingsCategory(categorySettings);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Check if setting requires admin authentication
|
|
304
|
+
*/
|
|
305
|
+
requiresAdminAuth(key) {
|
|
306
|
+
const adminProtectedSettings = [
|
|
307
|
+
'security.adminPinEnabled',
|
|
308
|
+
'security.sessionTimeout',
|
|
309
|
+
'advanced.strictMode',
|
|
310
|
+
'debug.enabled',
|
|
311
|
+
'debug.verboseLogging',
|
|
312
|
+
'advanced.backupBeforeChanges'
|
|
313
|
+
];
|
|
314
|
+
return adminProtectedSettings.includes(key);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get helper text for a setting
|
|
319
|
+
*/
|
|
320
|
+
getHelperText(key) {
|
|
321
|
+
const helperTexts = {
|
|
322
|
+
'language': 'Select the interface language for the toolkit. Changes take effect immediately.',
|
|
323
|
+
'theme': 'Choose the color theme for the interface. Options: light, dark, auto.',
|
|
324
|
+
'dateFormat': 'Set the date format for reports and logs. Examples: YYYY-MM-DD, DD/MM/YYYY, MM-DD-YYYY.',
|
|
325
|
+
'notifications.enabled': 'Enable or disable system notifications. Enter: true or false.',
|
|
326
|
+
'directories.locales': 'Path to the directory containing translation files.',
|
|
327
|
+
'directories.reports': 'Path where analysis reports will be saved.',
|
|
328
|
+
'directories.backup': 'Path for backup files. Leave empty for default.',
|
|
329
|
+
'processing.batchSize': 'Number of files to process simultaneously. Range: 1-100.',
|
|
330
|
+
'processing.concurrentFiles': 'Maximum concurrent file operations. Range: 1-20.',
|
|
331
|
+
'processing.sizingThreshold': 'File size threshold in KB for warnings. Range: 1-10000.',
|
|
332
|
+
'processing.autoSave': 'Auto-save interval in minutes. 0 to disable. Range: 0-60.',
|
|
333
|
+
'advanced.strictMode': '🔒 Enable strict validation mode. Requires admin PIN.',
|
|
334
|
+
'debug.enabled': '🔒 Enable debug mode for troubleshooting. Requires admin PIN.',
|
|
335
|
+
'debug.verboseLogging': '🔒 Enable detailed logging. Requires admin PIN.',
|
|
336
|
+
'security.adminPinEnabled': '🔒 Enable admin PIN protection. Requires admin PIN.',
|
|
337
|
+
'security.sessionTimeout': '🔒 Session timeout in minutes. Requires admin PIN.',
|
|
338
|
+
'advanced.backupBeforeChanges': '🔒 Auto-backup before changes. Requires admin PIN.'
|
|
339
|
+
};
|
|
340
|
+
return helperTexts[key] || 'No additional information available.';
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Get valid options for a setting
|
|
345
|
+
*/
|
|
346
|
+
getValidOptions(key, schema) {
|
|
347
|
+
if (schema && schema.enum) {
|
|
348
|
+
return schema.enum;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const validOptions = {
|
|
352
|
+
'language': ['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'],
|
|
353
|
+
'theme': ['light', 'dark', 'auto'],
|
|
354
|
+
'dateFormat': ['YYYY-MM-DD', 'DD/MM/YYYY', 'MM-DD-YYYY'],
|
|
355
|
+
'notifications.enabled': ['true', 'false'],
|
|
356
|
+
'advanced.strictMode': ['true', 'false'],
|
|
357
|
+
'debug.enabled': ['true', 'false'],
|
|
358
|
+
'debug.verboseLogging': ['true', 'false'],
|
|
359
|
+
'security.adminPinEnabled': ['true', 'false'],
|
|
360
|
+
'advanced.backupBeforeChanges': ['true', 'false']
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
return validOptions[key] || null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Validate input value
|
|
368
|
+
*/
|
|
369
|
+
validateInput(value, key, schema) {
|
|
370
|
+
const validOptions = this.getValidOptions(key, schema);
|
|
371
|
+
|
|
372
|
+
if (validOptions) {
|
|
373
|
+
if (!validOptions.includes(value.toLowerCase())) {
|
|
374
|
+
return { valid: false, message: `Invalid option. Valid options: ${validOptions.join(', ')}` };
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Numeric validations
|
|
379
|
+
if (key.includes('batchSize')) {
|
|
380
|
+
const num = parseInt(value);
|
|
381
|
+
if (isNaN(num) || num < 1 || num > 100) {
|
|
382
|
+
return { valid: false, message: 'Batch size must be between 1 and 100.' };
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (key.includes('concurrentFiles')) {
|
|
387
|
+
const num = parseInt(value);
|
|
388
|
+
if (isNaN(num) || num < 1 || num > 20) {
|
|
389
|
+
return { valid: false, message: 'Concurrent files must be between 1 and 20.' };
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (key.includes('sizingThreshold')) {
|
|
394
|
+
const num = parseInt(value);
|
|
395
|
+
if (isNaN(num) || num < 1 || num > 10000) {
|
|
396
|
+
return { valid: false, message: 'Sizing threshold must be between 1 and 10000 KB.' };
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (key.includes('autoSave') || key.includes('sessionTimeout')) {
|
|
401
|
+
const num = parseInt(value);
|
|
402
|
+
if (isNaN(num) || num < 0 || num > 60) {
|
|
403
|
+
return { valid: false, message: 'Value must be between 0 and 60 minutes.' };
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return { valid: true };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Edit a specific setting
|
|
412
|
+
*/
|
|
413
|
+
async editSetting(key, label) {
|
|
414
|
+
// Special handling for PIN setup
|
|
415
|
+
if (key === '_setupPin') {
|
|
416
|
+
await this.handlePinSetup();
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Check if admin authentication is required
|
|
421
|
+
if (this.requiresAdminAuth(key) && !this.adminAuthenticated) {
|
|
422
|
+
console.log(`\n🔒 Admin authentication required for: ${label}`);
|
|
423
|
+
const authenticated = await this.adminPin.verifyPin();
|
|
424
|
+
if (!authenticated) {
|
|
425
|
+
console.log('❌ Access denied. Returning to menu.');
|
|
426
|
+
await this.pause();
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
this.adminAuthenticated = true;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const currentValue = this.getNestedValue(this.settings, key);
|
|
433
|
+
const schema = this.getSettingSchema(key);
|
|
434
|
+
|
|
435
|
+
console.log(`\n${colors.bright}Editing: ${label}${colors.reset}`);
|
|
436
|
+
|
|
437
|
+
// Show helper text
|
|
438
|
+
const helperText = this.getHelperText(key);
|
|
439
|
+
console.log(`${colors.dim}${helperText}${colors.reset}\n`);
|
|
440
|
+
|
|
441
|
+
// Show current value with special handling for admin PIN
|
|
442
|
+
if (key === 'security.adminPinEnabled') {
|
|
443
|
+
const pinDisplay = this.adminPin.getPinDisplay();
|
|
444
|
+
console.log(`Current value: ${this.formatValue(currentValue)} (PIN: ${pinDisplay})`);
|
|
445
|
+
} else {
|
|
446
|
+
console.log(`Current value: ${this.formatValue(currentValue)}`);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Show valid options
|
|
450
|
+
const validOptions = this.getValidOptions(key, schema);
|
|
451
|
+
if (validOptions) {
|
|
452
|
+
console.log(`\n${colors.cyan}Valid options:${colors.reset}`);
|
|
453
|
+
validOptions.forEach((option, index) => {
|
|
454
|
+
const marker = option.toLowerCase() === String(currentValue).toLowerCase() ? ' ← current' : '';
|
|
455
|
+
console.log(` ${index + 1}) ${option}${colors.dim}${marker}${colors.reset}`);
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
console.log();
|
|
460
|
+
const newValue = await this.prompt(`Enter new value (or press Enter to keep current): `);
|
|
461
|
+
|
|
462
|
+
if (newValue.trim() === '') {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Validate input
|
|
467
|
+
const validation = this.validateInput(newValue.trim(), key, schema);
|
|
468
|
+
if (!validation.valid) {
|
|
469
|
+
this.error(validation.message);
|
|
470
|
+
await this.pause();
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Convert the value
|
|
475
|
+
const convertedValue = this.convertValue(newValue.trim(), schema);
|
|
476
|
+
if (convertedValue === null) {
|
|
477
|
+
this.error('Invalid value format.');
|
|
478
|
+
await this.pause();
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Special handling for admin PIN setup
|
|
483
|
+
if (key === 'security.adminPinEnabled' && convertedValue === true && !this.adminPin.isPinSet()) {
|
|
484
|
+
console.log('\n🔐 Setting up admin PIN...');
|
|
485
|
+
const pinSetup = await this.adminPin.setupPin();
|
|
486
|
+
if (!pinSetup) {
|
|
487
|
+
console.log('❌ Failed to set up admin PIN. Setting not changed.');
|
|
488
|
+
await this.pause();
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Update the setting
|
|
494
|
+
this.setNestedValue(this.settings, key, convertedValue);
|
|
495
|
+
this.modified = true;
|
|
496
|
+
|
|
497
|
+
// Special handling for language changes
|
|
498
|
+
if (key === 'language') {
|
|
499
|
+
// Refresh UI language immediately
|
|
500
|
+
if (typeof uiI18n.refreshLanguageFromSettings === 'function') {
|
|
501
|
+
uiI18n.refreshLanguageFromSettings();
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
this.success(`${label} updated successfully.`);
|
|
506
|
+
await this.pause();
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Handle PIN setup/change
|
|
511
|
+
*/
|
|
512
|
+
async handlePinSetup() {
|
|
513
|
+
this.clearScreen();
|
|
514
|
+
this.showHeader();
|
|
515
|
+
console.log(`${colors.bright}Admin PIN Setup${colors.reset}\n`);
|
|
516
|
+
|
|
517
|
+
if (this.adminPin.isPinSet()) {
|
|
518
|
+
console.log('📌 Admin PIN is currently configured.');
|
|
519
|
+
console.log('\nOptions:');
|
|
520
|
+
console.log(' 1) Change existing PIN');
|
|
521
|
+
console.log(' 2) Remove PIN protection');
|
|
522
|
+
console.log(' 3) Cancel');
|
|
523
|
+
console.log();
|
|
524
|
+
|
|
525
|
+
const choice = await this.prompt('Select option: ');
|
|
526
|
+
|
|
527
|
+
switch (choice) {
|
|
528
|
+
case '1':
|
|
529
|
+
console.log('\n🔐 Verify current PIN first:');
|
|
530
|
+
const verified = await this.adminPin.verifyPin();
|
|
531
|
+
if (verified) {
|
|
532
|
+
console.log('\n🔄 Setting up new PIN...');
|
|
533
|
+
const success = await this.adminPin.setupPin();
|
|
534
|
+
if (success) {
|
|
535
|
+
this.success('Admin PIN updated successfully!');
|
|
536
|
+
} else {
|
|
537
|
+
this.error('Failed to update admin PIN.');
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
break;
|
|
541
|
+
case '2':
|
|
542
|
+
console.log('\n🔐 Verify current PIN to remove protection:');
|
|
543
|
+
const verifiedForRemoval = await this.adminPin.verifyPin();
|
|
544
|
+
if (verifiedForRemoval) {
|
|
545
|
+
// Remove PIN file
|
|
546
|
+
const fs = require('fs');
|
|
547
|
+
const pinFile = this.adminPin.pinFile;
|
|
548
|
+
if (fs.existsSync(pinFile)) {
|
|
549
|
+
fs.unlinkSync(pinFile);
|
|
550
|
+
this.success('Admin PIN protection removed.');
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
break;
|
|
554
|
+
case '3':
|
|
555
|
+
console.log('Operation cancelled.');
|
|
556
|
+
break;
|
|
557
|
+
default:
|
|
558
|
+
this.error('Invalid option.');
|
|
559
|
+
}
|
|
560
|
+
} else {
|
|
561
|
+
console.log('🔓 No admin PIN is currently configured.');
|
|
562
|
+
console.log('\nSetting up admin PIN will add security for:');
|
|
563
|
+
console.log(' • Changing security settings');
|
|
564
|
+
console.log(' • Modifying advanced configurations');
|
|
565
|
+
console.log(' • Accessing debug tools');
|
|
566
|
+
console.log(' • Resetting settings');
|
|
567
|
+
console.log();
|
|
568
|
+
|
|
569
|
+
const response = await this.prompt('Would you like to set up an admin PIN? (y/N): ');
|
|
570
|
+
|
|
571
|
+
if (response.toLowerCase() === 'y' || response.toLowerCase() === 'yes') {
|
|
572
|
+
const success = await this.adminPin.setupPin();
|
|
573
|
+
if (success) {
|
|
574
|
+
this.success('Admin PIN configured successfully!');
|
|
575
|
+
// Enable admin PIN protection in settings
|
|
576
|
+
this.setNestedValue(this.settings, 'security.adminPinEnabled', true);
|
|
577
|
+
this.modified = true;
|
|
578
|
+
} else {
|
|
579
|
+
this.error('Failed to configure admin PIN.');
|
|
580
|
+
}
|
|
581
|
+
} else {
|
|
582
|
+
console.log('⏭️ Admin PIN setup cancelled.');
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
await this.pause();
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Show all current settings
|
|
591
|
+
*/
|
|
592
|
+
async showAllSettings() {
|
|
593
|
+
this.clearScreen();
|
|
594
|
+
this.showHeader();
|
|
595
|
+
console.log(`${colors.bright}Current Settings${colors.reset}\n`);
|
|
596
|
+
|
|
597
|
+
this.displaySettingsTree(this.settings, '');
|
|
598
|
+
|
|
599
|
+
console.log(`\nPress Enter to continue...`);
|
|
600
|
+
await this.prompt('');
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Display settings in a tree format
|
|
605
|
+
*/
|
|
606
|
+
displaySettingsTree(obj, prefix) {
|
|
607
|
+
Object.keys(obj).forEach(key => {
|
|
608
|
+
const value = obj[key];
|
|
609
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
610
|
+
console.log(`${prefix}${colors.bright}${key}:${colors.reset}`);
|
|
611
|
+
this.displaySettingsTree(value, prefix + ' ');
|
|
612
|
+
} else {
|
|
613
|
+
console.log(`${prefix}${colors.cyan}${key}:${colors.reset} ${this.formatValue(value)}`);
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Show import/export options
|
|
620
|
+
*/
|
|
621
|
+
async showImportExport() {
|
|
622
|
+
this.clearScreen();
|
|
623
|
+
this.showHeader();
|
|
624
|
+
console.log(`${colors.bright}Import/Export Settings${colors.reset}\n`);
|
|
625
|
+
|
|
626
|
+
console.log(` ${colors.cyan}1${colors.reset}) Export current settings`);
|
|
627
|
+
console.log(` ${colors.cyan}2${colors.reset}) Import settings from file`);
|
|
628
|
+
console.log(` ${colors.cyan}3${colors.reset}) Create backup`);
|
|
629
|
+
console.log(` ${colors.yellow}b${colors.reset}) Back to main menu\n`);
|
|
630
|
+
|
|
631
|
+
const choice = await this.prompt('Select option: ');
|
|
632
|
+
|
|
633
|
+
switch (choice) {
|
|
634
|
+
case '1':
|
|
635
|
+
await this.exportSettings();
|
|
636
|
+
break;
|
|
637
|
+
case '2':
|
|
638
|
+
await this.importSettings();
|
|
639
|
+
break;
|
|
640
|
+
case '3':
|
|
641
|
+
await this.createBackup();
|
|
642
|
+
break;
|
|
643
|
+
case 'b':
|
|
644
|
+
return;
|
|
645
|
+
default:
|
|
646
|
+
this.error('Invalid option.');
|
|
647
|
+
await this.pause();
|
|
648
|
+
await this.showImportExport();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Export settings to a file
|
|
654
|
+
*/
|
|
655
|
+
async exportSettings() {
|
|
656
|
+
const filename = await this.prompt('Enter filename (or press Enter for default): ');
|
|
657
|
+
const exportFile = filename.trim() || `i18n-settings-${new Date().toISOString().split('T')[0]}.json`;
|
|
658
|
+
|
|
659
|
+
try {
|
|
660
|
+
fs.writeFileSync(exportFile, JSON.stringify(this.settings, null, 2));
|
|
661
|
+
this.success(`Settings exported to ${exportFile}`);
|
|
662
|
+
} catch (error) {
|
|
663
|
+
this.error(`Failed to export settings: ${error.message}`);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
await this.pause();
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Import settings from a file
|
|
671
|
+
*/
|
|
672
|
+
async importSettings() {
|
|
673
|
+
const filename = await this.prompt('Enter filename to import: ');
|
|
674
|
+
|
|
675
|
+
if (!filename.trim()) {
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
try {
|
|
680
|
+
if (!fs.existsSync(filename)) {
|
|
681
|
+
this.error('File not found.');
|
|
682
|
+
await this.pause();
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const importedSettings = JSON.parse(fs.readFileSync(filename, 'utf8'));
|
|
687
|
+
|
|
688
|
+
// Validate imported settings
|
|
689
|
+
if (!settingsManager.validateSettings(importedSettings)) {
|
|
690
|
+
this.error('Invalid settings file format.');
|
|
691
|
+
await this.pause();
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const confirm = await this.prompt('This will replace all current settings. Continue? (y/N): ');
|
|
696
|
+
if (confirm.toLowerCase() === 'y') {
|
|
697
|
+
this.settings = importedSettings;
|
|
698
|
+
this.modified = true;
|
|
699
|
+
this.success('Settings imported successfully.');
|
|
700
|
+
}
|
|
701
|
+
} catch (error) {
|
|
702
|
+
this.error(`Failed to import settings: ${error.message}`);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
await this.pause();
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Create a backup of current settings
|
|
710
|
+
*/
|
|
711
|
+
async createBackup() {
|
|
712
|
+
try {
|
|
713
|
+
const backupFile = settingsManager.createBackup();
|
|
714
|
+
this.success(`Backup created: ${backupFile}`);
|
|
715
|
+
} catch (error) {
|
|
716
|
+
this.error(`Failed to create backup: ${error.message}`);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
await this.pause();
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Reset settings to defaults
|
|
724
|
+
*/
|
|
725
|
+
async resetToDefaults() {
|
|
726
|
+
this.clearScreen();
|
|
727
|
+
this.showHeader();
|
|
728
|
+
console.log(`${colors.bright}Reset to Defaults${colors.reset}\n`);
|
|
729
|
+
|
|
730
|
+
console.log(`${colors.yellow}⚠️ This will reset ALL settings to their default values.${colors.reset}`);
|
|
731
|
+
console.log(`${colors.yellow}⚠️ Any unsaved changes will be lost.${colors.reset}\n`);
|
|
732
|
+
|
|
733
|
+
const confirm = await this.prompt('Are you sure you want to continue? (y/N): ');
|
|
734
|
+
|
|
735
|
+
if (confirm.toLowerCase() === 'y') {
|
|
736
|
+
try {
|
|
737
|
+
settingsManager.resetToDefaults();
|
|
738
|
+
this.settings = settingsManager.getSettings();
|
|
739
|
+
this.modified = false;
|
|
740
|
+
this.success('Settings reset to defaults successfully.');
|
|
741
|
+
} catch (error) {
|
|
742
|
+
this.error(`Failed to reset settings: ${error.message}`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
await this.pause();
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Save current settings
|
|
751
|
+
*/
|
|
752
|
+
async saveSettings() {
|
|
753
|
+
try {
|
|
754
|
+
const success = settingsManager.saveSettings(this.settings);
|
|
755
|
+
if (success) {
|
|
756
|
+
this.modified = false;
|
|
757
|
+
this.success('Settings saved successfully.');
|
|
758
|
+
} else {
|
|
759
|
+
this.error('Failed to save settings.');
|
|
760
|
+
}
|
|
761
|
+
} catch (error) {
|
|
762
|
+
this.error(`Failed to save settings: ${error.message}`);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
await this.pause();
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Show help information
|
|
770
|
+
*/
|
|
771
|
+
async showHelp() {
|
|
772
|
+
this.clearScreen();
|
|
773
|
+
this.showHeader();
|
|
774
|
+
console.log(`${colors.bright}Help Information${colors.reset}\n`);
|
|
775
|
+
|
|
776
|
+
console.log(`${colors.cyan}Navigation:${colors.reset}`);
|
|
777
|
+
console.log(` • Use number keys to select menu options`);
|
|
778
|
+
console.log(` • Use 'b' to go back to previous menu`);
|
|
779
|
+
console.log(` • Use 'q' to quit (with save prompt if needed)`);
|
|
780
|
+
console.log(` • Use 's' to save changes at any time\n`);
|
|
781
|
+
|
|
782
|
+
console.log(`${colors.cyan}Settings Categories:${colors.reset}`);
|
|
783
|
+
console.log(` • UI Settings: Interface language, theme, and display options`);
|
|
784
|
+
console.log(` • Directory Settings: Paths for locales, reports, and backups`);
|
|
785
|
+
console.log(` • Processing Settings: Performance and batch processing options`);
|
|
786
|
+
console.log(` • Advanced Settings: Validation, logging, and expert features\n`);
|
|
787
|
+
|
|
788
|
+
console.log(`${colors.cyan}Environment Variables:${colors.reset}`);
|
|
789
|
+
console.log(` • I18N_CONFIG_FILE: Custom config file path`);
|
|
790
|
+
console.log(` • I18N_LOCALE_DIR: Override locales directory`);
|
|
791
|
+
console.log(` • I18N_REPORTS_DIR: Override reports directory\n`);
|
|
792
|
+
|
|
793
|
+
console.log(`${colors.cyan}Command Line Usage:${colors.reset}`);
|
|
794
|
+
console.log(` • i18ntk manage --command=settings`);
|
|
795
|
+
console.log(` • node settings-cli.js (direct access)`);
|
|
796
|
+
console.log(` • All scripts support --config flag for custom config files\n`);
|
|
797
|
+
|
|
798
|
+
console.log(`Press Enter to continue...`);
|
|
799
|
+
await this.prompt('');
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Report a bug - opens GitHub issues page
|
|
804
|
+
*/
|
|
805
|
+
async reportBug() {
|
|
806
|
+
this.clearScreen();
|
|
807
|
+
this.showHeader();
|
|
808
|
+
console.log(`${colors.bright}Report a Bug${colors.reset}\n`);
|
|
809
|
+
|
|
810
|
+
console.log(`${colors.cyan}GitHub Issues Page:${colors.reset}`);
|
|
811
|
+
console.log(`https://github.com/vladnoskv/i18n-management-toolkit-main/issues\n`);
|
|
812
|
+
|
|
813
|
+
console.log(`${colors.yellow}Before reporting a bug, please:${colors.reset}`);
|
|
814
|
+
console.log(` • Check if the issue already exists`);
|
|
815
|
+
console.log(` • Include steps to reproduce the problem`);
|
|
816
|
+
console.log(` • Provide error messages and logs`);
|
|
817
|
+
console.log(` • Mention your operating system and Node.js version\n`);
|
|
818
|
+
|
|
819
|
+
console.log(`${colors.cyan}Opening GitHub issues page...${colors.reset}`);
|
|
820
|
+
|
|
821
|
+
try {
|
|
822
|
+
const { exec } = require('child_process');
|
|
823
|
+
const url = 'https://github.com/vladnoskv/i18n-management-toolkit-main/issues';
|
|
824
|
+
|
|
825
|
+
// Try to open the URL in the default browser
|
|
826
|
+
let command;
|
|
827
|
+
switch (process.platform) {
|
|
828
|
+
case 'darwin': // macOS
|
|
829
|
+
command = `open "${url}"`;
|
|
830
|
+
break;
|
|
831
|
+
case 'win32': // Windows
|
|
832
|
+
command = `start "" "${url}"`;
|
|
833
|
+
break;
|
|
834
|
+
default: // Linux and others
|
|
835
|
+
command = `xdg-open "${url}"`;
|
|
836
|
+
break;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
exec(command, (error) => {
|
|
840
|
+
if (error) {
|
|
841
|
+
console.log(`${colors.yellow}Could not automatically open browser.${colors.reset}`);
|
|
842
|
+
console.log(`Please manually visit: ${url}`);
|
|
843
|
+
} else {
|
|
844
|
+
console.log(`${colors.green}✅ Browser opened successfully!${colors.reset}`);
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
} catch (error) {
|
|
848
|
+
console.log(`${colors.yellow}Could not automatically open browser.${colors.reset}`);
|
|
849
|
+
console.log(`Please manually visit: https://github.com/vladnoskv/i18n-management-toolkit-main/issues`);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
await this.pause();
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Quit the application
|
|
857
|
+
*/
|
|
858
|
+
async quit() {
|
|
859
|
+
if (this.modified) {
|
|
860
|
+
console.log(`\n${colors.yellow}⚠️ You have unsaved changes.${colors.reset}`);
|
|
861
|
+
const save = await this.prompt('Save before quitting? (Y/n): ');
|
|
862
|
+
|
|
863
|
+
if (save.toLowerCase() !== 'n') {
|
|
864
|
+
await this.saveSettings();
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
console.log(`\n${colors.green}Thank you for using the i18n settings manager!${colors.reset}`);
|
|
869
|
+
this.rl.close();
|
|
870
|
+
process.exit(0);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Utility methods
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* Prompt user for input
|
|
877
|
+
*/
|
|
878
|
+
prompt(question) {
|
|
879
|
+
return new Promise(resolve => {
|
|
880
|
+
this.rl.question(question, resolve);
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* Pause and wait for user input
|
|
886
|
+
*/
|
|
887
|
+
async pause() {
|
|
888
|
+
await this.prompt('Press Enter to continue...');
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Display success message
|
|
893
|
+
*/
|
|
894
|
+
success(message) {
|
|
895
|
+
console.log(`${colors.green}✅ ${message}${colors.reset}`);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Display error message
|
|
900
|
+
*/
|
|
901
|
+
error(message) {
|
|
902
|
+
console.log(`${colors.red}❌ ${message}${colors.reset}`);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Display warning message
|
|
907
|
+
*/
|
|
908
|
+
warning(message) {
|
|
909
|
+
console.log(`${colors.yellow}⚠️ ${message}${colors.reset}`);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Get nested value from object using dot notation
|
|
914
|
+
*/
|
|
915
|
+
getNestedValue(obj, path) {
|
|
916
|
+
return path.split('.').reduce((current, key) => current && current[key], obj);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Set nested value in object using dot notation
|
|
921
|
+
*/
|
|
922
|
+
setNestedValue(obj, path, value) {
|
|
923
|
+
const keys = path.split('.');
|
|
924
|
+
const lastKey = keys.pop();
|
|
925
|
+
const target = keys.reduce((current, key) => {
|
|
926
|
+
if (!current[key] || typeof current[key] !== 'object') {
|
|
927
|
+
current[key] = {};
|
|
928
|
+
}
|
|
929
|
+
return current[key];
|
|
930
|
+
}, obj);
|
|
931
|
+
target[lastKey] = value;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* Get schema for a specific setting
|
|
936
|
+
*/
|
|
937
|
+
getSettingSchema(path) {
|
|
938
|
+
// This would need to be implemented based on your schema structure
|
|
939
|
+
return this.schema && this.schema.properties ?
|
|
940
|
+
this.getNestedValue(this.schema.properties, path) : null;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* Format value for display
|
|
945
|
+
*/
|
|
946
|
+
formatValue(value) {
|
|
947
|
+
if (value === null || value === undefined) {
|
|
948
|
+
return `${colors.dim}(not set)${colors.reset}`;
|
|
949
|
+
}
|
|
950
|
+
if (typeof value === 'boolean') {
|
|
951
|
+
return value ? `${colors.green}enabled${colors.reset}` : `${colors.red}disabled${colors.reset}`;
|
|
952
|
+
}
|
|
953
|
+
if (Array.isArray(value)) {
|
|
954
|
+
return `[${value.join(', ')}]`;
|
|
955
|
+
}
|
|
956
|
+
return String(value);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* Convert string input to appropriate type
|
|
961
|
+
*/
|
|
962
|
+
convertValue(input, schema) {
|
|
963
|
+
if (!schema) {
|
|
964
|
+
return input;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
switch (schema.type) {
|
|
968
|
+
case 'boolean':
|
|
969
|
+
const lower = input.toLowerCase();
|
|
970
|
+
if (['true', 'yes', 'y', '1', 'on', 'enabled'].includes(lower)) {
|
|
971
|
+
return true;
|
|
972
|
+
}
|
|
973
|
+
if (['false', 'no', 'n', '0', 'off', 'disabled'].includes(lower)) {
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
return null;
|
|
977
|
+
|
|
978
|
+
case 'number':
|
|
979
|
+
case 'integer':
|
|
980
|
+
const num = Number(input);
|
|
981
|
+
return isNaN(num) ? null : num;
|
|
982
|
+
|
|
983
|
+
case 'array':
|
|
984
|
+
try {
|
|
985
|
+
return input.split(',').map(s => s.trim());
|
|
986
|
+
} catch {
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
default:
|
|
991
|
+
return input;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Run method for compatibility with manager
|
|
997
|
+
*/
|
|
998
|
+
async run() {
|
|
999
|
+
try {
|
|
1000
|
+
console.log('🎛️ Starting Settings CLI...');
|
|
1001
|
+
await this.start();
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
console.error('❌ Settings CLI Error:', error.message);
|
|
1004
|
+
console.error('Stack trace:', error.stack);
|
|
1005
|
+
throw error;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// Export the class
|
|
1011
|
+
module.exports = SettingsCLI;
|
|
1012
|
+
|
|
1013
|
+
// If run directly, start the CLI
|
|
1014
|
+
if (require.main === module) {
|
|
1015
|
+
const cli = new SettingsCLI();
|
|
1016
|
+
cli.start().catch(error => {
|
|
1017
|
+
console.error('❌ Failed to start settings CLI:', error.message);
|
|
1018
|
+
console.error('Stack trace:', error.stack);
|
|
1019
|
+
process.exit(1);
|
|
1020
|
+
});
|
|
1021
|
+
}
|