Refactor `Entrypoint.rootDir` preparing for workspaces (#4179)

diff --git a/lib/src/command/add.dart b/lib/src/command/add.dart
index 16cfa97..9d74d3e 100644
--- a/lib/src/command/add.dart
+++ b/lib/src/command/add.dart
@@ -192,7 +192,9 @@
     /// Compute a pubspec that will depend on all the given packages, but the
     /// actual constraint will only be determined after a resolution decides the
     /// best version.
-    var resolutionPubspec = entrypoint.root.pubspec;
+    // TODO(https://github.com/dart-lang/pub/issues/4127): This should
+    // operate on entrypoint.workPackage.
+    var resolutionPubspec = entrypoint.workspaceRoot.pubspec;
     for (final update in updates) {
       /// Perform version resolution in-memory.
       resolutionPubspec = await _addPackageToPubspec(resolutionPubspec, update);
@@ -211,8 +213,8 @@
         cache,
         Package(
           resolutionPubspec,
-          entrypoint.rootDir,
-          entrypoint.root.workspaceChildren,
+          entrypoint.workspaceRoot.dir,
+          entrypoint.workspaceRoot.workspaceChildren,
         ),
       );
     } on GitException {
@@ -250,12 +252,12 @@
       /// ensure that the modification timestamp on `pubspec.lock` and
       /// `.dart_tool/package_config.json` is newer than `pubspec.yaml`,
       /// ensuring that [entrypoint.assertUptoDate] will pass.
-      writeTextFile(entrypoint.pubspecPath, newPubspecText);
+      writeTextFile(entrypoint.workspaceRoot.pubspecPath, newPubspecText);
     }
 
     String? overridesFileContents;
     final overridesPath =
-        p.join(entrypoint.rootDir, Pubspec.pubspecOverridesFilename);
+        p.join(entrypoint.workspaceRoot.dir, Pubspec.pubspecOverridesFilename);
     try {
       overridesFileContents = readTextFile(overridesPath);
     } on IOException {
@@ -270,10 +272,11 @@
           Pubspec.parse(
             newPubspecText,
             cache.sources,
-            location: Uri.parse(entrypoint.pubspecPath),
+            location: Uri.parse(entrypoint.workspaceRoot.pubspecPath),
             overridesFileContents: overridesFileContents,
             overridesLocation: Uri.file(overridesPath),
-            containingDescription: RootDescription(entrypoint.rootDir),
+            containingDescription:
+                RootDescription(entrypoint.workspaceRoot.dir),
           ),
         )
         .acquireDependencies(
@@ -680,8 +683,9 @@
     List<PackageId> resultPackages,
     List<_ParseResult> updates,
   ) {
-    final yamlEditor = YamlEditor(readTextFile(entrypoint.pubspecPath));
-    log.io('Reading ${entrypoint.pubspecPath}.');
+    final yamlEditor =
+        YamlEditor(readTextFile(entrypoint.workspaceRoot.pubspecPath));
+    log.io('Reading ${entrypoint.workspaceRoot.pubspecPath}.');
     log.fine('Contents:\n$yamlEditor');
 
     for (final update in updates) {
@@ -701,7 +705,7 @@
                   : VersionConstraint.any),
         ),
         cache,
-        entrypoint,
+        entrypoint.workPackage,
       );
 
       if (yamlEditor.parseAt(
diff --git a/lib/src/command/dependency_services.dart b/lib/src/command/dependency_services.dart
index 5d66468..b1f17ac 100644
--- a/lib/src/command/dependency_services.dart
+++ b/lib/src/command/dependency_services.dart
@@ -65,15 +65,16 @@
       throw FormatException('"target" should be a String.');
     }
 
-    final compatiblePubspec = stripDependencyOverrides(entrypoint.root.pubspec);
+    final compatiblePubspec =
+        stripDependencyOverrides(entrypoint.workspaceRoot.pubspec);
 
     final breakingPubspec = stripVersionBounds(compatiblePubspec);
 
     final compatiblePackagesResult = await _tryResolve(
       Package(
         compatiblePubspec,
-        entrypoint.rootDir,
-        entrypoint.root.workspaceChildren,
+        entrypoint.workspaceRoot.dir,
+        entrypoint.workspaceRoot.workspaceChildren,
       ),
       cache,
       additionalConstraints: additionalConstraints,
@@ -82,8 +83,8 @@
     final breakingPackagesResult = await _tryResolve(
       Package(
         breakingPubspec,
-        entrypoint.rootDir,
-        entrypoint.root.workspaceChildren,
+        entrypoint.workspaceRoot.dir,
+        entrypoint.workspaceRoot.workspaceChildren,
       ),
       cache,
       additionalConstraints: additionalConstraints,
@@ -123,8 +124,8 @@
         final singleBreakingPackagesResult = await _tryResolve(
           Package(
             singleBreakingPubspec,
-            entrypoint.rootDir,
-            entrypoint.root.workspaceChildren,
+            entrypoint.workspaceRoot.dir,
+            entrypoint.workspaceRoot.workspaceChildren,
           ),
           cache,
         );
@@ -144,8 +145,8 @@
         final smallestUpgradeResult = await _tryResolve(
           Package(
             atLeastCurrentPubspec,
-            entrypoint.rootDir,
-            entrypoint.root.workspaceChildren,
+            entrypoint.workspaceRoot.dir,
+            entrypoint.workspaceRoot.workspaceChildren,
           ),
           cache,
           solveType: SolveType.downgrade,
@@ -230,15 +231,15 @@
 
   @override
   Future<void> runProtected() async {
-    final pubspec = entrypoint.root.pubspec;
+    final pubspec = entrypoint.workspaceRoot.pubspec;
 
     final currentPackages = fileExists(entrypoint.lockFilePath)
         ? entrypoint.lockFile.packages.values.toList()
         : (await _tryResolve(
               Package(
                 pubspec,
-                entrypoint.rootDir,
-                entrypoint.root.workspaceChildren,
+                entrypoint.workspaceRoot.dir,
+                entrypoint.workspaceRoot.workspaceChildren,
               ),
               cache,
             ) ??
@@ -308,7 +309,7 @@
 
   @override
   Future<void> runProtected() async {
-    YamlEditor(readTextFile(entrypoint.pubspecPath));
+    YamlEditor(readTextFile(entrypoint.workspaceRoot.pubspecPath));
     final toApply = <_PackageVersion>[];
     final input = json.decode(await utf8.decodeStream(stdin));
     for (final change in input['dependencyChanges'] as Iterable) {
@@ -323,8 +324,9 @@
       );
     }
 
-    final pubspec = entrypoint.root.pubspec;
-    final pubspecEditor = YamlEditor(readTextFile(entrypoint.pubspecPath));
+    final pubspec = entrypoint.workspaceRoot.pubspec;
+    final pubspecEditor =
+        YamlEditor(readTextFile(entrypoint.workspaceRoot.pubspecPath));
     final lockFile = fileExists(entrypoint.lockFilePath)
         ? readTextFile(entrypoint.lockFilePath)
         : null;
@@ -450,16 +452,17 @@
             Pubspec.parse(
               updatedPubspec,
               cache.sources,
-              location: toUri(entrypoint.pubspecPath),
-              containingDescription: RootDescription(entrypoint.rootDir),
+              location: toUri(entrypoint.workspaceRoot.pubspecPath),
+              containingDescription:
+                  RootDescription(entrypoint.workspaceRoot.dir),
             ),
-            entrypoint.rootDir,
-            entrypoint.root.workspaceChildren,
+            entrypoint.workspaceRoot.dir,
+            entrypoint.workspaceRoot.workspaceChildren,
           ),
           lockFile: updatedLockfile,
         );
         if (pubspecEditor.edits.isNotEmpty) {
-          writeTextFile(entrypoint.pubspecPath, updatedPubspec);
+          writeTextFile(entrypoint.workspaceRoot.pubspecPath, updatedPubspec);
         }
         // Only if we originally had a lock-file we write the resulting lockfile back.
         if (updatedLockfile != null) {
@@ -717,14 +720,14 @@
   if (fileExists(entrypoint.lockFilePath)) {
     currentPackages = Map<String, PackageId>.from(entrypoint.lockFile.packages);
   } else {
-    final resolution = await _tryResolve(entrypoint.root, cache) ??
+    final resolution = await _tryResolve(entrypoint.workspaceRoot, cache) ??
         (throw DataException('Failed to resolve pubspec'));
     currentPackages = Map<String, PackageId>.fromIterable(
       resolution,
       key: (e) => (e as PackageId).name,
     );
   }
-  currentPackages.remove(entrypoint.root.name);
+  currentPackages.remove(entrypoint.workspaceRoot.name);
   return currentPackages;
 }
 
@@ -762,7 +765,11 @@
         ? SolveType.downgrade
         : SolveType.get,
     cache,
-    Package(pubspec, entrypoint.rootDir, entrypoint.root.workspaceChildren),
+    Package(
+      pubspec,
+      entrypoint.workspaceRoot.dir,
+      entrypoint.workspaceRoot.workspaceChildren,
+    ),
     lockFile: lockFile,
     additionalConstraints: additionalConstraints,
   );
@@ -785,7 +792,7 @@
         'name': p.name,
         'version': p.versionOrHash(),
         'kind': _kindString(pubspec, p.name),
-        'source': _source(p, containingDir: entrypoint.root.dir),
+        'source': _source(p, containingDir: entrypoint.workspaceRoot.dir),
         'constraintBumped': originalConstraint == null
             ? null
             : upgradeType == _UpgradeType.compatible
@@ -807,7 +814,10 @@
         'previousConstraint': originalConstraint?.toString(),
         'previousSource': currentPackage == null
             ? null
-            : _source(currentPackage, containingDir: entrypoint.root.dir),
+            : _source(
+                currentPackage,
+                containingDir: entrypoint.workspaceRoot.dir,
+              ),
       };
     }),
     // Find packages that were removed by the resolution
@@ -825,7 +835,7 @@
           'previousConstraint': null,
           'previous': _source(
             currentPackages[oldPackageName]!,
-            containingDir: entrypoint.root.dir,
+            containingDir: entrypoint.workspaceRoot.dir,
           ),
         },
   ];
diff --git a/lib/src/command/deps.dart b/lib/src/command/deps.dart
index 50f28d7..404fa61 100644
--- a/lib/src/command/deps.dart
+++ b/lib/src/command/deps.dart
@@ -89,7 +89,7 @@
         usageException('Cannot combine --json and --style.');
       }
       final visited = <String>[];
