diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..ac03088e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# 1.1.5 +Deprecated several commands, in favor of the `make` +command: +* `controller` +* `plugin` +* `service` +* `test` + +The `rename` command will now replace *all* occurrences +of the old project names with the new one in `config/` +YAML files, and also operates on the glob `config/**/*.yaml`. + +Changed the call to run `angel start` to run `dart bin/server.dart` instead, after an +`init` command. \ No newline at end of file diff --git a/bin/angel.dart b/bin/angel.dart index 97f470fb..90aa8cd0 100644 --- a/bin/angel.dart +++ b/bin/angel.dart @@ -22,6 +22,7 @@ main(List args) async { ..addCommand(new StartCommand()) ..addCommand(new RenameCommand()) ..addCommand(new UpdateCommand()) + ..addCommand(new MakeCommand()) ..addCommand(new VersionCommand()); return await runner.run(args).then((_) {}).catchError((exc) { diff --git a/lib/src/commands/commands.dart b/lib/src/commands/commands.dart index 4264689c..d5360558 100644 --- a/lib/src/commands/commands.dart +++ b/lib/src/commands/commands.dart @@ -4,6 +4,7 @@ export 'controller.dart'; export "doctor.dart"; export "key.dart"; export "init.dart"; +export "make.dart"; export "plugin.dart"; export "rename.dart"; export "service.dart"; diff --git a/lib/src/commands/controller.dart b/lib/src/commands/controller.dart index fbf5a06d..ce9458e6 100644 --- a/lib/src/commands/controller.dart +++ b/lib/src/commands/controller.dart @@ -4,6 +4,7 @@ import 'package:code_builder/code_builder.dart'; import "package:console/console.dart"; import 'package:pubspec/pubspec.dart'; import 'package:recase/recase.dart'; +import 'deprecated.dart'; class ControllerCommand extends Command { final TextPen _pen = new TextPen(); @@ -17,6 +18,8 @@ class ControllerCommand extends Command { @override run() async { + warnDeprecated(this.name, _pen); + final name = await readInput("Name of Controller: "), recase = new ReCase(name), lower = recase.snakeCase; diff --git a/lib/src/commands/deprecated.dart b/lib/src/commands/deprecated.dart new file mode 100644 index 00000000..992da105 --- /dev/null +++ b/lib/src/commands/deprecated.dart @@ -0,0 +1,10 @@ +import 'package:console/console.dart'; + +void warnDeprecated(String command, [TextPen pen]) { + pen ??= new TextPen(); + pen + ..yellow() + ..call('The `$command` command is deprecated, and will be removed by v1.2.0.') + ..call() + ..reset(); +} diff --git a/lib/src/commands/init.dart b/lib/src/commands/init.dart index 57e1a8e3..c854f249 100644 --- a/lib/src/commands/init.dart +++ b/lib/src/commands/init.dart @@ -60,7 +60,7 @@ class InitCommand extends Command { ..text('\nCongratulations! You are ready to start developing with Angel!') ..text('\nTo start the server (with file watching), run ') ..magenta() - ..text('`angel start`') + ..text('`dart bin/server.dart`') ..normal() ..text(' in your terminal.') ..text('\n\nFind more documentation about Angel:') @@ -192,7 +192,8 @@ const BoilerplateInfo ormBoilerplate = const BoilerplateInfo( const List allBoilerplates = const [ fullApplicationBoilerplate, - lightBoilerplate + lightBoilerplate, + ormBoilerplate ]; class BoilerplateInfo { diff --git a/lib/src/commands/make.dart b/lib/src/commands/make.dart new file mode 100644 index 00000000..0a71c5be --- /dev/null +++ b/lib/src/commands/make.dart @@ -0,0 +1,22 @@ +import 'package:args/command_runner.dart'; +import 'make/controller.dart'; +import 'make/model.dart'; +import 'make/plugin.dart'; +import 'make/service.dart'; +import 'make/test.dart'; + +class MakeCommand extends Command { + @override + String get name => 'make'; + + @override + String get description => 'Generates common code for your project, such as projects and controllers.'; + + MakeCommand() { + addSubcommand(new ControllerCommand()); + addSubcommand(new ModelCommand()); + addSubcommand(new PluginCommand()); + addSubcommand(new TestCommand()); + addSubcommand(new ServiceCommand()); + } +} \ No newline at end of file diff --git a/lib/src/commands/make/controller.dart b/lib/src/commands/make/controller.dart new file mode 100644 index 00000000..ae36bd80 --- /dev/null +++ b/lib/src/commands/make/controller.dart @@ -0,0 +1,105 @@ +import 'dart:io'; +import 'package:args/command_runner.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:console/console.dart'; +import 'package:pubspec/pubspec.dart'; +import 'package:recase/recase.dart'; +import 'maker.dart'; + +class ControllerCommand extends Command { + final TextPen _pen = new TextPen(); + + @override + String get name => 'controller'; + + @override + String get description => 'Generates a controller class.'; + + ControllerCommand() { + argParser + ..addFlag('websocket', + abbr: 'w', + help: + 'Generates a WebSocketController, instead of an HTTP controller.', + negatable: false) + ..addOption('name', + abbr: 'n', help: 'Specifies a name for the model class.') + ..addOption('output-dir', + help: 'Specifies a directory to create the controller class in.', + defaultsTo: 'lib/src/controllers'); + } + + @override + run() async { + var pubspec = await PubSpec.load(Directory.current); + String name; + if (argResults.wasParsed('name')) name = argResults['name']; + + if (name?.isNotEmpty != true) { + var p = new Prompter('Name of Controller class: '); + name = await p.prompt(checker: (s) => s.isNotEmpty); + } + + List deps = [ + const MakerDependency('angel_framework', '^1.0.0') + ]; + + var rc = new ReCase(name); + var controllerLib = + new LibraryBuilder('${pubspec.name}.src.controllers.${rc.snakeCase}'); + + if (argResults['websocket']) { + deps.add(const MakerDependency('angel_websocket', '^1.0.0')); + controllerLib.addDirective( + new ImportBuilder('package:angel_websocket/server.dart')); + } else + controllerLib.addDirective( + new ImportBuilder('package:angel_framework/angel_framework.dart')); + + TypeBuilder parentType = new TypeBuilder( + argResults['websocket'] ? 'WebSocketController' : 'Controller'); + ClassBuilder clazz = + new ClassBuilder('${rc.pascalCase}Controller', asExtends: parentType); + controllerLib.addMember(clazz); + + if (argResults['websocket']) { + var meth = new MethodBuilder('hello', returnType: lib$core.$void); + meth.addAnnotation(new TypeBuilder('ExposeWs') + .constInstance([literal('get_${rc.snakeCase}')])); + meth.addPositional( + parameter('socket', [new TypeBuilder('WebSocketContext')])); + meth.addStatement(reference('socket').invoke('send', [ + literal('got_${rc.snakeCase}'), + map({'message': literal('Hello, world!')}) + ])); + clazz.addMethod(meth); + } else { + clazz.addAnnotation(new TypeBuilder('Expose') + .constInstance([literal('/${rc.snakeCase}')])); + + var meth = new MethodBuilder('hello', + returnType: lib$core.String, returns: literal('Hello, world!')); + meth.addAnnotation( + new TypeBuilder('Expose').constInstance([literal('/')])); + clazz.addMethod(meth); + } + + var outputDir = new Directory.fromUri( + Directory.current.uri.resolve(argResults['output-dir'])); + var controllerFile = + new File.fromUri(outputDir.uri.resolve('${rc.snakeCase}.dart')); + if (!await controllerFile.exists()) + await controllerFile.create(recursive: true); + await controllerFile + .writeAsString(prettyToSource(controllerLib.buildAst())); + _pen + ..green() + ..call( + '${Icon.CHECKMARK} Created controller file "${controllerFile.absolute.path}".') + ..call() + ..reset(); + + if (deps.isNotEmpty) await depend(deps); + } +} diff --git a/lib/src/commands/make/maker.dart b/lib/src/commands/make/maker.dart new file mode 100644 index 00000000..bbee58b4 --- /dev/null +++ b/lib/src/commands/make/maker.dart @@ -0,0 +1,58 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:pubspec/pubspec.dart'; +import 'package:pub_semver/pub_semver.dart'; +import '../pub.dart'; + +class MakerDependency { + final String name, version; + final bool dev; + const MakerDependency(this.name, this.version, {this.dev: false}); +} + +Future depend(Iterable deps) async { + var pubspec = await PubSpec.load(Directory.current); + Map newDeps = {}, newDevDeps = {}; + + for (var dep in deps) { + var isPresent = false; + if (dep.dev) + isPresent = pubspec.devDependencies.containsKey(dep.name); + else + isPresent = pubspec.dependencies.containsKey(dep.name); + + if (!isPresent) { + print('Installing ${dep.name}@${dep.version}...'); + + if (dep.dev) + newDevDeps[dep.name] = + new HostedReference(new VersionConstraint.parse(dep.version)); + else + newDeps[dep.name] = + new HostedReference(new VersionConstraint.parse(dep.version)); + } + + if (newDeps.isNotEmpty || newDevDeps.isNotEmpty) { + var newPubspec = pubspec.copy( + dependencies: + new Map.from(pubspec.dependencies) + ..addAll(newDeps), + devDependencies: + new Map.from(pubspec.devDependencies) + ..addAll(newDevDeps)); + + await newPubspec.save(Directory.current); + var pubPath = resolvePub(); + + print('Now running `$pubPath get`...'); + + var pubGet = await Process.start(pubPath, ['get']); + pubGet.stdout.listen(stdout.add); + pubGet.stderr.listen(stderr.add); + + var code = await pubGet.exitCode; + + if (code != 0) throw 'pub get terminated with exit code $code'; + } + } +} diff --git a/lib/src/commands/make/model.dart b/lib/src/commands/make/model.dart new file mode 100644 index 00000000..208678af --- /dev/null +++ b/lib/src/commands/make/model.dart @@ -0,0 +1,161 @@ +import 'dart:io'; +import 'package:args/command_runner.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:console/console.dart'; +import 'package:inflection/inflection.dart'; +import 'package:pubspec/pubspec.dart'; +import 'package:recase/recase.dart'; +import 'maker.dart'; + +class ModelCommand extends Command { + final TextPen _pen = new TextPen(); + + @override + String get name => 'model'; + + @override + String get description => 'Generates a model class.'; + + ModelCommand() { + argParser + ..addFlag('migration', + abbr: 'm', + help: 'Generate an angel_orm migration file.', + negatable: false) + ..addFlag('orm', help: 'Generate angel_orm code.', negatable: false) + ..addFlag('serializable', + help: 'Generate angel_serialize annotations.', defaultsTo: true) + ..addOption('name', + abbr: 'n', help: 'Specifies a name for the model class.') + ..addOption('output-dir', + help: 'Specifies a directory to create the model class in.', + defaultsTo: 'lib/src/models') + ..addOption('migration-dir', + help: 'Specifies a directory to create the migration class in.', + defaultsTo: 'tool/migrations'); + } + + @override + run() async { + var pubspec = await PubSpec.load(Directory.current); + String name; + if (argResults.wasParsed('name')) name = argResults['name']; + + if (name?.isNotEmpty != true) { + var p = new Prompter('Name of Model class: '); + name = await p.prompt(checker: (s) => s.isNotEmpty); + } + + List deps = [ + const MakerDependency('angel_framework', '^1.0.0'), + const MakerDependency('angel_model', '^1.0.0'), + ]; + + var rc = new ReCase(name); + var modelLib = + new LibraryBuilder('${pubspec.name}.src.models.${rc.snakeCase}'); + modelLib.addDirective( + new ImportBuilder('package:angel_model/angel_model.dart')); + + var needsSerialize = argResults['serializable'] || argResults['orm']; + + if (needsSerialize) { + modelLib.addDirective( + new ImportBuilder('package:angel_serialize/angel_serialize.dart')); + deps.add(const MakerDependency('angel_serialize', '^1.0.0-alpha')); + } + + if (argResults['orm']) { + modelLib + .addDirective(new ImportBuilder('package:angel_orm/angel_orm.dart')); + deps.add(const MakerDependency('angel_orm', '^1.0.0-alpha')); + } + + var modelClazz = new ClassBuilder( + needsSerialize ? '_${rc.pascalCase}' : rc.pascalCase, + asExtends: new TypeBuilder('Model')); + modelLib.addMember(modelClazz); + + if (needsSerialize) { + modelLib.addDirective(new PartBuilder('${rc.snakeCase}.g.dart')); + modelClazz.addAnnotation(reference('serializable')); + } + + if (argResults['orm']) { + modelClazz.addAnnotation(reference('orm')); + } + + // Save model file + var outputDir = new Directory.fromUri( + Directory.current.uri.resolve(argResults['output-dir'])); + var modelFile = + new File.fromUri(outputDir.uri.resolve('${rc.snakeCase}.dart')); + if (!await modelFile.exists()) await modelFile.create(recursive: true); + await modelFile.writeAsString(prettyToSource(modelLib.buildAst())); + _pen + ..green() + ..call( + '${Icon.CHECKMARK} Created model file "${modelFile.absolute.path}".') + ..call() + ..reset(); + + if (argResults['migration']) { + deps.add( + const MakerDependency('angel_migration', '^1.0.0-alpha', dev: true)); + + var migrationLib = new LibraryBuilder() + ..addDirective( + new ImportBuilder('package:angel_migration/angel_migration.dart')); + var migrationClazz = new ClassBuilder('${rc.pascalCase}Migration', + asExtends: new TypeBuilder('Migration')); + migrationLib.addMember(migrationClazz); + var tableName = pluralize(rc.snakeCase); + + // up() + var up = new MethodBuilder('up', returnType: lib$core.$void); + migrationClazz.addMethod(up); + up.addAnnotation(lib$core.override); + up.addPositional(parameter('schema', [new TypeBuilder('Schema')])); + + // (table) { ... } + var callback = new MethodBuilder.closure(); + callback.addPositional(parameter('table')); + + var cascade = reference('table').cascade((table) => [ + table.invoke('serial', [literal('id')]).invoke('primaryKey', []), + table.invoke('date', [literal('created_at')]), + table.invoke('date', [literal('updated_at')]) + ]); + callback.addStatement(cascade); + + up.addStatement(reference('schema').invoke('create', [callback])); + + // down() + var down = new MethodBuilder('down', returnType: lib$core.$void); + migrationClazz.addMethod(down); + down.addAnnotation(lib$core.override); + down.addPositional(parameter('schema', [new TypeBuilder('Schema')])); + down.addStatement( + reference('schema').invoke('drop', [literal(tableName)])); + + // Save migration file + var migrationDir = new Directory.fromUri( + Directory.current.uri.resolve(argResults['migration-dir'])); + var migrationFile = + new File.fromUri(migrationDir.uri.resolve('${rc.snakeCase}.dart')); + if (!await migrationFile.exists()) + await migrationFile.create(recursive: true); + await migrationFile + .writeAsString(prettyToSource(migrationLib.buildAst())); + _pen + ..green() + ..call( + '${Icon.CHECKMARK} Created migration file "${migrationFile.absolute.path}".') + ..call() + ..reset(); + } + + if (deps.isNotEmpty) await depend(deps); + } +} diff --git a/lib/src/commands/make/plugin.dart b/lib/src/commands/make/plugin.dart new file mode 100644 index 00000000..b6c1183f --- /dev/null +++ b/lib/src/commands/make/plugin.dart @@ -0,0 +1,74 @@ +import 'dart:io'; +import 'package:args/command_runner.dart'; +import "package:console/console.dart"; +import 'package:dart_style/dart_style.dart'; +import 'package:pubspec/pubspec.dart'; +import 'package:recase/recase.dart'; +import 'maker.dart'; + +class PluginCommand extends Command { + final TextPen _pen = new TextPen(); + + @override + String get name => "plugin"; + + @override + String get description => "Creates a new plug-in within the given project."; + + PluginCommand() { + argParser + ..addOption('name', + abbr: 'n', help: 'Specifies a name for the plug-in class.') + ..addOption('output-dir', + help: 'Specifies a directory to create the plug-in class in.', + defaultsTo: 'lib/src/config/plugins'); + } + + @override + run() async { + var pubspec = await PubSpec.load(Directory.current); + String name; + if (argResults.wasParsed('name')) name = argResults['name']; + + if (name?.isNotEmpty != true) { + var p = new Prompter('Name of Controller class: '); + name = await p.prompt(checker: (s) => s.isNotEmpty); + } + + List deps = [ + const MakerDependency('angel_framework', '^1.0.0') + ]; + + var rc = new ReCase(name); + final pluginDir = new Directory.fromUri( + Directory.current.uri.resolve(argResults['output-dir'])); + final pluginFile = + new File.fromUri(pluginDir.uri.resolve("${rc.snakeCase}.dart")); + if (!await pluginFile.exists()) await pluginFile.create(recursive: true); + await pluginFile.writeAsString( + new DartFormatter().format(_generatePlugin(pubspec, rc))); + + if (deps.isNotEmpty) await depend(deps); + + _pen.green(); + _pen( + '${Icon.CHECKMARK} Successfully generated plug-in file "${pluginFile.absolute.path}".'); + _pen(); + } + + String _generatePlugin(PubSpec pubspec, ReCase rc) { + return ''' +library ${pubspec.name}.src.config.plugins.${rc.snakeCase}; + +import 'dart:async'; +import 'package:angel_framework/angel_framework.dart'; + +class ${rc.pascalCase}Plugin extends AngelPlugin { + @override + Future call(Angel app) async { + // Work some magic... + } +} + '''; + } +} diff --git a/lib/src/commands/make/service.dart b/lib/src/commands/make/service.dart new file mode 100644 index 00000000..62707fd4 --- /dev/null +++ b/lib/src/commands/make/service.dart @@ -0,0 +1,126 @@ +import 'dart:io'; +import 'package:args/command_runner.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:console/console.dart'; +import 'package:inflection/inflection.dart'; +import 'package:pubspec/pubspec.dart'; +import 'package:recase/recase.dart'; +import '../service_generators/service_generators.dart'; +import 'maker.dart'; + +class ServiceCommand extends Command { + final TextPen _pen = new TextPen(); + + @override + String get name => 'service'; + + @override + String get description => 'Generates an Angel service.'; + + ServiceCommand() { + argParser + ..addFlag('typed', + abbr: 't', + help: 'Wrap the generated service in a `TypedService` instance.', + negatable: false) + ..addOption('name', + abbr: 'n', help: 'Specifies a name for the service file.') + ..addOption('output-dir', + help: 'Specifies a directory to create the service file.', + defaultsTo: 'lib/src/services'); + } + + @override + run() async { + var pubspec = await PubSpec.load(Directory.current); + String name; + if (argResults.wasParsed('name')) name = argResults['name']; + + if (name?.isNotEmpty != true) { + var p = new Prompter('Name of Service: '); + name = await p.prompt(checker: (s) => s.isNotEmpty); + } + + List deps = [ + const MakerDependency('angel_framework', '^1.0.0') + ]; + + var rc = new ReCase(name); + var serviceLib = + new LibraryBuilder('${pubspec.name}.src.services.${rc.snakeCase}'); + + ServiceGenerator generator; + + var chooser = new Chooser( + serviceGenerators.map((g) => g.name).toList(), + message: 'What type of service would you like to create? '); + var type = await chooser.choose(); + + generator = + serviceGenerators.firstWhere((g) => g.name == type, orElse: () => null); + + if (generator == null) { + _pen.red(); + _pen('${Icon.BALLOT_X} \'$type\' services are not yet implemented. :('); + _pen(); + throw 'Unrecognized service type: "$type".'; + } + + for (var dep in generator.dependencies) { + if (!deps.any((d) => d.name == dep.name)) deps.add(dep); + } + + if (generator.goesFirst) { + generator.applyToLibrary(serviceLib, name, rc.snakeCase); + serviceLib.addMember( + new ImportBuilder('package:angel_framework/angel_framework.dart')); + } else { + serviceLib.addMember( + new ImportBuilder('package:angel_framework/angel_framework.dart')); + generator.applyToLibrary(serviceLib, name, rc.snakeCase); + } + + if (argResults['typed']) { + serviceLib + ..addMember(new ImportBuilder('../models/${rc.snakeCase}.dart')); + } + + // configureServer() {} + var configureServer = new MethodBuilder('configureServer', + returnType: new TypeBuilder('AngelConfigurer')); + generator.applyToConfigureServer(configureServer, name, rc.snakeCase); + + // return (Angel app) async {} + var closure = new MethodBuilder.closure(modifier: MethodModifier.asAsync) + ..addPositional(parameter('app', [new TypeBuilder('Angel')])); + generator.beforeService(closure, name, rc.snakeCase); + + // app.use('/api/todos', new MapService()); + var service = generator.createInstance(closure, name, rc.snakeCase); + + if (argResults['typed']) { + service = new TypeBuilder('TypedService', + genericTypes: [new TypeBuilder(rc.pascalCase)]) + .newInstance([service]); + } + + closure.addStatement(reference('app') + .invoke('use', [literal('/api/${pluralize(rc.snakeCase)}'), service])); + configureServer.addStatement(closure.asReturn()); + serviceLib.addMember(configureServer); + + final outputDir = new Directory.fromUri( + Directory.current.uri.resolve(argResults['output-dir'])); + final serviceFile = + new File.fromUri(outputDir.uri.resolve("${rc.snakeCase}.dart")); + if (!await serviceFile.exists()) await serviceFile.create(recursive: true); + await serviceFile.writeAsString(prettyToSource(serviceLib.buildAst())); + + _pen.green(); + _pen( + '${Icon.CHECKMARK} Successfully generated service file "${serviceFile.absolute.path}".'); + _pen(); + + if (deps.isNotEmpty) await depend(deps); + } +} diff --git a/lib/src/commands/make/test.dart b/lib/src/commands/make/test.dart new file mode 100644 index 00000000..417e6eb5 --- /dev/null +++ b/lib/src/commands/make/test.dart @@ -0,0 +1,115 @@ +import 'dart:io'; +import 'package:args/command_runner.dart'; +import "package:console/console.dart"; +import 'package:dart_style/dart_style.dart'; +import 'package:pubspec/pubspec.dart'; +import 'package:recase/recase.dart'; +import 'maker.dart'; + +class TestCommand extends Command { + final TextPen _pen = new TextPen(); + + @override + String get name => "test"; + + @override + String get description => "Creates a new test within the given project."; + + TestCommand() { + argParser + ..addFlag('run-configuration', + help: 'Generate a run configuration for JetBrains IDE\'s.', + defaultsTo: true) + ..addOption('name', + abbr: 'n', help: 'Specifies a name for the plug-in class.') + ..addOption('output-dir', + help: 'Specifies a directory to create the plug-in class in.', + defaultsTo: 'test'); + } + + @override + run() async { + var pubspec = await PubSpec.load(Directory.current); + String name; + if (argResults.wasParsed('name')) name = argResults['name']; + + if (name?.isNotEmpty != true) { + var p = new Prompter('Name of Test: '); + name = await p.prompt(checker: (s) => s.isNotEmpty); + } + + List deps = [ + const MakerDependency('angel_framework', '^1.0.0'), + const MakerDependency('angel_test', '^1.0.0', dev: true), + const MakerDependency('test', '^0.12.0', dev: true), + ]; + + var rc = new ReCase(name); + final testDir = new Directory.fromUri( + Directory.current.uri.resolve(argResults['output-dir'])); + final testFile = + new File.fromUri(testDir.uri.resolve("${rc.snakeCase}_test.dart")); + if (!await testFile.exists()) await testFile.create(recursive: true); + await testFile + .writeAsString(new DartFormatter().format(_generateTest(pubspec, rc))); + + if (deps.isNotEmpty) await depend(deps); + + _pen.green(); + _pen( + '${Icon.CHECKMARK} Successfully generated test file "${testFile.absolute.path}".'); + _pen(); + + if (argResults['run-configuration']) { + final runConfig = new File.fromUri(Directory.current.uri + .resolve('.idea/runConfigurations/${name}_Tests.xml')); + + if (!await runConfig.exists()) await runConfig.create(recursive: true); + await runConfig.writeAsString(_generateRunConfiguration(name, rc)); + + _pen.reset(); + _pen.green(); + _pen( + '${Icon.CHECKMARK} Successfully generated run configuration "$name Tests" at "${runConfig.absolute.path}".'); + _pen(); + } + } + + _generateRunConfiguration(String name, ReCase rc) { + return ''' + + + + +''' + .trim(); + } + + String _generateTest(PubSpec pubspec, ReCase rc) { + return ''' +import 'dart:io'; +import 'package:${pubspec.name}/${pubspec.name}.dart'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_test/angel_test.dart'; +import 'package:test/test.dart'; + +main() async { + TestClient client; + + setUp(() async { + var app = await createServer(); + client = await connectTo(app); + }); + + tearDown(() => client.close()); + + test('${rc.snakeCase}', () async { + final response = await client.get('/${rc.snakeCase}'); + expect(response, hasStatus(HttpStatus.OK)); + }); +} + '''; + } +} diff --git a/lib/src/commands/plugin.dart b/lib/src/commands/plugin.dart index 2ded982f..49d5fc10 100644 --- a/lib/src/commands/plugin.dart +++ b/lib/src/commands/plugin.dart @@ -4,6 +4,7 @@ import "package:console/console.dart"; import 'package:dart_style/dart_style.dart'; import 'package:pubspec/pubspec.dart'; import 'package:recase/recase.dart'; +import 'deprecated.dart'; class PluginCommand extends Command { final TextPen _pen = new TextPen(); @@ -16,6 +17,8 @@ class PluginCommand extends Command { @override run() async { + warnDeprecated(this.name, _pen); + var pubspec = await PubSpec.load(Directory.current); final name = await readInput("Name of Plugin: "), lower = new ReCase(name).snakeCase; diff --git a/lib/src/commands/rename.dart b/lib/src/commands/rename.dart index 2c90d961..f64c4047 100644 --- a/lib/src/commands/rename.dart +++ b/lib/src/commands/rename.dart @@ -3,6 +3,7 @@ import 'package:analyzer/analyzer.dart'; import 'package:args/command_runner.dart'; import 'package:console/console.dart'; import 'package:dart_style/dart_style.dart'; +import 'package:glob/glob.dart'; import 'package:pubspec/pubspec.dart'; import 'pub.dart'; @@ -63,14 +64,15 @@ renamePubspec(Directory dir, String oldName, String newName) async { renameDartFiles(Directory dir, String oldName, String newName) async { // Try to replace MongoDB URL - var defaultYaml = new File.fromUri(dir.uri.resolve('config/default.yaml')); + var configGlob = new Glob('config/**/*.yaml'); - if (await defaultYaml.exists()) { - print('Changing MongoDB URL in file "${defaultYaml.absolute.path}"...'); - var contents = await defaultYaml.readAsString(); - contents = contents.replaceAll('mongodb://localhost:27017/$oldName', - 'mongodb://localhost:27017/$newName'); - await defaultYaml.writeAsString(contents); + await for (var yamlFile in configGlob.list(root: dir.absolute.path)) { + if (yamlFile is File) { + print('Replacing occurrences of "$oldName" with "$newName" in file "${yamlFile.absolute.path}"...'); + var contents = await yamlFile.readAsString(); + contents = contents.replaceAll(oldName, newName); + await yamlFile.writeAsString(contents); + } } var entry = new File.fromUri(dir.uri.resolve('lib/$oldName.dart')); diff --git a/lib/src/commands/service.dart b/lib/src/commands/service.dart index 1db296e7..9fd28165 100644 --- a/lib/src/commands/service.dart +++ b/lib/src/commands/service.dart @@ -7,16 +7,9 @@ import 'package:inflection/inflection.dart'; import 'package:pubspec/pubspec.dart'; import 'package:recase/recase.dart'; import 'service_generators/service_generators.dart'; +import 'deprecated.dart'; import 'init.dart' show preBuild; -const List GENERATORS = const [ - const MapServiceGenerator(), - const FileServiceGenerator(), - const MongoServiceGenerator(), - const RethinkServiceGenerator(), - const CustomServiceGenerator() -]; - class ServiceCommand extends Command { final TextPen _pen = new TextPen(); @@ -28,10 +21,12 @@ class ServiceCommand extends Command { @override run() async { + warnDeprecated(this.name, _pen); + var pubspec = await PubSpec.load(Directory.current); var name = await readInput('Name of Service (not plural): '); var chooser = new Chooser( - GENERATORS.map((g) => g.name).toList(), + serviceGenerators.map((g) => g.name).toList(), message: 'What type of service would you like to create? '); var type = await chooser.choose(); @@ -40,7 +35,7 @@ class ServiceCommand extends Command { var typed = (await chooser.choose()) == 'Yes'; var generator = - GENERATORS.firstWhere((g) => g.name == type, orElse: () => null); + serviceGenerators.firstWhere((g) => g.name == type, orElse: () => null); if (generator == null) { _pen.blue(); diff --git a/lib/src/commands/service_generators/file_service.dart b/lib/src/commands/service_generators/file_service.dart index 7778e2d9..d536cbc8 100644 --- a/lib/src/commands/service_generators/file_service.dart +++ b/lib/src/commands/service_generators/file_service.dart @@ -1,12 +1,17 @@ import 'generator.dart'; import 'package:code_builder/code_builder.dart'; import 'package:inflection/inflection.dart'; +import '../make/maker.dart'; class FileServiceGenerator extends ServiceGenerator { const FileServiceGenerator() : super('Persistent JSON File'); @override - bool get createsModel => false; + List get dependencies => + const [const MakerDependency('angel_file_service', '^1.0.0')]; + + @override + bool get goesFirst => true; @override void applyToLibrary(LibraryBuilder library, String name, String lower) { diff --git a/lib/src/commands/service_generators/generator.dart b/lib/src/commands/service_generators/generator.dart index 79ccc4f8..970bb0fa 100644 --- a/lib/src/commands/service_generators/generator.dart +++ b/lib/src/commands/service_generators/generator.dart @@ -1,16 +1,30 @@ import 'package:code_builder/code_builder.dart'; +import '../make/maker.dart'; class ServiceGenerator { final String name; const ServiceGenerator(this.name); + List get dependencies => []; + + @deprecated bool get createsModel => true; + + @deprecated bool get createsValidator => true; + + @deprecated bool get exportedInServiceLibrary => true; + + @deprecated bool get injectsSingleton => false; + + @deprecated bool get shouldRunBuild => false; + bool get goesFirst => false; + void applyToLibrary(LibraryBuilder library, String name, String lower) {} void beforeService(MethodBuilder methodBuilder, String name, String lower) {} diff --git a/lib/src/commands/service_generators/mongo.dart b/lib/src/commands/service_generators/mongo.dart index 16e7c363..c81153ef 100644 --- a/lib/src/commands/service_generators/mongo.dart +++ b/lib/src/commands/service_generators/mongo.dart @@ -1,10 +1,15 @@ import 'generator.dart'; import 'package:code_builder/code_builder.dart'; import 'package:inflection/inflection.dart'; +import '../make/maker.dart'; class MongoServiceGenerator extends ServiceGenerator { const MongoServiceGenerator() : super('MongoDB'); + @override + List get dependencies => + const [const MakerDependency('angel_mongo', '^1.0.0')]; + @override bool get createsModel => false; diff --git a/lib/src/commands/service_generators/rethink.dart b/lib/src/commands/service_generators/rethink.dart index 22d0bd64..17c55b44 100644 --- a/lib/src/commands/service_generators/rethink.dart +++ b/lib/src/commands/service_generators/rethink.dart @@ -1,10 +1,15 @@ import 'generator.dart'; import 'package:code_builder/code_builder.dart'; import 'package:inflection/inflection.dart'; +import '../make/maker.dart'; class RethinkServiceGenerator extends ServiceGenerator { const RethinkServiceGenerator() : super('RethinkDB'); + @override + List get dependencies => + const [const MakerDependency('angel_rethink', '^1.0.0')]; + @override bool get createsModel => false; diff --git a/lib/src/commands/service_generators/service_generators.dart b/lib/src/commands/service_generators/service_generators.dart index 658cc16b..0dfd685e 100644 --- a/lib/src/commands/service_generators/service_generators.dart +++ b/lib/src/commands/service_generators/service_generators.dart @@ -1,6 +1,15 @@ -export 'custom.dart'; -export 'file_service.dart'; +import 'custom.dart'; +import 'file_service.dart'; +import 'generator.dart'; +import 'map.dart'; +import 'mongo.dart'; +import 'rethink.dart'; export 'generator.dart'; -export 'map.dart'; -export 'mongo.dart'; -export 'rethink.dart'; \ No newline at end of file + +const List serviceGenerators = const [ + const MapServiceGenerator(), + const FileServiceGenerator(), + const MongoServiceGenerator(), + const RethinkServiceGenerator(), + const CustomServiceGenerator() +]; \ No newline at end of file diff --git a/lib/src/commands/start.dart b/lib/src/commands/start.dart index eae1456c..696ebf09 100644 --- a/lib/src/commands/start.dart +++ b/lib/src/commands/start.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:args/command_runner.dart'; import 'package:watcher/watcher.dart'; import 'package:yaml/yaml.dart'; +import 'deprecated.dart'; import 'pub.dart'; Process server; @@ -34,6 +35,8 @@ class StartCommand extends Command { @override run() async { + warnDeprecated(this.name); + stderr ..writeln( 'WARNING: `angel start` is now deprecated, in favor of `package:angel_hot`.') diff --git a/lib/src/commands/test.dart b/lib/src/commands/test.dart index b74da8fd..b519b62e 100644 --- a/lib/src/commands/test.dart +++ b/lib/src/commands/test.dart @@ -4,6 +4,7 @@ import "package:console/console.dart"; import 'package:dart_style/dart_style.dart'; import 'package:pubspec/pubspec.dart'; import 'package:recase/recase.dart'; +import 'deprecated.dart'; class TestCommand extends Command { final TextPen _pen = new TextPen(); @@ -16,6 +17,8 @@ class TestCommand extends Command { @override run() async { + warnDeprecated(this.name, _pen); + final name = await readInput("Name of Test: "), lower = new ReCase(name).snakeCase; final testDir = new Directory("test/services");