Skip to content

Commit af33df7

Browse files
Add offline mode (dart-lang#2483)
* Add offline mode
1 parent 5c805f1 commit af33df7

10 files changed

+173
-8
lines changed

dwds/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
## 24.3.9-wip
2+
13
## 24.3.8
24

35
- Updated DWDS to include a boolean flag that enables debugging support only when set to true. [#60289](https://github.com/dart-lang/sdk/issues/60289)

dwds/lib/src/version.dart

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dwds/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: dwds
22
# Every time this changes you need to run `dart run build_runner build`.
3-
version: 24.3.8
3+
version: 24.3.9-wip
44
description: >-
55
A service that proxies between the Chrome debug protocol and the Dart VM
66
service protocol.

webdev/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## 3.7.2-wip
22

3+
- Adds `--offline` flag [#2483](https://github.com/dart-lang/webdev/pull/2483)
4+
35
## 3.7.1
46

57
- Update `dwds` constraint to `24.3.5`.

webdev/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ Common:
114114
in the debugger.
115115
(defaults to on)
116116
-v, --verbose Enables verbose logging.
117+
--offline Disable fetching from pub.dev.
117118
118119
Run "webdev help" to see global options.
119120
@@ -147,6 +148,7 @@ Usage: webdev build [arguments]
147148
in the debugger.
148149
(defaults to on)
149150
-v, --verbose Enables verbose logging.
151+
--offline Disable fetching from pub.dev.
150152
151153
Run "webdev help" to see global options.
152154

webdev/lib/src/command/configuration.dart

+14-2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const nullSafetyAuto = 'auto';
3737
const disableDdsFlag = 'disable-dds';
3838
const enableExperimentOption = 'enable-experiment';
3939
const canaryFeaturesFlag = 'canary';
40+
const offlineFlag = 'offline';
4041

4142
ReloadConfiguration _parseReloadConfiguration(ArgResults argResults) {
4243
var auto = argResults.options.contains(autoOption)
@@ -107,6 +108,7 @@ class Configuration {
107108
final String? _nullSafety;
108109
final List<String>? _experiments;
109110
final bool? _canaryFeatures;
111+
final bool? _offline;
110112

111113
Configuration({
112114
bool? autoRun,
@@ -133,6 +135,7 @@ class Configuration {
133135
String? nullSafety,
134136
List<String>? experiments,
135137
bool? canaryFeatures,
138+
bool? offline,
136139
}) : _autoRun = autoRun,
137140
_chromeDebugPort = chromeDebugPort,
138141
_debugExtension = debugExtension,
@@ -154,7 +157,8 @@ class Configuration {
154157
_verbose = verbose,
155158
_nullSafety = nullSafety,
156159
_experiments = experiments,
157-
_canaryFeatures = canaryFeatures {
160+
_canaryFeatures = canaryFeatures,
161+
_offline = offline {
158162
_validateConfiguration();
159163
}
160164

@@ -229,7 +233,8 @@ class Configuration {
229233
verbose: other._verbose ?? _verbose,
230234
nullSafety: other._nullSafety ?? _nullSafety,
231235
experiments: other._experiments ?? _experiments,
232-
canaryFeatures: other._canaryFeatures ?? _canaryFeatures);
236+
canaryFeatures: other._canaryFeatures ?? _canaryFeatures,
237+
offline: other._offline ?? _offline);
233238

234239
factory Configuration.noInjectedClientDefaults() =>
235240
Configuration(autoRun: false, debug: false, debugExtension: false);
@@ -284,6 +289,8 @@ class Configuration {
284289

285290
bool get canaryFeatures => _canaryFeatures ?? false;
286291

292+
bool get offline => _offline ?? false;
293+
287294
/// Returns a new configuration with values updated from the parsed args.
288295
static Configuration fromArgs(ArgResults? argResults,
289296
{Configuration? defaultConfiguration}) {
@@ -408,6 +415,10 @@ class Configuration {
408415
? argResults[canaryFeaturesFlag] as bool?
409416
: defaultConfiguration.canaryFeatures;
410417

418+
final offline = argResults.options.contains(offlineFlag)
419+
? argResults[offlineFlag] as bool?
420+
: defaultConfiguration.verbose;
421+
411422
return Configuration(
412423
autoRun: defaultConfiguration.autoRun,
413424
chromeDebugPort: chromeDebugPort,
@@ -433,6 +444,7 @@ class Configuration {
433444
nullSafety: nullSafety,
434445
experiments: experiments,
435446
canaryFeatures: canaryFeatures,
447+
offline: offline,
436448
);
437449
}
438450
}

webdev/lib/src/command/shared.dart

+6-2
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ void addSharedArgs(ArgParser argParser,
7474
abbr: 'v',
7575
defaultsTo: false,
7676
negatable: false,
77-
help: 'Enables verbose logging.');
77+
help: 'Enables verbose logging.')
78+
..addFlag(offlineFlag,
79+
defaultsTo: false,
80+
negatable: false,
81+
help: 'Disable fetching from pub.dev.');
7882
}
7983

8084
/// Parses the provided [Configuration] to return a list of
@@ -103,7 +107,7 @@ List<String> buildRunnerArgs(Configuration configuration) {
103107
}
104108

105109
Future<void> validatePubspecLock(Configuration configuration) async {
106-
final pubspecLock = await PubspecLock.read();
110+
final pubspecLock = await PubspecLock.read(offline: configuration.offline);
107111
await checkPubspecLock(pubspecLock,
108112
requireBuildWebCompilers: configuration.requireBuildWebCompilers);
109113
}

webdev/lib/src/pubspec.dart

+4-2
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,10 @@ class PubspecLock {
6868

6969
PubspecLock(this._packages);
7070

71-
static Future<PubspecLock> read() async {
72-
await _runPubDeps();
71+
static Future<PubspecLock> read({bool offline = false}) async {
72+
if (!offline) {
73+
await _runPubDeps();
74+
}
7375
var dir = p.absolute(p.current);
7476
while (true) {
7577
final candidate = p.join(

webdev/test/installation_test.dart

+43
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ void main() {
3030
Process? serveProcess;
3131
Directory? tempDir0;
3232

33+
final testScript =
34+
File(p.join(p.dirname(Platform.script.toFilePath()), 'test.dart'))
35+
.readAsStringSync();
36+
final thisScript = File.fromUri(Uri.parse(testScript.substring(
37+
testScript.lastIndexOf('import', testScript.indexOf('as test;')) + 8,
38+
testScript.indexOf('as test;') - 2)));
39+
final packageDir = p.dirname(p.dirname(thisScript.path));
40+
3341
Future<void> expectStdoutAndCleanExit(Process process,
3442
{required String expectedStdout}) async {
3543
final stdoutCompleter = _captureOutput(
@@ -141,6 +149,41 @@ void main() {
141149
await expectStdoutThenExit(serveProcess!,
142150
expectedStdout: 'Serving `web` on');
143151
});
152+
153+
test('activate and serve webdev fails with offline', () async {
154+
final tempDir = tempDir0!;
155+
final tempPath = tempDir.path;
156+
157+
// Verify that we can create a new Dart app:
158+
createProcess = await Process.start(
159+
'dart',
160+
['create', '--no-pub', '--template', 'web', 'temp_app'],
161+
workingDirectory: tempPath,
162+
);
163+
await expectStdoutAndCleanExit(
164+
createProcess!,
165+
expectedStdout: 'Created project temp_app in temp_app!',
166+
);
167+
final appPath = p.join(tempPath, 'temp_app');
168+
expect(await Directory(appPath).exists(), isTrue);
169+
170+
// Verify that `dart pub global activate` works:
171+
activateProcess = await Process.start(
172+
'dart',
173+
['pub', 'global', 'activate', '--source', 'path', packageDir],
174+
);
175+
await expectStdoutAndCleanExit(
176+
activateProcess!,
177+
expectedStdout: 'Activated webdev',
178+
);
179+
180+
// Verify that `webdev serve` works for our new app:
181+
serveProcess = await Process.start('dart',
182+
['pub', 'global', 'run', 'webdev', 'serve', '--offline', 'web:8081'],
183+
workingDirectory: appPath);
184+
await expectStdoutThenExit(serveProcess!,
185+
expectedStdout: 'Cannot open file\n pubspec.lock\n');
186+
});
144187
}
145188

146189
Future<int> _waitForExitOrTimeout(Process process) {

webdev/test/integration_test.dart

+98
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,63 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
@Timeout(Duration(minutes: 3))
6+
library;
7+
58
import 'dart:async';
9+
import 'dart:convert';
10+
import 'dart:io';
611

12+
import 'package:path/path.dart' as p;
713
import 'package:test/test.dart';
814
import 'package:test_descriptor/test_descriptor.dart' as d;
915

1016
import 'test_utils.dart';
1117

18+
enum StreamType {
19+
stdout,
20+
stderr,
21+
}
22+
23+
const processTimeout = Duration(minutes: 1);
24+
1225
void main() {
1326
final testRunner = TestRunner();
1427
setUpAll(testRunner.setUpAll);
1528
tearDownAll(testRunner.tearDownAll);
1629

30+
Future<void> expectStdoutAndCleanExit(Process process,
31+
{required String expectedStdout}) async {
32+
final stdoutCompleter = _captureOutput(
33+
process,
34+
streamType: StreamType.stdout,
35+
stopCaptureFuture: process.exitCode,
36+
);
37+
final stderrCompleter = _captureOutput(
38+
process,
39+
streamType: StreamType.stderr,
40+
stopCaptureFuture: process.exitCode,
41+
);
42+
final exitCode = await _waitForExitOrTimeout(process);
43+
final stderrLogs = await stderrCompleter.future;
44+
final stdoutLogs = await stdoutCompleter.future;
45+
expect(
46+
exitCode,
47+
equals(0),
48+
// Include the stderr and stdout logs if the process does not terminate
49+
// cleanly:
50+
reason: 'stderr: $stderrLogs, stdout: $stdoutLogs',
51+
);
52+
expect(
53+
stderrLogs,
54+
isEmpty,
55+
);
56+
expect(
57+
stdoutLogs,
58+
contains(expectedStdout),
59+
);
60+
}
61+
1762
test('non-existent commands create errors', () async {
1863
final process = await testRunner.runWebDev(['monkey']);
1964

@@ -214,6 +259,26 @@ dependencies:
214259
await checkProcessStdout(process, ['webdev could not run']);
215260
await process.shouldExit(78);
216261
});
262+
263+
if (command != 'daemon') {
264+
test('failure with offline and unresolved dependencies', () async {
265+
final createProcess = await Process.start(
266+
'dart',
267+
['create', '--no-pub', '--template', 'web', 'temp_app'],
268+
workingDirectory: d.sandbox,
269+
);
270+
await expectStdoutAndCleanExit(createProcess,
271+
expectedStdout: 'Created project temp_app');
272+
273+
final appPath = p.join(d.sandbox, 'temp_app');
274+
275+
final process = await testRunner
276+
.runWebDev([command, '--offline'], workingDirectory: appPath);
277+
278+
await checkProcessStdout(process, ['webdev could not run']);
279+
await process.shouldExit(78);
280+
});
281+
}
217282
});
218283
}
219284
}
@@ -286,3 +351,36 @@ packages:
286351

287352
return buffer.toString();
288353
}
354+
355+
Future<int> _waitForExitOrTimeout(Process process) {
356+
Timer(processTimeout, () {
357+
process.kill(ProcessSignal.sigint);
358+
});
359+
return process.exitCode;
360+
}
361+
362+
Completer<String> _captureOutput(
363+
Process process, {
364+
required StreamType streamType,
365+
required Future stopCaptureFuture,
366+
}) {
367+
final stream =
368+
streamType == StreamType.stdout ? process.stdout : process.stderr;
369+
final completer = Completer<String>();
370+
var output = '';
371+
stream.transform(utf8.decoder).listen((line) {
372+
output += line;
373+
if (line.contains('[SEVERE]')) {
374+
process.kill(ProcessSignal.sigint);
375+
if (!completer.isCompleted) {
376+
completer.complete(output);
377+
}
378+
}
379+
});
380+
unawaited(stopCaptureFuture.then((_) {
381+
if (!completer.isCompleted) {
382+
completer.complete(output);
383+
}
384+
}));
385+
return completer;
386+
}

0 commit comments

Comments
 (0)