1
+ import 'dart:collection' ;
1
2
import 'dart:convert' ;
2
3
import 'dart:io' as io;
3
4
4
- import 'package:collection/collection.dart' ;
5
5
import 'package:dart_frog_cli/src/command.dart' ;
6
6
import 'package:dart_frog_cli/src/commands/commands.dart' ;
7
7
import 'package:dart_frog_cli/src/commands/dev/templates/dart_frog_dev_server_bundle.dart' ;
8
8
import 'package:mason/mason.dart' ;
9
- import 'package:meta/meta.dart' ;
10
9
import 'package:path/path.dart' as path;
11
10
import 'package:stream_transform/stream_transform.dart' ;
12
11
import 'package:watcher/watcher.dart' ;
@@ -32,11 +31,12 @@ typedef DirectoryWatcherBuilder = DirectoryWatcher Function(
32
31
/// Typedef for [io.exit] .
33
32
typedef Exit = dynamic Function (int exitCode);
34
33
35
- RestorableDirectoryGeneratorTarget get _defaultGeneratorTarget {
34
+ RestorableDirectoryGeneratorTarget _defaultGeneratorTarget ( Logger ? logger) {
36
35
return RestorableDirectoryGeneratorTarget (
37
36
io.Directory (
38
37
path.join (io.Directory .current.path, '.dart_frog' ),
39
38
),
39
+ logger: logger,
40
40
);
41
41
}
42
42
@@ -62,7 +62,7 @@ class DevCommand extends DartFrogCommand {
62
62
_runProcess = runProcess ?? io.Process .run,
63
63
_sigint = sigint ?? io.ProcessSignal .sigint,
64
64
_startProcess = startProcess ?? io.Process .start,
65
- _generatorTarget = generatorTarget ?? _defaultGeneratorTarget {
65
+ _generatorTarget = generatorTarget ?? _defaultGeneratorTarget (logger) {
66
66
argParser.addOption (
67
67
'port' ,
68
68
abbr: 'p' ,
@@ -94,27 +94,35 @@ class DevCommand extends DartFrogCommand {
94
94
final generator = await _generator (dartFrogDevServerBundle);
95
95
96
96
Future <void > codegen () async {
97
+ logger.detail ('[codegen] running pre-gen...' );
97
98
var vars = < String , dynamic > {'port' : port};
98
99
await generator.hooks.preGen (
99
100
vars: vars,
100
101
workingDirectory: cwd.path,
101
102
onVarsChanged: (v) => vars = v,
102
103
);
103
104
105
+ logger.detail ('[codegen] running generate...' );
104
106
final _ = await generator.generate (
105
107
_generatorTarget,
106
108
vars: vars,
107
109
fileConflictResolution: FileConflictResolution .overwrite,
108
110
);
111
+ logger.detail ('[codegen] complete.' );
109
112
}
110
113
111
114
Future <void > reload () async {
115
+ logger.detail ('[codegen] reloading...' );
112
116
reloading = true ;
113
117
await codegen ();
114
118
reloading = false ;
119
+ logger.detail ('[codegen] reload complete.' );
115
120
}
116
121
117
122
Future <void > serve () async {
123
+ logger.detail (
124
+ '''[process] dart --enable-vm-service ${path .join ('.dart_frog' , 'server.dart' )}''' ,
125
+ );
118
126
final process = await _startProcess (
119
127
'dart' ,
120
128
['--enable-vm-service' , path.join ('.dart_frog' , 'server.dart' )],
@@ -129,6 +137,7 @@ class DevCommand extends DartFrogCommand {
129
137
var hasError = false ;
130
138
process.stderr.listen ((_) async {
131
139
hasError = true ;
140
+
132
141
if (reloading) return ;
133
142
134
143
final message = utf8.decode (_).trim ();
@@ -138,18 +147,19 @@ class DevCommand extends DartFrogCommand {
138
147
139
148
if (! hotReloadEnabled) {
140
149
await _killProcess (process);
150
+ logger.detail ('[process] exit(1)' );
141
151
_exit (1 );
142
152
}
143
153
144
- await _generatorTarget.restore ();
154
+ await _generatorTarget.rollback ();
145
155
});
146
156
147
157
process.stdout.listen ((_) {
148
158
final message = utf8.decode (_).trim ();
149
- if (message.contains ('[hotreload]' )) hotReloadEnabled = true ;
159
+ final containsHotReload = message.contains ('[hotreload]' );
160
+ if (containsHotReload) hotReloadEnabled = true ;
150
161
if (message.isNotEmpty) logger.info (message);
151
- final shouldCacheSnapshot =
152
- hotReloadEnabled && ! hasError && message.isNotEmpty;
162
+ final shouldCacheSnapshot = containsHotReload && ! hasError;
153
163
if (shouldCacheSnapshot) _generatorTarget.cacheLatestSnapshot ();
154
164
hasError = false ;
155
165
});
@@ -164,6 +174,7 @@ class DevCommand extends DartFrogCommand {
164
174
final routes = path.join (cwd.path, 'routes' );
165
175
166
176
bool shouldReload (WatchEvent event) {
177
+ logger.detail ('[watcher] $event ' );
167
178
return path.isWithin (routes, event.path) ||
168
179
path.isWithin (public, event.path);
169
180
}
@@ -180,11 +191,14 @@ class DevCommand extends DartFrogCommand {
180
191
}
181
192
182
193
Future <void > _killProcess (io.Process process) async {
194
+ logger.detail ('[process] killing process...' );
183
195
if (_isWindows) {
196
+ logger.detail ('[process] taskkill /F /T /PID ${process .pid }' );
184
197
final result = await _runProcess (
185
198
'taskkill' ,
186
199
['/F' , '/T' , '/PID' , '${process .pid }' ],
187
200
);
201
+ logger.detail ('[process] exit(${result .exitCode })' );
188
202
return _exit (result.exitCode);
189
203
}
190
204
process.kill ();
@@ -194,7 +208,6 @@ class DevCommand extends DartFrogCommand {
194
208
/// {@template cached_file}
195
209
/// A cached file which consists of the file path and contents.
196
210
/// {@endtemplate}
197
- @immutable
198
211
class CachedFile {
199
212
/// {@macro cached_file}
200
213
const CachedFile ({required this .path, required this .contents});
@@ -204,19 +217,6 @@ class CachedFile {
204
217
205
218
/// The contents of the generated file.s
206
219
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]);
220
220
}
221
221
222
222
/// Signature for the `createFile` method on [DirectoryGeneratorTarget] .
@@ -233,25 +233,58 @@ typedef CreateFile = Future<GeneratedFile> Function(
233
233
/// {@endtemplate}
234
234
class RestorableDirectoryGeneratorTarget extends DirectoryGeneratorTarget {
235
235
/// {@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;
238
243
239
244
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
+
241
251
CachedFile ? _latestSnapshot;
242
252
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
+
243
271
/// Restore the latest cached snapshot.
244
- Future <void > restore () async {
272
+ Future <void > _restoreLatestSnapshot () async {
245
273
final snapshot = _cachedSnapshot;
246
274
if (snapshot == null ) return ;
275
+ _logger? .detail ('[codegen] restoring previous snapshot...' );
247
276
await createFile (snapshot.path, snapshot.contents);
277
+ _logger? .detail ('[codegen] restored previous snapshot.' );
248
278
}
249
279
250
280
/// Cache the latest recorded snapshot.
251
281
void cacheLatestSnapshot () {
252
282
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 ();
255
288
}
256
289
257
290
@override
0 commit comments