@@ -20,6 +20,46 @@ public sealed class CliApplication
2020 private static readonly Uri HomebrewFormulaUri = new ( "https://raw.githubusercontent.com/QuickCodeNet/homebrew-quickcode-cli/main/Formula/quickcode-cli.rb" ) ;
2121 private readonly ConfigService _configService = new ( ) ;
2222
23+ private static bool ValidateModuleName ( string moduleName , out string errorMessage )
24+ {
25+ errorMessage = string . Empty ;
26+
27+ if ( string . IsNullOrWhiteSpace ( moduleName ) )
28+ {
29+ errorMessage = "Module name cannot be empty." ;
30+ return false ;
31+ }
32+
33+ // Must start with a letter (a-z, A-Z)
34+ if ( ! char . IsLetter ( moduleName [ 0 ] ) )
35+ {
36+ errorMessage = "Module name must start with a letter (a-z, A-Z)." ;
37+ return false ;
38+ }
39+
40+ // Must contain only letters and numbers (no spaces, no special characters)
41+ if ( ! System . Text . RegularExpressions . Regex . IsMatch ( moduleName , @"^[a-zA-Z][a-zA-Z0-9]*$" ) )
42+ {
43+ errorMessage = "Module name can only contain letters (a-z, A-Z) and numbers (0-9). No spaces or special characters allowed." ;
44+ return false ;
45+ }
46+
47+ // After a digit, the next character must be uppercase (camelCase rule)
48+ for ( int i = 0 ; i < moduleName . Length - 1 ; i ++ )
49+ {
50+ if ( char . IsDigit ( moduleName [ i ] ) && char . IsLetter ( moduleName [ i + 1 ] ) )
51+ {
52+ if ( ! char . IsUpper ( moduleName [ i + 1 ] ) )
53+ {
54+ errorMessage = "Module name must follow camelCase: after a digit, the next character must be uppercase (e.g., 'SmsModule2Test' not 'SmsModule2test')." ;
55+ return false ;
56+ }
57+ }
58+ }
59+
60+ return true ;
61+ }
62+
2363 private static async Task HandleWithExceptionCatchingAsync ( Func < Task > action )
2464 {
2565 try
@@ -680,6 +720,15 @@ private Command BuildModuleAddCommand(Option<bool> verboseOption)
680720 {
681721 await HandleWithExceptionCatchingAsync ( async ( ) =>
682722 {
723+ // Validate module name
724+ if ( ! ValidateModuleName ( moduleName , out var moduleNameError ) )
725+ {
726+ Console . ForegroundColor = ConsoleColor . Red ;
727+ Console . WriteLine ( $ "❌ { moduleNameError } ") ;
728+ Console . ResetColor ( ) ;
729+ return ;
730+ }
731+
683732 // Validate db-type value
684733 var validDbTypes = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) { "mssql" , "mysql" , "postgresql" } ;
685734 if ( ! validDbTypes . Contains ( dbType ) )
@@ -747,7 +796,7 @@ await HandleWithExceptionCatchingAsync(async () =>
747796
748797 private Command BuildModuleRemoveCommand ( Option < bool > verboseOption )
749798 {
750- var command = new Command ( "remove" , "Remove module from project" ) ;
799+ var command = new Command ( "remove" , "Remove module from project. ⚠️ Warning: Once deleted, your data cannot be recovered. " ) ;
751800 var projectOption = new Option < string ? > ( "--project" ) ;
752801 var emailOption = new Option < string ? > ( "--email" ) ;
753802 var secretOption = new Option < string ? > ( "--secret-code" ) ;
@@ -762,8 +811,35 @@ private Command BuildModuleRemoveCommand(Option<bool> verboseOption)
762811 {
763812 await HandleWithExceptionCatchingAsync ( async ( ) =>
764813 {
814+ // Validate module name
815+ if ( ! ValidateModuleName ( moduleName , out var moduleNameError ) )
816+ {
817+ Console . ForegroundColor = ConsoleColor . Red ;
818+ Console . WriteLine ( $ "❌ { moduleNameError } ") ;
819+ Console . ResetColor ( ) ;
820+ return ;
821+ }
822+
765823 var config = _configService . Load ( ) ;
766824 var ( name , resolvedEmail , resolvedSecret ) = _configService . ResolveProjectCredentials ( config , projectName , email , secret ) ;
825+
826+ // Show warning and ask for confirmation
827+ Console . ForegroundColor = ConsoleColor . Yellow ;
828+ Console . WriteLine ( "⚠️ WARNING: You are about to delete a module." ) ;
829+ Console . WriteLine ( " Once deleted, your data cannot be recovered." ) ;
830+ Console . ResetColor ( ) ;
831+ Console . WriteLine ( ) ;
832+ Console . Write ( $ "Are you sure you want to delete module '{ moduleName } ' from project '{ name } '? (yes/no): ") ;
833+
834+ var confirmation = Console . ReadLine ( ) ? . Trim ( ) . ToLowerInvariant ( ) ;
835+
836+ if ( confirmation != "yes" && confirmation != "y" )
837+ {
838+ Console . WriteLine ( "❌ Module removal cancelled." ) ;
839+ return ;
840+ }
841+
842+ Console . WriteLine ( ) ;
767843 using var client = new QuickCodeApiClient ( config . ApiUrl , verbose ) ;
768844 var result = await client . RemoveProjectModuleAsync ( name , resolvedEmail , resolvedSecret , moduleName ) ;
769845 Console . WriteLine ( result ? "✅ Module removed." : "⚠️ Module removal failed." ) ;
0 commit comments