Skip to content

Commit d76f6f7

Browse files
authored
feat(create): validate output directory (#7)
1 parent b69c45a commit d76f6f7

File tree

3 files changed

+71
-19
lines changed

3 files changed

+71
-19
lines changed

lib/src/commands/create.dart

+46-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import 'dart:io';
2+
13
import 'package:args/command_runner.dart';
24
import 'package:io/io.dart';
35
import 'package:mason/mason.dart';
6+
import 'package:path/path.dart' as path;
47

58
// A valid Dart identifier that can be used for a package, i.e. no
69
// capital letters.
@@ -32,29 +35,59 @@ class CreateCommand extends Command<int> {
3235

3336
@override
3437
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);
38+
// ignore: unused_local_variable
39+
final outputDirectory = _outputDirectory;
40+
41+
// ignore: unused_local_variable
42+
final projectName = _projectName;
43+
44+
_logger.alert('Created a Very Good App! 🦄');
45+
return ExitCode.success.code;
46+
}
47+
48+
/// Gets the project name.
49+
///
50+
/// Uses the current directory path name
51+
/// if the `--project-name` option is not explicitly specified.
52+
String get _projectName {
53+
final projectName =
54+
argResults['project-name'] ?? path.basename(_outputDirectory.path);
55+
_validateProjectName(projectName);
56+
return projectName;
57+
}
58+
59+
void _validateProjectName(String name) {
60+
final isValidProjectName = _isValidPackageName(name);
4461
if (!isValidProjectName) {
4562
throw UsageException(
46-
'"$projectName" is not a valid package name.\n\n'
63+
'"$name" is not a valid package name.\n\n'
4764
'See https://dart.dev/tools/pub/pubspec#name for more information.',
4865
usage,
4966
);
5067
}
51-
_logger.alert('Created a Very Good App! 🦄');
52-
return ExitCode.success.code;
5368
}
5469

55-
/// Whether [name] is a valid Dart package name.
5670
bool _isValidPackageName(String name) {
5771
final match = _identifierRegExp.matchAsPrefix(name);
5872
return match != null && match.end == name.length;
5973
}
74+
75+
Directory get _outputDirectory {
76+
final rest = argResults.rest;
77+
_validateOutputDirectoryArg(rest);
78+
return Directory(rest.first);
79+
}
80+
81+
void _validateOutputDirectoryArg(List<String> args) {
82+
if (args.isEmpty) {
83+
throw UsageException(
84+
'No option specified for the output directory.',
85+
usage,
86+
);
87+
}
88+
89+
if (args.length > 1) {
90+
throw UsageException('Multiple output directories specified.', usage);
91+
}
92+
}
6093
}

pubspec.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ dependencies:
1010
args: ^1.6.0
1111
io: ^0.3.4
1212
mason: ^0.0.1-dev.22
13+
path: ^1.7.0
1314

1415
dev_dependencies:
1516
coverage: ^0.13.4

test/src/commands/create_test.dart

+24-6
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ void main() {
2222
expect(command, isNotNull);
2323
});
2424

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']);
25+
test(
26+
'throws UsageException when --project-name is missing '
27+
'and directory base is not a valid package name', () async {
28+
const expectedErrorMessage = '".tmp" is not a valid package name.\n\n'
29+
'See https://dart.dev/tools/pub/pubspec#name for more information.';
30+
final result = await commandRunner.run(['create', '.tmp']);
2931
expect(result, equals(ExitCode.usage.code));
3032
verify(logger.err(expectedErrorMessage)).called(1);
3133
});
@@ -34,15 +36,31 @@ void main() {
3436
const expectedErrorMessage = '"My App" is not a valid package name.\n\n'
3537
'See https://dart.dev/tools/pub/pubspec#name for more information.';
3638
final result = await commandRunner.run(
37-
['create', '--project-name', 'My App'],
39+
['create', '.', '--project-name', 'My App'],
3840
);
3941
expect(result, equals(ExitCode.usage.code));
4042
verify(logger.err(expectedErrorMessage)).called(1);
4143
});
4244

45+
test('throws UsageException when output directory is missing', () async {
46+
const expectedErrorMessage =
47+
'No option specified for the output directory.';
48+
final result = await commandRunner.run(['create']);
49+
expect(result, equals(ExitCode.usage.code));
50+
verify(logger.err(expectedErrorMessage)).called(1);
51+
});
52+
53+
test('throws UsageException when multiple output directories are provided',
54+
() async {
55+
const expectedErrorMessage = 'Multiple output directories specified.';
56+
final result = await commandRunner.run(['create', './a', './b']);
57+
expect(result, equals(ExitCode.usage.code));
58+
verify(logger.err(expectedErrorMessage)).called(1);
59+
});
60+
4361
test('completes successfully with correct output', () async {
4462
final result = await commandRunner.run(
45-
['create', '--project-name', 'my_app'],
63+
['create', '.', '--project-name', 'my_app'],
4664
);
4765
expect(result, equals(ExitCode.success.code));
4866
verify(logger.alert('Created a Very Good App! 🦄')).called(1);

0 commit comments

Comments
 (0)