Skip to content

Commit f3be0de

Browse files
authored
feat(create): validate project name (#6)
1 parent da2d452 commit f3be0de

File tree

3 files changed

+60
-5
lines changed

3 files changed

+60
-5
lines changed

lib/src/command_runner.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class VeryGoodCommandRunner extends CommandRunner<int> {
1919
negatable: false,
2020
help: 'Print the current version.',
2121
);
22-
addCommand(CreateCommand());
22+
addCommand(CreateCommand(logger: logger));
2323
}
2424

2525
final Logger _logger;

lib/src/commands/create.dart

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,24 @@ import 'package:args/command_runner.dart';
22
import 'package:io/io.dart';
33
import 'package:mason/mason.dart';
44

5+
// A valid Dart identifier that can be used for a package, i.e. no
6+
// capital letters.
7+
// https://dart.dev/guides/language/language-tour#important-concepts
8+
final RegExp _identifierRegExp = RegExp('[a-z_][a-z0-9_]*');
9+
510
/// {@template create_command}
611
/// `very_good create` command creates a new very good flutter app.
712
/// {@endtemplate}
813
class CreateCommand extends Command<int> {
914
/// {@macro create_command}
10-
CreateCommand({Logger logger}) : _logger = logger ?? Logger();
15+
CreateCommand({Logger logger}) : _logger = logger ?? Logger() {
16+
argParser.addOption(
17+
'project-name',
18+
help: 'The project name for this new Flutter project. '
19+
'This must be a valid dart package name.',
20+
defaultsTo: null,
21+
);
22+
}
1123

1224
final Logger _logger;
1325

@@ -20,7 +32,29 @@ class CreateCommand extends Command<int> {
2032

2133
@override
2234
Future<int> run() async {
35+
final projectName = argResults['project-name'];
36+
if (projectName == null) {
37+
throw UsageException(
38+
'Required: --project-name.\n\n'
39+
'e.g: very_good create --project-name my_app',
40+
usage,
41+
);
42+
}
43+
final isValidProjectName = _isValidPackageName(projectName);
44+
if (!isValidProjectName) {
45+
throw UsageException(
46+
'"$projectName" is not a valid package name.\n\n'
47+
'See https://dart.dev/tools/pub/pubspec#name for more information.',
48+
usage,
49+
);
50+
}
2351
_logger.alert('Created a Very Good App! 🦄');
2452
return ExitCode.success.code;
2553
}
54+
55+
/// Whether [name] is a valid Dart package name.
56+
bool _isValidPackageName(String name) {
57+
final match = _identifierRegExp.matchAsPrefix(name);
58+
return match != null && match.end == name.length;
59+
}
2660
}

test/src/commands/create_test.dart

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,48 @@ import 'package:io/io.dart';
22
import 'package:mason/mason.dart';
33
import 'package:mockito/mockito.dart';
44
import 'package:test/test.dart';
5+
import 'package:very_good_cli/src/command_runner.dart';
56
import 'package:very_good_cli/src/commands/create.dart';
67

78
class MockLogger extends Mock implements Logger {}
89

910
void main() {
1011
group('Create', () {
1112
Logger logger;
12-
CreateCommand command;
13+
VeryGoodCommandRunner commandRunner;
1314

1415
setUp(() {
1516
logger = MockLogger();
16-
command = CreateCommand(logger: logger);
17+
commandRunner = VeryGoodCommandRunner(logger: logger);
1718
});
1819

1920
test('can be instantiated without an explicit Logger instance', () {
2021
final command = CreateCommand();
2122
expect(command, isNotNull);
2223
});
2324

25+
test('throws UsageException when --project-name is missing', () async {
26+
const expectedErrorMessage = 'Required: --project-name.\n\n'
27+
'e.g: very_good create --project-name my_app';
28+
final result = await commandRunner.run(['create']);
29+
expect(result, equals(ExitCode.usage.code));
30+
verify(logger.err(expectedErrorMessage)).called(1);
31+
});
32+
33+
test('throws UsageException when --project-name is invalid', () async {
34+
const expectedErrorMessage = '"My App" is not a valid package name.\n\n'
35+
'See https://dart.dev/tools/pub/pubspec#name for more information.';
36+
final result = await commandRunner.run(
37+
['create', '--project-name', 'My App'],
38+
);
39+
expect(result, equals(ExitCode.usage.code));
40+
verify(logger.err(expectedErrorMessage)).called(1);
41+
});
42+
2443
test('completes successfully with correct output', () async {
25-
final result = await command.run();
44+
final result = await commandRunner.run(
45+
['create', '--project-name', 'my_app'],
46+
);
2647
expect(result, equals(ExitCode.success.code));
2748
verify(logger.alert('Created a Very Good App! 🦄')).called(1);
2849
});

0 commit comments

Comments
 (0)