Fix detection of root packages in PackageGraph.transitiveDependencies (#4620)

diff --git a/lib/src/command/deps.dart b/lib/src/command/deps.dart
index 222a594..f74aeb5 100644
--- a/lib/src/command/deps.dart
+++ b/lib/src/command/deps.dart
@@ -420,7 +420,7 @@
         .expand(
           (p) => graph.transitiveDependencies(
             p,
-            followDevDependenciesFromRoot: false,
+            followDevDependenciesFromPackage: false,
           ),
         )
         .map((package) => package.name)
diff --git a/lib/src/command/upgrade.dart b/lib/src/command/upgrade.dart
index 0e93a64..a817ff4 100644
--- a/lib/src/command/upgrade.dart
+++ b/lib/src/command/upgrade.dart
@@ -124,7 +124,7 @@
             (package) => graph
                 .transitiveDependencies(
                   package,
-                  followDevDependenciesFromRoot: true,
+                  followDevDependenciesFromPackage: true,
                 )
                 .map((p) => p.name),
           )
diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart
index b4b708e..592592f 100644
--- a/lib/src/global_packages.dart
+++ b/lib/src/global_packages.dart
@@ -212,7 +212,10 @@
     final activatedPackage = entrypoint.workPackage;
     final name = activatedPackage.name;
     for (final package in (await entrypoint.packageGraph)
-        .transitiveDependencies(name, followDevDependenciesFromRoot: false)) {
+        .transitiveDependencies(
+          name,
+          followDevDependenciesFromPackage: false,
+        )) {
       _testForHooks(package, name);
     }
     _describeActive(name, cache);
diff --git a/lib/src/package_graph.dart b/lib/src/package_graph.dart
index 07f0666..98a5a03 100644
--- a/lib/src/package_graph.dart
+++ b/lib/src/package_graph.dart
@@ -46,12 +46,11 @@
 
   /// Returns all transitive dependencies of [package].
   ///
-  /// For the entrypoint this returns all packages in [packages], which includes
-  /// dev and override. For any other package, it ignores dev and override
-  /// dependencies.
+  /// If [package] is a root, this will explore the dev_dependencies of
+  /// [package] if [followDevDependenciesFromPackage] is true.
   Set<Package> transitiveDependencies(
     String package, {
-    required bool followDevDependenciesFromRoot,
+    required bool followDevDependenciesFromPackage,
   }) {
     final result = <Package>{};
 
@@ -63,7 +62,11 @@
       final currentPackage = packages[current]!;
       result.add(currentPackage);
       stack.addAll(currentPackage.dependencies.keys);
-      if (followDevDependenciesFromRoot && current == package) {
+      if (followDevDependenciesFromPackage &&
+          current == package &&
+          entrypoint.workspaceRoot.transitiveWorkspace.any(
+            (p) => p.name == current,
+          )) {
         stack.addAll(currentPackage.devDependencies.keys);
       }
     }
@@ -89,7 +92,9 @@
 
     return transitiveDependencies(
       package,
-      followDevDependenciesFromRoot: true,
+      // If package is a root package it is not immutable itself, and we don't
+      // need to consider its dev_dependencies.
+      followDevDependenciesFromPackage: false,
     ).any((dep) => !_isPackageFromImmutableSource(dep.name));
   }
 }
diff --git a/test/package_graph_test.dart b/test/package_graph_test.dart
new file mode 100644
index 0000000..609c3b3
--- /dev/null
+++ b/test/package_graph_test.dart
@@ -0,0 +1,114 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+import 'package:path/path.dart' as p;
+import 'package:pub/src/entrypoint.dart';
+import 'package:pub/src/system_cache.dart';
+import 'package:test/test.dart';
+
+import 'descriptor.dart' as d;
+import 'test_pub.dart';
+
+void main() {
+  test('transitiveDependencies', () async {
+    final server = await servePackages();
+    server.serve(
+      'foo',
+      '1.0.0',
+      deps: {
+        'transitive': {'hosted': globalServer.url},
+      },
+      pubspec: {
+        'dev_dependencies': {
+          'transitive_dev_dep': {'hosted': globalServer.url},
+        }, // This should **not** be included.
+      },
+    );
+    server.serve(
+      'dev_dep',
+      '1.0.0',
+      deps: {
+        'dev_dep_transitive': {'hosted': globalServer.url},
+      },
+      pubspec: {
+        'dev_dependencies': {
+          'transitive_dev_dep': {
+            'hosted': globalServer.url,
+          }, // This should **not** be included.
+        },
+      },
+    );
+    server.serve('dev_dep_transitive', '1.0.0');
+    server.serve('transitive', '1.0.0');
+    server.serve('a_dev_dep', '1.0.0');
+    await d.dir(appPath, [
+      d.appPubspec(
+        dependencies: {
+          'a': null,
+          'foo': {'hosted': globalServer.url},
+        },
+        extras: {
+          'environment': {'sdk': '^3.5.0'},
+          'workspace': ['a'],
+          'dev_dependencies': {
+            'dev_dep': {'hosted': globalServer.url},
+          },
+        },
+      ),
+      d.dir('a', [
+        d.libPubspec(
+          'a',
+          '1.0.0',
+          resolutionWorkspace: true,
+          devDeps: {
+            'a_dev_dep': {'hosted': globalServer.url},
+          },
+        ),
+      ]),
+    ]).create();
+    await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'});
+    final entrypoint = Entrypoint(
+      p.join(d.sandbox, appPath),
+      SystemCache(rootDir: p.join(d.sandbox, cachePath)),
+    );
+    final graph = await entrypoint.packageGraph;
+
+    expect(
+      graph
+          .transitiveDependencies('foo', followDevDependenciesFromPackage: true)
+          .map((p) => p.name),
+      {'foo', 'transitive'},
+    );
+
+    expect(
+      graph
+          .transitiveDependencies(
+            'foo',
+            followDevDependenciesFromPackage: false,
+          )
+          .map((p) => p.name),
+      {'foo', 'transitive'},
+    );
+
+    expect(
+      graph
+          .transitiveDependencies(
+            'myapp',
+            followDevDependenciesFromPackage: true,
+          )
+          .map((p) => p.name),
+      {'myapp', 'foo', 'dev_dep', 'dev_dep_transitive', 'transitive', 'a'},
+    );
+
+    expect(
+      graph
+          .transitiveDependencies(
+            'myapp',
+            followDevDependenciesFromPackage: false,
+          )
+          .map((p) => p.name),
+      {'myapp', 'foo', 'transitive', 'a'},
+    );
+  });
+}