From 11b81811a671574281c4c8ff84a980b605cab715 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Sun, 31 Jan 2021 23:06:17 -0600 Subject: [PATCH] feat(create): generate project using very_good_core template (#12) --- analysis_options.yaml | 1 + lib/src/commands/create.dart | 46 ++++++++++++++++++++++++------ pubspec.yaml | 1 + test/src/commands/create_test.dart | 40 +++++++++++++++++++++++--- 4 files changed, 76 insertions(+), 12 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index d9bc91a3..688da44c 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -2,3 +2,4 @@ include: package:very_good_analysis/analysis_options.yaml analyzer: exclude: - "**/version.dart" + - "bricks/**" diff --git a/lib/src/commands/create.dart b/lib/src/commands/create.dart index 62f6ca51..a624117b 100644 --- a/lib/src/commands/create.dart +++ b/lib/src/commands/create.dart @@ -1,10 +1,18 @@ import 'dart:io'; +import 'package:args/args.dart'; import 'package:args/command_runner.dart'; +import 'package:io/ansi.dart'; import 'package:io/io.dart'; import 'package:mason/mason.dart'; +import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; +const _veryGoodCoreGitPath = GitPath( + 'git@github.com:VeryGoodOpenSource/very_good_cli.git', + path: 'bricks/very_good_core', +); + // A valid Dart identifier that can be used for a package, i.e. no // capital letters. // https://dart.dev/guides/language/language-tour#important-concepts @@ -15,7 +23,11 @@ final RegExp _identifierRegExp = RegExp('[a-z_][a-z0-9_]*'); /// {@endtemplate} class CreateCommand extends Command { /// {@macro create_command} - CreateCommand({Logger logger}) : _logger = logger ?? Logger() { + CreateCommand({ + Logger logger, + Future Function(GitPath) generate, + }) : _logger = logger ?? Logger(), + _generate = generate ?? MasonGenerator.fromGitPath { argParser.addOption( 'project-name', help: 'The project name for this new Flutter project. ' @@ -25,6 +37,7 @@ class CreateCommand extends Command { } final Logger _logger; + final Future Function(GitPath) _generate; @override final String description = @@ -33,15 +46,32 @@ class CreateCommand extends Command { @override final String name = 'create'; + /// [ArgResults] which can be overridden for testing. + @visibleForTesting + ArgResults argResultOverrides; + + ArgResults get _argResults => argResultOverrides ?? argResults; + @override Future run() async { - // ignore: unused_local_variable final outputDirectory = _outputDirectory; - - // ignore: unused_local_variable final projectName = _projectName; + final generateDone = _logger.progress('Bootstrapping'); + final generator = await _generate(_veryGoodCoreGitPath); - _logger.alert('Created a Very Good App! 🦄'); + final target = DirectoryGeneratorTarget(outputDirectory, _logger); + final fileCount = await generator.generate( + target, + vars: {'project_name': projectName}, + ); + generateDone('Bootstrapping complete'); + _logger + ..info( + '${lightGreen.wrap('✓')} ' + 'Generated $fileCount file(s):', + ) + ..flush(_logger.success) + ..alert('Created a Very Good App! 🦄'); return ExitCode.success.code; } @@ -50,8 +80,8 @@ class CreateCommand extends Command { /// Uses the current directory path name /// if the `--project-name` option is not explicitly specified. String get _projectName { - final projectName = - argResults['project-name'] ?? path.basename(_outputDirectory.path); + final projectName = _argResults['project-name'] ?? + path.basename(path.normalize(_outputDirectory.absolute.path)); _validateProjectName(projectName); return projectName; } @@ -73,7 +103,7 @@ class CreateCommand extends Command { } Directory get _outputDirectory { - final rest = argResults.rest; + final rest = _argResults.rest; _validateOutputDirectoryArg(rest); return Directory(rest.first); } diff --git a/pubspec.yaml b/pubspec.yaml index 5eb3a36d..059ab7ee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,6 +10,7 @@ dependencies: args: ^1.6.0 io: ^0.3.4 mason: ^0.0.1-dev.22 + meta: ^1.2.4 path: ^1.7.0 dev_dependencies: diff --git a/test/src/commands/create_test.dart b/test/src/commands/create_test.dart index 8145a58f..696c4cf7 100644 --- a/test/src/commands/create_test.dart +++ b/test/src/commands/create_test.dart @@ -1,3 +1,5 @@ +import 'package:args/args.dart'; +import 'package:io/ansi.dart'; import 'package:io/io.dart'; import 'package:mason/mason.dart'; import 'package:mockito/mockito.dart'; @@ -5,8 +7,12 @@ import 'package:test/test.dart'; import 'package:very_good_cli/src/command_runner.dart'; import 'package:very_good_cli/src/commands/create.dart'; +class MockArgResults extends Mock implements ArgResults {} + class MockLogger extends Mock implements Logger {} +class MockMasonGenerator extends Mock implements MasonGenerator {} + void main() { group('Create', () { Logger logger; @@ -14,10 +20,11 @@ void main() { setUp(() { logger = MockLogger(); + when(logger.progress(any)).thenReturn((_) {}); commandRunner = VeryGoodCommandRunner(logger: logger); }); - test('can be instantiated without an explicit Logger instance', () { + test('can be instantiated without any explicit dependencies', () { final command = CreateCommand(); expect(command, isNotNull); }); @@ -59,11 +66,36 @@ void main() { }); test('completes successfully with correct output', () async { - final result = await commandRunner.run( - ['create', '.', '--project-name', 'my_app'], - ); + final argResults = MockArgResults(); + final generator = MockMasonGenerator(); + final command = CreateCommand( + logger: logger, + generate: (_) async => generator, + )..argResultOverrides = argResults; + when(argResults['project-name']).thenReturn('my_app'); + when(argResults.rest).thenReturn(['.tmp']); + when(generator.generate(any, vars: anyNamed('vars'))) + .thenAnswer((_) async => 62); + final result = await command.run(); expect(result, equals(ExitCode.success.code)); + verify(logger.progress('Bootstrapping')).called(1); + verify(logger.info( + '${lightGreen.wrap('✓')} ' + 'Generated 62 file(s):', + )); verify(logger.alert('Created a Very Good App! 🦄')).called(1); + verify( + generator.generate( + argThat( + isA().having( + (g) => g.dir.path, + 'dir', + '.tmp', + ), + ), + vars: {'project_name': 'my_app'}, + ), + ).called(1); }); }); }