From c0e2e7a8fe5ffb92d9d163ef8a4b025da200d67c Mon Sep 17 00:00:00 2001 From: "Stephen (Alex) Wallen" Date: Mon, 18 Mar 2024 21:40:52 -1000 Subject: [PATCH 1/7] chore: add test --- .../create/create_subcommand_test.dart | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/test/src/commands/create/create_subcommand_test.dart b/test/src/commands/create/create_subcommand_test.dart index e1178279b..0d66ac2bd 100644 --- a/test/src/commands/create/create_subcommand_test.dart +++ b/test/src/commands/create/create_subcommand_test.dart @@ -4,6 +4,7 @@ import 'package:args/args.dart'; import 'package:args/command_runner.dart'; import 'package:mason/mason.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:path/path.dart' as path; import 'package:test/test.dart'; import 'package:very_good_cli/src/commands/create/commands/create_subcommand.dart'; import 'package:very_good_cli/src/commands/create/templates/template.dart'; @@ -285,7 +286,61 @@ Run "runner help" to see global options.'''; that: isA().having( (d) => d.path, 'path', - 'test_dir/test_project', + path.join('test_dir', 'test_project'), + ), + ), + ), + ).called(1); + }); + + test('allows projects to be cwd (.)', () async { + final expectedProjectName = path.basename(Directory.current.path); + + final result = await runner.run([ + 'create_subcommand', + '.', + ]); + + expect(result, equals(ExitCode.success.code)); + verify(() => logger.progress('Bootstrapping')).called(1); + + verify( + () => hooks.preGen( + vars: { + 'project_name': expectedProjectName, + 'description': 'A Very Good Project created by Very Good CLI.', + }, + onVarsChanged: any(named: 'onVarsChanged'), + ), + ); + verify( + () => generator.generate( + any( + that: isA().having( + (g) => g.dir.path, + 'dir', + '.', + ), + ), + vars: { + 'project_name': expectedProjectName, + 'description': 'A Very Good Project created by Very Good CLI.', + }, + logger: logger, + ), + ).called(1); + expect( + progressLogs, + equals(['Generated ${generatedFiles.length} file(s)']), + ); + verify( + () => template.onGenerateComplete( + logger, + any( + that: isA().having( + (d) => d.path, + 'path', + '.', ), ), ), From ad42aa8fe3785746fc0ba645b1210199729477b7 Mon Sep 17 00:00:00 2001 From: "Stephen (Alex) Wallen" Date: Mon, 18 Mar 2024 21:41:10 -1000 Subject: [PATCH 2/7] feat: modify create sub command to allow . --- .../create/commands/create_subcommand.dart | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/src/commands/create/commands/create_subcommand.dart b/lib/src/commands/create/commands/create_subcommand.dart index 3f6a829ef..b0de35902 100644 --- a/lib/src/commands/create/commands/create_subcommand.dart +++ b/lib/src/commands/create/commands/create_subcommand.dart @@ -121,13 +121,24 @@ abstract class CreateSubCommand extends Command { return Directory(directory); } - /// Gets the project name. - String get projectName { + /// The project name that the user provided. + /// + /// Since the project name could be user provided as either: + /// 1. A valid package name + /// 2. '.', the current working directory (the project assumes the cwd's name) + /// this needs to exist to provide a fallback value for [outputDirectory] and + /// [projectName]. + String get _projectName { final args = argResults.rest; _validateProjectName(args); return args.first; } + /// Gets the project name. + String get projectName => _projectName == '.' + ? path.basename(Directory.current.path) + : _projectName; + /// Gets the description for the project. String get projectDescription => argResults['description'] as String? ?? ''; @@ -159,6 +170,15 @@ abstract class CreateSubCommand extends Command { } final name = args.first; + + if (name == '.') { + logger.detail( + 'Since the project name is ".", the current directory will' + ' be used as the project name.', + ); + return; + } + final isValidProjectName = _isValidPackageName(name); if (!isValidProjectName) { usageException( @@ -211,7 +231,12 @@ abstract class CreateSubCommand extends Command { await template.onGenerateComplete( logger, - Directory(path.join(target.dir.path, projectName)), + Directory( + [ + target.dir.path, + if (_projectName != '.') projectName, + ].join(Platform.pathSeparator), + ), ); return ExitCode.success.code; From a053e9d9f9c149d40be508471be2a6e6d1f5ee3d Mon Sep 17 00:00:00 2001 From: "Stephen (Alex) Wallen" Date: Mon, 18 Mar 2024 21:40:52 -1000 Subject: [PATCH 3/7] chore: add test --- .../create/create_subcommand_test.dart | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/test/src/commands/create/create_subcommand_test.dart b/test/src/commands/create/create_subcommand_test.dart index e1178279b..0d66ac2bd 100644 --- a/test/src/commands/create/create_subcommand_test.dart +++ b/test/src/commands/create/create_subcommand_test.dart @@ -4,6 +4,7 @@ import 'package:args/args.dart'; import 'package:args/command_runner.dart'; import 'package:mason/mason.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:path/path.dart' as path; import 'package:test/test.dart'; import 'package:very_good_cli/src/commands/create/commands/create_subcommand.dart'; import 'package:very_good_cli/src/commands/create/templates/template.dart'; @@ -285,7 +286,61 @@ Run "runner help" to see global options.'''; that: isA().having( (d) => d.path, 'path', - 'test_dir/test_project', + path.join('test_dir', 'test_project'), + ), + ), + ), + ).called(1); + }); + + test('allows projects to be cwd (.)', () async { + final expectedProjectName = path.basename(Directory.current.path); + + final result = await runner.run([ + 'create_subcommand', + '.', + ]); + + expect(result, equals(ExitCode.success.code)); + verify(() => logger.progress('Bootstrapping')).called(1); + + verify( + () => hooks.preGen( + vars: { + 'project_name': expectedProjectName, + 'description': 'A Very Good Project created by Very Good CLI.', + }, + onVarsChanged: any(named: 'onVarsChanged'), + ), + ); + verify( + () => generator.generate( + any( + that: isA().having( + (g) => g.dir.path, + 'dir', + '.', + ), + ), + vars: { + 'project_name': expectedProjectName, + 'description': 'A Very Good Project created by Very Good CLI.', + }, + logger: logger, + ), + ).called(1); + expect( + progressLogs, + equals(['Generated ${generatedFiles.length} file(s)']), + ); + verify( + () => template.onGenerateComplete( + logger, + any( + that: isA().having( + (d) => d.path, + 'path', + '.', ), ), ), From 589a7917cb0eefb53fc5cace562099837a7d1899 Mon Sep 17 00:00:00 2001 From: "Stephen (Alex) Wallen" Date: Mon, 18 Mar 2024 21:41:10 -1000 Subject: [PATCH 4/7] feat: modify create sub command to allow . --- .../create/commands/create_subcommand.dart | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/src/commands/create/commands/create_subcommand.dart b/lib/src/commands/create/commands/create_subcommand.dart index 969a52be3..df28188fe 100644 --- a/lib/src/commands/create/commands/create_subcommand.dart +++ b/lib/src/commands/create/commands/create_subcommand.dart @@ -121,13 +121,24 @@ abstract class CreateSubCommand extends Command { return Directory(directory); } - /// Gets the project name. - String get projectName { + /// The project name that the user provided. + /// + /// Since the project name could be user provided as either: + /// 1. A valid package name + /// 2. '.', the current working directory (the project assumes the cwd's name) + /// this needs to exist to provide a fallback value for [outputDirectory] and + /// [projectName]. + String get _projectName { final args = argResults.rest; _validateProjectName(args); return args.first; } + /// Gets the project name. + String get projectName => _projectName == '.' + ? path.basename(Directory.current.path) + : _projectName; + /// Gets the description for the project. String get projectDescription => argResults['description'] as String? ?? ''; @@ -159,6 +170,15 @@ abstract class CreateSubCommand extends Command { } final name = args.first; + + if (name == '.') { + logger.detail( + 'Since the project name is ".", the current directory will' + ' be used as the project name.', + ); + return; + } + final isValidProjectName = _isValidPackageName(name); if (!isValidProjectName) { usageException( @@ -211,7 +231,12 @@ abstract class CreateSubCommand extends Command { await template.onGenerateComplete( logger, - Directory(path.join(target.dir.path, projectName)), + Directory( + [ + target.dir.path, + if (_projectName != '.') projectName, + ].join(Platform.pathSeparator), + ), ); return ExitCode.success.code; From cc786aadfefa4ca777e0f4097c75ce0bcb2ced55 Mon Sep 17 00:00:00 2001 From: "Stephen (Alex) Wallen" Date: Tue, 9 Jul 2024 11:02:16 -1000 Subject: [PATCH 5/7] chore: remove warning --- lib/src/commands/create/commands/create_subcommand.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/src/commands/create/commands/create_subcommand.dart b/lib/src/commands/create/commands/create_subcommand.dart index df28188fe..3d6037b08 100644 --- a/lib/src/commands/create/commands/create_subcommand.dart +++ b/lib/src/commands/create/commands/create_subcommand.dart @@ -171,14 +171,6 @@ abstract class CreateSubCommand extends Command { final name = args.first; - if (name == '.') { - logger.detail( - 'Since the project name is ".", the current directory will' - ' be used as the project name.', - ); - return; - } - final isValidProjectName = _isValidPackageName(name); if (!isValidProjectName) { usageException( From b706a2d2e07023e4ef3fc2cf1ff061ccb36fd3ea Mon Sep 17 00:00:00 2001 From: "Stephen (Alex) Wallen" Date: Tue, 9 Jul 2024 11:25:06 -1000 Subject: [PATCH 6/7] wip: use paths instead of names with backwards compatibility --- .../create/commands/create_subcommand.dart | 78 +++---- .../create/create_subcommand_test.dart | 193 +++++++++++++----- 2 files changed, 171 insertions(+), 100 deletions(-) diff --git a/lib/src/commands/create/commands/create_subcommand.dart b/lib/src/commands/create/commands/create_subcommand.dart index 3d6037b08..714a498bc 100644 --- a/lib/src/commands/create/commands/create_subcommand.dart +++ b/lib/src/commands/create/commands/create_subcommand.dart @@ -117,27 +117,44 @@ abstract class CreateSubCommand extends Command { /// Gets the output [Directory]. Directory get outputDirectory { - final directory = argResults['output-directory'] as String? ?? '.'; - return Directory(directory); - } + final directory = argResults['output-directory'] as String?; + + if (directory != null) { + return Directory(directory); + } - /// The project name that the user provided. - /// - /// Since the project name could be user provided as either: - /// 1. A valid package name - /// 2. '.', the current working directory (the project assumes the cwd's name) - /// this needs to exist to provide a fallback value for [outputDirectory] and - /// [projectName]. - String get _projectName { final args = argResults.rest; - _validateProjectName(args); - return args.first; + + return Directory(args.first); } /// Gets the project name. - String get projectName => _projectName == '.' - ? path.basename(Directory.current.path) - : _projectName; + String get projectName { + final args = argResults.rest; + + if (args.isEmpty) { + usageException('No option specified for the project name.'); + } + + if (args.length > 1) { + usageException('Multiple project names specified.'); + } + + final name = args.first == '.' + ? path.basename(Directory.current.path) + : path.basename(args.first); + + final isValidPackageName = _isValidPackageName(name); + + if (!isValidPackageName) { + usageException( + '"$name" is not a valid package name.\n\n' + 'See https://dart.dev/tools/pub/pubspec#name for more information.', + ); + } + + return name; + } /// Gets the description for the project. String get projectDescription => argResults['description'] as String? ?? ''; @@ -158,28 +175,6 @@ abstract class CreateSubCommand extends Command { return match != null && match.end == name.length; } - void _validateProjectName(List args) { - logger.detail('Validating project name; args: $args'); - - if (args.isEmpty) { - usageException('No option specified for the project name.'); - } - - if (args.length > 1) { - usageException('Multiple project names specified.'); - } - - final name = args.first; - - final isValidProjectName = _isValidPackageName(name); - if (!isValidProjectName) { - usageException( - '"$name" is not a valid package name.\n\n' - 'See https://dart.dev/tools/pub/pubspec#name for more information.', - ); - } - } - Future _getGeneratorForTemplate() async { try { final brick = Brick.version( @@ -223,12 +218,7 @@ abstract class CreateSubCommand extends Command { await template.onGenerateComplete( logger, - Directory( - [ - target.dir.path, - if (_projectName != '.') projectName, - ].join(Platform.pathSeparator), - ), + outputDirectory, ); return ExitCode.success.code; diff --git a/test/src/commands/create/create_subcommand_test.dart b/test/src/commands/create/create_subcommand_test.dart index 0d66ac2bd..7038ef1c4 100644 --- a/test/src/commands/create/create_subcommand_test.dart +++ b/test/src/commands/create/create_subcommand_test.dart @@ -237,60 +237,131 @@ Run "runner help" to see global options.'''; }); group('parsing of options', () { - test('parses description, output dir and project name', () async { - final result = await runner.run([ - 'create_subcommand', - 'test_project', - '--description', - 'test_desc', - '--output-directory', - 'test_dir', - ]); + group('for project name', () { + test('uses current directory basename as name if . provided', + () async { + final expectedProjectName = path.basename(Directory.current.path); - expect(result, equals(ExitCode.success.code)); - verify(() => logger.progress('Bootstrapping')).called(1); + final result = await runner.run([ + 'create_subcommand', + '.', + ]); - verify( - () => hooks.preGen( - vars: { - 'project_name': 'test_project', - 'description': 'test_desc', - }, - onVarsChanged: any(named: 'onVarsChanged'), - ), - ); - verify( - () => generator.generate( - any( - that: isA().having( - (g) => g.dir.path, - 'dir', - 'test_dir', - ), + expect(result, equals(ExitCode.success.code)); + verify(() => logger.progress('Bootstrapping')).called(1); + + verify( + () => hooks.preGen( + vars: { + 'project_name': expectedProjectName, + 'description': + 'A Very Good Project created by Very Good CLI.', + }, + onVarsChanged: any(named: 'onVarsChanged'), ), - vars: { - 'project_name': 'test_project', - 'description': 'test_desc', - }, - logger: logger, - ), - ).called(1); - expect( - progressLogs, - equals(['Generated ${generatedFiles.length} file(s)']), - ); - verify( - () => template.onGenerateComplete( - logger, - any( - that: isA().having( - (d) => d.path, - 'path', - path.join('test_dir', 'test_project'), - ), + ); + }); + + test('uses name if just a name is provided', () async { + final result = await runner.run([ + 'create_subcommand', + 'name', + ]); + + expect(result, equals(ExitCode.success.code)); + verify(() => logger.progress('Bootstrapping')).called(1); + + verify( + () => hooks.preGen( + vars: { + 'project_name': 'name', + 'description': + 'A Very Good Project created by Very Good CLI.', + }, + onVarsChanged: any(named: 'onVarsChanged'), ), - ), - ).called(1); + ); + }); + + test('uses last path segment if absolute path is provided', () async { + final result = await runner.run([ + 'create_subcommand', + '/path/to/name', + ]); + + expect(result, equals(ExitCode.success.code)); + verify(() => logger.progress('Bootstrapping')).called(1); + + verify( + () => hooks.preGen( + vars: { + 'project_name': 'name', + 'description': + 'A Very Good Project created by Very Good CLI.', + }, + onVarsChanged: any(named: 'onVarsChanged'), + ), + ); + }); + + test('uses last path segment if a relative path is provided', + () async { + final result = await runner.run([ + 'create_subcommand', + './name', + ]); + + expect(result, equals(ExitCode.success.code)); + verify(() => logger.progress('Bootstrapping')).called(1); + + verify( + () => hooks.preGen( + vars: { + 'project_name': 'name', + 'description': + 'A Very Good Project created by Very Good CLI.', + }, + onVarsChanged: any(named: 'onVarsChanged'), + ), + ); + }); + }); + + group('for output directory', () { + test( + 'uses directory provided in --output-directory instead of the ' + 'one parsed from project', + () async { + final result = await runner.run([ + 'create_subcommand', + 'path/to/test_project', + '--output-directory', + 'path/to/test_dir', + '--description', + 'test_desc', + ]); + + expect(result, equals(ExitCode.success.code)); + verify(() => logger.progress('Bootstrapping')).called(1); + + verify( + () => generator.generate( + any( + that: isA().having( + (g) => g.dir.path, + 'dir', + 'path/to/test_dir', + ), + ), + vars: { + 'project_name': 'test_project', + 'description': 'test_desc', + }, + logger: logger, + ), + ).called(1); + }, + ); }); test('allows projects to be cwd (.)', () async { @@ -317,7 +388,9 @@ Run "runner help" to see global options.'''; () => generator.generate( any( that: isA().having( - (g) => g.dir.path, + (g) { + return g.dir.path; + }, 'dir', '.', ), @@ -338,7 +411,11 @@ Run "runner help" to see global options.'''; logger, any( that: isA().having( - (d) => d.path, + (d) { + print(Directory.current); + print(d.absolute.path); + return d.path; + }, 'path', '.', ), @@ -373,9 +450,11 @@ Run "runner help" to see global options.'''; () => generator.generate( any( that: isA().having( - (g) => g.dir.path, + (g) { + return g.dir.path; + }, 'dir', - '.', + 'test_project', ), ), vars: { @@ -391,9 +470,11 @@ Run "runner help" to see global options.'''; logger, any( that: isA().having( - (d) => d.path, + (d) { + return d.path; + }, 'path', - './test_project', + 'test_project', ), ), ), From 644570bcc391a41be1d038f5691fc600a85b5e5d Mon Sep 17 00:00:00 2001 From: Alex Wallen Date: Thu, 18 Jul 2024 11:42:57 -1000 Subject: [PATCH 7/7] chore: remove print statements --- test/src/commands/create/create_subcommand_test.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/src/commands/create/create_subcommand_test.dart b/test/src/commands/create/create_subcommand_test.dart index 7038ef1c4..45e3109e9 100644 --- a/test/src/commands/create/create_subcommand_test.dart +++ b/test/src/commands/create/create_subcommand_test.dart @@ -412,8 +412,6 @@ Run "runner help" to see global options.'''; any( that: isA().having( (d) { - print(Directory.current); - print(d.absolute.path); return d.path; }, 'path',