-      final toVisit = [entrypoint.root.name];
+      final toVisit = [entrypoint.workspaceRoot.name];
       final packagesJson = <dynamic>[];
       final graph = await entrypoint.packageGraph;
       while (toVisit.isNotEmpty) {
@@ -98,13 +98,14 @@
         visited.add(current);
         final currentPackage =
             (await entrypoint.packageGraph).packages[current]!;
-        final next = (current == entrypoint.root.name
-                ? entrypoint.root.immediateDependencies
+        final next = (current == entrypoint.workspaceRoot.name
+                ? entrypoint.workspaceRoot.immediateDependencies
                 : currentPackage.dependencies)
             .keys
             .toList();
-        final dependencyType = entrypoint.root.pubspec.dependencyType(current);
-        final kind = currentPackage == entrypoint.root
+        final dependencyType =
+            entrypoint.workspaceRoot.pubspec.dependencyType(current);
+        final kind = currentPackage == entrypoint.workspaceRoot
             ? 'root'
             : (dependencyType == DependencyType.direct
                 ? 'direct'
@@ -124,12 +125,12 @@
       }
       var executables = [
         for (final package in [
-          entrypoint.root,
-          ...entrypoint.root.immediateDependencies.keys
+          entrypoint.workspaceRoot,
+          ...entrypoint.workspaceRoot.immediateDependencies.keys
               .map((name) => graph.packages[name]),
         ])
           ...package!.executableNames.map(
-            (name) => package == entrypoint.root
+            (name) => package == entrypoint.workspaceRoot
                 ? ':$name'
                 : (package.name == name ? name : '${package.name}:$name'),
           ),
@@ -138,7 +139,7 @@
       buffer.writeln(
         JsonEncoder.withIndent('  ').convert(
           {
-            'root': entrypoint.root.name,
+            'root': entrypoint.workspaceRoot.name,
             'packages': packagesJson,
             'sdks': [
               for (var sdk in sdks.values)
@@ -158,7 +159,7 @@
           buffer.writeln("${log.bold('${sdk.name} SDK')} ${sdk.version}");
         }
 
-        buffer.writeln(_labelPackage(entrypoint.root));
+        buffer.writeln(_labelPackage(entrypoint.workspaceRoot));
 
         switch (argResults['style']) {
           case 'compact':
@@ -186,7 +187,7 @@
   Future<void> _outputCompact(
     StringBuffer buffer,
   ) async {
-    var root = entrypoint.root;
+    var root = entrypoint.workspaceRoot;
     await _outputCompactPackages(
       'dependencies',
       root.dependencies.keys,
@@ -239,7 +240,7 @@
   /// For each dependency listed, *that* package's immediate dependencies are
   /// shown.
   Future<void> _outputList(StringBuffer buffer) async {
-    var root = entrypoint.root;
+    var root = entrypoint.workspaceRoot;
     await _outputListSection('dependencies', root.dependencies.keys, buffer);
     if (_includeDev) {
       await _outputListSection(
@@ -300,14 +301,15 @@
     // 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.root.name};
+    var visited = <String>{entrypoint.workspaceRoot.name};
 
     // Start with the root dependencies.
     var packageTree = <String, Map>{};
     var immediateDependencies =
-        entrypoint.root.immediateDependencies.keys.toSet();
+        entrypoint.workspaceRoot.immediateDependencies.keys.toSet();
     if (!_includeDev) {
-      immediateDependencies.removeAll(entrypoint.root.devDependencies.keys);
+      immediateDependencies
+          .removeAll(entrypoint.workspaceRoot.devDependencies.keys);
     }
     for (var name in immediateDependencies) {
       toWalk.add((await _getPackage(name), packageTree));
@@ -343,7 +345,7 @@
   /// Gets the names of the non-immediate dependencies of the root package.
   Future<Set<String>> _getTransitiveDependencies() async {
     var transitive = await _getAllDependencies();
-    var root = entrypoint.root;
+    var root = entrypoint.workspaceRoot;
     transitive.remove(root.name);
     transitive.removeAll(root.dependencies.keys);
     if (_includeDev) {
@@ -359,8 +361,8 @@
       return graph.packages.keys.toSet();
     }
 
-    var nonDevDependencies = entrypoint.root.dependencies.keys.toList()
-      ..addAll(entrypoint.root.dependencyOverrides.keys);
+    var nonDevDependencies = entrypoint.workspaceRoot.dependencies.keys.toList()
+      ..addAll(entrypoint.workspaceRoot.dependencyOverrides.keys);
     return nonDevDependencies
         .expand(graph.transitiveDependencies)
         .map((package) => package.name)
@@ -384,10 +386,10 @@
   Future<void> _outputExecutables(StringBuffer buffer) async {
     final graph = await entrypoint.packageGraph;
     var packages = [
-      entrypoint.root,
+      entrypoint.workspaceRoot,
       ...(_includeDev
-              ? entrypoint.root.immediateDependencies
-              : entrypoint.root.dependencies)
+              ? entrypoint.workspaceRoot.immediateDependencies
+              : entrypoint.workspaceRoot.dependencies)
           .keys
           .map((name) => graph.packages[name]!),
     ];
diff --git a/lib/src/command/lish.dart b/lib/src/command/lish.dart
index f716b1b..dec54f1 100644
--- a/lib/src/command/lish.dart
+++ b/lib/src/command/lish.dart
@@ -295,19 +295,19 @@
       await entrypoint.acquireDependencies(SolveType.get);
     }
 
-    var files = entrypoint.root.listFiles();
-    log.fine('Archiving and publishing ${entrypoint.root.name}.');
+    var files = entrypoint.workPackage.listFiles();
+    log.fine('Archiving and publishing ${entrypoint.workPackage.name}.');
 
     // Show the package contents so the user can verify they look OK.
-    var package = entrypoint.root;
+    var package = entrypoint.workPackage;
     final host = computeHost(package.pubspec);
     log.message(
       'Publishing ${package.name} ${package.version} to $host:\n'
-      '${tree.fromFiles(files, baseDir: entrypoint.rootDir, showFileSizes: true)}',
+      '${tree.fromFiles(files, baseDir: entrypoint.workPackage.dir, showFileSizes: true)}',
     );
 
     final packageBytes =
-        await createTarGz(files, baseDir: entrypoint.rootDir).toBytes();
+        await createTarGz(files, baseDir: entrypoint.workPackage.dir).toBytes();
 
     log.message(
       '\nTotal compressed archive size: ${_readableFileSize(packageBytes.length)}.\n',
diff --git a/lib/src/command/outdated.dart b/lib/src/command/outdated.dart
index 192f1bd..212e967 100644
--- a/lib/src/command/outdated.dart
+++ b/lib/src/command/outdated.dart
@@ -128,8 +128,8 @@
     }
 
     final rootPubspec = includeDependencyOverrides
-        ? entrypoint.root.pubspec
-        : stripDependencyOverrides(entrypoint.root.pubspec);
+        ? entrypoint.workspaceRoot.pubspec
+        : stripDependencyOverrides(entrypoint.workspaceRoot.pubspec);
 
     final upgradablePubspec = includeDevDependencies
         ? rootPubspec
@@ -148,8 +148,8 @@
         final upgradablePackagesResult = await _tryResolve(
           Package(
             upgradablePubspec,
-            entrypoint.rootDir,
-            entrypoint.root.workspaceChildren,
+            entrypoint.workspaceRoot.dir,
+            entrypoint.workspaceRoot.workspaceChildren,
           ),
           cache,
           lockFile: entrypoint.lockFile,
@@ -160,8 +160,8 @@
         final resolvablePackagesResult = await _tryResolve(
           Package(
             resolvablePubspec,
-            entrypoint.rootDir,
-            entrypoint.root.workspaceChildren,
+            entrypoint.workspaceRoot.dir,
+            entrypoint.workspaceRoot.workspaceChildren,
           ),
           cache,
           lockFile: entrypoint.lockFile,
@@ -179,9 +179,18 @@
     /// closure of the non-dev dependencies from the root in at least one of
     /// the current, upgradable and resolvable resolutions.
     final nonDevDependencies = <String>{
-      ...await _nonDevDependencyClosure(entrypoint.root, currentPackages),
-      ...await _nonDevDependencyClosure(entrypoint.root, upgradablePackages),
-      ...await _nonDevDependencyClosure(entrypoint.root, resolvablePackages),
+      ...await _nonDevDependencyClosure(
+        entrypoint.workspaceRoot,
+        currentPackages,
+      ),
+      ...await _nonDevDependencyClosure(
+        entrypoint.workspaceRoot,
+        upgradablePackages,
+      ),
+      ...await _nonDevDependencyClosure(
+        entrypoint.workspaceRoot,
+        resolvablePackages,
+      ),
     };
 
     Future<_PackageDetails> analyzeDependency(PackageRef packageRef) async {
@@ -197,7 +206,8 @@
       var latestIsOverridden = false;
       PackageId? latest;
       // If not overridden in current resolution we can use this
-      if (!entrypoint.root.pubspec.dependencyOverrides.containsKey(name)) {
+      if (!entrypoint.workspaceRoot.pubspec.dependencyOverrides
+          .containsKey(name)) {
         latest ??= await cache.getLatest(
           current?.toRef(),
           version: current?.version,
@@ -263,7 +273,7 @@
 
       final currentVersionDetails = await _describeVersion(
         current,
-        entrypoint.root.pubspec.dependencyOverrides.containsKey(name),
+        entrypoint.workspaceRoot.pubspec.dependencyOverrides.containsKey(name),
       );
 
       final upgradableVersionDetails = await _describeVersion(
@@ -288,7 +298,8 @@
         // Filter out advisories added to `ignored_advisores` in the root pubspec.
         packageAdvisories = packageAdvisories
             .where(
-              (adv) => entrypoint.root.pubspec.ignoredAdvisories.intersection({
+              (adv) => entrypoint.workspaceRoot.pubspec.ignoredAdvisories
+                  .intersection({
                 ...adv.aliases,
                 adv.id,
               }).isEmpty,
@@ -322,7 +333,7 @@
     final rows = <_PackageDetails>[];
 
     final visited = <String>{
-      entrypoint.root.name,
+      entrypoint.workspaceRoot.name,
     };
     // Add all dependencies from the lockfile.
     for (final id in [
@@ -961,9 +972,9 @@
   Entrypoint entrypoint,
   Set<String> nonDevTransitive,
 ) {
-  if (entrypoint.root.dependencies.containsKey(name)) {
+  if (entrypoint.workspaceRoot.dependencies.containsKey(name)) {
     return _DependencyKind.direct;
-  } else if (entrypoint.root.devDependencies.containsKey(name)) {
+  } else if (entrypoint.workspaceRoot.devDependencies.containsKey(name)) {
     return _DependencyKind.dev;
   } else {
     if (nonDevTransitive.contains(name)) {
diff --git a/lib/src/command/remove.dart b/lib/src/command/remove.dart
index 5490098..152c1df 100644
--- a/lib/src/command/remove.dart
+++ b/lib/src/command/remove.dart
@@ -88,7 +88,9 @@
       /// Update the pubspec.
       _writeRemovalToPubspec(targets);
     }
-    final rootPubspec = entrypoint.root.pubspec;
+    // TODO(https://github.com/dart-lang/pub/issues/4127): This should
+    // operate on entrypoint.workPackage.
+    final rootPubspec = entrypoint.workspaceRoot.pubspec;
     final newPubspec = _removePackagesFromPubspec(rootPubspec, targets);
 
     await entrypoint.withPubspec(newPubspec).acquireDependencies(
@@ -138,7 +140,8 @@
   void _writeRemovalToPubspec(Iterable<_PackageRemoval> packages) {
     ArgumentError.checkNotNull(packages, 'packages');
 
-    final yamlEditor = YamlEditor(readTextFile(entrypoint.pubspecPath));
+    final yamlEditor =
+        YamlEditor(readTextFile(entrypoint.workspaceRoot.pubspecPath));
 
     for (final package in packages) {
       final dependencyKeys = package.removeFromOverride
@@ -165,13 +168,13 @@
       }
       if (!found) {
         log.warning(
-          'Package "$name" was not found in ${entrypoint.pubspecPath}!',
+          'Package "$name" was not found in ${entrypoint.workspaceRoot.pubspecPath}!',
         );
       }
     }
 
     /// Windows line endings are already handled by [yamlEditor]
-    writeTextFile(entrypoint.pubspecPath, yamlEditor.toString());
+    writeTextFile(entrypoint.workspaceRoot.pubspecPath, yamlEditor.toString());
   }
 }
 
diff --git a/lib/src/command/run.dart b/lib/src/command/run.dart
index 9fe1a03..c949551 100644
--- a/lib/src/command/run.dart
+++ b/lib/src/command/run.dart
@@ -69,7 +69,7 @@
       dataError('The --(no-)sound-null-safety flag is no longer supported.');
     }
 
-    var package = entrypoint.root.name;
+    var package = entrypoint.workspaceRoot.name;
     var executable = argResults.rest[0];
     var args = argResults.rest.skip(1).toList();
 
diff --git a/lib/src/command/unpack.dart b/lib/src/command/unpack.dart
index ef7df99..803d34c 100644
--- a/lib/src/command/unpack.dart
+++ b/lib/src/command/unpack.dart
@@ -144,7 +144,9 @@
       } finally {
         log.message('To explore type: cd $destinationDir');
         if (e.example != null) {
-          log.message('To explore the example type: cd ${e.example!.rootDir}');
+          log.message(
+            'To explore the example type: cd ${e.example!.workspaceRoot.dir}',
+          );
         }
       }
     }
diff --git a/lib/src/command/upgrade.dart b/lib/src/command/upgrade.dart
index 5fb176a..91b0e26 100644
--- a/lib/src/command/upgrade.dart
+++ b/lib/src/command/upgrade.dart
@@ -133,7 +133,7 @@
     if (_upgradeMajorVersions) {
       if (argResults.flag('example') && entrypoint.example != null) {
         log.warning(
-          'Running `upgrade --major-versions` only in `${entrypoint.rootDir}`. Run `$topLevelProgram pub upgrade --major-versions --directory example/` separately.',
+          'Running `upgrade --major-versions` only in `${entrypoint.workspaceRoot.dir}`. Run `$topLevelProgram pub upgrade --major-versions --directory example/` separately.',
         );
       }
       await _runUpgradeMajorVersions();
@@ -141,14 +141,14 @@
       await _runUpgrade(entrypoint);
       if (_tighten) {
         final changes = tighten(
-          entrypoint.root.pubspec,
+          entrypoint.workspaceRoot.pubspec,
           entrypoint.lockFile.packages.values.toList(),
         );
         if (!_dryRun) {
           final newPubspecText = _updatePubspec(changes);
 
           if (changes.isNotEmpty) {
-            writeTextFile(entrypoint.pubspecPath, newPubspecText);
+            writeTextFile(entrypoint.workspaceRoot.pubspecPath, newPubspecText);
           }
         }
         _outputChangeSummary(changes);
@@ -192,7 +192,7 @@
     final result = {...existingChanges};
     if (argResults.flag('example') && entrypoint.example != null) {
       log.warning(
-        'Running `upgrade --tighten` only in `${entrypoint.rootDir}`. Run `$topLevelProgram pub upgrade --tighten --directory example/` separately.',
+        'Running `upgrade --tighten` only in `${entrypoint.workspaceRoot.dir}`. Run `$topLevelProgram pub upgrade --tighten --directory example/` separately.',
       );
     }
     final toTighten = _packagesToUpgrade.isEmpty
@@ -237,8 +237,8 @@
     assert(_upgradeMajorVersions);
 
     final directDeps = [
-      ...entrypoint.root.pubspec.dependencies.keys,
-      ...entrypoint.root.pubspec.devDependencies.keys,
+      ...entrypoint.workspaceRoot.pubspec.dependencies.keys,
+      ...entrypoint.workspaceRoot.pubspec.devDependencies.keys,
     ];
     final toUpgrade =
         _packagesToUpgrade.isEmpty ? directDeps : _packagesToUpgrade;
@@ -263,10 +263,12 @@
   }
 
   Future<void> _runUpgradeMajorVersions() async {
+    // TODO(https://github.com/dart-lang/pub/issues/4127): This should operate
+    // on all pubspecs in the workspace.
     final toUpgrade = _directDependenciesToUpgrade();
 
     final resolvablePubspec = stripVersionBounds(
-      entrypoint.root.pubspec,
+      entrypoint.workspaceRoot.pubspec,
       stripOnly: toUpgrade,
     );
 
@@ -281,8 +283,8 @@
           cache,
           Package(
             resolvablePubspec,
-            entrypoint.rootDir,
-            entrypoint.root.workspaceChildren,
+            entrypoint.workspaceRoot.dir,
+            entrypoint.workspaceRoot.workspaceChildren,
           ),
         );
       },
@@ -296,8 +298,8 @@
     // Mapping from original to changed value.
     var changes = <PackageRange, PackageRange>{};
     final declaredHostedDependencies = [
-      ...entrypoint.root.pubspec.dependencies.values,
-      ...entrypoint.root.pubspec.devDependencies.values,
+      ...entrypoint.workspaceRoot.pubspec.dependencies.values,
+      ...entrypoint.workspaceRoot.pubspec.devDependencies.values,
     ].where((dep) => dep.source is HostedSource);
     for (final dep in declaredHostedDependencies) {
       final resolvedPackage = resolvedPackages[dep.name]!;
@@ -308,7 +310,7 @@
       }
 
       // Skip [dep] if it has a dependency_override.
-      if (entrypoint.root.dependencyOverrides.containsKey(dep.name)) {
+      if (entrypoint.workspaceRoot.dependencyOverrides.containsKey(dep.name)) {
         continue;
       }
 
@@ -335,12 +337,12 @@
         cache,
         Package(
           _updatedPubspec(newPubspecText, entrypoint),
-          entrypoint.rootDir,
-          entrypoint.root.workspaceChildren,
+          entrypoint.workspaceRoot.dir,
+          entrypoint.workspaceRoot.workspaceChildren,
         ),
       );
       changes = tighten(
-        entrypoint.root.pubspec,
+        entrypoint.workspaceRoot.pubspec,
         solveResult.packages,
         existingChanges: changes,
       );
@@ -357,7 +359,7 @@
 
     if (!_dryRun) {
       if (changes.isNotEmpty) {
-        writeTextFile(entrypoint.pubspecPath, newPubspecText);
+        writeTextFile(entrypoint.workspaceRoot.pubspecPath, newPubspecText);
       }
     }
 
@@ -373,8 +375,8 @@
 
     // If any of the packages to upgrade are dependency overrides, then we
     // show a warning.
-    final toUpgradeOverrides =
-        toUpgrade.where(entrypoint.root.dependencyOverrides.containsKey);
+    final toUpgradeOverrides = toUpgrade
+        .where(entrypoint.workspaceRoot.dependencyOverrides.containsKey);
     if (toUpgradeOverrides.isNotEmpty) {
       log.warning(
         'Warning: dependency_overrides prevents upgrades for: '
@@ -388,7 +390,7 @@
   Pubspec _updatedPubspec(String contents, Entrypoint entrypoint) {
     String? overridesFileContents;
     final overridesPath =
-        p.join(entrypoint.rootDir, Pubspec.pubspecOverridesFilename);
+        p.join(entrypoint.workspaceRoot.dir, Pubspec.pubspecOverridesFilename);
     try {
       overridesFileContents = readTextFile(overridesPath);
     } on IOException {
@@ -397,10 +399,10 @@
     return Pubspec.parse(
       contents,
       cache.sources,
-      location: Uri.parse(entrypoint.pubspecPath),
+      location: Uri.parse(entrypoint.workspaceRoot.pubspecPath),
       overridesFileContents: overridesFileContents,
       overridesLocation: Uri.file(overridesPath),
-      containingDescription: RootDescription(entrypoint.rootDir),
+      containingDescription: RootDescription(entrypoint.workspaceRoot.dir),
     );
   }
 
@@ -409,15 +411,16 @@
     Map<PackageRange, PackageRange> changes,
   ) {
     ArgumentError.checkNotNull(changes, 'changes');
-    final yamlEditor = YamlEditor(readTextFile(entrypoint.pubspecPath));
-    final deps = entrypoint.root.pubspec.dependencies.keys;
+    final yamlEditor =
+        YamlEditor(readTextFile(entrypoint.workspaceRoot.pubspecPath));
+    final deps = entrypoint.workspaceRoot.pubspec.dependencies.keys;
 
     for (final change in changes.values) {
       final section =
           deps.contains(change.name) ? 'dependencies' : 'dev_dependencies';
       yamlEditor.update(
         [section, change.name],
-        pubspecDescription(change, cache, entrypoint),
+        pubspecDescription(change, cache, entrypoint.workspaceRoot),
       );
     }
     return yamlEditor.toString();
diff --git a/lib/src/command/uploader.dart b/lib/src/command/uploader.dart
index c3ff772..8a1602a 100644
--- a/lib/src/command/uploader.dart
+++ b/lib/src/command/uploader.dart
@@ -49,7 +49,7 @@
   Future<void> runProtected() async {
     var packageName = '<packageName>';
     try {
-      packageName = entrypoint.root.name;
+      packageName = entrypoint.workspaceRoot.name;
     } on Exception catch (_) {
       // Probably run without a pubspec.
       // Just print error below without a specific package name.
diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index 37e8368..e20ef20 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -37,51 +37,75 @@
 import 'system_cache.dart';
 import 'utils.dart';
 
-/// The context surrounding the root package pub is operating on.
+/// The context surrounding the workspace pub is operating on.
 ///
 /// Pub operates over a directed graph of dependencies that starts at a root
 /// "entrypoint" package. This is typically the package where the current
-/// working directory is located. An entrypoint knows the [root] package it is
-/// associated with and is responsible for managing the "packages" directory
-/// for it.
+/// working directory is located.
 ///
-/// That directory contains symlinks to all packages used by an app. These links
-/// point either to the [SystemCache] or to some other location on the local
-/// filesystem.
+/// An entrypoint knows the [workspaceRoot] package it is associated with and is
+/// responsible for managing the package config (.dart_tool/package_config.json)
+/// and lock file (pubspec.lock) for it.
 ///
 /// While entrypoints are typically applications, a pure library package may end
-/// up being used as an entrypoint. Also, a single package may be used as an
-/// entrypoint in one context but not in another. For example, a package that
-/// contains a reusable library may not be the entrypoint when used by an app,
-/// but may be the entrypoint when you're running its tests.
+/// up being used as an entrypoint while under development. Also, a single
+/// package may be used as an entrypoint in one context but not in another. For
+/// example, a package that contains a reusable library may not be the
+/// entrypoint when used by an app, but may be the entrypoint when you're
+/// running its tests.
 class Entrypoint {
-  /// The directory where the package is stored.
+  /// The directory where this entrypoint is created.
   ///
-  /// For cached global packages this is `PUB_CACHE/global_packages/foo
-  ///
-  /// For path-activated global packages this is the actual package dir.
-  ///
-  /// The lock file and package configurations are to be found relative to here.
-  final String rootDir;
+  /// [workspaceRoot] will be the package in the nearest parent directory that
+  /// has `resolution: null`
+  // TODO(https://github.com/dart-lang/pub/issues/4127): make this actually
+  // true.
+  final String workingDir;
 
-  Package? _root;
+  Package? _workspaceRoot;
 
   /// The root package this entrypoint is associated with.
   ///
   /// For a global package, this is the activated package.
-  Package get root => _root ??= Package.load(
+  Package get workspaceRoot => _workspaceRoot ??= Package.load(
         null,
-        rootDir,
+        workingDir,
         cache.sources,
         withPubspecOverrides: true,
       );
 
+  bool get canFindWorkspaceRoot {
+    try {
+      workspaceRoot;
+      return true;
+    } on FileException {
+      return false;
+    }
+  }
+
+  /// The "focus" package that the current command should act upon.
+  ///
+  /// It will be the package in the nearest parent directory to `workingDir`.
+  /// Example: if a workspace looks like this:
+  // TODO(https://github.com/dart-lang/pub/issues/4127): make this actually
+  // true.
+  ///
+  /// foo/ pubspec.yaml # contains `workspace: [- 'bar'] bar/ pubspec.yaml #
+  ///   contains `resolution: workspace`.
+  ///
+  /// Running `pub add` in `foo/bar` will have bar as workPackage, and add
+  /// dependencies to `foo/bar/pubspec.yaml`.
+  ///
+  /// Running `pub add` in `foo` will have foo as workPackage, and add
+  /// dependencies to `foo/pubspec.yaml`.
+  Package get workPackage => workspaceRoot;
+
   /// The system-wide cache which caches packages that need to be fetched over
   /// the network.
   final SystemCache cache;
 
   /// Whether this entrypoint exists within the package cache.
-  bool get isCached => p.isWithin(cache.rootDir, rootDir);
+  bool get isCached => p.isWithin(cache.rootDir, workingDir);
 
   /// Whether this is an entrypoint for a globally-activated package.
   ///
@@ -165,7 +189,7 @@
     // TODO(sigurdm): consider having [ensureUptoDate] and [acquireDependencies]
     // return the package-graph, such it by construction will always made from an
     // up-to-date package-config.
-    await ensureUpToDate(rootDir, cache: cache);
+    await ensureUpToDate(workspaceRoot.dir, cache: cache);
     var packages = {
       for (var packageEntry in packageConfig.nonInjectedPackages)
         packageEntry.name: Package.load(
@@ -174,7 +198,7 @@
           cache.sources,
         ),
     };
-    packages[root.name] = root;
+    packages[workspaceRoot.name] = workspaceRoot;
 
     return PackageGraph(this, packages);
   }
@@ -184,32 +208,28 @@
   /// The path to the entrypoint's ".dart_tool/package_config.json" file
   /// relative to the current working directory .
   late String packageConfigPath = p.relative(
-    p.normalize(p.join(rootDir, '.dart_tool', 'package_config.json')),
+    p.normalize(p.join(workspaceRoot.dir, '.dart_tool', 'package_config.json')),
   );
 
-  /// The path to the entrypoint package's pubspec.
-  String get pubspecPath => p.normalize(p.join(rootDir, 'pubspec.yaml'));
-
-  /// The path to the entrypoint package's pubspec overrides file.
-  String get pubspecOverridesPath =>
-      p.normalize(p.join(rootDir, 'pubspec_overrides.yaml'));
-
-  /// The path to the entrypoint package's lockfile.
-  String get lockFilePath => p.normalize(p.join(rootDir, 'pubspec.lock'));
+  /// The path to the entrypoint workspace's lockfile.
+  String get lockFilePath =>
+      p.normalize(p.join(workspaceRoot.dir, 'pubspec.lock'));
 
   /// The path to the directory containing dependency executable snapshots.
   String get _snapshotPath => p.join(
-        isCachedGlobal ? rootDir : p.join(rootDir, '.dart_tool/pub'),
+        isCachedGlobal
+            ? workspaceRoot.dir
+            : p.join(workspaceRoot.dir, '.dart_tool/pub'),
         'bin',
       );
 
   Entrypoint._(
-    this.rootDir,
+    this.workingDir,
     this._lockFile,
     this._example,
     this._packageGraph,
     this.cache,
-    this._root,
+    this._workspaceRoot,
     this.isCachedGlobal,
   );
 
@@ -218,15 +238,19 @@
   /// If [checkInCache] is `true` (the default) an error will be thrown if
   /// [rootDir] is located inside [cache.rootDir].
   Entrypoint(
-    this.rootDir,
+    this.workingDir,
     this.cache, {
     ({Pubspec pubspec, List<Package> workspacePackages})? preloaded,
     bool checkInCache = true,
-  })  : _root = preloaded == null
+  })  : _workspaceRoot = preloaded == null
             ? null
-            : Package(preloaded.pubspec, rootDir, preloaded.workspacePackages),
+            : Package(
+                preloaded.pubspec,
+                workingDir,
+                preloaded.workspacePackages,
+              ),
         isCachedGlobal = false {
-    if (checkInCache && p.isWithin(cache.rootDir, rootDir)) {
+    if (checkInCache && p.isWithin(cache.rootDir, workingDir)) {
       fail('Cannot operate on packages inside the cache.');
     }
   }
@@ -235,15 +259,15 @@
   /// resolution.
   Entrypoint withPubspec(Pubspec pubspec) {
     return Entrypoint._(
-      rootDir,
+      workingDir,
       _lockFile,
       _example,
       _packageGraph,
       cache,
       Package(
         pubspec,
-        rootDir,
-        root.workspaceChildren,
+        workingDir,
+        workspaceRoot.workspaceChildren,
       ),
       isCachedGlobal,
     );
@@ -252,11 +276,11 @@
   /// Creates an entrypoint given package and lockfile objects.
   /// If a SolveResult is already created it can be passed as an optimization.
   Entrypoint.global(
-    Package this._root,
+    Package this._workspaceRoot,
     this._lockFile,
     this.cache, {
     SolveResult? solveResult,
-  })  : rootDir = _root.dir,
+  })  : workingDir = _workspaceRoot.dir,
         isCachedGlobal = true {
     if (solveResult != null) {
       _packageGraph =
@@ -269,10 +293,10 @@
   /// This will be null if the example folder doesn't have a `pubspec.yaml`.
   Entrypoint? get example {
     if (_example != null) return _example;
-    if (!fileExists(root.path('example', 'pubspec.yaml'))) {
+    if (!fileExists(workspaceRoot.path('example', 'pubspec.yaml'))) {
       return null;
     }
-    return _example = Entrypoint(root.path('example'), cache);
+    return _example = Entrypoint(workspaceRoot.path('example'), cache);
   }
 
   Entrypoint? _example;
@@ -284,8 +308,8 @@
       packageConfigPath,
       await _packageConfigFile(
         cache,
-        entrypointSdkConstraint:
-            root.pubspec.sdkConstraints[sdk.identifier]?.effectiveConstraint,
+        entrypointSdkConstraint: workspaceRoot
+            .pubspec.sdkConstraints[sdk.identifier]?.effectiveConstraint,
       ),
     );
   }
@@ -293,13 +317,13 @@
   /// Returns the contents of the `.dart_tool/package_config` file generated
   /// from this entrypoint based on [lockFile].
   ///
-  /// If [isCachedGlobal] no entry will be created for [root].
+  /// If [isCachedGlobal] no entry will be created for [workspaceRoot].
   Future<String> _packageConfigFile(
     SystemCache cache, {
     VersionConstraint? entrypointSdkConstraint,
   }) async {
     final entries = <PackageConfigEntry>[];
-    late final relativeFromPath = p.join(rootDir, '.dart_tool');
+    late final relativeFromPath = p.join(workspaceRoot.dir, '.dart_tool');
     for (final name in ordered(lockFile.packages.keys)) {
       final id = lockFile.packages[name]!;
       final rootPath = cache.getDirectory(id, relativeFrom: relativeFromPath);
@@ -317,12 +341,15 @@
     if (!isCachedGlobal) {
       /// Run through the entire workspace transitive closure and add an entry
       /// for each package.
-      for (final package in root.transitiveWorkspace) {
+      for (final package in workspaceRoot.transitiveWorkspace) {
         entries.add(
           PackageConfigEntry(
             name: package.name,
             rootUri: p.toUri(
-              p.relative(package.dir, from: p.join(rootDir, '.dart_tool')),
+              p.relative(
+                package.dir,
+                from: p.join(workspaceRoot.dir, '.dart_tool'),
+              ),
             ),
             packageUri: p.toUri('lib/'),
             languageVersion: package.pubspec.languageVersion,
@@ -350,7 +377,7 @@
     return '${JsonEncoder.withIndent('  ').convert(packageConfig.toJson())}\n';
   }
 
-  /// Gets all dependencies of the [root] package.
+  /// Gets all dependencies of the [workspaceRoot] package.
   ///
   /// Performs version resolution according to [SolveType].
   ///
@@ -381,9 +408,9 @@
     bool summaryOnly = false,
     bool enforceLockfile = false,
   }) async {
-    root; // This will throw early if pubspec.yaml could not be found.
+    workspaceRoot; // This will throw early if pubspec.yaml could not be found.
     summaryOnly = summaryOnly || _summaryOnlyEnvironment;
-    final suffix = rootDir == '.' ? '' : ' in $rootDir';
+    final suffix = workspaceRoot.dir == '.' ? '' : ' in ${workspaceRoot.dir}';
 
     if (enforceLockfile && !fileExists(lockFilePath)) {
       throw ApplicationException('''
@@ -397,11 +424,13 @@
 
     try {
       result = await log.progress('Resolving dependencies$suffix', () async {
-        _checkSdkConstraint(root.pubspec);
+        // TODO(https://github.com/dart-lang/pub/issues/4127): Check this for
+        // all workspace pubspecs.
+        _checkSdkConstraint(workspaceRoot.pubspecPath, workspaceRoot.pubspec);
         return resolveVersions(
           type,
           cache,
-          root,
+          workspaceRoot,
           lockFile: lockFile,
           unlock: unlock ?? [],
         );
@@ -425,8 +454,8 @@
 
     final report = SolveReport(
       type,
-      rootDir,
-      root.pubspec,
+      workspaceRoot.dir,
+      workspaceRoot.pubspec,
       lockFile,
       newLockFile,
       result.availableVersions,
@@ -439,7 +468,7 @@
     await report.show(summary: true);
     if (enforceLockfile && !_lockfilesMatch(lockFile, newLockFile)) {
       dataError('''
-Unable to satisfy `$pubspecPath` using `$lockFilePath`$suffix.
+Unable to satisfy `${workspaceRoot.pubspecPath}` using `$lockFilePath`$suffix.
 
 To update `$lockFilePath` run `$topLevelProgram pub get`$suffix without
 `--enforce-lockfile`.''');
@@ -481,7 +510,7 @@
   /// the package itself if they are immutable.
   Future<List<Executable>> get _builtExecutables async {
     final graph = await packageGraph;
-    final r = root.immediateDependencies.keys.expand((packageName) {
+    final r = workspaceRoot.immediateDependencies.keys.expand((packageName) {
       final package = graph.packages[packageName]!;
       return package.executablePaths
           .map((path) => Executable(packageName, path));
@@ -568,8 +597,8 @@
   /// [path] must be relative.
   String pathOfSnapshot(Executable executable) {
     return isCachedGlobal
-        ? executable.pathOfGlobalSnapshot(rootDir)
-        : executable.pathOfSnapshot(rootDir);
+        ? executable.pathOfGlobalSnapshot(workspaceRoot.dir)
+        : executable.pathOfSnapshot(workspaceRoot.dir);
   }
 
   /// Deletes cached snapshots that are from a different sdk.
@@ -1002,7 +1031,7 @@
   /// We require an SDK constraint lower-bound as of Dart 2.12.0
   ///
   /// We don't allow unknown sdks.
-  void _checkSdkConstraint(Pubspec pubspec) {
+  void _checkSdkConstraint(String pubspecPath, Pubspec pubspec) {
     final dartSdkConstraint = pubspec.dartSdkConstraint.effectiveConstraint;
     // Suggest an sdk constraint giving the same language version as the
     // current sdk.
diff --git a/lib/src/executable.dart b/lib/src/executable.dart
index 8ffc20c..dc2b901 100644
--- a/lib/src/executable.dart
+++ b/lib/src/executable.dart
@@ -56,8 +56,8 @@
 
   // Make sure the package is an immediate dependency of the entrypoint or the
   // entrypoint itself.
-  if (entrypoint.root.name != executable.package &&
-      !entrypoint.root.immediateDependencies.containsKey(package)) {
+  if (entrypoint.workspaceRoot.name != executable.package &&
+      !entrypoint.workspaceRoot.immediateDependencies.containsKey(package)) {
     if ((await entrypoint.packageGraph).packages.containsKey(package)) {
       dataError('Package "$package" is not an immediate dependency.\n'
           'Cannot run executables in transitive dependencies.');
@@ -84,7 +84,7 @@
   if (!fileExists(executablePath)) {
     var message =
         'Could not find ${log.bold(p.normalize(executable.relativePath))}';
-    if (entrypoint.isCachedGlobal || package != entrypoint.root.name) {
+    if (entrypoint.isCachedGlobal || package != entrypoint.workspaceRoot.name) {
       message += ' in package ${log.bold(package)}';
     }
     log.error('$message.');
@@ -95,7 +95,7 @@
     // Since we don't access the package graph, this doesn't happen
     // automatically.
     await Entrypoint.ensureUpToDate(
-      entrypoint.rootDir,
+      entrypoint.workspaceRoot.dir,
       cache: entrypoint.cache,
     );
 
diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart
index fe82886..990869a 100644
--- a/lib/src/global_packages.dart
+++ b/lib/src/global_packages.dart
@@ -182,14 +182,14 @@
 
     // Get the package's dependencies.
     await entrypoint.acquireDependencies(SolveType.get);
-    var name = entrypoint.root.name;
+    var name = entrypoint.workspaceRoot.name;
     _describeActive(name, cache);
 
     // Write a lockfile that points to the local package.
-    var fullPath = canonicalize(entrypoint.rootDir);
+    var fullPath = canonicalize(entrypoint.workspaceRoot.dir);
     var id = cache.path.idFor(
       name,
-      entrypoint.root.version,
+      entrypoint.workspaceRoot.version,
       fullPath,
       p.current,
     );
@@ -205,7 +205,7 @@
 
     _updateBinStubs(
       entrypoint,
-      entrypoint.root,
+      entrypoint.workspaceRoot,
       executables,
       overwriteBinStubs: overwriteBinStubs,
     );
@@ -565,7 +565,7 @@
             );
           } else {
             await activatePath(
-              entrypoint.rootDir,
+              entrypoint.workspaceRoot.dir,
               packageExecutables,
               overwriteBinStubs: true,
             );
@@ -618,17 +618,18 @@
         log.fine('Could not parse binstub $file:\n$contents');
         continue;
       }
-      if (binStubPackage == entrypoint.root.name &&
+      if (binStubPackage == entrypoint.workspaceRoot.name &&
           binStubScript ==
               p.basenameWithoutExtension(executable.relativePath)) {
         log.fine('Replacing old binstub $file');
         deleteEntry(file);
         _createBinStub(
-          entrypoint.root,
+          entrypoint.workspaceRoot,
           p.basenameWithoutExtension(file),
           binStubScript,
           overwrite: true,
-          snapshot: executable.pathOfGlobalSnapshot(entrypoint.rootDir),
+          snapshot:
+              executable.pathOfGlobalSnapshot(entrypoint.workspaceRoot.dir),
         );
       }
     }
diff --git a/lib/src/log.dart b/lib/src/log.dart
index e8aca47..a7fdd2b 100644
--- a/lib/src/log.dart
+++ b/lib/src/log.dart
@@ -376,20 +376,31 @@
 ''');
 
   if (entrypoint != null) {
-    buffer.writeln('---- ${p.absolute(entrypoint.pubspecPath)} ----');
-    if (fileExists(entrypoint.pubspecPath)) {
-      buffer.writeln(limitLength(readTextFile(entrypoint.pubspecPath), 5000));
+    // TODO(https://github.com/dart-lang/pub/issues/4127): We probably want to
+    // log all pubspecs in workspace?
+
+    if (entrypoint.canFindWorkspaceRoot) {
+      buffer.writeln(
+        '---- ${p.absolute(entrypoint.workspaceRoot.pubspecPath)} ----',
+      );
+      buffer.writeln(
+        limitLength(
+          readTextFile(entrypoint.workspaceRoot.pubspecPath),
+          5000,
+        ),
+      );
+      buffer.writeln('---- End pubspec.yaml ----');
     } else {
       buffer.writeln('<No pubspec.yaml>');
     }
-    buffer.writeln('---- End pubspec.yaml ----');
-    buffer.writeln('---- ${p.absolute(entrypoint.lockFilePath)} ----');
-    if (fileExists(entrypoint.lockFilePath)) {
+    if (entrypoint.canFindWorkspaceRoot &&
+        fileExists(entrypoint.lockFilePath)) {
+      buffer.writeln('---- ${p.absolute(entrypoint.lockFilePath)} ----');
       buffer.writeln(limitLength(readTextFile(entrypoint.lockFilePath), 5000));
+      buffer.writeln('---- End pubspec.lock ----');
     } else {
       buffer.writeln('<No pubspec.lock>');
     }
-    buffer.writeln('---- End pubspec.lock ----');
   }
 
   buffer.writeln('---- Log transcript ----');
diff --git a/lib/src/package.dart b/lib/src/package.dart
index 954b038..fa99394 100644
--- a/lib/src/package.dart
+++ b/lib/src/package.dart
@@ -45,6 +45,13 @@
   /// The parsed pubspec associated with this package.
   final Pubspec pubspec;
 
+  /// The path to the entrypoint package's pubspec.
+  String get pubspecPath => p.normalize(p.join(dir, 'pubspec.yaml'));
+
+  /// The path to the entrypoint package's pubspec overrides file.
+  String get pubspecOverridesPath =>
+      p.normalize(p.join(dir, 'pubspec_overrides.yaml'));
+
   /// The (non-transitive) workspace packages.
   final List<Package> workspaceChildren;
 
diff --git a/lib/src/package_graph.dart b/lib/src/package_graph.dart
index 0aa61b1..158b11a 100644
--- a/lib/src/package_graph.dart
+++ b/lib/src/package_graph.dart
@@ -38,7 +38,7 @@
     final packages = {
       for (final id in result.packages)
         id.name: id.isRoot
-            ? entrypoint.root
+            ? entrypoint.workspaceRoot
             : Package(
                 result.pubspecs[id.name]!,
                 entrypoint.cache.getDirectory(id),
@@ -55,7 +55,9 @@
   /// dev and override. For any other package, it ignores dev and override
   /// dependencies.
   Set<Package> transitiveDependencies(String package) {
-    if (package == entrypoint.root.name) return packages.values.toSet();
+    if (package == entrypoint.workspaceRoot.name) {
+      return packages.values.toSet();
+    }
 
     if (_transitiveDependencies == null) {
       var closure = transitiveClosure(
diff --git a/lib/src/pubspec_utils.dart b/lib/src/pubspec_utils.dart
index a8a3d69..74b2ce8 100644
--- a/lib/src/pubspec_utils.dart
+++ b/lib/src/pubspec_utils.dart
@@ -5,7 +5,7 @@
 import 'package:collection/collection.dart';
 import 'package:pub_semver/pub_semver.dart';
 
-import 'entrypoint.dart';
+import 'package.dart';
 import 'package_name.dart';
 import 'pubspec.dart';
 import 'source/hosted.dart';
@@ -166,7 +166,7 @@
 Object pubspecDescription(
   PackageRange range,
   SystemCache cache,
-  Entrypoint relativeEntrypoint,
+  Package receivingPackage,
 ) {
   final description = range.description;
 
@@ -177,8 +177,8 @@
   } else {
     return {
       range.source.name: description.serializeForPubspec(
-        containingDir: relativeEntrypoint.rootDir,
-        languageVersion: relativeEntrypoint.root.pubspec.languageVersion,
+        containingDir: receivingPackage.dir,
+        languageVersion: receivingPackage.pubspec.languageVersion,
       ),
       if (!constraint.isAny) 'version': constraint.toString(),
     };
diff --git a/lib/src/solver/solve_suggestions.dart b/lib/src/solver/solve_suggestions.dart
index fbff1c9..9b14f7a 100644
--- a/lib/src/solver/solve_suggestions.dart
+++ b/lib/src/solver/solve_suggestions.dart
@@ -93,7 +93,8 @@
 
 String packageAddDescription(Entrypoint entrypoint, PackageId id) {
   final name = id.name;
-  final isDev = entrypoint.root.pubspec.devDependencies.containsKey(name);
+  final isDev =
+      entrypoint.workspaceRoot.pubspec.devDependencies.containsKey(name);
   final resolvedDescription = id.description;
   final String descriptor;
   final d = resolvedDescription.description.serializeForPubspec(
@@ -102,7 +103,7 @@
     // This currently should have no implications as we don't create suggestions
     // for path-packages.
     ,
-    languageVersion: entrypoint.root.pubspec.languageVersion,
+    languageVersion: entrypoint.workspaceRoot.pubspec.languageVersion,
   );
   if (d == null) {
     descriptor = VersionConstraint.compatibleWith(id.version).toString();
@@ -148,7 +149,7 @@
         await inferBestFlutterRelease({cause.sdk.identifier: constraint});
     if (bestRelease == null) return null;
     final result = await _tryResolve(
-      entrypoint.root,
+      entrypoint.workspaceRoot,
       sdkOverrides: {
         'dart': bestRelease.dartVersion,
         'flutter': bestRelease.flutterVersion,
@@ -170,8 +171,10 @@
   /// Attempt another resolution with a relaxed constraint on [name]. If that
   /// resolves, suggest upgrading to that version.
   Future<_ResolutionSuggestion?> suggestSinglePackageUpdate(String name) async {
-    final originalRange = entrypoint.root.dependencies[name] ??
-        entrypoint.root.devDependencies[name];
+    // TODO(https://github.com/dart-lang/pub/issues/4127): This should
+    // operate on all packages in workspace.
+    final originalRange = entrypoint.workspaceRoot.dependencies[name] ??
+        entrypoint.workspaceRoot.devDependencies[name];
     if (originalRange == null ||
         originalRange.description is! HostedDescription) {
       // We can only relax constraints on hosted dependencies.
@@ -179,7 +182,7 @@
     }
     final originalConstraint = originalRange.constraint;
     final relaxedPubspec = stripVersionBounds(
-      entrypoint.root.pubspec,
+      entrypoint.workspaceRoot.pubspec,
       stripOnly: [name],
       stripLowerBound: true,
     );
@@ -187,8 +190,8 @@
     final result = await _tryResolve(
       Package(
         relaxedPubspec,
-        entrypoint.rootDir,
-        entrypoint.root.workspaceChildren,
+        entrypoint.workspaceRoot.dir,
+        entrypoint.workspaceRoot.workspaceChildren,
       ),
     );
     if (result == null) {
@@ -224,15 +227,15 @@
   Future<_ResolutionSuggestion?> suggestUnlockingAll({
     required bool stripLowerBound,
   }) async {
-    final originalPubspec = entrypoint.root.pubspec;
+    final originalPubspec = entrypoint.workspaceRoot.pubspec;
     final relaxedPubspec =
         stripVersionBounds(originalPubspec, stripLowerBound: stripLowerBound);
 
     final result = await _tryResolve(
       Package(
         relaxedPubspec,
-        entrypoint.rootDir,
-        entrypoint.root.workspaceChildren,
+        entrypoint.workspaceRoot.dir,
+        entrypoint.workspaceRoot.workspaceChildren,
       ),
     );
     if (result == null) {
diff --git a/lib/src/validator.dart b/lib/src/validator.dart
index 6c46f65..38b4024 100644
--- a/lib/src/validator.dart
+++ b/lib/src/validator.dart
@@ -10,7 +10,9 @@
 
 import 'entrypoint.dart';
 import 'log.dart' as log;
+import 'package.dart';
 import 'sdk.dart';
+import 'system_cache.dart';
 import 'validator/analyze.dart';
 import 'validator/changelog.dart';
 import 'validator/compiled_dartdoc.dart';
@@ -60,7 +62,8 @@
   final hints = <String>[];
 
   late ValidationContext context;
-  Entrypoint get entrypoint => context.entrypoint;
+  Package get package => context.entrypoint.workPackage;
+  SystemCache get cache => context.entrypoint.cache;
   int get packageSize => context.packageSize;
   Uri get serverUrl => context.serverUrl;
   List<String> get files => context.files;
@@ -75,7 +78,7 @@
   void validateSdkConstraint(Version firstSdkVersion, String message) {
     // If the SDK constraint disallowed all versions before [firstSdkVersion],
     // no error is necessary.
-    if (entrypoint.root.pubspec.dartSdkConstraint.originalConstraint
+    if (package.pubspec.dartSdkConstraint.originalConstraint
         .intersect(VersionRange(max: firstSdkVersion))
         .isEmpty) {
       return;
@@ -97,8 +100,7 @@
           : firstSdkVersion.nextBreaking,
     );
 
-    var newSdkConstraint = entrypoint
-        .root.pubspec.dartSdkConstraint.originalConstraint
+    var newSdkConstraint = package.pubspec.dartSdkConstraint.originalConstraint
         .intersect(allowedSdks);
     if (newSdkConstraint.isEmpty) newSdkConstraint = allowedSdks;
 
@@ -217,7 +219,7 @@
   /// entrypoint).
   // TODO(sigurdm): Consider moving this to a more central location.
   List<String> filesBeneath(String dir, {required bool recursive}) {
-    final base = p.canonicalize(p.join(entrypoint.rootDir, dir));
+    final base = p.canonicalize(p.join(package.dir, dir));
     return files
         .where(
           recursive
diff --git a/lib/src/validator/analyze.dart b/lib/src/validator/analyze.dart
index ae332d9..839db28 100644
--- a/lib/src/validator/analyze.dart
+++ b/lib/src/validator/analyze.dart
@@ -25,11 +25,11 @@
   @override
   Future<void> validate() async {
     final entries = _entriesToAnalyze
-        .map((dir) => p.join(entrypoint.rootDir, dir))
+        .map((dir) => p.join(package.dir, dir))
         .where(entryExists);
     final result = await runProcess(
       Platform.resolvedExecutable,
-      ['analyze', ...entries, p.join(entrypoint.rootDir, 'pubspec.yaml')],
+      ['analyze', ...entries, p.join(package.dir, 'pubspec.yaml')],
     );
     if (result.exitCode != 0) {
       final limitedOutput = limitLength(result.stdout.join('\n'), 1000);
diff --git a/lib/src/validator/changelog.dart b/lib/src/validator/changelog.dart
index aa47b70..65d7fdc 100644
--- a/lib/src/validator/changelog.dart
+++ b/lib/src/validator/changelog.dart
@@ -46,7 +46,7 @@
       return;
     }
 
-    final version = entrypoint.root.pubspec.version.toString();
+    final version = package.pubspec.version.toString();
 
     if (!contents.contains(version)) {
       warnings.add("$changelog doesn't mention current version ($version).\n"
diff --git a/lib/src/validator/dependency.dart b/lib/src/validator/dependency.dart
index ebfa101..f6134e2 100644
--- a/lib/src/validator/dependency.dart
+++ b/lib/src/validator/dependency.dart
@@ -37,8 +37,7 @@
     Future warnAboutSource(PackageRange dep) async {
       List<Version> versions;
       try {
-        var ids = await entrypoint.cache
-            .getVersions(entrypoint.cache.hosted.refFor(dep.name));
+        var ids = await cache.getVersions(cache.hosted.refFor(dep.name));
         versions = ids.map((id) => id.version).toList();
       } on ApplicationException catch (_) {
         versions = [];
@@ -88,7 +87,7 @@
     void warnAboutNoConstraint(PackageRange dep) {
       var message = 'Your dependency on "${dep.name}" should have a version '
           'constraint.';
-      var locked = entrypoint.lockFile.packages[dep.name];
+      var locked = context.entrypoint.lockFile.packages[dep.name];
       if (locked != null) {
         message = '$message For example:\n'
             '\n'
@@ -118,7 +117,7 @@
     void warnAboutNoConstraintLowerBound(PackageRange dep) {
       var message =
           'Your dependency on "${dep.name}" should have a lower bound.';
-      var locked = entrypoint.lockFile.packages[dep.name];
+      var locked = context.entrypoint.lockFile.packages[dep.name];
       if (locked != null) {
         String constraint;
         if (locked.version == (dep.constraint as VersionRange).max) {
@@ -160,7 +159,7 @@
     }
 
     void warnAboutPrerelease(String dependencyName, VersionRange constraint) {
-      final packageVersion = entrypoint.root.version;
+      final packageVersion = package.version;
       if (constraint.min != null &&
           constraint.min!.isPreRelease &&
           !packageVersion.isPreRelease) {
@@ -203,6 +202,6 @@
       }
     }
 
-    await validateDependencies(entrypoint.root.pubspec.dependencies.values);
+    await validateDependencies(package.pubspec.dependencies.values);
   }
 }
diff --git a/lib/src/validator/dependency_override.dart b/lib/src/validator/dependency_override.dart
index 2fda8d5..1b875ab 100644
--- a/lib/src/validator/dependency_override.dart
+++ b/lib/src/validator/dependency_override.dart
@@ -13,13 +13,12 @@
 class DependencyOverrideValidator extends Validator {
   @override
   Future validate() {
-    var overridden = MapKeySet(entrypoint.root.dependencyOverrides);
-    var dev = MapKeySet(entrypoint.root.devDependencies);
+    var overridden = MapKeySet(package.dependencyOverrides);
+    var dev = MapKeySet(package.devDependencies);
     if (overridden.difference(dev).isNotEmpty) {
-      final overridesFile =
-          entrypoint.root.pubspec.dependencyOverridesFromOverridesFile
-              ? entrypoint.pubspecOverridesPath
-              : entrypoint.pubspecPath;
+      final overridesFile = package.pubspec.dependencyOverridesFromOverridesFile
+          ? package.pubspecOverridesPath
+          : package.pubspecPath;
 
       hints.add('''
 Non-dev dependencies are overridden in $overridesFile.
diff --git a/lib/src/validator/deprecated_fields.dart b/lib/src/validator/deprecated_fields.dart
index 942c8f5..788996c 100644
--- a/lib/src/validator/deprecated_fields.dart
+++ b/lib/src/validator/deprecated_fields.dart
@@ -11,19 +11,19 @@
 class DeprecatedFieldsValidator extends Validator {
   @override
   Future validate() async {
-    if (entrypoint.root.pubspec.fields.containsKey('transformers')) {
+    if (package.pubspec.fields.containsKey('transformers')) {
       warnings.add('Your pubspec.yaml includes a "transformers" section which'
           ' is no longer used and may be removed.');
     }
-    if (entrypoint.root.pubspec.fields.containsKey('web')) {
+    if (package.pubspec.fields.containsKey('web')) {
       warnings.add('Your pubspec.yaml includes a "web" section which'
           ' is no longer used and may be removed.');
     }
-    if (entrypoint.root.pubspec.fields.containsKey('author')) {
+    if (package.pubspec.fields.containsKey('author')) {
       warnings.add('Your pubspec.yaml includes an "author" section which'
           ' is no longer used and may be removed.');
     }
-    if (entrypoint.root.pubspec.fields.containsKey('authors')) {
+    if (package.pubspec.fields.containsKey('authors')) {
       warnings.add('Your pubspec.yaml includes an "authors" section which'
           ' is no longer used and may be removed.');
     }
diff --git a/lib/src/validator/devtools_extension.dart b/lib/src/validator/devtools_extension.dart
index 20012f3..9776c9a 100644
--- a/lib/src/validator/devtools_extension.dart
+++ b/lib/src/validator/devtools_extension.dart
@@ -15,12 +15,12 @@
 
   @override
   Future<void> validate() async {
-    if (dirExists(p.join(entrypoint.rootDir, 'extension', 'devtools'))) {
+    if (dirExists(p.join(package.dir, 'extension', 'devtools'))) {
       if (!files.any(
             (f) => p.equals(
               f,
               p.join(
-                entrypoint.rootDir,
+                package.dir,
                 'extension',
                 'devtools',
                 'config.yaml',
@@ -29,7 +29,7 @@
           ) ||
           !files.any(
             (f) => p.isWithin(
-              p.join(entrypoint.rootDir, 'extension', 'devtools', 'build'),
+              p.join(package.dir, 'extension', 'devtools', 'build'),
               f,
             ),
           )) {
diff --git a/lib/src/validator/directory.dart b/lib/src/validator/directory.dart
index c0d6a12..1870b0e 100644
--- a/lib/src/validator/directory.dart
+++ b/lib/src/validator/directory.dart
@@ -27,8 +27,8 @@
     for (final file in files) {
       // Find the topmost directory name of [file].
       final dir = path.join(
-        entrypoint.rootDir,
-        path.split(path.relative(file, from: entrypoint.rootDir)).first,
+        package.dir,
+        path.split(path.relative(file, from: package.dir)).first,
       );
       if (!visited.add(dir)) continue;
       if (!dirExists(dir)) continue;
diff --git a/lib/src/validator/executable.dart b/lib/src/validator/executable.dart
index b7679eb..ff178c8 100644
--- a/lib/src/validator/executable.dart
+++ b/lib/src/validator/executable.dart
@@ -14,9 +14,9 @@
   @override
   Future validate() async {
     final binFiles =
-        filesBeneath('bin', recursive: false).map(entrypoint.root.relative);
+        filesBeneath('bin', recursive: false).map(package.relative);
 
-    entrypoint.root.pubspec.executables.forEach((executable, script) {
+    package.pubspec.executables.forEach((executable, script) {
       var scriptPath = p.join('bin', '$script.dart');
       if (binFiles.contains(scriptPath)) return;
 
diff --git a/lib/src/validator/flutter_constraint.dart b/lib/src/validator/flutter_constraint.dart
index 1b1e224..cacb7c0 100644
--- a/lib/src/validator/flutter_constraint.dart
+++ b/lib/src/validator/flutter_constraint.dart
@@ -15,7 +15,7 @@
 
   @override
   Future validate() async {
-    final environment = entrypoint.root.pubspec.fields['environment'];
+    final environment = package.pubspec.fields['environment'];
     if (environment is Map) {
       final flutterConstraint = environment['flutter'];
       if (flutterConstraint is String) {
diff --git a/lib/src/validator/flutter_plugin_format.dart b/lib/src/validator/flutter_plugin_format.dart
index 79f286c..3b5c1d8 100644
--- a/lib/src/validator/flutter_plugin_format.dart
+++ b/lib/src/validator/flutter_plugin_format.dart
@@ -20,7 +20,7 @@
 class FlutterPluginFormatValidator extends Validator {
   @override
   Future validate() async {
-    final pubspec = entrypoint.root.pubspec;
+    final pubspec = package.pubspec;
 
     // Ignore all packages that do not have the `flutter.plugin` property.
     if (pubspec.fields['flutter'] is! Map ||
diff --git a/lib/src/validator/gitignore.dart b/lib/src/validator/gitignore.dart
index f45e233..6afb25d 100644
--- a/lib/src/validator/gitignore.dart
+++ b/lib/src/validator/gitignore.dart
@@ -21,7 +21,7 @@
 class GitignoreValidator extends Validator {
   @override
   Future<void> validate() async {
-    if (entrypoint.root.inGitRepo) {
+    if (package.inGitRepo) {
       late final List<String> checkedIntoGit;
       try {
         checkedIntoGit = git.runSync(
@@ -33,7 +33,7 @@
             '--exclude-standard',
             '--recurse-submodules',
           ],
-          workingDir: entrypoint.rootDir,
+          workingDir: package.dir,
           stdoutEncoding: Utf8Codec(),
         );
       } on git.GitException catch (e) {
@@ -43,9 +43,9 @@
         // --recurse-submodules we just continue silently.
         return;
       }
-      final root = git.repoRoot(entrypoint.rootDir) ?? entrypoint.rootDir;
+      final root = git.repoRoot(package.dir) ?? package.dir;
       var beneath = p.posix.joinAll(
-        p.split(p.normalize(p.relative(entrypoint.rootDir, from: root))),
+        p.split(p.normalize(p.relative(package.dir, from: root))),
       );
       if (beneath == './') {
         beneath = '';
@@ -77,7 +77,7 @@
         },
         isDir: (dir) => dirExists(resolve(dir)),
       ).map((file) {
-        final relative = p.relative(resolve(file), from: entrypoint.rootDir);
+        final relative = p.relative(resolve(file), from: package.dir);
         return Platform.isWindows
             ? p.posix.joinAll(p.split(relative))
             : relative;
diff --git a/lib/src/validator/leak_detection.dart b/lib/src/validator/leak_detection.dart
index d5c2803..945ffb7 100644
--- a/lib/src/validator/leak_detection.dart
+++ b/lib/src/validator/leak_detection.dart
@@ -29,14 +29,14 @@
   Future<void> validate() async {
     // Load `false_secrets` from `pubspec.yaml`.
     final falseSecrets = Ignore(
-      entrypoint.root.pubspec.falseSecrets,
+      package.pubspec.falseSecrets,
       ignoreCase: Platform.isWindows || Platform.isMacOS,
     );
 
     final pool = Pool(20); // don't read more than 20 files concurrently!
     final leaks = await Future.wait(
       files.map((f) async {
-        final relPath = entrypoint.root.relative(f);
+        final relPath = package.relative(f);
 
         // Skip files matching patterns in `false_secrets`
         final nixPath = p.posix.joinAll(p.split(relPath));
diff --git a/lib/src/validator/name.dart b/lib/src/validator/name.dart
index 9574c5b..24b1ace 100644
--- a/lib/src/validator/name.dart
+++ b/lib/src/validator/name.dart
@@ -15,15 +15,15 @@
   @override
   Future validate() {
     return Future.sync(() {
-      _checkName(entrypoint.root.name);
+      _checkName(package.name);
 
       var libraries = _libraries(files);
 
       if (libraries.length == 1) {
         var libName = path.basenameWithoutExtension(libraries[0]);
-        if (libName == entrypoint.root.name) return;
+        if (libName == package.name) return;
         warnings.add('The name of "${libraries[0]}", "$libName", should match '
-            'the name of the package, "${entrypoint.root.name}".\n'
+            'the name of the package, "${package.name}".\n'
             'This helps users know what library to import.');
       }
     });
@@ -32,7 +32,7 @@
   /// Returns a list of all libraries in the current package as paths relative
   /// to the package's root directory.
   List<String> _libraries(List<String> files) {
-    var libDir = entrypoint.root.path('lib');
+    var libDir = package.path('lib');
     return filesBeneath('lib', recursive: true)
         .map((file) => path.relative(file, from: path.dirname(libDir)))
         .where(
diff --git a/lib/src/validator/pubspec_field.dart b/lib/src/validator/pubspec_field.dart
index 91d8da3..385fbd5 100644
--- a/lib/src/validator/pubspec_field.dart
+++ b/lib/src/validator/pubspec_field.dart
@@ -29,18 +29,18 @@
 
     // Pubspec errors are detected lazily, so we make sure there aren't any
     // here.
-    for (var error in entrypoint.root.pubspec.allErrors) {
+    for (var error in package.pubspec.allErrors) {
       errors.add('In your pubspec.yaml, ${error.message}');
     }
 
     return Future.value();
   }
 
-  bool _hasField(String field) => entrypoint.root.pubspec.fields[field] != null;
+  bool _hasField(String field) => package.pubspec.fields[field] != null;
 
   /// Adds an error if [field] doesn't exist or isn't a string.
   void _validateFieldIsString(String field) {
-    var value = entrypoint.root.pubspec.fields[field];
+    var value = package.pubspec.fields[field];
     if (value == null) {
       errors.add('Your pubspec.yaml is missing a "$field" field.');
     } else if (value is! String) {
@@ -51,7 +51,7 @@
 
   /// Adds an error if the URL for [field] is invalid.
   void _validateFieldUrl(String field) {
-    var url = entrypoint.root.pubspec.fields[field];
+    var url = package.pubspec.fields[field];
     if (url == null) return;
 
     if (url is! String) {
diff --git a/lib/src/validator/pubspec_typo.dart b/lib/src/validator/pubspec_typo.dart
index 4510b3c..4ca08fa 100644
--- a/lib/src/validator/pubspec_typo.dart
+++ b/lib/src/validator/pubspec_typo.dart
@@ -9,7 +9,7 @@
 class PubspecTypoValidator extends Validator {
   @override
   Future validate() async {
-    final fields = entrypoint.root.pubspec.fields;
+    final fields = package.pubspec.fields;
 
     /// Limit the number of typo warnings so as not to drown out the other
     /// warnings
diff --git a/lib/src/validator/relative_version_numbering.dart b/lib/src/validator/relative_version_numbering.dart
index fc2b432..91a6d91 100644
--- a/lib/src/validator/relative_version_numbering.dart
+++ b/lib/src/validator/relative_version_numbering.dart
@@ -25,18 +25,21 @@
 
   @override
   Future<void> validate() async {
-    final hostedSource = entrypoint.cache.hosted;
+    final hostedSource = cache.hosted;
     List<PackageId> existingVersions;
     try {
-      existingVersions = await entrypoint.cache.getVersions(
-        hostedSource.refFor(entrypoint.root.name, url: serverUrl.toString()),
+      existingVersions = await cache.getVersions(
+        hostedSource.refFor(
+          package.name,
+          url: serverUrl.toString(),
+        ),
       );
     } on PackageNotFoundException {
       existingVersions = [];
     }
     existingVersions.sort((a, b) => a.version.compareTo(b.version));
 
-    final currentVersion = entrypoint.root.pubspec.version;
+    final currentVersion = package.pubspec.version;
 
     final latestVersion =
         existingVersions.isEmpty ? null : existingVersions.last.version;
@@ -46,8 +49,8 @@
 Your version $currentVersion is earlier than that.''');
     }
 
-    final previousRelease = existingVersions
-        .lastWhereOrNull((id) => id.version < entrypoint.root.version);
+    final previousRelease =
+        existingVersions.lastWhereOrNull((id) => id.version < package.version);
 
     if (previousRelease == null) return;
 
@@ -85,10 +88,9 @@
       hints.add(hint + suggestion);
     }
 
-    final previousPubspec = await entrypoint.cache.describe(previousRelease);
+    final previousPubspec = await cache.describe(previousRelease);
 
-    final currentOptedIn =
-        entrypoint.root.pubspec.languageVersion.supportsNullSafety;
+    final currentOptedIn = package.pubspec.languageVersion.supportsNullSafety;
     final previousOptedIn = previousPubspec.languageVersion.supportsNullSafety;
 
     if (currentOptedIn && !previousOptedIn) {
diff --git a/lib/src/validator/sdk_constraint.dart b/lib/src/validator/sdk_constraint.dart
index c95cd91..c44f9b8 100644
--- a/lib/src/validator/sdk_constraint.dart
+++ b/lib/src/validator/sdk_constraint.dart
@@ -18,7 +18,7 @@
 class SdkConstraintValidator extends Validator {
   @override
   Future validate() async {
-    final dartConstraint = entrypoint.root.pubspec.dartSdkConstraint;
+    final dartConstraint = package.pubspec.dartSdkConstraint;
     final originalConstraint = dartConstraint.originalConstraint;
     final effectiveConstraint = dartConstraint.effectiveConstraint;
     if (originalConstraint is VersionRange) {
@@ -32,7 +32,7 @@
       }
 
       final constraintMin = originalConstraint.min;
-      final packageVersion = entrypoint.root.version;
+      final packageVersion = package.version;
 
       if (constraintMin != null &&
           constraintMin.isPreRelease &&
diff --git a/lib/src/validator/size.dart b/lib/src/validator/size.dart
index a8a7eae..fac5e16 100644
--- a/lib/src/validator/size.dart
+++ b/lib/src/validator/size.dart
@@ -17,17 +17,17 @@
     if (packageSize <= _maxSize) return;
     var sizeInMb = (packageSize / (1 << 20)).toStringAsPrecision(4);
     // Current implementation of Package.listFiles skips hidden files
-    var ignoreExists = fileExists(entrypoint.root.path('.gitignore'));
+    var ignoreExists = fileExists(package.path('.gitignore'));
 
     var hint = StringBuffer('''
 Your package is $sizeInMb MB.
 
 Consider the impact large downloads can have on the package consumer.''');
 
-    if (ignoreExists && !entrypoint.root.inGitRepo) {
+    if (ignoreExists && !package.inGitRepo) {
       hint.write('\nYour .gitignore has no effect since your project '
           'does not appear to be in version control.');
-    } else if (!ignoreExists && entrypoint.root.inGitRepo) {
+    } else if (!ignoreExists && package.inGitRepo) {
       hint.write('\nConsider adding a .gitignore to avoid including '
           'temporary files.');
     }
diff --git a/lib/src/validator/strict_dependencies.dart b/lib/src/validator/strict_dependencies.dart
index bd1674e..ea0e36f 100644
--- a/lib/src/validator/strict_dependencies.dart
+++ b/lib/src/validator/strict_dependencies.dart
@@ -23,7 +23,7 @@
   /// Files that do not parse and directives that don't import or export
   /// `package:` URLs are ignored.
   Iterable<_Usage> _findPackages(Iterable<String> files) sync* {
-    final packagePath = p.normalize(p.absolute(entrypoint.rootDir));
+    final packagePath = p.normalize(p.absolute(package.dir));
     final analysisContextManager = AnalysisContextManager(packagePath);
 
     for (var file in files) {
@@ -63,9 +63,8 @@
 
   @override
   Future validate() async {
-    var dependencies = entrypoint.root.dependencies.keys.toSet()
-      ..add(entrypoint.root.name);
-    var devDependencies = MapKeySet(entrypoint.root.devDependencies);
+    var dependencies = package.dependencies.keys.toSet()..add(package.name);
+    var devDependencies = MapKeySet(package.devDependencies);
     _validateLibBin(dependencies, devDependencies);
     _validateBenchmarkTestTool(dependencies, devDependencies);
   }
diff --git a/test/package_list_files_test.dart b/test/package_list_files_test.dart
index 7285b21..ab2d949 100644
--- a/test/package_list_files_test.dart
+++ b/test/package_list_files_test.dart
@@ -33,7 +33,7 @@
     createEntrypoint();
 
     expect(
-      entrypoint!.root.listFiles(),
+      entrypoint!.workspaceRoot.listFiles(),
       unorderedEquals([
         p.join(root, 'pubspec.yaml'),
         p.join(root, 'file1.txt'),
@@ -75,7 +75,7 @@
     createEntrypoint();
 
     expect(
-      () => entrypoint!.root.listFiles(),
+      () => entrypoint!.workspaceRoot.listFiles(),
       throwsA(
         isA<DataException>().having(
           (e) => e.message,
@@ -106,7 +106,7 @@
       SystemCache(rootDir: p.join(d.sandbox, cachePath)),
     );
 
-    expect(entrypoint.root.listFiles(), {
+    expect(entrypoint.workspaceRoot.listFiles(), {
       p.join(root, 'pubspec.yaml'),
       p.join(root, 'file1.txt'),
       p.join(root, 'file2.txt'),
@@ -129,7 +129,7 @@
     createEntrypoint();
 
     expect(
-      () => entrypoint!.root.listFiles(),
+      () => entrypoint!.workspaceRoot.listFiles(),
       throwsA(
         isA<DataException>().having(
           (e) => e.message,
@@ -158,7 +158,7 @@
     createEntrypoint();
 
     expect(
-      () => entrypoint!.root.listFiles(),
+      () => entrypoint!.workspaceRoot.listFiles(),
       throwsA(
         isA<DataException>().having(
           (e) => e.message,
@@ -177,7 +177,7 @@
       d.file('.foo', ''),
     ]).create();
     createEntrypoint();
-    expect(entrypoint!.root.listFiles(), {
+    expect(entrypoint!.workspaceRoot.listFiles(), {
       p.join(root, '.foo'),
       p.join(root, 'pubspec.yaml'),
     });
@@ -201,7 +201,7 @@
         ]),
       ]).create();
 
-      expect(entrypoint!.root.listFiles(), {
+      expect(entrypoint!.workspaceRoot.listFiles(), {
         p.join(root, 'pubspec.yaml'),
         p.join(root, 'file1.txt'),
         p.join(root, 'file2.txt'),
@@ -221,7 +221,7 @@
         ]),
       ]).create();
 
-      expect(entrypoint!.root.listFiles(), {
+      expect(entrypoint!.workspaceRoot.listFiles(), {
         p.join(root, 'pubspec.yaml'),
         p.join(root, 'file2.text'),
         p.join(root, 'subdir', 'subfile2.text'),
@@ -254,7 +254,7 @@
 
       createEntrypoint(p.join(appPath, 'rep', 'sub'));
 
-      expect(entrypoint!.root.listFiles(), {
+      expect(entrypoint!.workspaceRoot.listFiles(), {
         p.join(root, 'pubspec.yaml'),
         p.join(root, 'file2.text'),
         p.join(root, 'file4.gak'),
@@ -284,7 +284,7 @@
       });
 
       test('respects its .gitignore with useGitIgnore', () {
-        expect(entrypoint!.root.listFiles(), {
+        expect(entrypoint!.workspaceRoot.listFiles(), {
           p.join(root, 'pubspec.yaml'),
           p.join(root, 'submodule', 'file2.text'),
         });
@@ -297,7 +297,10 @@
         d.dir('subdir', [d.file('pubspec.lock')]),
       ]).create();
 
-      expect(entrypoint!.root.listFiles(), {p.join(root, 'pubspec.yaml')});
+      expect(
+        entrypoint!.workspaceRoot.listFiles(),
+        {p.join(root, 'pubspec.yaml')},
+      );
     });
 
     test('allows pubspec.lock directories', () async {
@@ -307,7 +310,7 @@
         ]),
       ]).create();
 
-      expect(entrypoint!.root.listFiles(), {
+      expect(entrypoint!.workspaceRoot.listFiles(), {
         p.join(root, 'pubspec.yaml'),
         p.join(root, 'pubspec.lock', 'file.txt'),
       });
@@ -328,7 +331,7 @@
           ]),
         ]).create();
 
-        expect(entrypoint!.root.listFiles(beneath: 'subdir'), {
+        expect(entrypoint!.workspaceRoot.listFiles(beneath: 'subdir'), {
           p.join(root, 'subdir', 'subfile1.txt'),
           p.join(root, 'subdir', 'subfile2.txt'),
           p.join(root, 'subdir', 'subsubdir', 'subsubfile1.txt'),
@@ -347,7 +350,7 @@
         d.dir('lib', [d.file('not_ignored.dart', 'content')]),
       ]).create();
       createEntrypoint();
-      expect(entrypoint!.root.listFiles(), {
+      expect(entrypoint!.workspaceRoot.listFiles(), {
         p.join(root, 'LICENSE'),
         p.join(root, 'CHANGELOG.md'),
         p.join(root, 'README.md'),
@@ -399,7 +402,7 @@
     ]).create();
 
     createEntrypoint();
-    expect(entrypoint!.root.listFiles(), {
+    expect(entrypoint!.workspaceRoot.listFiles(), {
       p.join(root, 'pubspec.yaml'),
       p.join(root, 'not_ignored_by_gitignore.txt'),
       p.join(root, 'ignored_by_gitignore.txt'),
@@ -431,7 +434,7 @@
         await repo.create();
         createEntrypoint(p.join(appPath, 'packages', 'nested'));
 
-        expect(entrypoint!.root.listFiles(), {
+        expect(entrypoint!.workspaceRoot.listFiles(), {
           p.join(root, 'pubspec.yaml'),
         });
       });
@@ -449,7 +452,7 @@
         await repo.create();
         createEntrypoint(p.join(appPath, 'packages', 'nested'));
 
-        expect(entrypoint!.root.listFiles(), {
+        expect(entrypoint!.workspaceRoot.listFiles(), {
           p.join(root, 'pubspec.yaml'),
           p.join(root, 'bin'),
         });
@@ -470,7 +473,7 @@
         await repo.create();
         createEntrypoint(p.join(appPath, 'packages', 'nested'));
 
-        expect(entrypoint!.root.listFiles(), {
+        expect(entrypoint!.workspaceRoot.listFiles(), {
           p.join(root, 'pubspec.yaml'),
         });
       });
@@ -493,7 +496,7 @@
         await repo.create();
         createEntrypoint(p.join(appPath, 'packages', 'nested'));
 
-        expect(entrypoint!.root.listFiles(), {
+        expect(entrypoint!.workspaceRoot.listFiles(), {
           p.join(root, 'pubspec.yaml'),
           p.join(root, 'bin', 'nested_again', 'run.dart'),
         });
@@ -518,7 +521,7 @@
         await repo.create();
         createEntrypoint(p.join(appPath, 'packages', 'nested'));
 
-        expect(entrypoint!.root.listFiles(), {
+        expect(entrypoint!.workspaceRoot.listFiles(), {
           p.join(root, 'pubspec.yaml'),
           p.join(root, 'bin', 'run.dart'),
         });
@@ -545,7 +548,7 @@
       await repo.create();
       createEntrypoint(p.join(appPath, 'packages', 'nested'));
 
-      expect(entrypoint!.root.listFiles(), {
+      expect(entrypoint!.workspaceRoot.listFiles(), {
         p.join(root, 'pubspec.yaml'),
         p.join(root, 'bin', 'nested_again', 'run.dart'),
       });
@@ -569,7 +572,7 @@
       await repo.create();
       createEntrypoint(p.join(appPath, 'packages', 'nested'));
 
-      expect(entrypoint!.root.listFiles(), {
+      expect(entrypoint!.workspaceRoot.listFiles(), {
         p.join(root, 'pubspec.yaml'),
         p.join(root, 'bin', 'run.dart'),
         p.join(root, 'bin', 'nested_again', 'run.dart'),
diff --git a/test/test_pub.dart b/test/test_pub.dart
index 7aa3cf6..41649e9 100644
--- a/test/test_pub.dart
+++ b/test/test_pub.dart
@@ -911,7 +911,7 @@
     _globalServer == null
         ? Uri.parse('https://pub.dev')
         : Uri.parse(globalServer.url),
-    entrypoint.root.listFiles(),
+    entrypoint.workspaceRoot.listFiles(),
   );
   await validator.validate();
   return validator;
diff --git a/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt b/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt
index c489d00..26b9598 100644
--- a/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt
+++ b/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt
@@ -331,12 +331,8 @@
 Command: dart pub fail
 Platform: $OS
 
----- $SANDBOX/empty/pubspec.yaml ----
 <No pubspec.yaml>
----- End pubspec.yaml ----
----- $SANDBOX/empty/pubspec.lock ----
 <No pubspec.lock>
----- End pubspec.lock ----
 ---- Log transcript ----
 FINE: Pub 3.1.2+3
 ERR : Bad state: Pub has crashed