Skip to content

Commit aa391d6

Browse files
authored
feat: create dart package new usage (#611)
* feat: create dart package new usage * tests
1 parent e01a127 commit aa391d6

File tree

10 files changed

+580
-2
lines changed

10 files changed

+580
-2
lines changed

.github/workflows/very_good_cli.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ jobs:
5555
- test/src/commands/test/e2e
5656
# E2E tests for the create command
5757
- test/src/commands/create/e2e/flutter_app/core_test.dart
58+
- test/src/commands/create/e2e/dart_package/dart_pkg_test.dart
5859

5960
# E2E tests for the legacy create command syntax
6061
- test/src/commands/create/e2e/legacy/core_test.dart
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export 'create_subcommand.dart';
2+
export 'dart_package.dart';
23
export 'flutter_app.dart';
34
export 'legacy.dart';

lib/src/commands/create/commands/create_subcommand.dart

+19
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ abstract class CreateSubCommand extends Command<int> {
9797
aliases: ['org'],
9898
);
9999
}
100+
101+
if (this is Publishable) {
102+
argParser.addFlag(
103+
'publishable',
104+
negatable: false,
105+
help: 'Whether the generated project is intended to be published.',
106+
);
107+
}
100108
}
101109

102110
final Analytics _analytics;
@@ -236,6 +244,7 @@ abstract class CreateSubCommand extends Command<int> {
236244
'project_name': projectName,
237245
'description': projectDescription,
238246
if (this is OrgName) 'org_name': (this as OrgName).orgName,
247+
if (this is Publishable) 'publishable': (this as Publishable).publishable,
239248
};
240249
}
241250
}
@@ -298,3 +307,13 @@ mixin MultiTemplates on CreateSubCommand {
298307
);
299308
}
300309
}
310+
311+
/// Mixin for [CreateSubCommand] subclasses that receives the publishable
312+
/// flag.
313+
///
314+
/// Takes care of parsing it from [argResults] and pass it
315+
/// to the brick generator.
316+
mixin Publishable on CreateSubCommand {
317+
/// Gets the publishable flag.
318+
bool get publishable => argResults['publishable'] as bool? ?? false;
319+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import 'package:mason_logger/mason_logger.dart';
2+
import 'package:usage/usage.dart';
3+
import 'package:very_good_cli/src/commands/commands.dart';
4+
import 'package:very_good_cli/src/commands/create/templates/templates.dart';
5+
6+
/// {@template very_good_create_dart_package_command}
7+
/// A [CreateSubCommand] for creating Dart packages.
8+
/// {@endtemplate}
9+
class CreateDartPackage extends CreateSubCommand with Publishable {
10+
/// {@macro very_good_create_dart_package_command}
11+
CreateDartPackage({
12+
required Analytics analytics,
13+
required Logger logger,
14+
required MasonGeneratorFromBundle? generatorFromBundle,
15+
required MasonGeneratorFromBrick? generatorFromBrick,
16+
}) : super(
17+
analytics: analytics,
18+
logger: logger,
19+
generatorFromBundle: generatorFromBundle,
20+
generatorFromBrick: generatorFromBrick,
21+
);
22+
23+
@override
24+
String get name => 'dart_package';
25+
26+
@override
27+
List<String> get aliases => ['dart_pkg'];
28+
29+
@override
30+
String get description =>
31+
'Creates a new very good Dart package in the specified directory.';
32+
33+
@override
34+
Template get template => DartPkgTemplate();
35+
}

lib/src/commands/create/create.dart

+10
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ class CreateCommand extends Command<int> {
3838
generatorFromBrick: generatorFromBrick,
3939
),
4040
);
41+
42+
// very_good create dart_pkg <args>
43+
addSubcommand(
44+
CreateDartPackage(
45+
analytics: analytics,
46+
logger: logger,
47+
generatorFromBundle: generatorFromBundle,
48+
generatorFromBrick: generatorFromBrick,
49+
),
50+
);
4151
}
4252

