diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index 56d4e2fa6..aeca03c9e 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -1,3 +1,5 @@ +## 24.3.9-wip + ## 24.3.8 - 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) diff --git a/dwds/lib/src/version.dart b/dwds/lib/src/version.dart index b4e574a30..81d8048d2 100644 --- a/dwds/lib/src/version.dart +++ b/dwds/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '24.3.8'; +const packageVersion = '24.3.9-wip'; diff --git a/dwds/pubspec.yaml b/dwds/pubspec.yaml index 6836050e1..a8d827126 100644 --- a/dwds/pubspec.yaml +++ b/dwds/pubspec.yaml @@ -1,6 +1,6 @@ name: dwds # Every time this changes you need to run `dart run build_runner build`. -version: 24.3.8 +version: 24.3.9-wip description: >- A service that proxies between the Chrome debug protocol and the Dart VM service protocol. diff --git a/webdev/CHANGELOG.md b/webdev/CHANGELOG.md index cf55b6512..6d0f66c0b 100644 --- a/webdev/CHANGELOG.md +++ b/webdev/CHANGELOG.md @@ -1,5 +1,7 @@ ## 3.7.2-wip +- Adds `--offline` flag [#2483](https://github.com/dart-lang/webdev/pull/2483) + ## 3.7.1 - Update `dwds` constraint to `24.3.5`. diff --git a/webdev/README.md b/webdev/README.md index 1a80a1f21..8e16a53b7 100644 --- a/webdev/README.md +++ b/webdev/README.md @@ -114,6 +114,7 @@ Common: in the debugger. (defaults to on) -v, --verbose Enables verbose logging. + --offline Disable fetching from pub.dev. Run "webdev help" to see global options. @@ -147,6 +148,7 @@ Usage: webdev build [arguments] in the debugger. (defaults to on) -v, --verbose Enables verbose logging. + --offline Disable fetching from pub.dev. Run "webdev help" to see global options. diff --git a/webdev/lib/src/command/configuration.dart b/webdev/lib/src/command/configuration.dart index 51598870e..07f532261 100644 --- a/webdev/lib/src/command/configuration.dart +++ b/webdev/lib/src/command/configuration.dart @@ -37,6 +37,7 @@ const nullSafetyAuto = 'auto'; const disableDdsFlag = 'disable-dds'; const enableExperimentOption = 'enable-experiment'; const canaryFeaturesFlag = 'canary'; +const offlineFlag = 'offline'; ReloadConfiguration _parseReloadConfiguration(ArgResults argResults) { var auto = argResults.options.contains(autoOption) @@ -107,6 +108,7 @@ class Configuration { final String? _nullSafety; final List? _experiments; final bool? _canaryFeatures; + final bool? _offline; Configuration({ bool? autoRun, @@ -133,6 +135,7 @@ class Configuration { String? nullSafety, List? experiments, bool? canaryFeatures, + bool? offline, }) : _autoRun = autoRun, _chromeDebugPort = chromeDebugPort, _debugExtension = debugExtension, @@ -154,7 +157,8 @@ class Configuration { _verbose = verbose, _nullSafety = nullSafety, _experiments = experiments, - _canaryFeatures = canaryFeatures { + _canaryFeatures = canaryFeatures, + _offline = offline { _validateConfiguration(); } @@ -229,7 +233,8 @@ class Configuration { verbose: other._verbose ?? _verbose, nullSafety: other._nullSafety ?? _nullSafety, experiments: other._experiments ?? _experiments, - canaryFeatures: other._canaryFeatures ?? _canaryFeatures); + canaryFeatures: other._canaryFeatures ?? _canaryFeatures, + offline: other._offline ?? _offline); factory Configuration.noInjectedClientDefaults() => Configuration(autoRun: false, debug: false, debugExtension: false); @@ -284,6 +289,8 @@ class Configuration { bool get canaryFeatures => _canaryFeatures ?? false; + bool get offline => _offline ?? false; + /// Returns a new configuration with values updated from the parsed args. static Configuration fromArgs(ArgResults? argResults, {Configuration? defaultConfiguration}) { @@ -408,6 +415,10 @@ class Configuration { ? argResults[canaryFeaturesFlag] as bool? : defaultConfiguration.canaryFeatures; + final offline = argResults.options.contains(offlineFlag) + ? argResults[offlineFlag] as bool? + : defaultConfiguration.verbose; + return Configuration( autoRun: defaultConfiguration.autoRun, chromeDebugPort: chromeDebugPort, @@ -433,6 +444,7 @@ class Configuration { nullSafety: nullSafety, experiments: experiments, canaryFeatures: canaryFeatures, + offline: offline, ); } } diff --git a/webdev/lib/src/command/shared.dart b/webdev/lib/src/command/shared.dart index e05c31da8..097a893c9 100644 --- a/webdev/lib/src/command/shared.dart +++ b/webdev/lib/src/command/shared.dart @@ -74,7 +74,11 @@ void addSharedArgs(ArgParser argParser, abbr: 'v', defaultsTo: false, negatable: false, - help: 'Enables verbose logging.'); + help: 'Enables verbose logging.') + ..addFlag(offlineFlag, + defaultsTo: false, + negatable: false, + help: 'Disable fetching from pub.dev.'); } /// Parses the provided [Configuration] to return a list of @@ -103,7 +107,7 @@ List buildRunnerArgs(Configuration configuration) { } Future validatePubspecLock(Configuration configuration) async { - final pubspecLock = await PubspecLock.read(); + final pubspecLock = await PubspecLock.read(offline: configuration.offline); await checkPubspecLock(pubspecLock, requireBuildWebCompilers: configuration.requireBuildWebCompilers); } diff --git a/webdev/lib/src/pubspec.dart b/webdev/lib/src/pubspec.dart index 6bcec17b3..9dc7b4e39 100644 --- a/webdev/lib/src/pubspec.dart +++ b/webdev/lib/src/pubspec.dart @@ -68,8 +68,10 @@ class PubspecLock { PubspecLock(this._packages); - static Future read() async { - await _runPubDeps(); + static Future read({bool offline = false}) async { + if (!offline) { + await _runPubDeps(); + } var dir = p.absolute(p.current); while (true) { final candidate = p.join( diff --git a/webdev/test/installation_test.dart b/webdev/test/installation_test.dart index d79674521..e06d1ffde 100644 --- a/webdev/test/installation_test.dart +++ b/webdev/test/installation_test.dart @@ -30,6 +30,14 @@ void main() { Process? serveProcess; Directory? tempDir0; + final testScript = + File(p.join(p.dirname(Platform.script.toFilePath()), 'test.dart')) + .readAsStringSync(); + final thisScript = File.fromUri(Uri.parse(testScript.substring( + testScript.lastIndexOf('import', testScript.indexOf('as test;')) + 8, + testScript.indexOf('as test;') - 2))); + final packageDir = p.dirname(p.dirname(thisScript.path)); + Future expectStdoutAndCleanExit(Process process, {required String expectedStdout}) async { final stdoutCompleter = _captureOutput( @@ -141,6 +149,41 @@ void main() { await expectStdoutThenExit(serveProcess!, expectedStdout: 'Serving `web` on'); }); + + test('activate and serve webdev fails with offline', () async { + final tempDir = tempDir0!; + final tempPath = tempDir.path; + + // Verify that we can create a new Dart app: + createProcess = await Process.start( + 'dart', + ['create', '--no-pub', '--template', 'web', 'temp_app'], + workingDirectory: tempPath, + ); + await expectStdoutAndCleanExit( + createProcess!, + expectedStdout: 'Created project temp_app in temp_app!', + ); + final appPath = p.join(tempPath, 'temp_app'); + expect(await Directory(appPath).exists(), isTrue); + + // Verify that `dart pub global activate` works: + activateProcess = await Process.start( + 'dart', + ['pub', 'global', 'activate', '--source', 'path', packageDir], + ); + await expectStdoutAndCleanExit( + activateProcess!, + expectedStdout: 'Activated webdev', + ); + + // Verify that `webdev serve` works for our new app: + serveProcess = await Process.start('dart', + ['pub', 'global', 'run', 'webdev', 'serve', '--offline', 'web:8081'], + workingDirectory: appPath); + await expectStdoutThenExit(serveProcess!, + expectedStdout: 'Cannot open file\n pubspec.lock\n'); + }); } Future _waitForExitOrTimeout(Process process) { diff --git a/webdev/test/integration_test.dart b/webdev/test/integration_test.dart index 93e90e223..44b79275f 100644 --- a/webdev/test/integration_test.dart +++ b/webdev/test/integration_test.dart @@ -2,18 +2,63 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +@Timeout(Duration(minutes: 3)) +library; + import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:path/path.dart' as p; import 'package:test/test.dart'; import 'package:test_descriptor/test_descriptor.dart' as d; import 'test_utils.dart'; +enum StreamType { + stdout, + stderr, +} + +const processTimeout = Duration(minutes: 1); + void main() { final testRunner = TestRunner(); setUpAll(testRunner.setUpAll); tearDownAll(testRunner.tearDownAll); + Future expectStdoutAndCleanExit(Process process, + {required String expectedStdout}) async { + final stdoutCompleter = _captureOutput( + process, + streamType: StreamType.stdout, + stopCaptureFuture: process.exitCode, + ); + final stderrCompleter = _captureOutput( + process, + streamType: StreamType.stderr, + stopCaptureFuture: process.exitCode, + ); + final exitCode = await _waitForExitOrTimeout(process); + final stderrLogs = await stderrCompleter.future; + final stdoutLogs = await stdoutCompleter.future; + expect( + exitCode, + equals(0), + // Include the stderr and stdout logs if the process does not terminate + // cleanly: + reason: 'stderr: $stderrLogs, stdout: $stdoutLogs', + ); + expect( + stderrLogs, + isEmpty, + ); + expect( + stdoutLogs, + contains(expectedStdout), + ); + } + test('non-existent commands create errors', () async { final process = await testRunner.runWebDev(['monkey']); @@ -214,6 +259,26 @@ dependencies: await checkProcessStdout(process, ['webdev could not run']); await process.shouldExit(78); }); + + if (command != 'daemon') { + test('failure with offline and unresolved dependencies', () async { + final createProcess = await Process.start( + 'dart', + ['create', '--no-pub', '--template', 'web', 'temp_app'], + workingDirectory: d.sandbox, + ); + await expectStdoutAndCleanExit(createProcess, + expectedStdout: 'Created project temp_app'); + + final appPath = p.join(d.sandbox, 'temp_app'); + + final process = await testRunner + .runWebDev([command, '--offline'], workingDirectory: appPath); + + await checkProcessStdout(process, ['webdev could not run']); + await process.shouldExit(78); + }); + } }); } } @@ -286,3 +351,36 @@ packages: return buffer.toString(); } + +Future _waitForExitOrTimeout(Process process) { + Timer(processTimeout, () { + process.kill(ProcessSignal.sigint); + }); + return process.exitCode; +} + +Completer _captureOutput( + Process process, { + required StreamType streamType, + required Future stopCaptureFuture, +}) { + final stream = + streamType == StreamType.stdout ? process.stdout : process.stderr; + final completer = Completer(); + var output = ''; + stream.transform(utf8.decoder).listen((line) { + output += line; + if (line.contains('[SEVERE]')) { + process.kill(ProcessSignal.sigint); + if (!completer.isCompleted) { + completer.complete(output); + } + } + }); + unawaited(stopCaptureFuture.then((_) { + if (!completer.isCompleted) { + completer.complete(output); + } + })); + return completer; +}