Repository: dart-lang/webdev
Pull request #2483: Add offline mode
Author: login: RossComputerGuy
Labels: name: package:webdev
name: package:dwds
Created: 2024-08-23T03:09:25Z | Merged: 2025-03-21T19:33:05Z

- Adds an `--offline` flag which is useful for sandbox/networkless build systems such as Nix.

---

- [x] I’ve reviewed the contributor guide and applied the relevant portions to this PR.

<details>
  <summary>Contribution guidelines:</summary><br>

- See our [contributor guide](https://github.com/dart-lang/.github/blob/main/CONTRIBUTING.md) for general expectations for PRs.
- Larger or significant changes should be discussed in an issue before creating a PR.
- Contributions to our repos should follow the [Dart style guide](https://dart.dev/guides/language/effective-dart) and use `dart format`.
- Most changes should add an entry to the changelog and may need to [rev the pubspec package version](https://github.com/dart-lang/sdk/blob/main/docs/External-Package-Maintenance.md#making-a-change).
- Changes to packages require [corresponding tests](https://github.com/dart-lang/.github/blob/main/CONTRIBUTING.md#Testing).

Note that many Dart repos have a weekly cadence for reviewing PRs - please allow for some latency before initial review feedback.
</details>



Diff:
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<String>? _experiments;
   final bool? _canaryFeatures;
+  final bool? _offline;
 
   Configuration({
     bool? autoRun,
@@ -133,6 +135,7 @@ class Configuration {
     String? nullSafety,
     List<String>? 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<String> buildRunnerArgs(Configuration configuration) {
 }
 
 Future<void> 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<PubspecLock> read() async {
-    await _runPubDeps();
+  static Future<PubspecLock> 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<void> 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<int> _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<void> 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<int> _waitForExitOrTimeout(Process process) {
+  Timer(processTimeout, () {
+    process.kill(ProcessSignal.sigint);
+  });
+  return process.exitCode;
+}
+
+Completer<String> _captureOutput(
+  Process process, {
+  required StreamType streamType,
+  required Future stopCaptureFuture,
+}) {
+  final stream =
+      streamType == StreamType.stdout ? process.stdout : process.stderr;
+  final completer = Completer<String>();
+  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;
+}


Reviews:
user: login: bkonyi
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2024-10-08T19:13:20Z
user: login: RossComputerGuy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-08T19:14:34Z
user: login: bkonyi
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2024-10-08T19:18:43Z
user: login: RossComputerGuy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-08T19:20:11Z
user: login: bkonyi
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2024-10-08T20:05:05Z
user: login: RossComputerGuy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-08T20:36:22Z
user: login: RossComputerGuy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-08T21:10:59Z
user: login: jyameo
body: Thanks so much for your contribution! Apologies for the delay in getting this over the line. Overall looks good! We really appreciate your work on this! 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T20:18:26Z
user: login: RossComputerGuy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T01:06:22Z
user: login: jyameo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T18:57:40Z
user: login: jyameo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T18:57:55Z
user: login: RossComputerGuy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T19:01:36Z
user: login: RossComputerGuy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T19:01:42Z

Review comments:
diff_hunk: @@ -30,6 +30,14 @@ void main() {
   Process? serveProcess;
   Directory? tempDir0;
 
+  final testScript =
path: webdev/test/installation_test.dart
user: login: bkonyi
body: I'm not really sure what's going on with these lines. Could we not get the package directory simply by doing `p.dirname(p.dirname(Platform.script.toFilePath())`?
created_at: 2024-10-08T19:13:17Z
author_association: COLLABORATOR
diff_hunk: @@ -30,6 +30,14 @@ void main() {
   Process? serveProcess;
   Directory? tempDir0;
 
+  final testScript =
path: webdev/test/installation_test.dart
user: login: RossComputerGuy
body: That gives me `/tmp`
created_at: 2024-10-08T19:14:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,6 +30,14 @@ void main() {
   Process? serveProcess;
   Directory? tempDir0;
 
+  final testScript =
path: webdev/test/installation_test.dart
user: login: bkonyi
body: Ah right, because the test runner generates an entrypoint...

In that case, I think we should continue to activate `webdev` from pub instead of using `--source path`. This test should still work as long as we don't run `dart pub get` on the created project before running `webdev serve --offline`.
created_at: 2024-10-08T19:18:43Z
author_association: COLLABORATOR
diff_hunk: @@ -30,6 +30,14 @@ void main() {
   Process? serveProcess;
   Directory? tempDir0;
 
+  final testScript =
path: webdev/test/installation_test.dart
user: login: RossComputerGuy
body: Oh ok, I was assuming we would do it the way I did so new updates would actually work. I was getting an error saying `--offline` wasn't a recognized flag even if I did activate it outside the test.
created_at: 2024-10-08T19:20:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,6 +30,14 @@ void main() {
   Process? serveProcess;
   Directory? tempDir0;
 
+  final testScript =
path: webdev/test/installation_test.dart
user: login: bkonyi
body: Right, that's a great point. Given that, I think we should move the test to [`webdev/test/integration_test.dart`](https://github.com/dart-lang/webdev/blob/main/webdev/test/integration_test.dart). The tests there run `webdev` from source so we won't have to worry about publishing before this new test will pass.
created_at: 2024-10-08T20:05:05Z
author_association: COLLABORATOR
diff_hunk: @@ -30,6 +30,14 @@ void main() {
   Process? serveProcess;
   Directory? tempDir0;
 
+  final testScript =
path: webdev/test/installation_test.dart
user: login: RossComputerGuy
body: Tbf, I could see how it could fit into both. Especially since once the next update is published and this PR is in that, then the flag would be available.
created_at: 2024-10-08T20:36:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,6 +30,14 @@ void main() {
   Process? serveProcess;
   Directory? tempDir0;
 
+  final testScript =
path: webdev/test/installation_test.dart
user: login: RossComputerGuy
body: I've got the test added to the integration set.
created_at: 2024-10-08T21:10:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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 feching from pub.dev.');
path: webdev/lib/src/command/shared.dart
user: login: jyameo
body: nit: spelling error -`feching` -> `fetching` 
created_at: 2025-03-19T19:28:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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 feching from pub.dev.');
path: webdev/lib/src/command/shared.dart
user: login: RossComputerGuy
body: Done
created_at: 2025-03-20T01:06:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -114,6 +114,7 @@ Common:
                                            in the debugger.
                                            (defaults to on)
 -v, --verbose                              Enables verbose logging.
+--offline                                  Disable fetching from pub.dev.
path: webdev/README.md
user: login: jyameo
body: @RossComputerGuy The beginning of this line needs to be shifted by 4 spaces to match the output the the webdev build/serve --help command
created_at: 2025-03-21T18:57:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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.
path: webdev/README.md
user: login: jyameo
body: @RossComputerGuy same as above
created_at: 2025-03-21T18:57:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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.
path: webdev/README.md
user: login: RossComputerGuy
body: Done
created_at: 2025-03-21T19:01:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -114,6 +114,7 @@ Common:
                                            in the debugger.
                                            (defaults to on)
 -v, --verbose                              Enables verbose logging.
+--offline                                  Disable fetching from pub.dev.
path: webdev/README.md
user: login: RossComputerGuy
body: Done
created_at: 2025-03-21T19:01:42Z
author_association: CONTRIBUTOR

PR comments:
user: login: bkonyi
created_at: 2024-10-07T16:35:17Z
body: Sorry for the delay on this @RossComputerGuy! Overall, this change looks good to me, but we should add some tests before landing.
author_association: COLLABORATOR
user: login: RossComputerGuy
created_at: 2024-10-07T16:43:22Z
body: I've gone ahead and rebased it. I will look into getting tests in shortly.
author_association: CONTRIBUTOR
user: login: RossComputerGuy
created_at: 2024-10-07T17:40:12Z
body: Can't figure out how the test should be done, I see there's configuration tests but none invoke read.
author_association: CONTRIBUTOR
user: login: bkonyi
created_at: 2024-10-08T16:57:30Z
body: > Can't figure out how the test should be done, I see there's configuration tests but none invoke read.

I think you can either add a test [like this](https://github.com/dart-lang/webdev/blob/main/webdev/test/installation_test.dart#L110) or something similar to what's done [in this file](https://github.com/dart-lang/webdev/blob/main/webdev/test/integration_test.dart).

Basically, you'll just want a test that runs `webdev serve --no-pub` on a project that hasn't had `dart pub get` invoked to pull down the dependencies. The `webdev serve --no-pub` should fail to serve the project since there will be unresolved dependencies.
author_association: COLLABORATOR
user: login: RossComputerGuy
created_at: 2024-10-08T18:52:02Z
body: Ok, I added the test.
author_association: CONTRIBUTOR
user: login: bkonyi
created_at: 2025-03-13T18:07:43Z
body: Sorry for the delay on this @RossComputerGuy. Can you take this over the line @jyameo?
author_association: COLLABORATOR
user: login: jyameo
created_at: 2025-03-20T17:23:49Z
body: @RossComputerGuy Can you please address the CI failures below and include an entry in `changelog.md` detailing this change?
author_association: CONTRIBUTOR
user: login: RossComputerGuy
created_at: 2025-03-20T17:32:19Z
body: I was able to address everything except the `dart analyze` because the two annotations don't like being used together apparently.
author_association: CONTRIBUTOR
user: login: jyameo
created_at: 2025-03-20T18:51:04Z
body: > I was able to address everything except the `dart analyze` because the two annotations don't like being used together apparently.
I will take a look at the analyzer issue. For the changelog, it seems you a few entries as we are currently at [#3.7.2-wip](https://github.com/dart-lang/webdev/blob/main/webdev/CHANGELOG.md). Can you make sure to pull the latest main branch and merge it into yours 
author_association: CONTRIBUTOR
user: login: jyameo
created_at: 2025-03-20T19:17:40Z
body: > I was able to address everything except the `dart analyze` because the two annotations don't like being used together apparently.

You should be able to resolve the`dart analyze` issue by applying the library directive like this:

```
@Timeout(Duration(minutes: 3))
library;
```

Similar to what's being done in the `installation_test.dart`


author_association: CONTRIBUTOR
user: login: RossComputerGuy
created_at: 2025-03-20T19:29:02Z
body: Oh, I was looking at the wrong test.
author_association: CONTRIBUTOR
user: login: jyameo
created_at: 2025-03-21T18:02:34Z
body: Thanks for the update @RossComputerGuy. To finalize this PR, can you please address the following issue:

- Create an entry in the [webdev README](https://github.com/dart-lang/webdev/tree/main/webdev#readme) describing the use of `--offline`.
- Add a new section at the top of the [DWDS CHANGELOG](https://github.com/dart-lang/webdev/blob/main/dwds/CHANGELOG.md) for `##24.3.8-wip` (no need to enter any details, just add the section header).
- Update the version of DWDS to `24.3.8-wip` in the [pubspec](https://github.com/dart-lang/webdev/blob/main/dwds/pubspec.yaml) file.
- Update the package version to `24.3.8-wip` in the [version.dart](https://github.com/dart-lang/webdev/blob/main/dwds/lib/src/version.dart) file.

Also please pull and merge the latest version of the main branch before making the changes above. 

Once these are addressed, we should be good to go.

author_association: CONTRIBUTOR
user: login: RossComputerGuy
created_at: 2025-03-21T18:09:13Z
body: > To finalize this PR, can you please address the following issue

Done all those now.
author_association: CONTRIBUTOR
user: login: jyameo
created_at: 2025-03-21T18:12:15Z
body: > > To finalize this PR, can you please address the following issue
> 
> Done all those now.

Awesome. We just need to pull down and merge the latest version of the main branch and resolve the conflicts and we should be good

author_association: CONTRIBUTOR
user: login: RossComputerGuy
created_at: 2025-03-21T18:14:19Z
body: Done
author_association: CONTRIBUTOR
user: login: jyameo
created_at: 2025-03-21T18:18:06Z
body: > Done

Apologies @RossComputerGuy. I made a mistake... the version the `DWDS changelog`, `pubspec`, and `version.dart` should actually be `24.3.9-wip` instead of `24.3.8-wip`. Can you please rectify this. Sorry for the confusion.
author_association: CONTRIBUTOR
user: login: RossComputerGuy
created_at: 2025-03-21T18:19:42Z
body: Done
author_association: CONTRIBUTOR
user: login: jyameo
created_at: 2025-03-21T18:43:32Z
body: Hey @RossComputerGuy, for some reason the `test/readme_test.dart` is still failing. Can you please look into this. The other failures (ie. e2e_test.dart) can be ignored as it was already failing. Thanks for the patience and collaboration
author_association: CONTRIBUTOR
user: login: jyameo
created_at: 2025-03-21T18:53:59Z
body: > Hey @RossComputerGuy, for some reason the `test/readme_test.dart` is still failing. Can you please look into this. The other failures (ie. e2e_test.dart) can be ignored as it was already failing. Thanks for the patience and collaboration

Looks like it's caused by some white whitespace issues. the Readme needs to match exactly the output of the command `webdev build/serve`.
author_association: CONTRIBUTOR
user: login: jyameo
created_at: 2025-03-21T19:34:43Z
body: Thanks @RossComputerGuy for your contribution and patience in landing this. Your changes are in and will be part of the next release.
author_association: CONTRIBUTOR