4353
@override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import 'dart:io';
2+
3+
import 'package:args/args.dart';
4+
import 'package:mason/mason.dart';
5+
import 'package:mocktail/mocktail.dart';
6+
import 'package:path/path.dart' as path;
7+
import 'package:test/test.dart';
8+
import 'package:usage/usage.dart';
9+
import 'package:very_good_cli/src/commands/commands.dart';
10+
11+
import '../../../../helpers/helpers.dart';
12+
13+
class MockAnalytics extends Mock implements Analytics {}
14+
15+
class MockLogger extends Mock implements Logger {}
16+
17+
class MockMasonGenerator extends Mock implements MasonGenerator {}
18+
19+
class MockGeneratorHooks extends Mock implements GeneratorHooks {}
20+
21+
class MockArgResults extends Mock implements ArgResults {}
22+
23+
class FakeLogger extends Fake implements Logger {}
24+
25+
class FakeDirectoryGeneratorTarget extends Fake
26+
implements DirectoryGeneratorTarget {}
27+
28+
final expectedUsage = [
29+
'''
30+
Creates a new very good Dart package in the specified directory.
31+
32+
Usage: very_good create dart_package <project-name> [arguments]
33+
-h, --help Print this usage information.
34+
-o, --output-directory The desired output directory when creating a new project.
35+
--description The description for this new project.
36+
(defaults to "A Very Good Project created by Very Good CLI.")
37+
--publishable Whether the generated project is intended to be published.
38+
39+
Run "very_good help" to see global options.''',
40+
];
41+
42+
const pubspec = '''
43+
name: example
44+
environment:
45+
sdk: ">=2.13.0 <3.0.0"
46+
''';
47+
48+
void main() {
49+
late Analytics analytics;
50+
late Logger logger;
51+
52+
setUpAll(() {
53+
registerFallbackValue(FakeDirectoryGeneratorTarget());
54+
registerFallbackValue(FakeLogger());
55+
});
56+
57+
setUp(() {
58+
analytics = MockAnalytics();
59+
when(
60+
() => analytics.sendEvent(any(), any(), label: any(named: 'label')),
61+
).thenAnswer((_) async {});
62+
when(
63+
() => analytics.waitForLastPing(timeout: any(named: 'timeout')),
64+
).thenAnswer((_) async {});
65+
66+
logger = MockLogger();
67+
68+
final progress = MockProgress();
69+
70+
when(() => logger.progress(any())).thenReturn(progress);
71+
});
72+
73+
group('can be instantiated', () {
74+
test('with default options', () {
75+
final logger = Logger();
76+
final command = CreateDartPackage(
77+
analytics: analytics,
78+
logger: logger,
79+
generatorFromBundle: null,
80+
generatorFromBrick: null,
81+
);
82+
expect(command.name, equals('dart_package'));
83+
expect(
84+
command.description,
85+
equals(
86+
'Creates a new very good Dart package in the specified directory.',
87+
),
88+
);
89+
expect(command.logger, equals(logger));
90+
expect(command, isA<Publishable>());
91+
});
92+
});
93+
94+
group('create dart_package', () {
95+
test(
96+
'help',
97+
withRunner((commandRunner, logger, pubUpdater, printLogs) async {
98+
final result =
99+
await commandRunner.run(['create', 'dart_package', '--help']);
100+
expect(printLogs, equals(expectedUsage));
101+
expect(result, equals(ExitCode.success.code));
102+
103+
printLogs.clear();
104+
105+
final resultAbbr =
106+
await commandRunner.run(['create', 'dart_pkg', '-h']);
107+
expect(printLogs, equals(expectedUsage));
108+
expect(resultAbbr, equals(ExitCode.success.code));
109+
}),
110+
);
111+
112+
group('running the command', () {
113+
final generatedFiles =
114+
List.filled(10, const GeneratedFile.created(path: ''));
115+
116+
late GeneratorHooks hooks;
117+
late MasonGenerator generator;
118+
119+
setUp(() {
120+
hooks = MockGeneratorHooks();
121+
generator = MockMasonGenerator();
122+
123+
when(() => generator.hooks).thenReturn(hooks);
124+
when(
125+
() => hooks.preGen(
126+
vars: any(named: 'vars'),
127+
onVarsChanged: any(named: 'onVarsChanged'),
128+
),
129+
).thenAnswer((_) async {});
130+
131+
when(
132+
() => generator.generate(
133+
any(),
134+
vars: any(named: 'vars'),
135+
logger: any(named: 'logger'),
136+
),
137+
).thenAnswer((_) async {
138+
return generatedFiles;
139+
});
140+
141+
when(() => generator.id).thenReturn('generator_id');
142+
when(() => generator.description).thenReturn('generator description');
143+
when(() => generator.hooks).thenReturn(hooks);
144+
145+
when(
146+
() => hooks.preGen(
147+
vars: any(named: 'vars'),
148+
onVarsChanged: any(named: 'onVarsChanged'),
149+
),
150+
).thenAnswer((_) async {});
151+
when(
152+
() => generator.generate(
153+
any(),
154+
vars: any(named: 'vars'),
155+
logger: any(named: 'logger'),
156+
),
157+
).thenAnswer((_) async {
158+
final target =
159+
_.positionalArguments.first as DirectoryGeneratorTarget;
160+
File(path.join(target.dir.path, 'my_package', 'pubspec.yaml'))
161+
..createSync(recursive: true)
162+
..writeAsStringSync(pubspec);
163+
return generatedFiles;
164+
});
165+
});
166+
167+
test('creates dart package', () async {
168+
final tempDir = Directory.systemTemp.createTempSync();
169+
addTearDown(() => tempDir.deleteSync(recursive: true));
170+
final argResults = MockArgResults();
171+
final command = CreateDartPackage(
172+
analytics: analytics,
173+
logger: logger,
174+
generatorFromBundle: (_) async => throw Exception('oops'),
175+
generatorFromBrick: (_) async => generator,
176+
)..argResultOverrides = argResults;
177+
when(() => argResults['output-directory'] as String?)
178+
.thenReturn(tempDir.path);
179+
when(() => argResults.rest).thenReturn(['my_package']);
180+
181+
final result = await command.run();
182+
183+
expect(command.template.name, 'dart_pkg');
184+
expect(result, equals(ExitCode.success.code));
185+
186+
verify(() => logger.progress('Bootstrapping')).called(1);
187+
verify(
188+
() => hooks.preGen(
189+
vars: <String, dynamic>{
190+
'project_name': 'my_package',
191+
'description': '',
192+
'publishable': false,
193+
},
194+
onVarsChanged: any(named: 'onVarsChanged'),
195+
),
196+
);
197+
verify(
198+
() => generator.generate(
199+
any(),
200+
vars: <String, dynamic>{
201+
'project_name': 'my_package',
202+
'description': '',
203+
'publishable': false,
204+
},
205+
logger: logger,
206+
),
207+
).called(1);
208+
verify(
209+
() => logger.info('Created a Very Good Dart Package! 🦄'),
210+
).called(1);
211+
});
212+
});
213+
});
214+
}

test/src/commands/create/commands/legacy_test.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ Usage: very_good create <subcommand> <project-name> [arguments]
2323
-h, --help Print this usage information.
2424
2525
Available subcommands:
26-
flutter_app Creates a new very good Flutter app in the specified directory.
26+
dart_package Creates a new very good Dart package in the specified directory.
27+
flutter_app Creates a new very good Flutter app in the specified directory.
2728
2829
Run "very_good help" to see global options.'''
2930
];

0 commit comments

Comments
 (0)