UNPKG

i18ntk

Version:

i18ntk (i18n Toolkit) - A comprehensive, enterprise-grade internationalization (i18n) management toolkit for JavaScript/TypeScript projects with advanced analysis, validation, and automation features

1,223 lines (1,065 loc) 57.4 kB
/** * Settings CLI Interface * Interactive terminal-based settings management for i18n toolkit * No external dependencies - uses Node.js built-in readline */ const readline = require('readline'); const fs = require('fs'); const path = require('path'); const settingsManager = require('./settings-manager'); const UIi18n = require('../main/i18ntk-ui'); const AdminAuth = require('../utils/admin-auth'); const uiI18n = new UIi18n(); // ANSI color codes for terminal output const colors = { reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', white: '\x1b[37m', bgRed: '\x1b[41m', bgGreen: '\x1b[42m', bgYellow: '\x1b[43m' }; class SettingsCLI { constructor() { // Check if there's already an active readline interface if (global.activeReadlineInterface) { this.rl = global.activeReadlineInterface; this.shouldCloseRL = false; } else { this.rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true, historySize: 0 }); this.shouldCloseRL = true; global.activeReadlineInterface = this.rl; } this.settings = null; this.schema = null; this.modified = false; this.adminAuth = new AdminAuth(); this.settingsManager = settingsManager; this.adminAuthenticated = false; } /** * Translation helper function */ t(key, params = {}) { return uiI18n.t(key, params); } /** * Initialize the CLI interface */ async init() { try { this.settings = settingsManager.getSettings(); this.schema = settingsManager.getSettingsSchema(); return true; } catch (error) { this.error(this.t('settings.initFailed', { error: error.message })); return false; } } /** * Start the interactive settings interface */ async start() { if (!(await this.init())) { process.exit(1); } this.clearScreen(); this.showHeader(); await this.showMainMenu(); } /** * Run the settings interface (alias for start) */ async run() { await this.start(); } /** * Clear the terminal screen */ clearScreen() { process.stdout.write('\x1b[2J\x1b[0f'); } /** * Show the main header */ showHeader() { // Refresh language from settings to ensure consistency if (typeof uiI18n.refreshLanguageFromSettings === 'function') { uiI18n.refreshLanguageFromSettings(); } const title = uiI18n.t('operations.settings.title') || 'Settings Management'; const separator = uiI18n.t('operations.settings.separator') || '============================================================'; console.log(`${colors.cyan}${colors.bright}`); console.log(`${title}`); console.log(separator); console.log(colors.reset); if (this.modified) { console.log(`${colors.yellow}${this.t('settings.mainMenu.unsavedChangesWarning')}${colors.reset}\n`); } } /** * Show the main menu */ async showMainMenu() { // Check if admin PIN is configured for display purposes await this.adminAuth.initialize(); const config = await this.adminAuth.loadConfig(); const pinSet = config && config.enabled === true && !!config.pinHash; const pinStatus = pinSet ? `${colors.green}✅${colors.reset}` : `${colors.red}❌${colors.reset}`; const options = [ { key: '1', label: this.t('settings.mainMenu.uiSettings'), description: this.t('settings.mainMenu.uiSettingsDesc') }, { key: '2', label: this.t('settings.mainMenu.directorySettings'), description: this.t('settings.mainMenu.directorySettingsDesc') }, { key: '3', label: this.t('settings.mainMenu.scriptDirectorySettings'), description: this.t('settings.mainMenu.scriptDirectorySettingsDesc') }, { key: '4', label: this.t('settings.mainMenu.processingSettings'), description: this.t('settings.mainMenu.processingSettingsDesc') }, { key: '5', label: this.t('settings.mainMenu.securitySettings'), description: `${this.t('settings.mainMenu.securitySettingsDesc')} ${pinStatus}` }, { key: '6', label: this.t('settings.mainMenu.advancedSettings'), description: this.t('settings.mainMenu.advancedSettingsDesc') }, { key: '7', label: this.t('settings.mainMenu.viewAllSettings'), description: this.t('settings.mainMenu.viewAllSettingsDesc') }, { key: '8', label: this.t('settings.mainMenu.importExport'), description: this.t('settings.mainMenu.importExportDesc') }, { key: '9', label: this.t('settings.mainMenu.resetToDefaults'), description: this.t('settings.mainMenu.resetToDefaultsDesc') }, { key: '0', label: this.t('settings.mainMenu.reportBug'), description: this.t('settings.mainMenu.reportBugDesc') }, { key: 'u', label: this.t('settings.mainMenu.updatePackage'), description: this.t('settings.mainMenu.updatePackageDesc') }, { key: 's', label: this.t('settings.mainMenu.saveChanges'), description: this.t('settings.mainMenu.saveChangesDesc') }, { key: 'h', label: this.t('settings.mainMenu.help'), description: this.t('settings.mainMenu.helpDesc') }, { key: 'q', label: this.t('settings.mainMenu.quit'), description: this.t('settings.mainMenu.quitDesc') } ]; console.log(`${colors.bright}${this.t('settings.mainMenu.title')}${colors.reset}\n`); options.forEach(option => { const keyColor = option.key.match(/[0-9]/) ? colors.cyan : colors.yellow; console.log(` ${keyColor}${option.key}${colors.reset}) ${colors.bright}${option.label}${colors.reset}`); console.log(` ${colors.dim}${option.description}${colors.reset}`); }); console.log(); const choice = await this.prompt(this.t('settings.mainMenu.selectOption')); await this.handleMainMenuChoice(choice.toLowerCase()); } /** * Handle main menu choice */ async handleMainMenuChoice(choice) { switch (choice) { case '1': await this.showUISettings(); break; case '2': await this.showDirectorySettings(); break; case '3': await this.showScriptDirectorySettings(); break; case '4': await this.showProcessingSettings(); break; case '5': await this.showSecuritySettings(); break; case '6': await this.showAdvancedSettings(); break; case '7': await this.showAllSettings(); break; case '8': await this.showImportExport(); break; case '9': await this.resetToDefaults(); break; case '0': await this.reportBug(); break; case 'u': await this.updatePackage(); break; case 's': await this.saveSettings(); break; case 'h': await this.showHelp(); break; case 'q': await this.quit(); return; default: this.error('Invalid option. Please try again.'); await this.pause(); break; } this.clearScreen(); this.showHeader(); await this.showMainMenu(); } /** * Show UI settings menu */ async showUISettings() { // Refresh language from settings to ensure consistency if (typeof uiI18n.refreshLanguageFromSettings === 'function') { uiI18n.refreshLanguageFromSettings(); } this.clearScreen(); this.showHeader(); console.log(`${colors.bright}${this.t('settings.categories.uiSettings')}${colors.reset}\n`); const uiSettings = { 'language': this.t('settings.fields.language.label'), 'theme': this.t('settings.fields.theme.label'), 'dateFormat': this.t('settings.fields.dateFormat.label'), 'notifications.enabled': this.t('settings.fields.notifications.enabled.label') }; await this.showSettingsCategory(uiSettings); } /** * Show directory settings menu */ async showDirectorySettings() { // Refresh language from settings to ensure consistency if (typeof uiI18n.refreshLanguageFromSettings === 'function') { uiI18n.refreshLanguageFromSettings(); } this.clearScreen(); this.showHeader(); console.log(`${colors.bright}${this.t('settings.categories.directorySettings')}${colors.reset}\n`); const dirSettings = { 'sourceDir': this.t('settings.fields.sourceDir.label'), 'outputDir': this.t('settings.fields.outputDir.label') }; await this.showSettingsCategory(dirSettings); } /** * Show script-specific directory settings menu */ async showScriptDirectorySettings() { // Refresh language from settings to ensure consistency if (typeof uiI18n.refreshLanguageFromSettings === 'function') { uiI18n.refreshLanguageFromSettings(); } this.clearScreen(); this.showHeader(); console.log(`${colors.bright}${this.t('settings.categories.scriptDirectorySettings')}${colors.reset}\n`); console.log(`📁 ${colors.cyan}${this.t('settings.currentDirectory')}: ${process.cwd()}${colors.reset}`); console.log(`💡 ${colors.dim}${this.t('settings.relativePathHint')}${colors.reset}\n`); const scriptDirSettings = { 'scriptDirectories.analyze': this.t('settings.fields.scriptDirectories.analyzeLabel'), 'scriptDirectories.complete': this.t('settings.fields.scriptDirectories.completeLabel'), 'scriptDirectories.init': this.t('settings.fields.scriptDirectories.initLabel'), 'scriptDirectories.manage': this.t('settings.fields.scriptDirectories.manageLabel'), 'scriptDirectories.sizing': this.t('settings.fields.scriptDirectories.sizingLabel'), 'scriptDirectories.summary': this.t('settings.fields.scriptDirectories.summaryLabel'), 'scriptDirectories.usage': this.t('settings.fields.scriptDirectories.usageLabel'), 'scriptDirectories.validate': this.t('settings.fields.scriptDirectories.validateLabel') }; await this.showSettingsCategory(scriptDirSettings); } /** * Show processing settings menu */ async showProcessingSettings() { // Refresh language from settings to ensure consistency if (typeof uiI18n.refreshLanguageFromSettings === 'function') { uiI18n.refreshLanguageFromSettings(); } this.clearScreen(); this.showHeader(); console.log(`${colors.bright}${this.t('settings.categories.processingSettings')}${colors.reset}\n`); const processSettings = { 'advanced.batchSize': this.t('settings.fields.batchSize.label'), 'advanced.maxConcurrentFiles': this.t('settings.fields.maxConcurrentFiles.label'), 'advanced.sizingThreshold': this.t('settings.fields.sizingThreshold.label') }; await this.showSettingsCategory(processSettings); } /** * Show advanced settings menu */ async showSecuritySettings() { // Refresh language from settings to ensure consistency if (typeof uiI18n.refreshLanguageFromSettings === 'function') { uiI18n.refreshLanguageFromSettings(); } this.clearScreen(); this.showHeader(); console.log(`${colors.bright}${this.t('settings.security.title')}${colors.reset}\n`); const config = await this.adminAuth.loadConfig(); const pinSet = config && config.enabled === true && !!config.pinHash; const pinDisplay = pinSet ? '(set)' : '(not set)'; const pinStatus = pinSet ? `${colors.green}${this.t('settings.security.pinConfigured')}${colors.reset}` : `${colors.red}${this.t('settings.security.pinNotConfigured')}${colors.reset}`; console.log(`${this.t('settings.security.currentPin')}: ${pinStatus}`); console.log(`${this.t('settings.security.pinDisplay')}: ${colors.dim}${pinDisplay}${colors.reset}\n`); const securitySettings = { 'security.adminPinEnabled': this.t('settings.fields.adminPinEnabled.label'), '_setupPin': 'Setup/Change Admin PIN', 'security.sessionTimeout': 'Session Timeout (minutes)', 'security.maxFailedAttempts': 'Max Failed Attempts', 'security.lockoutDuration': 'Lockout Duration (minutes)', 'security.pinProtection.enabled': 'PIN Protection Master Switch', '_configurePinProtection': 'Configure PIN Protected Scripts' }; await this.showSettingsCategory(securitySettings); } async showAdvancedSettings() { // Refresh language from settings to ensure consistency if (typeof uiI18n.refreshLanguageFromSettings === 'function') { uiI18n.refreshLanguageFromSettings(); } this.clearScreen(); this.showHeader(); console.log(`${colors.bright}${this.t('settings.categories.advancedSettings')}${colors.reset}\n`); const advancedSettings = { 'advanced.strictMode': this.t('settings.fields.strictMode.label'), 'advanced.enableAuditLog': this.t('settings.fields.enableAuditLog.label'), 'advanced.backupBeforeChanges': this.t('settings.fields.backupBeforeChanges.label') }; await this.showSettingsCategory(advancedSettings); } /** * Show settings category with edit options */ async showSettingsCategory(categorySettings) { // Refresh language from settings to ensure consistency if (typeof uiI18n.refreshLanguageFromSettings === 'function') { uiI18n.refreshLanguageFromSettings(); } const keys = Object.keys(categorySettings); // Display current values for (let index = 0; index < keys.length; index++) { const key = keys[index]; let value = this.getNestedValue(this.settings, key); let displayValue = this.formatValue(value); // Special handling for _setupPin to show actual PIN status if (key === '_setupPin') { const config = await this.adminAuth.loadConfig(); const pinSet = config && config.enabled === true && !!config.pinHash; displayValue = pinSet ? '(set)' : '(not set)'; } console.log(` ${colors.cyan}${index + 1}${colors.reset}) ${categorySettings[key]}`); console.log(` ${colors.dim}${this.t('settings.current')}: ${colors.reset}${displayValue}`); } console.log(`\n ${colors.yellow}b${colors.reset}) ${this.t('settings.back')}`); // Add reset option for script directories const isScriptDirectory = Object.keys(categorySettings).some(key => key.startsWith('scriptDirectories.')); if (isScriptDirectory) { console.log(` ${colors.red}r${colors.reset}) ${this.t('settings.resetScriptDirectories')}`); } console.log(); const prompt = isScriptDirectory ? 'Select setting to edit (or b for back, r for reset): ' : 'Select setting to edit (or b for back): '; const choice = await this.prompt(prompt); if (choice.toLowerCase() === 'b') { return; } if (choice.toLowerCase() === 'r' && isScriptDirectory) { await this.resetScriptDirectories(); await this.showSettingsCategory(categorySettings); return; } const index = parseInt(choice) - 1; if (index >= 0 && index < keys.length) { const key = keys[index]; await this.editSetting(key, categorySettings[key]); await this.showSettingsCategory(categorySettings); } else { this.error(this.t('common.invalidOption')); await this.pause(); await this.showSettingsCategory(categorySettings); } } /** * Check if setting requires admin authentication */ requiresAdminAuth(key) { const adminProtectedSettings = [ 'security.adminPinEnabled', 'security.sessionTimeout', 'security.maxFailedAttempts', 'security.lockoutDuration', 'security.pinProtection.enabled', 'advanced.strictMode', 'debug.enabled', 'debug.verboseLogging', 'advanced.backupBeforeChanges' ]; return adminProtectedSettings.includes(key); } /** * Get helper text for a setting */ getHelperText(key) { const helperKey = key.replace(/\./g, '_'); return this.t(`settings.fields.${helperKey}.help`) || this.t('settings.noHelp'); } /** * Get valid options for a setting */ getValidOptions(key, schema) { if (schema && schema.enum) { return schema.enum; } const validOptions = { 'language': ['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'], 'theme': ['light', 'dark', 'auto'], 'dateFormat': ['YYYY-MM-DD', 'DD/MM/YYYY', 'MM-DD-YYYY'], 'notifications.enabled': ['true', 'false'], 'advanced.strictMode': ['true', 'false'], 'debug.enabled': ['true', 'false'], 'debug.verboseLogging': ['true', 'false'], 'security.adminPinEnabled': ['true', 'false'], 'security.pinProtection.enabled': ['true', 'false'], 'advanced.backupBeforeChanges': ['true', 'false'] }; return validOptions[key] || null; } /** * Validate input value */ validateInput(value, key, schema) { const validOptions = this.getValidOptions(key, schema); if (validOptions) { if (!validOptions.includes(value.toLowerCase())) { return { valid: false, message: `Invalid option. Valid options: ${validOptions.join(', ')}` }; } } // Numeric validations with proper ranges const validations = { 'batchSize': { min: 1, max: 100, type: 'int' }, 'concurrentFiles': { min: 1, max: 20, type: 'int' }, 'sizingThreshold': { min: 1, max: 10000, type: 'int', unit: 'KB' }, 'autoSave': { min: 0, max: 60, type: 'int', unit: 'minutes' }, 'sessionTimeout': { min: 0, max: 60, type: 'int', unit: 'minutes' }, 'maxFailedAttempts': { min: 1, max: 10, type: 'int' }, 'lockoutDuration': { min: 1, max: 60, type: 'int', unit: 'minutes' }, 'backupRetention': { min: 1, max: 30, type: 'int', unit: 'days' }, 'logRetention': { min: 1, max: 90, type: 'int', unit: 'days' } }; for (const [field, rules] of Object.entries(validations)) { if (key.includes(field)) { const num = parseInt(value); if (isNaN(num) || num < rules.min || num > rules.max) { const unit = rules.unit ? ` ${rules.unit}` : ''; return { valid: false, message: `${field} must be between ${rules.min} and ${rules.max}${unit}.` }; } } } // String validations if (key.includes('path') || key.includes('directory')) { if (value.includes('..') || value.includes('\\') || value.includes('//')) { return { valid: false, message: 'Invalid path format. Use forward slashes and avoid relative paths.' }; } } return { valid: true }; } /** * Edit a specific setting */ async editSetting(key, label) { // Special handling for PIN setup if (key === '_setupPin') { await this.handlePinSetup(); return; } // Special handling for PIN protection configuration if (key === '_configurePinProtection') { await this.configurePinProtection(); return; } // Check if admin authentication is required if (this.requiresAdminAuth(key) && !this.adminAuthenticated) { // Check if admin PIN is actually enabled and configured const adminPinEnabled = this.settingsManager.isAdminPinEnabled(); const config = await this.adminAuth.loadConfig(); const pinConfigured = adminPinEnabled && config && config.enabled === true && !!config.pinHash; if (pinConfigured) { console.log(`\n${this.t('settings.admin.authRequired', { label: label })}`); const pin = await this.promptPin(this.t('adminCli.enterPin')); if (!pin) { console.log(this.t('settings.admin.accessDenied')); await this.pause(); return; } const authenticated = await this.adminAuth.verifyPin(pin); if (!authenticated) { console.log(this.t('settings.admin.accessDenied')); await this.pause(); return; } this.adminAuthenticated = true; } else { // PIN not enabled or not configured, allow access this.adminAuthenticated = true; } } const currentValue = this.getNestedValue(this.settings, key); const schema = this.getSettingSchema(key); console.log(`\n${colors.bright}${this.t('settings.editing')}: ${label}${colors.reset}`); // Show helper text const helperText = this.getHelperText(key); console.log(`${colors.dim}${helperText}${colors.reset}\n`); // Show current value with special handling for admin PIN if (key === 'security.adminPinEnabled') { const config = await this.adminAuth.loadConfig(); const pinSet = config && config.enabled === true && !!config.pinHash; const pinDisplay = pinSet ? '****' : '(not set)'; console.log(`${this.t('settings.current')}: ${this.formatValue(currentValue)} (${this.t('settings.pin')}: ${pinDisplay})`); } else { console.log(`${this.t('settings.current')}: ${this.formatValue(currentValue)}`); } // Show valid options const validOptions = this.getValidOptions(key, schema); if (validOptions) { console.log(`\n${colors.cyan}${this.t('settings.validOptions')}:${colors.reset}`); validOptions.forEach((option, index) => { const marker = option.toLowerCase() === String(currentValue).toLowerCase() ? ` ← ${this.t('settings.current')}` : ''; console.log(` ${index + 1}) ${option}${colors.dim}${marker}${colors.reset}`); }); } console.log(); const newValue = await this.prompt(this.t('settings.enterNewValue')); if (newValue.trim() === '') { return; } // Handle "default" keyword for script directory settings if (newValue.trim().toLowerCase() === 'default') { if (key.startsWith('scriptDirectories.')) { this.setNestedValue(this.settings, key, null); this.modified = true; this.success(`${label} reset to system default.`); await this.pause(); return; } else { this.error('"default" keyword is only supported for script directory settings.'); await this.pause(); return; } } // Validate input const validation = this.validateInput(newValue.trim(), key, schema); if (!validation.valid) { this.error(validation.message); await this.pause(); return; } // Convert the value const convertedValue = this.convertValue(newValue.trim(), schema); if (convertedValue === null) { this.error(this.t('settings.invalidValueFormat')); await this.pause(); return; } // Special handling for admin PIN setup if (key === 'security.adminPinEnabled' && convertedValue === true) { const config = await this.adminAuth.loadConfig(); const pinSet = config && config.enabled && config.pinHash; if (!pinSet) { console.log(this.t('settings.admin.setupPin')); const pin = await this.promptPin('Enter new admin PIN (4-6 digits): '); if (pin) { const pinSetup = await this.adminAuth.setupPin(pin); if (!pinSetup) { console.log(this.t('settings.admin.setupFailed')); await this.pause(); return; } } else { console.log('PIN setup cancelled.'); await this.pause(); return; } } } // Update the setting this.setNestedValue(this.settings, key, convertedValue); this.modified = true; // Special handling for language changes if (key === 'language') { // Refresh UI language immediately if (typeof uiI18n.refreshLanguageFromSettings === 'function') { uiI18n.refreshLanguageFromSettings(); } // Refresh i18n-helper translations const { refreshLanguageFromSettings } = require('../utils/i18n-helper'); refreshLanguageFromSettings(); // Force reload settings to ensure consistency this.settings = settingsManager.getSettings(); } this.success(this.t('settings.updatedSuccessfully', { setting: label })); await this.pause(); } /** * Prompt for PIN input with masking */ async promptPin(prompt) { return new Promise((resolve) => { this.rl.question(prompt, (input) => { const pin = input.trim(); if (/^\d{4,6}$/.test(pin)) { resolve(pin); } else { console.log('❌ PIN must be 4-6 digits.'); resolve(null); } }); }); } /** * Handle PIN setup/change */ async handlePinSetup() { this.clearScreen(); this.showHeader(); console.log(`${colors.bright}${this.t('settings.admin.pinSetupTitle')}${colors.reset}\n`); const config = await this.adminAuth.loadConfig(); const pinSet = config && config.enabled === true && !!config.pinHash; if (pinSet) { console.log(this.t('settings.admin.pinConfigured')); console.log('\n' + this.t('settings.admin.options')); console.log(' ' + this.t('settings.admin.changePin')); console.log(' ' + this.t('settings.admin.removePin')); console.log(' ' + this.t('settings.admin.cancel')); console.log(); const choice = await this.prompt(this.t('settings.admin.selectOption')); switch (choice) { case '1': console.log(this.t('settings.admin.verifyCurrentPin')); const currentPin = await this.promptPin('Enter current PIN: '); if (currentPin && await this.adminAuth.verifyPin(currentPin)) { const newPin = await this.promptPin('Enter new PIN: '); if (newPin) { const confirmPin = await this.promptPin('Confirm new PIN: '); if (newPin === confirmPin) { const success = await this.adminAuth.setupPin(newPin); if (success) { this.success('Admin PIN updated successfully!'); } else { this.error('Failed to update admin PIN.'); } } else { this.error('PINs do not match.'); } } } else { this.error('Invalid current PIN.'); } break; case '2': console.log(this.t('settings.admin.verifyToRemove')); const removePin = await this.promptPin('Enter PIN to confirm removal: '); if (removePin && await this.adminAuth.verifyPin(removePin)) { const success = await this.adminAuth.disableAuth(); if (success) { this.success('Admin PIN protection removed.'); } else { this.error('Failed to remove PIN protection.'); } } else { this.error('Invalid PIN.'); } break; case '3': console.log(this.t('settings.admin.operationCancelled')); break; default: this.error(this.t('common.invalidOption')); } } else { console.log(this.t('settings.admin.noPinConfigured')); console.log('\n' + this.t('settings.admin.pinBenefits')); console.log(' ' + this.t('settings.admin.benefitSecurity')); console.log(' ' + this.t('settings.admin.benefitAdvanced')); console.log(' ' + this.t('settings.admin.benefitDebug')); console.log(' ' + this.t('settings.admin.benefitReset')); console.log(); const response = await this.prompt(this.t('settings.admin.setupPinPrompt')); if (response.toLowerCase() === 'y' || response.toLowerCase() === 'yes') { const pin = await this.promptPin('Enter new admin PIN (4-6 digits): '); if (pin) { const confirmPin = await this.promptPin('Confirm PIN: '); if (pin === confirmPin) { const success = await this.adminAuth.setupPin(pin); if (success) { this.success('Admin PIN configured successfully!'); // Enable admin PIN protection in settings this.setNestedValue(this.settings, 'security.adminPinEnabled', true); this.modified = true; } else { this.error('Failed to configure admin PIN.'); } } else { this.error('PINs do not match.'); } } } else { console.log(this.t('settings.admin.setupCancelled')); } } await this.pause(); } /** * Configure PIN protection for individual scripts */ async configurePinProtection() { this.clearScreen(); this.showHeader(); console.log(`${colors.bright}Configure PIN Protection${colors.reset}\n`); // Check if admin PIN is configured const adminPinConfigured = await this.adminAuth.isPinConfigured(); if (!adminPinConfigured) { console.log(`${colors.red}Admin PIN is not configured.${colors.reset}`); console.log(`${colors.yellow}Please configure an Admin PIN first in Security Settings.${colors.reset}\n`); const proceed = await this.prompt('Press Enter to continue...'); return; } // Check if PIN protection is enabled globally if (!this.settingsManager.isAdminPinEnabled()) { console.log(`${colors.red}PIN Protection is currently disabled.${colors.reset}`); console.log(`${colors.yellow}Please enable PIN Protection in Security Settings.${colors.reset}\n`); const proceed = await this.prompt('Press Enter to continue...'); return; } const pinProtection = this.getNestedValue(this.settings, 'security.pinProtection') || {}; const protectedScripts = pinProtection.protectedScripts || {}; console.log(`${colors.dim}Select which scripts require PIN protection:${colors.reset}\n`); const scriptOptions = [ { key: '1', label: 'Debug Tools Menu', setting: 'debugMenu', current: protectedScripts.debugMenu }, { key: '2', label: 'Delete Reports', setting: 'deleteReports', current: protectedScripts.deleteReports }, { key: '3', label: 'Summary Reports', setting: 'summaryReports', current: protectedScripts.summaryReports }, { key: '4', label: 'Settings Menu', setting: 'settingsMenu', current: protectedScripts.settingsMenu }, { key: '5', label: 'Init Script', setting: 'init', current: protectedScripts.init }, { key: '6', label: 'Analyze Script', setting: 'analyze', current: protectedScripts.analyze }, { key: '7', label: 'Validate Script', setting: 'validate', current: protectedScripts.validate }, { key: '8', label: 'Complete Script', setting: 'complete', current: protectedScripts.complete }, { key: '9', label: 'Manage Script', setting: 'manage', current: protectedScripts.manage }, { key: '10', label: 'Sizing Script', setting: 'sizing', current: protectedScripts.sizing }, { key: '11', label: 'Usage Script', setting: 'usage', current: protectedScripts.usage } ]; // Display current settings scriptOptions.forEach(option => { const status = option.current ? `${colors.green}Protected${colors.reset}` : `${colors.red}Not Protected${colors.reset}`; console.log(` ${colors.cyan}${option.key}${colors.reset}) ${option.label}: ${status}`); }); console.log(`\n ${colors.yellow}r${colors.reset}) Reset to defaults`); console.log(` ${colors.yellow}b${colors.reset}) Back to security settings`); console.log(); const choice = await this.prompt('Select script to toggle protection (or r/b): '); if (choice.toLowerCase() === 'b') { return; } if (choice.toLowerCase() === 'r') { // Reset to defaults const defaultConfig = { enabled: true, protectedScripts: { debugMenu: true, deleteReports: true, summaryReports: true, settingsMenu: true, init: false, analyze: false, validate: false, complete: false, manage: false, sizing: false, usage: false } }; this.setNestedValue(this.settings, 'security.pinProtection', defaultConfig); this.modified = true; this.success('PIN protection settings reset to defaults.'); await this.pause(); await this.configurePinProtection(); return; } const index = parseInt(choice) - 1; if (index >= 0 && index < scriptOptions.length) { const option = scriptOptions[index]; const newValue = !protectedScripts[option.setting]; this.setNestedValue(this.settings, `security.pinProtection.protectedScripts.${option.setting}`, newValue); this.modified = true; const status = newValue ? 'enabled' : 'disabled'; this.success(`PIN protection ${status} for ${option.label}.`); await this.pause(); await this.configurePinProtection(); } else { this.error('Invalid option.'); await this.pause(); await this.configurePinProtection(); } } /** * Show all current settings */ async showAllSettings() { this.clearScreen(); this.showHeader(); console.log(`${colors.bright}${this.t('settings.viewAll.title')}${colors.reset}\n`); this.displaySettingsTree(this.settings, ''); console.log(`\n${this.t('settings.pressEnter')}...`); await this.prompt(''); } /** * Display settings in a tree format */ displaySettingsTree(obj, prefix) { Object.keys(obj).forEach(key => { const value = obj[key]; if (typeof value === 'object' && value !== null && !Array.isArray(value)) { console.log(`${prefix}${colors.bright}${key}:${colors.reset}`); this.displaySettingsTree(value, prefix + ' '); } else { console.log(`${prefix}${colors.cyan}${key}:${colors.reset} ${this.formatValue(value)}`); } }); } /** * Show import/export options */ async showImportExport() { this.clearScreen(); this.showHeader(); console.log(`${colors.bright}${this.t('settings.importExport.title')}${colors.reset}\n`); console.log(` ${colors.cyan}1${colors.reset}) ${this.t('settings.importExport.export')}`); console.log(` ${colors.cyan}2${colors.reset}) ${this.t('settings.importExport.import')}`); console.log(` ${colors.cyan}3${colors.reset}) ${this.t('settings.importExport.backup')}`); console.log(` ${colors.yellow}b${colors.reset}) ${this.t('settings.back')}\n`); const choice = await this.prompt(this.t('settings.selectOption')); switch (choice) { case '1': await this.exportSettings(); break; case '2': await this.importSettings(); break; case '3': await this.createBackup(); break; case 'b': return; default: this.error(this.t('common.invalidOption')); await this.pause(); await this.showImportExport(); } } /** * Export settings to a file */ async exportSettings() { const filename = await this.prompt(this.t('settings.importExport.exportFilenamePrompt')); const exportFile = filename.trim() || `i18n-settings-${new Date().toISOString().split('T')[0]}.json`; try { fs.writeFileSync(exportFile, JSON.stringify(this.settings, null, 2)); this.success(`Settings exported to ${exportFile}`); } catch (error) { this.error(`Failed to export settings: ${error.message}`); } await this.pause(); } /** * Import settings from a file */ async importSettings() { const filename = await this.prompt(this.t('settings.importExport.importFilenamePrompt')); if (!filename.trim()) { return; } try { if (!fs.existsSync(filename)) { this.error('File not found.'); await this.pause(); return; } const importedSettings = JSON.parse(fs.readFileSync(filename, 'utf8')); // Validate imported settings if (!settingsManager.validateSettings(importedSettings)) { this.error('Invalid settings file format.'); await this.pause(); return; } const confirm = await this.prompt(this.t('settings.importExport.importConfirm')); if (confirm.toLowerCase() === 'y') { this.settings = importedSettings; this.modified = true; this.success('Settings imported successfully.'); } } catch (error) { this.error(`Failed to import settings: ${error.message}`); } await this.pause(); } /** * Create a backup of current settings */ async createBackup() { try { const backupFile = settingsManager.createBackup(); this.success(`Backup created: ${backupFile}`); } catch (error) { this.error(`Failed to create backup: ${error.message}`); } await this.pause(); } /** * Reset settings to defaults */ async resetToDefaults() { this.clearScreen(); this.showHeader(); console.log(`${colors.bright}${this.t('settings.resetToDefaultsTitle')}${colors.reset}\n`); console.log(`${colors.yellow}${this.t('settings.resetWarning1')}${colors.reset}`); console.log(`${colors.yellow}${this.t('settings.resetWarning2')}${colors.reset}\n`); const confirm = await this.prompt(this.t('settings.resetConfirm')); if (confirm.toLowerCase() === 'y') { try { settingsManager.resetToDefaults(); this.settings = settingsManager.getSettings(); this.modified = false; this.success('Settings reset to defaults successfully.'); } catch (error) { this.error(`Failed to reset settings: ${error.message}`); } } await this.pause(); } /** * Reset script directories to defaults */ async resetScriptDirectories() { this.clearScreen(); this.showHeader(); console.log(`${colors.bright}${this.t('settings.resetScriptDirectoriesTitle')}${colors.reset}\n`); console.log(`${colors.yellow}${this.t('settings.resetScriptDirectoriesWarning1')}${colors.reset}`); console.log(`${colors.yellow}${this.t('settings.resetScriptDirectoriesWarning2')}${colors.reset}\n`); const confirm = await this.prompt(this.t('settings.resetScriptDirectoriesConfirm')); if (confirm.toLowerCase() === 'y') { try { // Reset all script directory settings to null (use system defaults) const scriptDirKeys = [ 'scriptDirectories.analyze', 'scriptDirectories.complete', 'scriptDirectories.init', 'scriptDirectories.manage', 'scriptDirectories.sizing', 'scriptDirectories.summary', 'scriptDirectories.usage', 'scriptDirectories.validate' ]; scriptDirKeys.forEach(key => { this.setNestedValue(this.settings, key, null); }); this.modified = true; this.success('Script directories reset to system defaults successfully.'); } catch (error) { this.error(`Failed to reset script directories: ${error.message}`); } } await this.pause(); } /** * Save current settings */ async saveSettings() { try { const success = settingsManager.saveSettings(this.settings); if (success) { this.modified = false; this.success('Settings saved successfully.'); } else { this.error('Failed to save settings.'); } } catch (error) { this.error(`Failed to save settings: ${error.message}`); } await this.pause(); } /** * Show help information */ async showHelp() { this.clearScreen(); this.showHeader(); console.log(`${colors.bright}${this.t('settings.help.title')}${colors.reset}\n`); console.log(`${colors.cyan}${this.t('settings.help.navigationTitle')}${colors.reset}`); console.log(`${this.t('settings.help.navigation1')}`); console.log(`${this.t('settings.help.navigation2')}`); console.log(`${this.t('settings.help.navigation3')}`); console.log(`${this.t('settings.help.navigation4')}\n`); console.log(`${colors.cyan}${this.t('settings.help.categoriesTitle')}${colors.reset}`); console.log(`${this.t('settings.help.categoryUI')}`); console.log(`${this.t('settings.help.categoryDirectory')}`); console.log(`${this.t('settings.help.categoryProcessing')}`); console.log(`${this.t('settings.help.categoryAdvanced')}\n`); console.log(`${colors.cyan}${this.t('settings.help.envVarsTitle')}${colors.reset}`); console.log(`${this.t('settings.help.envVar1')}`); console.log(`${this.t('settings.help.envVar2')}`); console.log(`${this.t('settings.help.envVar3')}\n`); console.log(`${colors.cyan}${this.t('settings.help.cliUsageTitle')}${colors.reset}`); console.log(`${this.t('settings.help.cliUsage1')}`); console.log(`${this.t('settings.help.cliUsage2')}`); console.log(`${this.t('settings.help.cliUsage3')}\n`); console.log(`${this.t('settings.pressEnter')}\n`); await this.prompt(''); } /** * Report a bug - opens GitHub issues page */ async reportBug() { this.clearScreen(); this.showHeader(); console.log(`${colors.bright}${this.t('settings.reportBug.title')}${colors.reset}\n`); console.log(this.t('settings.reportBug.description')); console.log(` ${colors.dim}${this.t('settings.reportBug.link')}: https://github.com/vladnoskv/i18ntk/issues${colors.reset} `); try { const { exec } = require('child_process'); const url = 'https://github.com/vladnoskv/i18ntk/issues'; // Try to open the URL in the default browser let command; switch (process.platform) { case 'darwin': // macOS command = `open "${url}"`; break; case 'win32': // Windows command = `start "" "${url}"`; break; default: // Linux and others command = `xdg-open "${url}"`; break; } exec(command, (error) => { if (error) { console.log(`${colors.yellow}${this.t('settings.reportBug.browserOpenFailed')}${colors.reset}`); console.log(`${this.t('settings.reportBug.manualVisit', { url: url })}`); } else { console.log(`${colors.green}${this.t('settings.reportBug.browserOpened')}${colors.reset}`); } }); } catch (error) { console.log(`${colors.yellow}${this.t('settings.reportBug.browserOpenFailed')}${colors.reset}`); console.log(`${this.t('settings.reportBug.manualVisit', { url: 'https://github.com/vladnoskv/i18ntk/issues' })}`); } await this.pause(); } /** * Update the i18n-toolkit package via npm */ async updatePackage() { this.clearScreen(); this.show