UNPKG

prprompts-flutter-generator

Version:

AI-powered Flutter development with full automation + official extension support - Generate 32 security-audited guides & auto-implement in 2-3 hours. NEW v5.1: Official Claude Code plugin with hooks, Gemini TOML commands, Qwen MCP settings. Features: Comp

477 lines (422 loc) 11.2 kB
/** * config.js * Configuration management for React to Flutter conversion * * Provides default configurations, validation, and persistence */ const path = require('path'); const { createFileHandler } = require('./fileHandler'); const { createModuleLogger } = require('./logger'); const logger = createModuleLogger('Config'); /** * Default conversion configuration */ const DEFAULT_CONFIG = { // Project settings project: { name: 'Flutter App', version: '1.0.0', description: 'Converted from React', }, // Paths paths: { react: { root: './react-app', src: './react-app/src', components: './react-app/src/components', pages: './react-app/src/pages', }, flutter: { root: './flutter-app', lib: './flutter-app/lib', test: './flutter-app/test', }, }, // Conversion options conversion: { // State management stateManagement: { default: 'bloc', options: ['bloc', 'cubit', 'provider', 'riverpod', 'getx'], useComplexityHeuristic: true, }, // Type mappings typeMappings: { string: 'String', number: 'num', boolean: 'bool', any: 'dynamic', void: 'void', null: 'Null', undefined: 'Null', Date: 'DateTime', Array: 'List', Map: 'Map', Set: 'Set', Promise: 'Future', }, // Widget mappings widgetMappings: { View: 'Container', Text: 'Text', Image: 'Image', TouchableOpacity: 'InkWell', ScrollView: 'SingleChildScrollView', FlatList: 'ListView', TextInput: 'TextField', Button: 'ElevatedButton', Modal: 'Dialog', SafeAreaView: 'SafeArea', }, // Naming conventions naming: { widgets: 'PascalCase', files: 'snake_case', variables: 'camelCase', constants: 'UPPER_SNAKE_CASE', }, // Code generation generation: { nullSafety: true, freezed: true, jsonSerializable: true, equatable: true, generateTests: true, generateDocs: true, preserveComments: true, }, // Architecture architecture: { pattern: 'clean', layers: ['domain', 'data', 'presentation'], featureFirst: true, useDependencyInjection: true, injectionFramework: 'get_it', }, }, // Parsing options parsing: { // File patterns to include include: [ '**/*.jsx', '**/*.tsx', '**/*.js', '**/*.ts', ], // File patterns to exclude exclude: [ '**/node_modules/**', '**/dist/**', '**/build/**', '**/*.test.js', '**/*.test.tsx', '**/*.spec.js', '**/*.spec.tsx', ], // Parser options parser: { ecmaVersion: 2022, sourceType: 'module', jsx: true, typescript: true, }, }, // Validation options validation: { strict: false, failOnError: false, warnOnMissing: true, checkNaming: true, checkTypes: true, checkStructure: true, }, // Output options output: { format: 'dart', lineLength: 80, indent: 2, useTabs: false, trailingComma: true, singleQuotes: true, organizeImports: true, formatOnWrite: true, }, // Logging options logging: { level: 'info', // debug, info, warn, error, silent colorize: true, timestamp: true, logFile: null, }, // Performance options performance: { parallel: true, maxConcurrency: 4, cacheEnabled: true, cachePath: '.cache/refactoring', }, }; /** * Configuration manager class */ class Config { /** * @param {Object} [customConfig={}] - Custom configuration overrides */ constructor(customConfig = {}) { this.config = this._mergeConfig(DEFAULT_CONFIG, customConfig); this._validate(); } /** * Deep merge configurations * @param {Object} target * @param {Object} source * @returns {Object} * @private */ _mergeConfig(target, source) { const result = { ...target }; for (const key in source) { if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) { result[key] = this._mergeConfig(target[key] || {}, source[key]); } else { result[key] = source[key]; } } return result; } /** * Validate configuration * @private */ _validate() { // Validate state management const { stateManagement } = this.config.conversion; if (!stateManagement.options.includes(stateManagement.default)) { logger.warn(`Invalid default state management: ${stateManagement.default}`); stateManagement.default = 'bloc'; } // Validate architecture pattern const { architecture } = this.config.conversion; const validPatterns = ['clean', 'mvc', 'mvvm', 'layered']; if (!validPatterns.includes(architecture.pattern)) { logger.warn(`Invalid architecture pattern: ${architecture.pattern}`); architecture.pattern = 'clean'; } // Validate paths const { paths } = this.config; if (!paths.react.root || !paths.flutter.root) { throw new Error('React and Flutter root paths are required'); } logger.debug('Configuration validated successfully'); } /** * Get configuration value by path * @param {string} keyPath - Dot-separated path (e.g., 'conversion.stateManagement.default') * @returns {*} */ get(keyPath) { const keys = keyPath.split('.'); let value = this.config; for (const key of keys) { if (value && typeof value === 'object' && key in value) { value = value[key]; } else { return undefined; } } return value; } /** * Set configuration value by path * @param {string} keyPath - Dot-separated path * @param {*} value */ set(keyPath, value) { const keys = keyPath.split('.'); let current = this.config; for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; if (!(key in current) || typeof current[key] !== 'object') { current[key] = {}; } current = current[key]; } current[keys[keys.length - 1]] = value; logger.debug(`Config updated: ${keyPath} = ${JSON.stringify(value)}`); } /** * Get entire configuration * @returns {Object} */ getAll() { return { ...this.config }; } /** * Update configuration with partial config * @param {Object} partialConfig */ update(partialConfig) { this.config = this._mergeConfig(this.config, partialConfig); this._validate(); logger.debug('Configuration updated'); } /** * Reset to default configuration */ reset() { this.config = { ...DEFAULT_CONFIG }; logger.debug('Configuration reset to defaults'); } /** * Load configuration from file * @param {string} filePath * @returns {Promise<void>} */ async loadFromFile(filePath) { try { const fileHandler = createFileHandler(); logger.info(`Loading configuration from: ${filePath}`); const content = await fileHandler.readFile(filePath); const extension = fileHandler.getExtension(filePath); let loadedConfig; if (extension === '.json') { loadedConfig = JSON.parse(content); } else if (extension === '.js') { // For .js files, require them const absolutePath = path.resolve(filePath); delete require.cache[absolutePath]; loadedConfig = require(absolutePath); } else { throw new Error(`Unsupported config file format: ${extension}`); } this.update(loadedConfig); logger.success(`Configuration loaded from: ${filePath}`); } catch (error) { logger.error(`Failed to load configuration from file: ${filePath}`, { error: error.message, }); throw error; } } /** * Save configuration to file * @param {string} filePath * @returns {Promise<void>} */ async saveToFile(filePath) { try { const fileHandler = createFileHandler({ overwrite: true }); logger.info(`Saving configuration to: ${filePath}`); const content = JSON.stringify(this.config, null, 2); await fileHandler.writeFile(filePath, content); logger.success(`Configuration saved to: ${filePath}`); } catch (error) { logger.error(`Failed to save configuration to file: ${filePath}`, { error: error.message, }); throw error; } } /** * Get React source paths * @returns {Object} */ getReactPaths() { return this.config.paths.react; } /** * Get Flutter target paths * @returns {Object} */ getFlutterPaths() { return this.config.paths.flutter; } /** * Get state management configuration * @returns {Object} */ getStateManagement() { return this.config.conversion.stateManagement; } /** * Get type mappings * @returns {Object} */ getTypeMappings() { return this.config.conversion.typeMappings; } /** * Get widget mappings * @returns {Object} */ getWidgetMappings() { return this.config.conversion.widgetMappings; } /** * Get architecture configuration * @returns {Object} */ getArchitecture() { return this.config.conversion.architecture; } /** * Check if feature is enabled * @param {string} feature - Feature name (e.g., 'generation.nullSafety') * @returns {boolean} */ isEnabled(feature) { const value = this.get(feature); return value === true; } /** * Export configuration as JSON string * @param {boolean} [pretty=true] - Pretty print * @returns {string} */ toJSON(pretty = true) { return JSON.stringify(this.config, null, pretty ? 2 : 0); } /** * Clone configuration * @returns {Config} */ clone() { return new Config(this.config); } } /** * Create a configuration instance * @param {Object} [customConfig] - Custom configuration * @returns {Config} */ function createConfig(customConfig) { return new Config(customConfig); } /** * Load configuration from file * @param {string} filePath * @returns {Promise<Config>} */ async function loadConfig(filePath) { const config = new Config(); await config.loadFromFile(filePath); return config; } /** * Create default configuration file * @param {string} outputPath * @returns {Promise<void>} */ async function createDefaultConfigFile(outputPath) { const config = new Config(); await config.saveToFile(outputPath); logger.success(`Default configuration file created: ${outputPath}`); } module.exports = { Config, DEFAULT_CONFIG, createConfig, loadConfig, createDefaultConfigFile, };