Merge remote-tracking branch 'origin/cherry_pick_binstub_fix' into merge-cherry_pick_binstub_fix
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index bf82e2e..6a96ed7 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -24,7 +24,7 @@
       matrix:
         sdk: [dev]
     steps:
-      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633
       - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
         with:
           sdk: ${{ matrix.sdk }}
@@ -52,7 +52,7 @@
         sdk: [dev]
         shard: [0, 1, 2, 3, 4, 5, 6]
     steps:
-      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633
       - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
         with:
           sdk: ${{ matrix.sdk }}
diff --git a/lib/src/ascii_tree.dart b/lib/src/ascii_tree.dart
index f5c62fd..58e93f4 100644
--- a/lib/src/ascii_tree.dart
+++ b/lib/src/ascii_tree.dart
@@ -84,7 +84,7 @@
   }
 
   // Walk the map recursively and render to a string.
-  return fromMap(root);
+  return fromMap(root, startingAtTop: false);
 }
 
 /// Draws a tree from a nested map. Given a map like:
@@ -108,9 +108,17 @@
 ///     barback
 ///
 /// Items with no children should have an empty map as the value.
-String fromMap(Map<String, Map> map) {
+///
+/// If [startingAtTop] is `false`, the tree will be shown as:
+///
+///     |-- analyzer
+///     |   '-- args
+///     |   |   '-- collection
+///     '   '---logging
+///     '---barback
+String fromMap(Map<String, Map> map, {bool startingAtTop = true}) {
   var buffer = StringBuffer();
-  _draw(buffer, '', null, map);
+  _draw(buffer, '', null, map, depth: startingAtTop ? 0 : 1);
   return buffer.toString();
 }
 
@@ -119,10 +127,11 @@
   String prefix,
   bool isLastChild,
   String? name,
+  bool isRoot,
 ) {
   // Print lines.
   buffer.write(prefix);
-  if (name != null) {
+  if (!isRoot) {
     if (isLastChild) {
       buffer.write(log.gray(emoji('└── ', "'-- ")));
     } else {
@@ -147,15 +156,16 @@
   Map<String, Map> children, {
   bool showAllChildren = false,
   bool isLast = false,
+  required int depth,
 }) {
   // Don't draw a line for the root node.
-  if (name != null) _drawLine(buffer, prefix, isLast, name);
+  if (name != null) _drawLine(buffer, prefix, isLast, name, depth <= 1);
 
   // Recurse to the children.
   var childNames = ordered(children.keys);
 
   void drawChild(bool isLastChild, String child) {
-    var childPrefix = _getPrefix(name == null, isLast);
+    var childPrefix = _getPrefix(depth <= 1, isLast);
     _draw(
       buffer,
       '$prefix$childPrefix',
@@ -163,6 +173,7 @@
       children[child] as Map<String, Map>,
       showAllChildren: showAllChildren,
       isLast: isLastChild,
+      depth: depth + 1,
     );
   }
 
diff --git a/lib/src/command/deps.dart b/lib/src/command/deps.dart
index 404fa61..9258545 100644
--- a/lib/src/command/deps.dart
+++ b/lib/src/command/deps.dart
@@ -89,7 +89,10 @@
         usageException('Cannot combine --json and --style.');
       }
       final visited = <String>[];
-      final toVisit = [entrypoint.workspaceRoot.name];
+      final workspacePackageNames = [
+        ...entrypoint.workspaceRoot.transitiveWorkspace.map((p) => p.name),
+      ];
+      final toVisit = [...workspacePackageNames];
       final packagesJson = <dynamic>[];
       final graph = await entrypoint.packageGraph;
       while (toVisit.isNotEmpty) {
@@ -98,14 +101,15 @@
         visited.add(current);
         final currentPackage =
             (await entrypoint.packageGraph).packages[current]!;
-        final next = (current == entrypoint.workspaceRoot.name
-                ? entrypoint.workspaceRoot.immediateDependencies
+        final isRoot = workspacePackageNames.contains(currentPackage.name);
+        final next = (isRoot
+                ? currentPackage.immediateDependencies
                 : currentPackage.dependencies)
             .keys
             .toList();
         final dependencyType =
             entrypoint.workspaceRoot.pubspec.dependencyType(current);
-        final kind = currentPackage == entrypoint.workspaceRoot
+        final kind = isRoot
             ? 'root'
             : (dependencyType == DependencyType.direct
                 ? 'direct'
@@ -159,8 +163,6 @@
           buffer.writeln("${log.bold('${sdk.name} SDK')} ${sdk.version}");
         }
 
-        buffer.writeln(_labelPackage(entrypoint.workspaceRoot));
-
         switch (argResults['style']) {
           case 'compact':
             await _outputCompact(buffer);
@@ -187,24 +189,32 @@
   Future<void> _outputCompact(
     StringBuffer buffer,
   ) async {
-    var root = entrypoint.workspaceRoot;
-    await _outputCompactPackages(
-      'dependencies',
-      root.dependencies.keys,
-      buffer,
-    );
-    if (_includeDev) {
+    var first = true;
+    for (final root in entrypoint.workspaceRoot.transitiveWorkspace) {
+      if (!first) {
+        buffer.write('\n');
+      }
+      first = false;
+
+      buffer.writeln(_labelPackage(root));
       await _outputCompactPackages(
-        'dev dependencies',
-        root.devDependencies.keys,
+        'dependencies',
+        root.dependencies.keys,
+        buffer,
+      );
+      if (_includeDev) {
+        await _outputCompactPackages(
+          'dev dependencies',
+          root.devDependencies.keys,
+          buffer,
+        );
+      }
+      await _outputCompactPackages(
+        'dependency overrides',
+        root.dependencyOverrides.keys,
         buffer,
       );
     }
-    await _outputCompactPackages(
-      'dependency overrides',
-      root.dependencyOverrides.keys,
-      buffer,
-    );
 
     var transitive = await _getTransitiveDependencies();
     await _outputCompactPackages('transitive dependencies', transitive, buffer);
@@ -240,20 +250,28 @@
   /// For each dependency listed, *that* package's immediate dependencies are
   /// shown.
   Future<void> _outputList(StringBuffer buffer) async {
-    var root = entrypoint.workspaceRoot;
-    await _outputListSection('dependencies', root.dependencies.keys, buffer);
-    if (_includeDev) {
+    var first = true;
+    for (final root in entrypoint.workspaceRoot.transitiveWorkspace) {
+      if (!first) {
+        buffer.write('\n');
+      }
+      first = false;
+
+      buffer.writeln(_labelPackage(root));
+      await _outputListSection('dependencies', root.dependencies.keys, buffer);
+      if (_includeDev) {
+        await _outputListSection(
+          'dev dependencies',
+          root.devDependencies.keys,
+          buffer,
+        );
+      }
       await _outputListSection(
-        'dev dependencies',
-        root.devDependencies.keys,
+        'dependency overrides',
+        root.dependencyOverrides.keys,
         buffer,
       );
     }
-    await _outputListSection(
-      'dependency overrides',
-      root.dependencyOverrides.keys,
-      buffer,
-    );
 
     var transitive = await _getTransitiveDependencies();
     if (transitive.isEmpty) return;
@@ -301,57 +319,66 @@
     // being added to the tree, and the parent map that will receive that
     // package.
     var toWalk = Queue<(Package, Map<String, Map>)>();
-    var visited = <String>{entrypoint.workspaceRoot.name};
+    var visited = <String>{};
 
     // Start with the root dependencies.
     var packageTree = <String, Map>{};
+    final workspacePackageNames = [
+      ...entrypoint.workspaceRoot.transitiveWorkspace.map((p) => p.name),
+    ];
     var immediateDependencies =
         entrypoint.workspaceRoot.immediateDependencies.keys.toSet();
     if (!_includeDev) {
       immediateDependencies
           .removeAll(entrypoint.workspaceRoot.devDependencies.keys);
     }
-    for (var name in immediateDependencies) {
+    for (var name in workspacePackageNames) {
       toWalk.add((await _getPackage(name), packageTree));
     }
 
     // Do a breadth-first walk to the dependency graph.
     while (toWalk.isNotEmpty) {
-      var pair = toWalk.removeFirst();
-      var (package, map) = pair;
+      final (package, map) = toWalk.removeFirst();
 
-      if (visited.contains(package.name)) {
+      if (!visited.add(package.name)) {
         map[log.gray('${package.name}...')] = <String, Map>{};
         continue;
       }
 
-      visited.add(package.name);
-
       // Populate the map with this package's dependencies.
       var childMap = <String, Map>{};
       map[_labelPackage(package)] = childMap;
 
-      for (var dep in package.dependencies.values) {
-        toWalk.add((await _getPackage(dep.name), childMap));
+      final isRoot = workspacePackageNames.contains(package.name);
+      final children = [
+        ...isRoot
+            ? package.immediateDependencies.keys
+            : package.dependencies.keys,
+      ];
+      if (!_includeDev) {
+        children.removeWhere(package.devDependencies.keys.contains);
+      }
+      for (var dep in children) {
+        toWalk.add((await _getPackage(dep), childMap));
       }
     }
-
     buffer.write(tree.fromMap(packageTree));
   }
 
   String _labelPackage(Package package) =>
       '${log.bold(package.name)} ${package.version}';
 
-  /// Gets the names of the non-immediate dependencies of the root package.
+  /// Gets the names of the non-immediate dependencies of the workspace packages.
   Future<Set<String>> _getTransitiveDependencies() async {
     var transitive = await _getAllDependencies();
-    var root = entrypoint.workspaceRoot;
-    transitive.remove(root.name);
-    transitive.removeAll(root.dependencies.keys);
-    if (_includeDev) {
-      transitive.removeAll(root.devDependencies.keys);
+    for (final root in entrypoint.workspaceRoot.transitiveWorkspace) {
+      transitive.remove(root.name);
+      transitive.removeAll(root.dependencies.keys);
+      if (_includeDev) {
+        transitive.removeAll(root.devDependencies.keys);
+      }
+      transitive.removeAll(root.dependencyOverrides.keys);
     }
-    transitive.removeAll(root.dependencyOverrides.keys);
     return transitive;
   }
 
@@ -361,8 +388,12 @@
       return graph.packages.keys.toSet();
     }
 
-    var nonDevDependencies = entrypoint.workspaceRoot.dependencies.keys.toList()
-      ..addAll(entrypoint.workspaceRoot.dependencyOverrides.keys);
+    var nonDevDependencies = [
+      for (final package in entrypoint.workspaceRoot.transitiveWorkspace) ...[
+        ...package.dependencies.keys,
+        ...package.dependencyOverrides.keys,
+      ],
+    ];
     return nonDevDependencies
         .expand(graph.transitiveDependencies)
         .map((package) => package.name)
@@ -385,14 +416,14 @@
   /// Outputs all executables reachable from [entrypoint].
   Future<void> _outputExecutables(StringBuffer buffer) async {
     final graph = await entrypoint.packageGraph;
-    var packages = [
-      entrypoint.workspaceRoot,
-      ...(_includeDev
-              ? entrypoint.workspaceRoot.immediateDependencies
-              : entrypoint.workspaceRoot.dependencies)
-          .keys
-          .map((name) => graph.packages[name]!),
-    ];
+    final packages = {
+      for (final p in entrypoint.workspaceRoot.transitiveWorkspace) ...[
+        graph.packages[p.name]!,
+        ...(_includeDev ? p.immediateDependencies : p.dependencies)
+            .keys
+            .map((name) => graph.packages[name]!),
+      ],
+    };
 
     for (var package in packages) {
       var executables = package.executableNames;
diff --git a/pubspec.yaml b/pubspec.yaml
index eb197db..3fe357c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -11,7 +11,7 @@
   collection: ^1.18.0
   convert: ^3.1.1
   crypto: ^3.0.3
-  frontend_server_client: ^3.2.0
+  frontend_server_client: ^4.0.0
   http: ^1.1.2
   http_multi_server: ^3.2.1
   http_parser: ^4.0.2
diff --git a/test/ascii_tree_test.dart b/test/ascii_tree_test.dart
index b406f72..115cbc6 100644
--- a/test/ascii_tree_test.dart
+++ b/test/ascii_tree_test.dart
@@ -98,6 +98,6 @@
       },
     };
 
-    ctx.expectNextSection(tree.fromMap(map));
+    ctx.expectNextSection(tree.fromMap(map, startingAtTop: false));
   });
 }
diff --git a/test/workspace_test.dart b/test/workspace_test.dart
index 1a98308..cfa4079 100644
--- a/test/workspace_test.dart
+++ b/test/workspace_test.dart
@@ -355,6 +355,194 @@
     );
   });
 
+  test('`pub deps` lists dependencies for all members of workspace', () async {
+    final server = await servePackages();
+    server.serve(
+      'foo',
+      '1.0.0',
+      deps: {'transitive': '^1.0.0'},
+      contents: [
+        dir('bin', [file('foomain.dart')]),
+      ],
+    );
+    server.serve(
+      'transitive',
+      '1.0.0',
+      contents: [
+        dir('bin', [file('transitivemain.dart')]),
+      ],
+    );
+    server.serve(
+      'both',
+      '1.0.0',
+      contents: [
+        dir('bin', [file('bothmain.dart')]),
+      ],
+    );
+
+    await dir(appPath, [
+      libPubspec(
+        'myapp',
+        '1.2.3',
+        extras: {
+          'workspace': ['pkgs/a', 'pkgs/b'],
+        },
+        deps: {'both': '^1.0.0', 'b': null},
+        sdk: '^3.7.0',
+      ),
+      dir('bin', [file('myappmain.dart')]),
+      dir('pkgs', [
+        dir('a', [
+          libPubspec(
+            'a',
+            '1.1.1',
+            deps: {'myapp': null, 'foo': '^1.0.0'},
+            devDeps: {'both': '^1.0.0'},
+            resolutionWorkspace: true,
+          ),
+        ]),
+        dir('bin', [file('amain.dart')]),
+        dir('b', [
+          libPubspec(
+            'b',
+            '1.1.1',
+            deps: {'myapp': null, 'both': '^1.0.0'},
+            resolutionWorkspace: true,
+          ),
+          dir('bin', [file('bmain.dart')]),
+        ]),
+      ]),
+    ]).create();
+    await runPub(
+      args: ['deps'],
+      environment: {'_PUB_TEST_SDK_VERSION': '3.7.0'},
+      output: contains(
+        '''
+Dart SDK 3.7.0
+a 1.1.1
+├── both...
+├── foo 1.0.0
+│   └── transitive 1.0.0
+└── myapp...
+b 1.1.1
+├── both...
+└── myapp...
+myapp 1.2.3
+├── b...
+└── both 1.0.0''',
+      ),
+    );
+
+    await runPub(
+      args: ['deps', '--style=list', '--dev'],
+      environment: {'_PUB_TEST_SDK_VERSION': '3.7.0'},
+      output: '''
+Dart SDK 3.7.0
+myapp 1.2.3
+
+dependencies:
+- both 1.0.0
+- b 1.1.1
+  - myapp any
+  - both ^1.0.0
+
+b 1.1.1
+
+dependencies:
+- myapp 1.2.3
+  - both ^1.0.0
+  - b any
+- both 1.0.0
+
+a 1.1.1
+
+dependencies:
+- myapp 1.2.3
+  - both ^1.0.0
+  - b any
+- foo 1.0.0
+  - transitive ^1.0.0
+
+dev dependencies:
+- both 1.0.0
+
+transitive dependencies:
+- transitive 1.0.0''',
+    );
+
+    await runPub(
+      args: ['deps', '--style=list', '--no-dev'],
+      environment: {'_PUB_TEST_SDK_VERSION': '3.7.0'},
+      output: '''
+Dart SDK 3.7.0
+myapp 1.2.3
+
+dependencies:
+- both 1.0.0
+- b 1.1.1
+  - myapp any
+  - both ^1.0.0
+
+b 1.1.1
+
+dependencies:
+- myapp 1.2.3
+  - both ^1.0.0
+  - b any
+- both 1.0.0
+
+a 1.1.1
+
+dependencies:
+- myapp 1.2.3
+  - both ^1.0.0
+  - b any
+- foo 1.0.0
+  - transitive ^1.0.0
+
+transitive dependencies:
+- transitive 1.0.0''',
+    );
+    await runPub(
+      args: ['deps', '--style=compact'],
+      environment: {'_PUB_TEST_SDK_VERSION': '3.7.0'},
+      output: '''
+    Dart SDK 3.7.0
+myapp 1.2.3
+
+dependencies:
+- b 1.1.1 [myapp both]
+- both 1.0.0
+
+b 1.1.1
+
+dependencies:
+- both 1.0.0
+- myapp 1.2.3 [both b]
+
+a 1.1.1
+
+dependencies:
+- foo 1.0.0 [transitive]
+- myapp 1.2.3 [both b]
+
+dev dependencies:
+- both 1.0.0
+
+transitive dependencies:
+- transitive 1.0.0''',
+    );
+    await runPub(
+      args: ['deps', '--executables'],
+      environment: {'_PUB_TEST_SDK_VERSION': '3.7.0'},
+      output: '''
+myapp:myappmain
+both:bothmain
+b:bmain
+foo:foomain''',
+    );
+  });
+
   test('`pub add` acts on the work package', () async {
     final server = await servePackages();
     server.serve('foo', '1.0.0', sdk: '^3.7.0');