Allow overriding `resolution` and `workspace` fields in pubspec_overrides. (#4400)

diff --git a/lib/src/command/unpack.dart b/lib/src/command/unpack.dart
index 2ee228f..3ec9ddf 100644
--- a/lib/src/command/unpack.dart
+++ b/lib/src/command/unpack.dart
@@ -135,18 +135,50 @@
         await cache.hosted.downloadInto(id, destinationDir, cache);
       },
     );
-    final e = Entrypoint(
-      destinationDir,
-      cache,
-    );
+
     if (argResults.flag('resolve')) {
       try {
+        final pubspec = Pubspec.load(
+          destinationDir,
+          cache.sources,
+          containingDescription: RootDescription(destinationDir),
+        );
+        final buffer = StringBuffer();
+        if (pubspec.resolution != Resolution.none) {
+          log.message(
+            '''
+This package was developed as part of a workspace.
+
+Creating `pubspec_overrides.yaml` to resolve it alone.''',
+          );
+          buffer.writeln('resolution:');
+        }
+        if (pubspec.dependencyOverrides.isNotEmpty) {
+          log.message(
+            '''
+This package was developed with dependency_overrides.
+
+Creating `pubspec_overrides.yaml` to resolve it without those overrides.''',
+          );
+          buffer.writeln('dependency_overrides:');
+        }
+        if (buffer.isNotEmpty) {
+          writeTextFile(
+            p.join(destinationDir, 'pubspec_overrides.yaml'),
+            buffer.toString(),
+          );
+        }
+        final e = Entrypoint(
+          destinationDir,
+          cache,
+        );
         await e.acquireDependencies(SolveType.get);
       } finally {
         log.message('To explore type: cd $destinationDir');
-        if (e.example != null) {
+        final exampleDir = p.join(destinationDir, 'example');
+        if (dirExists(exampleDir)) {
           log.message(
-            'To explore the example type: cd ${e.example!.workspaceRoot.dir}',
+            'To explore the example type: cd $exampleDir',
           );
         }
       }
diff --git a/lib/src/pubspec.dart b/lib/src/pubspec.dart
index 1542768..8c40b6d 100644
--- a/lib/src/pubspec.dart
+++ b/lib/src/pubspec.dart
@@ -66,12 +66,13 @@
   /// Directories of packages that should resolve together with this package.
   late List<String> workspace = () {
     final result = <String>[];
-    final r = fields.nodes['workspace'];
-    if (r != null && !languageVersion.supportsWorkspaces) {
+    final workspaceNode =
+        _overridesFileFields?.nodes['workspace'] ?? fields.nodes['workspace'];
+    if (workspaceNode != null && !languageVersion.supportsWorkspaces) {
       _error(
         '`workspace` and `resolution` requires at least language version '
         '${LanguageVersion.firstVersionWithWorkspaces}',
-        r.span,
+        workspaceNode.span,
         hint: '''
 Consider updating the SDK constraint to:
 
@@ -80,12 +81,12 @@
 ''',
       );
     }
-    if (r == null || r.value == null) return <String>[];
+    if (workspaceNode == null || workspaceNode.value == null) return <String>[];
 
-    if (r is! YamlList) {
-      _error('"workspace" must be a list of strings', r.span);
+    if (workspaceNode is! YamlList) {
+      _error('"workspace" must be a list of strings', workspaceNode.span);
     }
-    for (final t in r.nodes) {
+    for (final t in workspaceNode.nodes) {
       final value = t.value;
       if (value is! String) {
         _error('"workspace" must be a list of strings', t.span);
@@ -103,12 +104,14 @@
 
   /// The resolution mode.
   late Resolution resolution = () {
-    final r = fields.nodes['resolution'];
-    if (r != null && !languageVersion.supportsWorkspaces) {
+    final resolutionNode =
+        _overridesFileFields?.nodes['resolution'] ?? fields.nodes['resolution'];
+
+    if (resolutionNode != null && !languageVersion.supportsWorkspaces) {
       _error(
         '`workspace` and `resolution` requires at least language version '
         '${LanguageVersion.firstVersionWithWorkspaces}',
-        r.span,
+        resolutionNode.span,
         hint: '''
 Consider updating the SDK constraint to:
 
@@ -117,14 +120,14 @@
 ''',
       );
     }
-    return switch (r?.value) {
+    return switch (resolutionNode?.value) {
       null => Resolution.none,
       'local' => Resolution.local,
       'workspace' => Resolution.workspace,
       'external' => Resolution.external,
       _ => _error(
           '"resolution" must be one of `workspace`, `local`, `external`',
-          r!.span,
+          resolutionNode!.span,
         )
     };
   }();
@@ -167,16 +170,6 @@
     if (_dependencyOverrides != null) return _dependencyOverrides!;
     final pubspecOverridesFields = _overridesFileFields;
     if (pubspecOverridesFields != null) {
-      pubspecOverridesFields.nodes.forEach((key, _) {
-        final keyNode = key as YamlNode;
-        if (!const {'dependency_overrides'}.contains(keyNode.value)) {
-          throw SourceSpanApplicationException(
-            'pubspec_overrides.yaml only supports the '
-            '`dependency_overrides` field.',
-            keyNode.span,
-          );
-        }
-      });
       if (pubspecOverridesFields.containsKey('dependency_overrides')) {
         _dependencyOverrides = _parseDependencies(
           'dependency_overrides',
@@ -394,6 +387,19 @@
               ? fields
               : YamlMap.wrap(fields, sourceUrl: location),
         ) {
+    if (overridesFields != null) {
+      overridesFields.nodes.forEach((key, _) {
+        final keyNode = key as YamlNode;
+        if (!const {'dependency_overrides', 'resolution', 'workspace'}
+            .contains(keyNode.value)) {
+          throw SourceSpanApplicationException(
+            'pubspec_overrides.yaml only supports the '
+            '`dependency_overrides`, `resolution` and `workspace` fields.',
+            keyNode.span,
+          );
+        }
+      });
+    }
     // If [expectedName] is passed, ensure that the actual 'name' field exists
     // and matches the expectation.
     if (expectedName == null) return;
@@ -845,8 +851,13 @@
 }
 
 enum Resolution {
+  // Still unused.
   external,
+  // This package is a member of a workspace, and should be resolved with a
+  // pubspec.yaml located higher.
   workspace,
+  // Still unused.
   local,
+  // This package is at the root of a workspace.
   none,
 }
diff --git a/test/pubspec_test.dart b/test/pubspec_test.dart
index 8dbff18..c181fcc 100644
--- a/test/pubspec_test.dart
+++ b/test/pubspec_test.dart
@@ -1060,8 +1060,13 @@
           );
         }
 
-        final pubspec = parsePubspecOverrides(contents);
-        expect(() => fn(pubspec), throwsA(expectation));
+        expect(
+          () {
+            final pubspec = parsePubspecOverrides(contents);
+            fn(pubspec);
+          },
+          throwsA(expectation),
+        );
       }
 
       test('allows empty overrides file', () {
diff --git a/test/unpack_test.dart b/test/unpack_test.dart
index 5e4ab2e..56fa6a0 100644
--- a/test/unpack_test.dart
+++ b/test/unpack_test.dart
@@ -125,4 +125,44 @@
       output: contains('Downloading foo 1.0.0 to `.${s}foo-1.0.0`...'),
     );
   });
+
+  test('unpacks and resolve workspace project', () async {
+    await d.dir(appPath).create();
+
+    final server = await servePackages();
+    server.serve('bar', '1.0.0');
+    server.serve(
+      'foo',
+      '1.0.0',
+      pubspec: {
+        'environment': {'sdk': '^3.5.0'},
+        'resolution': 'workspace',
+        'workspace': ['example'],
+      },
+      contents: [
+        d.dir('example', [
+          d.libPubspec(
+            'example',
+            '1.0.0',
+            sdk: '^3.5.0',
+            deps: {'foo': null, 'bar': '^1.0.0'},
+            extras: {'resolution': 'workspace'},
+          ),
+        ]),
+      ],
+    );
+    await runPub(
+      args: ['unpack', 'foo:1.0.0'],
+      output: allOf(
+        contains('Downloading foo 1.0.0 to `.${s}foo-1.0.0`...'),
+        contains(
+          '+ bar',
+        ),
+      ),
+      environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'},
+    );
+    await d.dir(appPath, [
+      d.dir('foo-1.0.0', [d.file('pubspec_overrides.yaml', 'resolution:\n')]),
+    ]).validate();
+  });
 }
diff --git a/test/workspace_test.dart b/test/workspace_test.dart
index 1dc9a63..b1336d4 100644
--- a/test/workspace_test.dart
+++ b/test/workspace_test.dart
@@ -1638,6 +1638,55 @@
 ''',
     );
   });
+
+  test(
+    '"workspace" and "resolution" fields can be overridden by '
+    '`pubspec_overrides`',
+    () async {
+      final server = await servePackages();
+      server.serve('foo', '1.0.0');
+      server.serve('bar', '1.0.0');
+      await dir(appPath, [
+        libPubspec(
+          'myapp',
+          '1.2.3',
+          extras: {
+            'workspace': ['pkgs/a'],
+          },
+          sdk: '^3.5.0',
+        ),
+        dir('pkgs', [
+          dir('a', [
+            libPubspec('a', '1.1.1', sdk: '^3.5.0', deps: {'foo': '^1.0.0'}),
+            file('pubspec_overrides.yaml', 'resolution: workspace'),
+          ]),
+          dir(
+            'b',
+            [
+              libPubspec(
+                'b',
+                '1.0.0',
+                deps: {'bar': '^1.0.0'},
+                resolutionWorkspace: true,
+              ),
+            ],
+          ),
+        ]),
+      ]).create();
+      await pubGet(
+        environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'},
+        output: contains('+ foo'),
+      );
+      await dir(
+        appPath,
+        [file('pubspec_overrides.yaml', 'workspace: ["pkgs/b/"]')],
+      ).create();
+      await pubGet(
+        environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'},
+        output: contains('+ bar'),
+      );
+    },
+  );
 }
 
 final s = p.separator;