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
331 lines (268 loc) ⢠9.58 kB
JavaScript
/**
* InteractiveCommand - Guided React-to-Flutter conversion wizard
* Provides user-friendly prompts for all conversion options
*/
const fs = require('fs').promises;
const path = require('path');
const readline = require('readline');
const RefactorCommand = require('./RefactorCommand');
const { createModuleLogger } = require('../utils/logger');
const logger = createModuleLogger('InteractiveCommand');
class InteractiveCommand {
constructor() {
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
this.answers = {};
}
/**
* Start interactive mode
*/
async execute() {
try {
logger.info('\nšØ Interactive React-to-Flutter Conversion Wizard\n');
logger.info('Answer the following questions to configure your conversion:\n');
// Step 1: Source directory
await this.askSourceDirectory();
// Step 2: Target directory
await this.askTargetDirectory();
// Step 3: State management
await this.askStateManagement();
// Step 4: Architecture
await this.askArchitecture();
// Step 5: AI provider
await this.askAIProvider();
// Step 6: Validation
await this.askValidation();
// Step 7: Show preview
await this.showPreview();
// Step 8: Confirm
const confirmed = await this.confirm();
if (!confirmed) {
logger.warn('\nā Conversion cancelled');
this.close();
return { success: false, cancelled: true };
}
// Step 9: Execute conversion
this.close();
const result = await this.executeConversion();
return result;
} catch (error) {
logger.error(`\nā Interactive mode failed: ${error.message}`);
this.close();
return { success: false, error: error.message };
}
}
/**
* Ask for React source directory
*/
async askSourceDirectory() {
const answer = await this.question(
'š React source directory (default: ./src): '
);
this.answers.reactSource = answer.trim() || './src';
// Validate directory exists
try {
const stats = await fs.stat(this.answers.reactSource);
if (!stats.isDirectory()) {
throw new Error('Not a directory');
}
logger.success(` ā
Found: ${this.answers.reactSource}\n`);
} catch {
logger.error(` ā Directory not found: ${this.answers.reactSource}`);
logger.warn(' Please ensure the path is correct and try again.\n');
return await this.askSourceDirectory();
}
}
/**
* Ask for Flutter target directory
*/
async askTargetDirectory() {
const answer = await this.question(
'š Flutter target directory (default: ./flutter-app): '
);
this.answers.flutterTarget = answer.trim() || './flutter-app';
// Check if exists
try {
await fs.access(this.answers.flutterTarget);
const overwrite = await this.question(
` ā ļø Directory exists. Overwrite? (y/N): `
);
if (overwrite.toLowerCase() !== 'y') {
logger.warn('\n Please choose a different directory.\n');
return await this.askTargetDirectory();
}
} catch {
// Directory doesn't exist - that's fine
}
logger.success(` ā
Target: ${this.answers.flutterTarget}\n`);
}
/**
* Ask for state management preference
*/
async askStateManagement() {
logger.info('š State Management:');
logger.info(' 1) Auto-detect (recommended)');
logger.info(' 2) BLoC (complex state, events)');
logger.info(' 3) Cubit (simple state)');
logger.info(' 4) Provider (simple state, legacy)');
const answer = await this.question('\n Choose (1-4, default: 1): ');
const choice = answer.trim() || '1';
const stateManagementMap = {
'1': 'auto',
'2': 'bloc',
'3': 'cubit',
'4': 'provider'
};
this.answers.stateManagement = stateManagementMap[choice] || 'auto';
logger.success(` ā
Using: ${this.answers.stateManagement}\n`);
}
/**
* Ask for architecture preference
*/
async askArchitecture() {
logger.info('šļø Architecture Pattern:');
logger.info(' 1) Clean Architecture (recommended)');
logger.info(' 2) MVC (Model-View-Controller)');
logger.info(' 3) MVVM (Model-View-ViewModel)');
const answer = await this.question('\n Choose (1-3, default: 1): ');
const choice = answer.trim() || '1';
const architectureMap = {
'1': 'clean',
'2': 'mvc',
'3': 'mvvm'
};
this.answers.architecture = architectureMap[choice] || 'clean';
logger.success(` ā
Using: ${this.answers.architecture}\n`);
}
/**
* Ask for AI provider
*/
async askAIProvider() {
logger.info('š¤ AI Enhancement (optional):');
logger.info(' 1) None (faster, basic conversion)');
logger.info(' 2) Claude (best quality, slower)');
logger.info(' 3) Qwen (balanced)');
logger.info(' 4) Gemini (fast, good quality)');
const answer = await this.question('\n Choose (1-4, default: 1): ');
const choice = answer.trim() || '1';
const aiMap = {
'1': 'none',
'2': 'claude',
'3': 'qwen',
'4': 'gemini'
};
this.answers.ai = aiMap[choice] || 'none';
if (this.answers.ai !== 'none') {
logger.success(` ā
AI enhancement enabled: ${this.answers.ai}`);
logger.warn(` ā±ļø Note: AI enhancement will significantly increase conversion time\n`);
} else {
logger.success(` ā
AI enhancement disabled (faster conversion)\n`);
}
}
/**
* Ask for validation preference
*/
async askValidation() {
const answer = await this.question(
'š Run validation after conversion? (Y/n): '
);
this.answers.validate = answer.toLowerCase() !== 'n';
if (this.answers.validate) {
logger.info('\n Validation strictness:');
logger.info(' 1) Standard (recommended)');
logger.info(' 2) Strict (high standards)');
logger.info(' 3) Lenient (permissive)');
const strictness = await this.question(' Choose (1-3, default: 1): ');
const strictnessChoice = strictness.trim() || '1';
const strictnessMap = {
'1': 'standard',
'2': 'strict',
'3': 'lenient'
};
this.answers.validationConfig = strictnessMap[strictnessChoice] || 'standard';
logger.success(` ā
Validation: ${this.answers.validationConfig}\n`);
} else {
logger.success(' ā
Validation disabled\n');
}
}
/**
* Show configuration preview
*/
async showPreview() {
logger.info('ā'.repeat(60));
logger.info('š Configuration Preview:');
logger.info('ā'.repeat(60));
logger.info(` Source: ${this.answers.reactSource}`);
logger.info(` Target: ${this.answers.flutterTarget}`);
logger.info(` State Mgmt: ${this.answers.stateManagement}`);
logger.info(` Architecture: ${this.answers.architecture}`);
logger.info(` AI Enhancement: ${this.answers.ai}`);
logger.info(` Validation: ${this.answers.validate ? this.answers.validationConfig : 'disabled'}`);
logger.info('ā'.repeat(60));
logger.info('');
}
/**
* Ask for confirmation
*/
async confirm() {
const answer = await this.question('ā
Proceed with conversion? (Y/n): ');
return answer.toLowerCase() !== 'n';
}
/**
* Execute conversion with collected answers
*/
async executeConversion() {
logger.info('\nš Starting conversion...\n');
const refactorCommand = new RefactorCommand({
reactSource: this.answers.reactSource,
flutterTarget: this.answers.flutterTarget,
stateManagement: this.answers.stateManagement,
architecture: this.answers.architecture,
ai: this.answers.ai,
validate: this.answers.validate,
validationConfig: this.answers.validationConfig,
verbose: true
});
const result = await refactorCommand.execute();
if (result.success) {
logger.success('\nš Conversion successful!');
logger.info('\nš Summary:');
logger.info(` ⢠Components converted: ${result.stats.components}`);
logger.info(` ⢠Files generated: ${result.stats.filesGenerated}`);
logger.info(` ⢠Duration: ${(result.duration / 1000).toFixed(1)}s`);
if (result.validationResults) {
logger.info(` ⢠Validation score: ${result.validationResults.summary.overallScore}/100`);
}
logger.info('\nš” Next steps:');
logger.info(` 1. cd ${this.answers.flutterTarget}`);
logger.info(' 2. flutter pub get');
logger.info(' 3. flutter analyze');
logger.info(' 4. flutter run');
} else {
logger.error('\nā Conversion failed');
if (result.error) {
logger.error(` Error: ${result.error}`);
}
}
return result;
}
/**
* Ask a question and return the answer
*/
question(query) {
return new Promise(resolve => {
this.rl.question(query, answer => {
resolve(answer);
});
});
}
/**
* Close readline interface
*/
close() {
this.rl.close();
}
}
module.exports = InteractiveCommand;