support nested packages in firehose (#277)

Fixes https://github.com/dart-lang/ecosystem/issues/276

Adds support for published packages nested under unpublished packages. In particular, this adds support for workspace packages to firehose.
diff --git a/pkgs/firehose/CHANGELOG.md b/pkgs/firehose/CHANGELOG.md
index 2e49ae3..d409f86 100644
--- a/pkgs/firehose/CHANGELOG.md
+++ b/pkgs/firehose/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.9.1
+
+- Support packages nested under a 'workspace' root package.
+
 ## 0.9.0
 
 - Add `leaking` check to the health workflow.
diff --git a/pkgs/firehose/lib/src/repo.dart b/pkgs/firehose/lib/src/repo.dart
index 1cdd1df..1d5fea1 100644
--- a/pkgs/firehose/lib/src/repo.dart
+++ b/pkgs/firehose/lib/src/repo.dart
@@ -55,16 +55,18 @@
     if (pubspecFile.existsSync()) {
       var pubspec = yaml.loadYaml(pubspecFile.readAsStringSync()) as Map;
       var publishTo = pubspec['publish_to'] as String?;
-      if (publishTo != 'none') {
+      if (publishTo != 'none' && !pubspec.containsKey('workspace')) {
         packages.add(Package(directory, this));
+        // There is an assumption here that published, non-workspace packages do
+        // not contain nested published packages.
+        return;
       }
-    } else {
-      if (directory.existsSync()) {
-        for (var child in directory.listSync().whereType<Directory>()) {
-          var name = path.basename(child.path);
-          if (!name.startsWith('.')) {
-            _recurseAndGather(child, packages);
-          }
+    }
+    if (directory.existsSync()) {
+      for (var child in directory.listSync().whereType<Directory>()) {
+        var name = path.basename(child.path);
+        if (!name.startsWith('.')) {
+          _recurseAndGather(child, packages);
         }
       }
     }
diff --git a/pkgs/firehose/pubspec.yaml b/pkgs/firehose/pubspec.yaml
index f6a6b6e..d9a079a 100644
--- a/pkgs/firehose/pubspec.yaml
+++ b/pkgs/firehose/pubspec.yaml
@@ -1,6 +1,6 @@
 name: firehose
 description: A tool to automate publishing of Pub packages from GitHub actions.
-version: 0.9.0
+version: 0.9.1
 repository: https://github.com/dart-lang/ecosystem/tree/main/pkgs/firehose
 
 environment:
diff --git a/pkgs/firehose/test/repo_test.dart b/pkgs/firehose/test/repo_test.dart
index d148423..27a774d 100644
--- a/pkgs/firehose/test/repo_test.dart
+++ b/pkgs/firehose/test/repo_test.dart
@@ -6,6 +6,7 @@
 library;
 
 import 'dart:io';
+import 'dart:isolate';
 
 import 'package:firehose/src/github.dart';
 import 'package:firehose/src/repo.dart';
@@ -13,13 +14,19 @@
 import 'package:test/test.dart';
 
 void main() {
-  group('repo', () {
-    late Repository packages;
+  late Repository packages;
+  late Uri packageRoot;
 
+  setUpAll(() async {
+    packageRoot = (await Isolate.resolvePackageUri(
+            Uri.parse('package:firehose/test.dart')))!
+        .resolve('../');
+  });
+
+  group('repo', () {
     setUp(() {
-      // Tests are run in the package directory; look up two levels to get the
-      // repo directory.
-      packages = Repository(Directory.current.parent.parent);
+      // Look up two levels from the package directory to get the repo dir.
+      packages = Repository(Directory.fromUri(packageRoot.resolve('../../')));
     });
 
     test('isSinglePackageRepo', () {
@@ -59,4 +66,38 @@
       expect(queryParams['body'], package.changelog.describeLatestChanges);
     });
   });
+
+  group('pub workspace repo', () {
+    setUp(() {
+      packages = Repository(
+          Directory.fromUri(packageRoot.resolve('test_data/workspace_repo')));
+    });
+
+    test('locatePackages', () {
+      var result = packages.locatePackages();
+      expect(
+          result,
+          equals([
+            isA<Package>().having((p) => p.name, 'name', 'pkg_1'),
+            isA<Package>().having((p) => p.name, 'name', 'pkg_2'),
+          ]));
+    });
+  });
+
+  group('repo with an unpublished root package', () {
+    setUp(() {
+      packages = Repository(Directory.fromUri(
+          packageRoot.resolve('test_data/root_unpublished_pkg')));
+    });
+
+    test('locatePackages', () {
+      var result = packages.locatePackages();
+      expect(
+          result,
+          equals([
+            isA<Package>().having((p) => p.name, 'name', 'pkg_1'),
+            isA<Package>().having((p) => p.name, 'name', 'pkg_2'),
+          ]));
+    });
+  });
 }
diff --git a/pkgs/firehose/test_data/root_unpublished_pkg/pkg_1/pubspec.yaml b/pkgs/firehose/test_data/root_unpublished_pkg/pkg_1/pubspec.yaml
new file mode 100644
index 0000000..935037e
--- /dev/null
+++ b/pkgs/firehose/test_data/root_unpublished_pkg/pkg_1/pubspec.yaml
@@ -0,0 +1 @@
+name: pkg_1
diff --git a/pkgs/firehose/test_data/root_unpublished_pkg/pkg_2/pubspec.yaml b/pkgs/firehose/test_data/root_unpublished_pkg/pkg_2/pubspec.yaml
new file mode 100644
index 0000000..a4598d0
--- /dev/null
+++ b/pkgs/firehose/test_data/root_unpublished_pkg/pkg_2/pubspec.yaml
@@ -0,0 +1 @@
+name: pkg_2
diff --git a/pkgs/firehose/test_data/root_unpublished_pkg/pubspec.yaml b/pkgs/firehose/test_data/root_unpublished_pkg/pubspec.yaml
new file mode 100644
index 0000000..a72f894
--- /dev/null
+++ b/pkgs/firehose/test_data/root_unpublished_pkg/pubspec.yaml
@@ -0,0 +1,2 @@
+name: root
+publish_to: none
diff --git a/pkgs/firehose/test_data/workspace_repo/pkg_1/pubspec.yaml b/pkgs/firehose/test_data/workspace_repo/pkg_1/pubspec.yaml
new file mode 100644
index 0000000..eecba70
--- /dev/null
+++ b/pkgs/firehose/test_data/workspace_repo/pkg_1/pubspec.yaml
@@ -0,0 +1,2 @@
+name: pkg_1
+resolution: workspace
diff --git a/pkgs/firehose/test_data/workspace_repo/pkg_2/pubspec.yaml b/pkgs/firehose/test_data/workspace_repo/pkg_2/pubspec.yaml
new file mode 100644
index 0000000..dac8e07
--- /dev/null
+++ b/pkgs/firehose/test_data/workspace_repo/pkg_2/pubspec.yaml
@@ -0,0 +1,2 @@
+name: pkg_2
+resolution: workspace
diff --git a/pkgs/firehose/test_data/workspace_repo/pubspec.yaml b/pkgs/firehose/test_data/workspace_repo/pubspec.yaml
new file mode 100644
index 0000000..c4fd076
--- /dev/null
+++ b/pkgs/firehose/test_data/workspace_repo/pubspec.yaml
@@ -0,0 +1,4 @@
+name: workspace
+workspace:
+  - pkg_1
+  - pkg_2