Skip to content

Commit

Permalink
feat: support custom org name (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
denangelov committed Jul 13, 2021
1 parent 8fa668c commit c0a2970
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 8 deletions.
52 changes: 45 additions & 7 deletions lib/src/commands/create.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ import 'package:very_good_cli/src/command_runner.dart';
import 'package:very_good_cli/src/flutter_cli.dart';
import 'package:very_good_cli/src/templates/very_good_core_bundle.dart';

const _defaultOrgName = 'com.example.verygoodcore';

// 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
final RegExp _identifierRegExp = RegExp('[a-z_][a-z0-9_]*');
final RegExp _orgNameRegExp =
RegExp(r'[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+');

/// A method which returns a [Future<MasonGenerator>] given a [MasonBundle].
typedef GeneratorBuilder = Future<MasonGenerator> Function(MasonBundle);
Expand All @@ -32,12 +36,18 @@ class CreateCommand extends Command<int> {
}) : _analytics = analytics,
_logger = logger ?? Logger(),
_generator = generator ?? MasonGenerator.fromBundle {
argParser.addOption(
'project-name',
help: 'The project name for this new Flutter project. '
'This must be a valid dart package name.',
defaultsTo: null,
);
argParser
..addOption(
'project-name',
help: 'The project name for this new Flutter project. '
'This must be a valid dart package name.',
defaultsTo: null,
)
..addOption(
'org-name',
help: 'The organization for this new Flutter project.',
defaultsTo: 'com.example.verygoodcore',
);
}

final Analytics _analytics;
Expand Down Expand Up @@ -67,11 +77,12 @@ class CreateCommand extends Command<int> {
Future<int> run() async {
final outputDirectory = _outputDirectory;
final projectName = _projectName;
final orgName = _orgName;
final generateDone = _logger.progress('Bootstrapping');
final generator = await _generator(veryGoodCoreBundle);
final fileCount = await generator.generate(
DirectoryGeneratorTarget(outputDirectory, _logger),
vars: {'project_name': projectName},
vars: {'project_name': projectName, 'org_name': orgName},
);
generateDone('Generated $fileCount file(s)');

Expand Down Expand Up @@ -126,6 +137,28 @@ class CreateCommand extends Command<int> {
return projectName;
}

/// Gets the organization name.
List<String> get _orgName {
if (_argResults['org-name'] == null) return _defaultOrgName.split('.');

final orgName = _argResults['org-name'] as String;
_validateOrgName(orgName);
return orgName.split('.');
}

void _validateOrgName(String name) {
final isValidOrgName = _isValidOrgName(name);
if (!isValidOrgName) {
throw UsageException(
'"$name" is not a valid org name.\n\n'
'A valid org name has 3 parts separated by "."'
'and only includes alphanumeric characters and underscores'
'(ex. very.good.org)',
usage,
);
}
}

void _validateProjectName(String name) {
final isValidProjectName = _isValidPackageName(name);
if (!isValidProjectName) {
Expand All @@ -137,6 +170,11 @@ class CreateCommand extends Command<int> {
}
}

bool _isValidOrgName(String name) {
final match = _orgNameRegExp.matchAsPrefix(name);
return match != null && match.end == name.length;
}

bool _isValidPackageName(String name) {
final match = _identifierRegExp.matchAsPrefix(name);
return match != null && match.end == name.length;
Expand Down
101 changes: 100 additions & 1 deletion test/src/commands/create_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,10 @@ void main() {
'.tmp',
),
),
vars: {'project_name': 'my_app'},
vars: {
'project_name': 'my_app',
'org_name': ['com', 'example', 'verygoodcore'],
},
),
).called(1);
verify(
Expand All @@ -140,5 +143,101 @@ void main() {
() => analytics.waitForLastPing(timeout: VeryGoodCommandRunner.timeout),
).called(1);
});

group('org-name', () {
group('invalid --org-name', () {
test('no delimiters', () async {
const expectedErrorMessage = '"My App" is not a valid org name.\n\n'
'A valid org name has 3 parts separated by "."'
'and only includes alphanumeric characters and underscores'
'(ex. very.good.org)';
final result = await commandRunner.run(
['create', '.', '--org-name', 'My App'],
);
expect(result, equals(ExitCode.usage.code));
verify(() => logger.err(expectedErrorMessage)).called(1);
});

test('more than 3 domains', () async {
const expectedErrorMessage =
'"very.bad.test.case" is not a valid org name.\n\n'
'A valid org name has 3 parts separated by "."'
'and only includes alphanumeric characters and underscores'
'(ex. very.good.org)';
final result = await commandRunner.run(
['create', '.', '--org-name', 'very.bad.test.case'],
);
expect(result, equals(ExitCode.usage.code));
verify(() => logger.err(expectedErrorMessage)).called(1);
});

test('invalid characters present', () async {
const expectedErrorMessage =
'"very%.bad@.#test" is not a valid org name.\n\n'
'A valid org name has 3 parts separated by "."'
'and only includes alphanumeric characters and underscores'
'(ex. very.good.org)';
final result = await commandRunner.run(
['create', '.', '--org-name', 'very%.bad@.#test'],
);
expect(result, equals(ExitCode.usage.code));
verify(() => logger.err(expectedErrorMessage)).called(1);
});
});

group('valid --org-name', () {
test('completes successfully with correct output', () async {
final argResults = MockArgResults();
final generator = MockMasonGenerator();
final command = CreateCommand(
analytics: analytics,
logger: logger,
generator: (_) async => generator,
)..argResultOverrides = argResults;
when(() => argResults['project-name']).thenReturn('my_app');
when(() => argResults['org-name']).thenReturn('very.good.ventures');
when(() => argResults.rest).thenReturn(['.tmp']);
when(() => generator.id).thenReturn('generator_id');
when(() => generator.description).thenReturn('generator description');
when(
() => generator.generate(any(), vars: any(named: 'vars')),
).thenAnswer((_) async => 62);
final result = await command.run();
expect(result, equals(ExitCode.success.code));
verify(() => logger.progress('Bootstrapping')).called(1);
expect(progressLogs, equals(['Generated 62 file(s)']));
verify(
() => logger.progress('Running "flutter packages get" in .tmp'),
).called(1);
verify(() => logger.alert('Created a Very Good App! 馃')).called(1);
verify(
() => generator.generate(
any(
that: isA<DirectoryGeneratorTarget>().having(
(g) => g.dir.path,
'dir',
'.tmp',
),
),
vars: {
'project_name': 'my_app',
'org_name': ['very', 'good', 'ventures'],
},
),
).called(1);
verify(
() => analytics.sendEvent(
'create',
'generator_id',
label: 'generator description',
),
).called(1);
verify(
() => analytics.waitForLastPing(
timeout: VeryGoodCommandRunner.timeout),
).called(1);
});
});
});
});
}

0 comments on commit c0a2970

Please sign in to comment.