Skip to content

Commit ac3921a

Browse files
committed
feat(dart_frog_cli): --verbose support for dev command
1 parent e5cc738 commit ac3921a

File tree

4 files changed

+226
-83
lines changed

4 files changed

+226
-83
lines changed

packages/dart_frog_cli/lib/src/command_runner.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ class DartFrogCommandRunner extends CommandRunner<int> {
4747
_logger.info(packageVersion);
4848
return ExitCode.success.code;
4949
}
50+
if (topLevelResults['verbose'] == true) {
51+
_logger.level = Level.verbose;
52+
}
5053
return super.runCommand(topLevelResults);
5154
}
5255
}
@@ -58,5 +61,10 @@ extension on ArgParser {
5861
negatable: false,
5962
help: 'Print the current version.',
6063
);
64+
addFlag(
65+
'verbose',
66+
negatable: false,
67+
help: 'Output additional logs.',
68+
);
6169
}
6270
}

packages/dart_frog_cli/lib/src/commands/dev/dev.dart

Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1+
import 'dart:collection';
12
import 'dart:convert';
23
import 'dart:io' as io;
34

4-
import 'package:collection/collection.dart';
55
import 'package:dart_frog_cli/src/command.dart';
66
import 'package:dart_frog_cli/src/commands/commands.dart';
77
import 'package:dart_frog_cli/src/commands/dev/templates/dart_frog_dev_server_bundle.dart';
88
import 'package:mason/mason.dart';
9-
import 'package:meta/meta.dart';
109
import 'package:path/path.dart' as path;
1110
import 'package:stream_transform/stream_transform.dart';
1211
import 'package:watcher/watcher.dart';
@@ -32,11 +31,12 @@ typedef DirectoryWatcherBuilder = DirectoryWatcher Function(
3231
/// Typedef for [io.exit].
3332
typedef Exit = dynamic Function(int exitCode);
3433

35-
RestorableDirectoryGeneratorTarget get _defaultGeneratorTarget {
34+
RestorableDirectoryGeneratorTarget _defaultGeneratorTarget(Logger? logger) {
3635
return RestorableDirectoryGeneratorTarget(
3736
io.Directory(
3837
path.join(io.Directory.current.path, '.dart_frog'),
3938
),
39+
logger: logger,
4040
);
4141
}
4242

@@ -62,7 +62,7 @@ class DevCommand extends DartFrogCommand {
6262
_runProcess = runProcess ?? io.Process.run,
6363
_sigint = sigint ?? io.ProcessSignal.sigint,
6464
_startProcess = startProcess ?? io.Process.start,
65-
_generatorTarget = generatorTarget ?? _defaultGeneratorTarget {
65+
_generatorTarget = generatorTarget ?? _defaultGeneratorTarget(logger) {
6666
argParser.addOption(
6767
'port',
6868
abbr: 'p',
@@ -94,27 +94,35 @@ class DevCommand extends DartFrogCommand {
9494
final generator = await _generator(dartFrogDevServerBundle);
9595

9696
Future<void> codegen() async {
97+
logger.detail('[codegen] running pre-gen...');
9798
var vars = <String, dynamic>{'port': port};
9899
await generator.hooks.preGen(
99100
vars: vars,
100101
workingDirectory: cwd.path,
101102
onVarsChanged: (v) => vars = v,
102103
);
103104

105+
logger.detail('[codegen] running generate...');
104106
final _ = await generator.generate(
105107
_generatorTarget,
106108
vars: vars,
107109
fileConflictResolution: FileConflictResolution.overwrite,
108110
);
111+
logger.detail('[codegen] complete.');
109112
}
110113

111114
Future<void> reload() async {
115+
logger.detail('[codegen] reloading...');
112116
reloading = true;
113117
await codegen();
114118
reloading = false;
119+
logger.detail('[codegen] reload complete.');
115120
}
116121

117122
Future<void> serve() async {
123+
logger.detail(
124+
'''[process] dart --enable-vm-service ${path.join('.dart_frog', 'server.dart')}''',
125+
);
118126
final process = await _startProcess(
119127
'dart',
120128
['--enable-vm-service', path.join('.dart_frog', 'server.dart')],
@@ -129,6 +137,7 @@ class DevCommand extends DartFrogCommand {
129137
var hasError = false;
130138
process.stderr.listen((_) async {
131139
hasError = true;
140+
132141
if (reloading) return;
133142

134143
final message = utf8.decode(_).trim();
@@ -138,18 +147,19 @@ class DevCommand extends DartFrogCommand {
138147

139148
if (!hotReloadEnabled) {
140149
await _killProcess(process);
150+
logger.detail('[process] exit(1)');
141151
_exit(1);
142152
}
143153

144-
await _generatorTarget.restore();
154+
await _generatorTarget.rollback();
145155
});
146156

147157
process.stdout.listen((_) {
148158
final message = utf8.decode(_).trim();
149-
if (message.contains('[hotreload]')) hotReloadEnabled = true;
159+
final containsHotReload = message.contains('[hotreload]');
160+
if (containsHotReload) hotReloadEnabled = true;
150161
if (message.isNotEmpty) logger.info(message);
151-
final shouldCacheSnapshot =
152-
hotReloadEnabled && !hasError && message.isNotEmpty;
162+
final shouldCacheSnapshot = containsHotReload && !hasError;
153163
if (shouldCacheSnapshot) _generatorTarget.cacheLatestSnapshot();
154164
hasError = false;
155165
});
@@ -164,6 +174,7 @@ class DevCommand extends DartFrogCommand {
164174
final routes = path.join(cwd.path, 'routes');
165175

166176
bool shouldReload(WatchEvent event) {
177+
logger.detail('[watcher] $event');
167178
return path.isWithin(routes, event.path) ||
168179
path.isWithin(public, event.path);
169180
}
@@ -180,11 +191,14 @@ class DevCommand extends DartFrogCommand {
180191
}
181192

182193
Future<void> _killProcess(io.Process process) async {
194+
logger.detail('[process] killing process...');
183195
if (_isWindows) {
196+
logger.detail('[process] taskkill /F /T /PID ${process.pid}');
184197
final result = await _runProcess(
185198
'taskkill',
186199
['/F', '/T', '/PID', '${process.pid}'],
187200
);
201+
logger.detail('[process] exit(${result.exitCode})');
188202
return _exit(result.exitCode);
189203
}
190204
process.kill();
@@ -194,7 +208,6 @@ class DevCommand extends DartFrogCommand {
194208
/// {@template cached_file}
195209
/// A cached file which consists of the file path and contents.
196210
/// {@endtemplate}
197-
@immutable
198211
class CachedFile {
199212
/// {@macro cached_file}
200213
const CachedFile({required this.path, required this.contents});
@@ -204,19 +217,6 @@ class CachedFile {
204217

205218
/// The contents of the generated file.s
206219
final List<int> contents;
207-
208-
@override
209-
bool operator ==(Object other) {
210-
if (identical(this, other)) return true;
211-
final listEquals = const DeepCollectionEquality().equals;
212-
213-
return other is CachedFile &&
214-
other.path == path &&
215-
listEquals(other.contents, contents);
216-
}
217-
218-
@override
219-
int get hashCode => Object.hashAll([contents, path]);
220220
}
221221

222222
/// Signature for the `createFile` method on [DirectoryGeneratorTarget].
@@ -233,25 +233,58 @@ typedef CreateFile = Future<GeneratedFile> Function(
233233
/// {@endtemplate}
234234
class RestorableDirectoryGeneratorTarget extends DirectoryGeneratorTarget {
235235
/// {@macro restorable_directory_generator_target}
236-
RestorableDirectoryGeneratorTarget(super.dir, {CreateFile? createFile})
237-
: _createFile = createFile;
236+
RestorableDirectoryGeneratorTarget(
237+
super.dir, {
238+
CreateFile? createFile,
239+
Logger? logger,
240+
}) : _cachedSnapshots = Queue<CachedFile>(),
241+
_createFile = createFile,
242+
_logger = logger;
238243

239244
final CreateFile? _createFile;
240-
CachedFile? _cachedSnapshot;
245+
final Logger? _logger;
246+
final Queue<CachedFile> _cachedSnapshots;
247+
CachedFile? get _cachedSnapshot {
248+
return _cachedSnapshots.isNotEmpty ? _cachedSnapshots.last : null;
249+
}
250+
241251
CachedFile? _latestSnapshot;
242252

253+
/// Removes the latest cached snapshot.
254+
void _removeLatestSnapshot() {
255+
_logger?.detail('[codegen] attempting to remove latest snapshot.');
256+
if (_cachedSnapshots.length > 1) {
257+
_cachedSnapshots.removeLast();
258+
_logger?.detail('[codegen] removed latest snapshot.');
259+
}
260+
}
261+
262+
/// Remove the latest snapshot and restore the previously
263+
/// cached snapshot.
264+
Future<void> rollback() async {
265+
_logger?.detail('[codegen] rolling back...');
266+
_removeLatestSnapshot();
267+
await _restoreLatestSnapshot();
268+
_logger?.detail('[codegen] rollback complete.');
269+
}
270+
243271
/// Restore the latest cached snapshot.
244-
Future<void> restore() async {
272+
Future<void> _restoreLatestSnapshot() async {
245273
final snapshot = _cachedSnapshot;
246274
if (snapshot == null) return;
275+
_logger?.detail('[codegen] restoring previous snapshot...');
247276
await createFile(snapshot.path, snapshot.contents);
277+
_logger?.detail('[codegen] restored previous snapshot.');
248278
}
249279

250280
/// Cache the latest recorded snapshot.
251281
void cacheLatestSnapshot() {
252282
final snapshot = _latestSnapshot;
253-
if (snapshot == null || _cachedSnapshot == snapshot) return;
254-
_cachedSnapshot = snapshot;
283+
if (snapshot == null) return;
284+
_cachedSnapshots.add(snapshot);
285+
_logger?.detail('[codegen] cached latest snapshot.');
286+
// Keep only the 2 most recent snapshots.
287+
if (_cachedSnapshots.length > 2) _cachedSnapshots.removeFirst();
255288
}
256289

257290
@override

packages/dart_frog_cli/test/src/command_runner_test.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const expectedUsage = [
1717
'Global options:\n'
1818
'-h, --help Print this usage information.\n'
1919
' --version Print the current version.\n'
20+
' --verbose Output additional logs.\n'
2021
'\n'
2122
'Available commands:\n'
2223
' build Create a production build.\n'
@@ -85,6 +86,17 @@ void main() {
8586
);
8687
});
8788

89+
group('--verbose', () {
90+
test(
91+
'sets correct log level',
92+
overridePrint(() async {
93+
final logger = Logger();
94+
await DartFrogCommandRunner(logger: logger).run(['--verbose']);
95+
expect(logger.level, equals(Level.verbose));
96+
}),
97+
);
98+
});
99+
88100
group('--version', () {
89101
test('outputs current version', () async {
90102
final result = await commandRunner.run(['--version']);

0 commit comments

Comments
 (0)