-
Notifications
You must be signed in to change notification settings - Fork 238
Expand file tree
/
Copy pathcli.dart
More file actions
186 lines (163 loc) · 5.14 KB
/
Copy pathcli.dart
File metadata and controls
186 lines (163 loc) · 5.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import 'dart:async';
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:glob/glob.dart';
import 'package:lcov_parser/lcov_parser.dart';
import 'package:mason/mason.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as p;
import 'package:pubspec_parse/pubspec_parse.dart';
import 'package:universal_io/io.dart';
import 'package:very_good_cli/src/cli/templates/templates.dart';
import 'package:very_good_test_runner/very_good_test_runner.dart';
part 'dart_cli.dart';
part 'flutter_cli.dart';
part 'git_cli.dart';
part 'test_cli_runner.dart';
const R Function<R>(
R Function(), {
Function? onError,
ZoneSpecification? zoneSpecification,
Map<Object?, Object?>? zoneValues,
})
_asyncRunZoned = runZoned;
/// Type definition for [Process.run].
typedef RunProcess =
Future<ProcessResult> Function(
String executable,
List<String> arguments, {
String? workingDirectory,
bool runInShell,
});
/// This class facilitates overriding [Process.run].
/// It should be extended by another class in client code with overrides
/// that construct a custom implementation.
@visibleForTesting
abstract class ProcessOverrides {
static final _token = Object();
/// Returns the current [ProcessOverrides] instance.
///
/// This will return `null` if the current [Zone] does not contain
/// any [ProcessOverrides].
///
/// See also:
/// * [ProcessOverrides.runZoned] to provide [ProcessOverrides]
/// in a fresh [Zone].
///
static ProcessOverrides? get current {
return Zone.current[_token] as ProcessOverrides?;
}
/// Runs [body] in a fresh [Zone] using the provided overrides.
static R runZoned<R>(R Function() body, {RunProcess? runProcess}) {
final overrides = _ProcessOverridesScope(runProcess);
return _asyncRunZoned(body, zoneValues: {_token: overrides});
}
/// The method used to run a [Process].
RunProcess get runProcess => Process.run;
}
class _ProcessOverridesScope extends ProcessOverrides {
_ProcessOverridesScope(this._runProcess);
final ProcessOverrides? _previous = ProcessOverrides.current;
final RunProcess? _runProcess;
@override
RunProcess get runProcess {
return _runProcess ?? _previous?.runProcess ?? super.runProcess;
}
}
/// Abstraction for running commands via command-line.
class _Cmd {
/// Runs the specified [cmd] with the provided [args].
static Future<ProcessResult> run(
String cmd,
List<String> args, {
required Logger logger,
bool throwOnError = true,
String? workingDirectory,
}) async {
logger.detail('Running: $cmd with $args');
final runProcess = ProcessOverrides.current?.runProcess ?? Process.run;
final result = await runProcess(
cmd,
args,
runInShell: true,
workingDirectory: workingDirectory,
);
logger
..detail('stdout:\n${result.stdout}')
..detail('stderr:\n${result.stderr}');
if (throwOnError) {
_throwIfProcessFailed(result, cmd, args);
}
return result;
}
static Iterable<Future<T>> runWhere<T>({
required Future<T> Function(FileSystemEntity) run,
required bool Function(FileSystemEntity) where,
String cwd = '.',
}) {
final directories =
Directory(cwd).listSync(recursive: true).where(where).toList()
..sort((a, b) {
/// Linux and macOS have different sorting behaviors
/// regarding the order that the list of folders/files are returned.
/// To ensure consistency across platforms, we apply a
/// uniform sorting logic.
final aSplit = p.split(a.path);
final bSplit = p.split(b.path);
final aLevel = aSplit.length;
final bLevel = bSplit.length;
if (aLevel == bLevel) {
return aSplit.last.compareTo(bSplit.last);
} else {
return aLevel.compareTo(bLevel);
}
});
return directories.map(run);
}
static void _throwIfProcessFailed(
ProcessResult pr,
String process,
List<String> args,
) {
if (pr.exitCode != 0) {
final values = {
'Standard out': pr.stdout.toString().trim(),
'Standard error': pr.stderr.toString().trim(),
}..removeWhere((k, v) => v.isEmpty);
var message = 'Unknown error';
if (values.isNotEmpty) {
message = values.entries.map((e) => '${e.key}\n${e.value}').join('\n');
}
throw ProcessException(process, args, message, pr.exitCode);
}
}
}
const _ignoredDirectories = {
'ios',
'android',
'windows',
'linux',
'macos',
'.symlinks',
'.plugin_symlinks',
'.dart_tool',
'build',
'.fvm',
};
bool _isPubspec(FileSystemEntity entity) {
if (entity is! File) return false;
return p.basename(entity.path) == 'pubspec.yaml';
}
extension on Set<String> {
bool excludes(FileSystemEntity entity) {
final segments = p.split(entity.path).toSet();
if (segments.intersection(_ignoredDirectories).isNotEmpty) return true;
if (segments.intersection(this).isNotEmpty) return true;
for (final value in this) {
if (value.isNotEmpty && Glob(value).matches(entity.path)) {
return true;
}
}
return false;
}
}