Initial commit of the Flutter Cursor Generator project, including the core generator tool, project brief schema, example project setup, and CI configuration. Added README documentation outlining repository structure, quick start guide, and detailed descriptions of features and architecture pillars.
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
// wizard.dart — Interactive CLI wizard (brief discovery UX gap fix)
|
||||
|
||||
import 'dart:io';
|
||||
import 'logger.dart';
|
||||
|
||||
class Wizard {
|
||||
static Future<void> run({required String outputPath}) async {
|
||||
Logger.info('\n🧙 cursor_gen Interactive Wizard');
|
||||
Logger.info('─' * 42);
|
||||
Logger.info('Answer the questions below to generate your project-brief.yaml.\n');
|
||||
|
||||
final answers = <String, dynamic>{};
|
||||
|
||||
// Project basics
|
||||
answers['name'] = _ask('Project name', hint: 'ShopEasy');
|
||||
answers['package'] = _ask('Package ID', hint: 'com.company.shopeasy');
|
||||
answers['description'] = _ask('Short description', hint: 'E-commerce app with real-time inventory');
|
||||
answers['scale'] = _askChoice('Project scale', ['small', 'medium', 'large'], defaultIdx: 1);
|
||||
|
||||
Logger.info('');
|
||||
|
||||
// Stack
|
||||
answers['state_management'] = _askChoice('State management',
|
||||
['bloc', 'riverpod', 'getx', 'hooks_riverpod'], defaultIdx: 1);
|
||||
answers['architecture'] = _askChoice('Architecture',
|
||||
['clean', 'feature_first', 'mvvm', 'mvc', 'layered'], defaultIdx: 0);
|
||||
answers['routing'] = _askChoice('Routing',
|
||||
['gorouter', 'getx_nav', 'auto_route'], defaultIdx: 0);
|
||||
answers['backend'] = _askChoice('Backend(s)',
|
||||
['firebase', 'supabase', 'rest', 'firebase+rest', 'supabase+rest'], defaultIdx: 2);
|
||||
answers['auth'] = _askChoice('Auth method',
|
||||
['firebase_auth', 'supabase_auth', 'jwt_rest', 'oauth2', 'none'], defaultIdx: 4);
|
||||
|
||||
Logger.info('');
|
||||
|
||||
// Platforms (Pillar 4)
|
||||
answers['platforms'] = _askMultiChoice('Target platforms',
|
||||
['ios', 'android', 'web', 'desktop'], defaults: [0, 1]);
|
||||
|
||||
// Codegen (Pillar 4)
|
||||
answers['codegen'] = _askMultiChoice('Code generation tools (optional)',
|
||||
['freezed', 'json_serializable', 'injectable', 'retrofit'], defaults: []);
|
||||
|
||||
Logger.info('');
|
||||
|
||||
// Environments
|
||||
final flavorsInput = _ask('Build flavors (comma-separated)', hint: 'dev,staging,prod');
|
||||
answers['flavors'] = flavorsInput.split(',').map((e) => e.trim()).toList();
|
||||
answers['cicd'] = _askChoice('CI/CD', ['github_actions', 'codemagic', 'fastlane', 'none'], defaultIdx: 0);
|
||||
|
||||
// Testing
|
||||
answers['testing_depth'] = _askChoice('Testing depth',
|
||||
['unit_widget', 'integration', 'e2e', 'full'], defaultIdx: 0);
|
||||
|
||||
// Localization
|
||||
final l10n = _askBool('Enable localization / i18n?', defaultYes: false);
|
||||
answers['i18n'] = l10n;
|
||||
|
||||
// Telemetry opt-in (Pillar 6)
|
||||
final telemetry = _askBool('\nOpt in to local telemetry (rule trigger logging, stored locally)?', defaultYes: false);
|
||||
answers['telemetry'] = telemetry;
|
||||
|
||||
Logger.info('');
|
||||
final yaml = _buildYaml(answers);
|
||||
final file = File(outputPath);
|
||||
await file.writeAsString(yaml);
|
||||
Logger.success('✔ project-brief.yaml written to $outputPath');
|
||||
Logger.info('\nNext: dart run cursor_gen --validate → dart run cursor_gen');
|
||||
}
|
||||
|
||||
static String _ask(String label, {String hint = ''}) {
|
||||
final display = hint.isNotEmpty ? '$label [$hint]: ' : '$label: ';
|
||||
stdout.write(' $display');
|
||||
final input = stdin.readLineSync()?.trim() ?? '';
|
||||
return input.isEmpty ? hint : input;
|
||||
}
|
||||
|
||||
static String _askChoice(String label, List<String> options, {int defaultIdx = 0}) {
|
||||
Logger.info(' $label:');
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
final marker = i == defaultIdx ? ' ◀' : '';
|
||||
Logger.dim(' ${i + 1}) ${options[i]}$marker');
|
||||
}
|
||||
stdout.write(' Choice [${defaultIdx + 1}]: ');
|
||||
final input = stdin.readLineSync()?.trim() ?? '';
|
||||
if (input.isEmpty) return options[defaultIdx];
|
||||
final idx = int.tryParse(input);
|
||||
if (idx != null && idx >= 1 && idx <= options.length) return options[idx - 1];
|
||||
return options[defaultIdx];
|
||||
}
|
||||
|
||||
static List<String> _askMultiChoice(String label, List<String> options, {List<int> defaults = const []}) {
|
||||
Logger.info(' $label (comma-separated numbers, or leave blank):');
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
final marker = defaults.contains(i) ? ' ◀' : '';
|
||||
Logger.dim(' ${i + 1}) ${options[i]}$marker');
|
||||
}
|
||||
final defaultStr = defaults.map((d) => '${d + 1}').join(',');
|
||||
stdout.write(' Choices [$defaultStr]: ');
|
||||
final input = stdin.readLineSync()?.trim() ?? '';
|
||||
if (input.isEmpty) return defaults.map((d) => options[d]).toList();
|
||||
return input.split(',')
|
||||
.map((s) => int.tryParse(s.trim()))
|
||||
.where((i) => i != null && i >= 1 && i <= options.length)
|
||||
.map((i) => options[i! - 1])
|
||||
.toList();
|
||||
}
|
||||
|
||||
static bool _askBool(String label, {bool defaultYes = false}) {
|
||||
final hint = defaultYes ? 'Y/n' : 'y/N';
|
||||
stdout.write(' $label ($hint): ');
|
||||
final input = stdin.readLineSync()?.trim().toLowerCase() ?? '';
|
||||
if (input.isEmpty) return defaultYes;
|
||||
return input == 'y' || input == 'yes';
|
||||
}
|
||||
|
||||
static String _buildYaml(Map<String, dynamic> a) {
|
||||
final platforms = (a['platforms'] as List).map((p) => '"$p"').join(', ');
|
||||
final codegen = (a['codegen'] as List).map((c) => '"$c"').join(', ');
|
||||
final flavors = (a['flavors'] as List).map((f) => '"$f"').join(', ');
|
||||
return '''# project-brief.yaml — cursor_gen configuration
|
||||
# Generated by cursor_gen --wizard
|
||||
# Run: dart run cursor_gen to generate .cursor/
|
||||
# Run: dart run cursor_gen --refresh to update after changes
|
||||
|
||||
# Pillar 1: Pin to template version for reproducibility
|
||||
cursor_templates_version: "1.0.0"
|
||||
|
||||
project:
|
||||
name: "${a['name']}"
|
||||
package: "${a['package']}"
|
||||
description: "${a['description']}"
|
||||
scale: "${a['scale']}"
|
||||
|
||||
stack:
|
||||
state_management: "${a['state_management']}"
|
||||
routing: "${a['routing']}"
|
||||
architecture: "${a['architecture']}"
|
||||
backend: "${a['backend']}"
|
||||
auth: "${a['auth']}"
|
||||
|
||||
# Pillar 4: Platform targets
|
||||
platforms: [$platforms]
|
||||
|
||||
# Pillar 4: Code generation tools
|
||||
codegen: [$codegen]
|
||||
|
||||
environments:
|
||||
flavors: [$flavors]
|
||||
cicd: "${a['cicd']}"
|
||||
|
||||
testing:
|
||||
depth: "${a['testing_depth']}"
|
||||
e2e_tool: "patrol"
|
||||
|
||||
design:
|
||||
source: "none"
|
||||
figma_url: ""
|
||||
|
||||
api_docs:
|
||||
format: "none"
|
||||
path: ""
|
||||
|
||||
references:
|
||||
repos: []
|
||||
local_paths: []
|
||||
|
||||
features:
|
||||
modules: []
|
||||
special: []
|
||||
|
||||
localization:
|
||||
enabled: ${a['i18n']}
|
||||
locales: ["en"]
|
||||
|
||||
# Pillar 6: Opt-in local telemetry (logs rule trigger frequency locally)
|
||||
telemetry_opt_in: ${a['telemetry']}
|
||||
''';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user