diff --git a/packages/jao_cli/lib/src/runner.dart b/packages/jao_cli/lib/src/runner.dart index f5d7c6b..7905cea 100644 --- a/packages/jao_cli/lib/src/runner.dart +++ b/packages/jao_cli/lib/src/runner.dart @@ -654,30 +654,8 @@ class $className extends Migration { return 0; } - String _generateMigrationName(String? customName, List operations) { - if (customName != null && customName.isNotEmpty) { - return _toPascalCase(customName); - } - if (operations.isEmpty) return 'EmptyMigration'; - - final firstOp = operations.first; - if (firstOp is CreateTable) { - if (operations.length == 1) { - return 'Create${_toPascalCase(firstOp.table.name)}'; - } else { - final tableCount = operations.whereType().length; - if (tableCount == operations.length) { - return 'CreateTables'; - } - } - } else if (firstOp is AddColumn) { - return 'Add${_toPascalCase(firstOp.column.name)}To${_toPascalCase(firstOp.table)}'; - } else if (firstOp is DropColumn) { - return 'Drop${_toPascalCase(firstOp.column)}From${_toPascalCase(firstOp.table)}'; - } - - return 'AutoMigration'; - } + String _generateMigrationName(String? customName, List operations) => + generateMigrationName(customName, operations); String _describeOperation(MigrationOperation op) => switch (op) { CreateTable() => 'Create table "${op.table.name}" with ${op.table.columns.length} columns', @@ -689,11 +667,6 @@ class $className extends Migration { _ => op.runtimeType.toString(), }; - String _generateTimestamp() { - final now = DateTime.now(); - return '${now.year}${_pad(now.month)}${_pad(now.day)}${_pad(now.hour)}${_pad(now.minute)}${_pad(now.second)}'; - } - Future _rollback(List args) async { final dryRun = args.contains('-n') || args.contains('--dry-run'); final verbose = args.contains('-v') || args.contains('--verbose'); @@ -1094,14 +1067,6 @@ class $className extends Migration { migrationsFile.writeAsStringSync(content); } - String _pad(int n) => n.toString().padLeft(2, '0'); - String _toPascalCase(String input) { - return input - .split(RegExp(r'[_\-\s]+')) - .map((word) => word.isEmpty ? '' : word[0].toUpperCase() + word.substring(1)) - .join(''); - } - String _toSnakeCase(String input) { return input.replaceAllMapped(RegExp(r'[A-Z]'), (match) { if (match.group(0) case final g?) return '_${g.toLowerCase()}'; @@ -1110,6 +1075,41 @@ class $className extends Migration { } } +String generateMigrationName(String? customName, List operations) { + if (customName != null && customName.isNotEmpty) { + return _toPascalCase(customName); + } + if (operations.isEmpty) return 'EmptyMigration'; + + return switch (operations.first) { + CreateTable(table: final t) when operations.length == 1 => 'Create${_toPascalCase(t.name)}', + CreateTable() when operations.whereType().length == operations.length => 'CreateTables', + AddColumn(column: final c, table: final t) => 'Add${_toPascalCase(c.name)}To${_toPascalCase(t)}', + DropColumn(column: final c, table: final t) => 'Drop${_toPascalCase(c)}From${_toPascalCase(t)}', + AlterColumn(modification: final m) => 'Alter${_toPascalCase(m.column)}On${_toPascalCase(m.table)}', + AddForeignKey(table: final t) => 'AddFkOn${_toPascalCase(t)}', + CreateIndex(table: final t) => 'AddIndexOn${_toPascalCase(t)}', + DropConstraint(table: final t) => 'DropConstraintOn${_toPascalCase(t)}', + RenameTable(oldName: final o, newName: final n) => 'Rename${_toPascalCase(o)}To${_toPascalCase(n)}', + RenameColumn(table: final t, oldName: final o) => 'Rename${_toPascalCase(o)}On${_toPascalCase(t)}', + _ => 'AutoMigration${_generateTimestamp()}', + }; +} + +String _toPascalCase(String input) { + return input + .split(RegExp(r'[_\-\s]+')) + .map((word) => word.isEmpty ? '' : word[0].toUpperCase() + word.substring(1)) + .join(''); +} + +String _generateTimestamp() { + final now = DateTime.now(); + return '${now.year}${_pad(now.month)}${_pad(now.day)}${_pad(now.hour)}${_pad(now.minute)}${_pad(now.second)}'; +} + +String _pad(int n) => n.toString().padLeft(2, '0'); + class CliOutput { final bool verbose; diff --git a/packages/jao_cli/test/commands/makemigrations_command_test.dart b/packages/jao_cli/test/commands/makemigrations_command_test.dart index 6085223..c00a828 100644 --- a/packages/jao_cli/test/commands/makemigrations_command_test.dart +++ b/packages/jao_cli/test/commands/makemigrations_command_test.dart @@ -114,6 +114,141 @@ void main() { expect(fileName, contains('short_name')); }); + test('Names migration CreateUsers for single CreateTable', () async { + final path = '${tempDir.path}/migrations'; + + await cli.run(['makemigrations', '-p=$path']); + + final files = Directory(path).listSync(); + final content = File(files.first.path).readAsStringSync(); + expect(content, contains('class CreateUsers')); + }); + + test('Names migration CreateUsers for single table model', () async { + final path = '${tempDir.path}/migrations'; + + await cli.run(['makemigrations', '-p=$path']); + + final files = Directory(path).listSync(); + final fileName = files.first.path.split('/').last; + expect(fileName, contains('create_users')); + }); + + test('Names migration CreateTables for multiple table models', () async { + final multiModels = [ + ...models, + const ModelSchema( + className: 'Post', + tableName: 'posts', + fields: [ + ModelFieldSchema( + name: 'id', + columnName: 'id', + dbType: FieldType.serial, + primaryKey: true, + autoIncrement: true, + ), + ModelFieldSchema(name: 'title', columnName: 'title', dbType: FieldType.varchar, maxLength: 200), + ], + ), + ]; + + cli = JaoCli( + MigrationRunnerConfig( + database: DatabaseConfig.sqlite(':memory:'), + adapter: const SqliteAdapter(), + migrations: [], + modelSchemas: multiModels, + ), + ); + + final path = '${tempDir.path}/migrations'; + await cli.run(['makemigrations', '-p=$path']); + + final files = Directory(path).listSync(); + final content = File(files.first.path).readAsStringSync(); + expect(content, contains('class CreateTables')); + }); + + group('generateMigrationName', () { + test('returns custom name in PascalCase', () { + expect(generateMigrationName('add_users', []), equals('AddUsers')); + }); + + test('returns EmptyMigration for empty operations', () { + expect(generateMigrationName(null, []), equals('EmptyMigration')); + }); + + test('returns CreateTable name for single CreateTable', () { + final ops = [ + CreateTable(TableDefinition( + name: 'users', + columns: [const ColumnDefinition(name: 'id', type: FieldType.serial)], + )), + ]; + expect(generateMigrationName(null, ops), equals('CreateUsers')); + }); + + test('returns CreateTables for multiple CreateTable ops', () { + final ops = [ + CreateTable(TableDefinition( + name: 'users', + columns: [const ColumnDefinition(name: 'id', type: FieldType.serial)], + )), + CreateTable(TableDefinition( + name: 'posts', + columns: [const ColumnDefinition(name: 'id', type: FieldType.serial)], + )), + ]; + expect(generateMigrationName(null, ops), equals('CreateTables')); + }); + + test('returns AddColumnToTable for AddColumn', () { + const ops = [AddColumn('users', ColumnDefinition(name: 'age', type: FieldType.integer))]; + expect(generateMigrationName(null, ops), equals('AddAgeToUsers')); + }); + + test('returns DropColumnFromTable for DropColumn', () { + const ops = [DropColumn('users', 'age')]; + expect(generateMigrationName(null, ops), equals('DropAgeFromUsers')); + }); + + test('returns AlterColumnOnTable for AlterColumn', () { + const ops = [AlterColumn(ColumnModification(table: 'users', column: 'email'))]; + expect(generateMigrationName(null, ops), equals('AlterEmailOnUsers')); + }); + + test('returns AddFkOnTable for AddForeignKey', () { + const ops = [ + AddForeignKey( + 'posts', ForeignKeyDefinition(column: 'user_id', referencedTable: 'users', referencedColumn: 'id')), + ]; + expect(generateMigrationName(null, ops), equals('AddFkOnPosts')); + }); + + test('returns AddIndexOnTable for CreateIndex', () { + final ops = [ + CreateIndex('users', const IndexDefinition(name: 'idx_users_email', columns: ['email'])), + ]; + expect(generateMigrationName(null, ops), equals('AddIndexOnUsers')); + }); + + test('returns DropConstraintOnTable for DropConstraint', () { + const ops = [DropConstraint('posts', 'fk_posts_user_id')]; + expect(generateMigrationName(null, ops), equals('DropConstraintOnPosts')); + }); + + test('returns RenameOldToNew for RenameTable', () { + const ops = [RenameTable('users', 'accounts')]; + expect(generateMigrationName(null, ops), equals('RenameUsersToAccounts')); + }); + + test('returns RenameColumnOnTable for RenameColumn', () { + const ops = [RenameColumn('users', 'name', 'full_name')]; + expect(generateMigrationName(null, ops), equals('RenameNameOnUsers')); + }); + }); + test('Error when no model schemas found', () async { cli = JaoCli( MigrationRunnerConfig(