dart pub get --enforce-lockfile (#3637)

diff --git a/lib/src/command/get.dart b/lib/src/command/get.dart
index 251028e..96cf525 100644
--- a/lib/src/command/get.dart
+++ b/lib/src/command/get.dart
@@ -28,6 +28,13 @@
         negatable: false,
         help: "Report what dependencies would change but don't change any.");
 
+    argParser.addFlag(
+      'enforce-lockfile',
+      negatable: false,
+      help:
+          'Enforce pubspec.lock. Fail resolution if pubspec.lock does not satisfy pubspec.yaml',
+    );
+
     argParser.addFlag('precompile',
         help: 'Build executables in immediate dependencies.');
 
@@ -55,6 +62,7 @@
       dryRun: argResults['dry-run'],
       precompile: argResults['precompile'],
       analytics: analytics,
+      enforceLockfile: argResults['enforce-lockfile'],
     );
 
     var example = entrypoint.example;
@@ -65,6 +73,7 @@
         precompile: argResults['precompile'],
         analytics: analytics,
         onlyReportSuccessOrFailure: true,
+        enforceLockfile: argResults['enforce-lockfile'],
       );
     }
   }
diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index ab5b80c..136b325 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -309,6 +309,10 @@
   /// shown --- in case of failure, a reproduction command is shown.
   ///
   /// Updates [lockFile] and [packageRoot] accordingly.
+  ///
+  /// If [enforceLockfile] is true no changes to the current lockfile are
+  /// allowed. Instead the existing lockfile is loaded, verified against
+  /// pubspec.yaml and all dependencies downloaded.
   Future<void> acquireDependencies(
     SolveType type, {
     Iterable<String>? unlock,
@@ -316,13 +320,32 @@
     bool precompile = false,
     required PubAnalytics? analytics,
     bool onlyReportSuccessOrFailure = false,
+    bool enforceLockfile = false,
   }) async {
+    final suffix = root.isInMemory || root.dir == '.' ? '' : ' in ${root.dir}';
+
+    String forDetails() {
+      if (!onlyReportSuccessOrFailure) return '';
+      final enforceLockfileOption =
+          enforceLockfile ? ' --enforce-lockfile' : '';
+      final directoryOption =
+          root.isInMemory || root.dir == '.' ? '' : ' --directory ${root.dir}';
+      return ' For details run `$topLevelProgram pub ${type.toString()}$directoryOption$enforceLockfileOption`';
+    }
+
+    if (enforceLockfile && !fileExists(lockFilePath)) {
+      throw ApplicationException('''
+Retrieving dependencies failed$suffix.
+Cannot do `--enforce-lockfile` without an existing `pubspec.lock`.
+
+Try running `$topLevelProgram pub get` to create `$lockFilePath`.''');
+    }
+
     if (!onlyReportSuccessOrFailure && hasPubspecOverrides) {
       log.warning(
           'Warning: pubspec.yaml has overrides from $pubspecOverridesPath');
     }
 
-    final suffix = root.isInMemory || root.dir == '.' ? '' : ' in ${root.dir}';
     SolveResult result;
     try {
       result = await log.progress('Resolving dependencies$suffix', () async {
@@ -337,11 +360,8 @@
       });
     } catch (e) {
       if (onlyReportSuccessOrFailure && (e is ApplicationException)) {
-        final directoryOption = root.isInMemory || root.dir == '.'
-            ? ''
-            : ' --directory ${root.dir}';
         throw ApplicationException(
-            'Resolving dependencies$suffix failed. For details run `$topLevelProgram pub ${type.toString()}$directoryOption`');
+            'Resolving dependencies$suffix failed.${forDetails()}');
       } else {
         rethrow;
       }
@@ -352,22 +372,34 @@
     final newLockFile = await result.downloadCachedPackages(cache);
 
     final report = SolveReport(
-        type, root, lockFile, newLockFile, result.availableVersions, cache,
-        dryRun: dryRun);
-    if (!onlyReportSuccessOrFailure) {
-      await report.show();
-    }
-    _lockFile = newLockFile;
+      type,
+      root,
+      lockFile,
+      newLockFile,
+      result.availableVersions,
+      cache,
+      dryRun: dryRun,
+      enforceLockfile: enforceLockfile,
+      quiet: onlyReportSuccessOrFailure,
+    );
 
-    if (!dryRun) {
+    final hasChanges = await report.show();
+    await report.summarize();
+    if (enforceLockfile && hasChanges) {
+      var suggestion = onlyReportSuccessOrFailure
+          ? ''
+          : '''
+\n\nTo update `$lockFilePath` run `$topLevelProgram pub get`$suffix without
+`--enforce-lockfile`.''';
+      dataError('''
+Unable to satisfy `$pubspecPath` using `$lockFilePath`$suffix.${forDetails()}$suggestion''');
+    }
+
+    if (!(dryRun || enforceLockfile)) {
       newLockFile.writeToFile(lockFilePath, cache);
     }
 
-    if (onlyReportSuccessOrFailure) {
-      log.message('Got dependencies$suffix.');
-    } else {
-      await report.summarize();
-    }
+    _lockFile = newLockFile;
 
     if (!dryRun) {
       if (analytics != null) {
diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart
index 0cbb15a..a39536f 100644
--- a/lib/src/global_packages.dart
+++ b/lib/src/global_packages.dart
@@ -245,6 +245,8 @@
           result.availableVersions,
           cache,
           dryRun: false,
+          quiet: false,
+          enforceLockfile: false,
         ).show();
       }
 
diff --git a/lib/src/solver/report.dart b/lib/src/solver/report.dart
index 176ab67..e9cd00e 100644
--- a/lib/src/solver/report.dart
+++ b/lib/src/solver/report.dart
@@ -2,7 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'package:path/path.dart' as path;
 import 'package:pub_semver/pub_semver.dart';
 
 import '../command_runner.dart';
@@ -30,6 +29,11 @@
   final SystemCache _cache;
   final bool _dryRun;
 
+  /// If quiet only a single summary line is output.
+  final bool _quiet;
+
+  final bool _enforceLockfile;
+
   /// The available versions of all selected packages from their source.
   ///
   /// An entry here may not include the full list of versions available if the
@@ -38,8 +42,6 @@
   /// Version list will not contain any retracted package versions.
   final Map<String, List<Version>> _availableVersions;
 
-  final _output = StringBuffer();
-
   SolveReport(
     this._type,
     this._root,
@@ -48,14 +50,22 @@
     this._availableVersions,
     this._cache, {
     required bool dryRun,
-  }) : _dryRun = dryRun;
+    required bool enforceLockfile,
+    required bool quiet,
+  })  : _dryRun = dryRun,
+        _quiet = quiet,
+        _enforceLockfile = enforceLockfile;
 
   /// Displays a report of the results of the version resolution in
   /// [_newLockFile] relative to the [_previousLockFile] file.
-  Future<void> show() async {
-    await _reportChanges();
-    await _reportOverrides();
+  ///
+  /// Returns `true` if there was any change of dependencies relative to the old
+  /// lockfile.
+  Future<bool> show() async {
+    final hasChanges = await _reportChanges();
     _checkContentHashesMatchOldLockfile();
+    await _reportOverrides();
+    return hasChanges;
   }
 
   void _checkContentHashesMatchOldLockfile() {
@@ -100,14 +110,14 @@
     }
 
     if (issues.isNotEmpty) {
-      log.warning('''
+      warning('''
 The existing content-hash from pubspec.lock doesn't match contents for:
  * ${issues.join('\n * ')}
 
 This indicates one of:
  * The content has changed on the server since you created the pubspec.lock.
  * The pubspec.lock has been corrupted.
-${_dryRun ? '' : '\nThe content-hashes in pubspec.lock has been updated.'}
+${_dryRun || _enforceLockfile ? '' : '\nThe content-hashes in pubspec.lock has been updated.'}
 
 For more information see:
 $contentHashesDocumentationUrl
@@ -118,7 +128,8 @@
   /// Displays a one-line message summarizing what changes were made (or would
   /// be made) to the lockfile.
   ///
-  /// If [dryRun] is true, describes it in terms of what would be done.
+  /// If [_dryRun] or [_enforceLockfile] is true, describes it in terms of what
+  /// would be done.
   ///
   /// [type] is the type of version resolution that was run.
 
@@ -145,77 +156,102 @@
 
     var suffix = '';
     if (!_root.isInMemory) {
-      final dir = path.normalize(_root.dir);
+      final dir = _root.dir;
       if (dir != '.') {
         suffix = ' in $dir';
       }
     }
 
-    if (_dryRun) {
-      if (numChanged == 0) {
-        log.message('No dependencies would change$suffix.');
-      } else if (numChanged == 1) {
-        log.message('Would change $numChanged dependency$suffix.');
+    if (_quiet) {
+      if (_dryRun) {
+        log.message('Would get dependencies$suffix.');
+      } else if (_enforceLockfile) {
+        if (numChanged == 0) {
+          log.message('Got dependencies$suffix.');
+        }
       } else {
-        log.message('Would change $numChanged dependencies$suffix.');
+        log.message('Got dependencies$suffix.');
       }
     } else {
-      if (numChanged == 0) {
-        if (_type == SolveType.get) {
-          log.message('Got dependencies$suffix!');
+      if (_dryRun) {
+        if (numChanged == 0) {
+          log.message('No dependencies would change$suffix.');
+        } else if (numChanged == 1) {
+          log.message('Would change $numChanged dependency$suffix.');
         } else {
-          log.message('No dependencies changed$suffix.');
+          log.message('Would change $numChanged dependencies$suffix.');
         }
-      } else if (numChanged == 1) {
-        log.message('Changed $numChanged dependency$suffix!');
+      } else if (_enforceLockfile) {
+        if (numChanged == 0) {
+          log.message('Got dependencies$suffix!');
+        } else if (numChanged == 1) {
+          log.message('Would change $numChanged dependency$suffix.');
+        } else {
+          log.message('Would change $numChanged dependencies$suffix.');
+        }
       } else {
-        log.message('Changed $numChanged dependencies$suffix!');
+        if (numChanged == 0) {
+          if (_type == SolveType.get) {
+            log.message('Got dependencies$suffix!');
+          } else {
+            log.message('No dependencies changed$suffix.');
+          }
+        } else if (numChanged == 1) {
+          log.message('Changed $numChanged dependency$suffix!');
+        } else {
+          log.message('Changed $numChanged dependencies$suffix!');
+        }
       }
-    }
-    if (_type == SolveType.upgrade) {
-      await reportDiscontinued();
-      reportOutdated();
+      if (_type == SolveType.upgrade) {
+        await reportDiscontinued();
+        reportOutdated();
+      }
     }
   }
 
   /// Displays a report of all of the previous and current dependencies and
   /// how they have changed.
-  Future<void> _reportChanges() async {
-    _output.clear();
-
+  ///
+  /// Returns true if anything changed.
+  Future<bool> _reportChanges() async {
+    final output = StringBuffer();
     // Show the new set of dependencies ordered by name.
     var names = _newLockFile.packages.keys.toList();
     names.remove(_root.name);
     names.sort();
+    var hasChanges = false;
     for (final name in names) {
-      await _reportPackage(name);
+      hasChanges |= await _reportPackage(name, output);
     }
     // Show any removed ones.
     var removed = _previousLockFile.packages.keys.toSet();
     removed.removeAll(names);
     removed.remove(_root.name); // Never consider root.
     if (removed.isNotEmpty) {
-      _output.writeln('These packages are no longer being depended on:');
+      output.writeln('These packages are no longer being depended on:');
       for (var name in ordered(removed)) {
-        await _reportPackage(name, alwaysShow: true);
+        await _reportPackage(name, output, alwaysShow: true);
       }
+      hasChanges = true;
     }
 
-    log.message(_output);
+    message(output.toString());
+    return hasChanges;
   }
 
   /// Displays a warning about the overrides currently in effect.
   Future<void> _reportOverrides() async {
-    _output.clear();
+    final output = StringBuffer();
 
     if (_root.dependencyOverrides.isNotEmpty) {
-      _output.writeln('Warning: You are using these overridden dependencies:');
+      output.writeln('Warning: You are using these overridden dependencies:');
 
       for (var name in ordered(_root.dependencyOverrides.keys)) {
-        await _reportPackage(name, alwaysShow: true, highlightOverride: false);
+        await _reportPackage(name, output,
+            alwaysShow: true, highlightOverride: false);
       }
 
-      log.warning(_output);
+      warning(output.toString());
     }
   }
 
@@ -235,9 +271,9 @@
     }
     if (numDiscontinued > 0) {
       if (numDiscontinued == 1) {
-        log.message('1 package is discontinued.');
+        message('1 package is discontinued.');
       } else {
-        log.message('$numDiscontinued packages are discontinued.');
+        message('$numDiscontinued packages are discontinued.');
       }
     }
   }
@@ -263,7 +299,7 @@
       } else {
         packageCountString = '$outdatedPackagesCount packages have';
       }
-      log.message('$packageCountString newer versions incompatible with '
+      message('$packageCountString newer versions incompatible with '
           'dependency constraints.\nTry `$topLevelProgram pub outdated` for more information.');
     }
   }
@@ -273,7 +309,9 @@
   /// If [alwaysShow] is true, the package is reported even if it didn't change,
   /// regardless of [_type]. If [highlightOverride] is true (or absent), writes
   /// "(override)" next to overridden packages.
-  Future<void> _reportPackage(String name,
+  ///
+  /// Returns true if the package had changed.
+  Future<bool> _reportPackage(String name, StringBuffer output,
       {bool alwaysShow = false, bool highlightOverride = true}) async {
     var newId = _newLockFile.packages[name];
     var oldId = _previousLockFile.packages[name];
@@ -380,38 +418,55 @@
 
     if (_type == SolveType.get &&
         !(alwaysShow || changed || addedOrRemoved || message != null)) {
-      return;
+      return changed || addedOrRemoved;
     }
 
-    _output.write(icon);
-    _output.write(log.bold(id.name));
-    _output.write(' ');
-    _writeId(id);
+    output.write(icon);
+    output.write(log.bold(id.name));
+    output.write(' ');
+    _writeId(id, output);
 
     // If the package was upgraded, show what it was upgraded from.
     if (changed) {
-      _output.write(' (was ');
-      _writeId(oldId!);
-      _output.write(')');
+      output.write(' (was ');
+      _writeId(oldId!, output);
+      output.write(')');
     }
 
     // Highlight overridden packages.
     if (isOverridden && highlightOverride) {
-      _output.write(" ${log.magenta('(overridden)')}");
+      output.write(" ${log.magenta('(overridden)')}");
     }
 
-    if (message != null) _output.write(' ${log.cyan(message)}');
+    if (message != null) output.write(' ${log.cyan(message)}');
 
-    _output.writeln();
+    output.writeln();
+    return changed || addedOrRemoved;
   }
 
   /// Writes a terse description of [id] (not including its name) to the output.
-  void _writeId(PackageId id) {
-    _output.write(id.version);
+  void _writeId(PackageId id, StringBuffer output) {
+    output.write(id.version);
 
     if (id.source != _cache.defaultSource) {
       var description = id.description.format();
-      _output.write(' from ${id.source} $description');
+      output.write(' from ${id.source} $description');
+    }
+  }
+
+  void warning(String message) {
+    if (_quiet) {
+      log.fine(message);
+    } else {
+      log.warning(message);
+    }
+  }
+
+  void message(String message) {
+    if (_quiet) {
+      log.fine(message);
+    } else {
+      log.message(message);
     }
   }
 }
diff --git a/test/get/enforce_lockfile_test.dart b/test/get/enforce_lockfile_test.dart
new file mode 100644
index 0000000..979db08
--- /dev/null
+++ b/test/get/enforce_lockfile_test.dart
@@ -0,0 +1,188 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:pub/src/exit_codes.dart';
+import 'package:test/test.dart';
+
+import '../descriptor.dart';
+import '../test_pub.dart';
+
+Future<void> main() async {
+  test('Recreates .dart_tool/package_config.json, redownloads archives',
+      () async {
+    final server = await servePackages();
+    server.serve('foo', '1.0.0');
+    await appDir({'foo': 'any'}).create();
+    await pubGet();
+    final packageConfig =
+        File(path(p.join(appPath, '.dart_tool', 'package_config.json')));
+    packageConfig.deleteSync();
+    await runPub(args: ['cache', 'clean', '-f']);
+    await pubGet(args: ['--enforce-lockfile']);
+    expect(packageConfig.existsSync(), isTrue);
+    await cacheDir({'foo': '1.0.0'}).validate();
+    await appPackageConfigFile([
+      packageConfigEntry(name: 'foo', version: '1.0.0'),
+    ]).validate();
+  });
+
+  test('Refuses to get if no lockfile exists', () async {
+    await appDir({}).create();
+    await pubGet(
+      args: ['--enforce-lockfile'],
+      error: '''
+Retrieving dependencies failed.
+Cannot do `--enforce-lockfile` without an existing `pubspec.lock`.
+
+Try running `dart pub get` to create `pubspec.lock`.
+''',
+    );
+  });
+
+  test('Refuses to get in ./example if hash is updated', () async {
+    final server = await servePackages();
+    server.serveContentHashes = true;
+    server.serve('foo', '1.0.0');
+    server.serve('bar', '1.0.0');
+
+    await appDir({'foo': '^1.0.0'}).create();
+    await dir(appPath, [
+      dir('example', [
+        libPubspec('example', '0.0.0', deps: {
+          'bar': '1.0.0',
+          'myapp': {'path': '../'}
+        })
+      ])
+    ]).create();
+    await pubGet(args: ['--example']);
+
+    server.serve('bar', '1.0.0', contents: [
+      file('README.md', 'Including this will change the content-hash.'),
+    ]);
+    // Deleting the version-listing cache will cause it to be refetched, and the
+    // error will happen.
+    File(p.join(globalServer.cachingPath, '.cache', 'bar-versions.json'))
+        .deleteSync();
+
+    final example = p.join('.', 'example');
+    final examplePubspec = p.join('example', 'pubspec.yaml');
+    final exampleLockfile = p.join('example', 'pubspec.lock');
+
+    await pubGet(
+      args: ['--enforce-lockfile', '--example'],
+      output: allOf(
+        contains('Got dependencies!'),
+        contains('Resolving dependencies in $example...'),
+      ),
+      error: contains(
+          'Unable to satisfy `$examplePubspec` using `$exampleLockfile` in $example. For details run `dart pub get --directory $example --enforce-lockfile'),
+      exitCode: DATA,
+    );
+  });
+
+  test('Refuses to get if lockfile is missing package', () async {
+    final server = await servePackages();
+    server.serve('foo', '1.0.0');
+    await appDir({}).create();
+    await pubGet();
+    await appDir({'foo': 'any'}).create();
+
+    await pubGet(
+      args: ['--enforce-lockfile'],
+      output: allOf(
+        contains('+ foo 1.0.0'),
+        contains('Would have changed 1 dependency.'),
+      ),
+      error: contains('Unable to satisfy `pubspec.yaml` using `pubspec.lock`.'),
+      exitCode: DATA,
+    );
+  });
+
+  test('Refuses to get if package is locked to version not matching constraint',
+      () async {
+    final server = await servePackages();
+    server.serve('foo', '1.0.0');
+    server.serve('foo', '2.0.0');
+    await appDir({'foo': '^1.0.0'}).create();
+    await pubGet();
+    await appDir({'foo': '^2.0.0'}).create();
+    await pubGet(
+      args: ['--enforce-lockfile'],
+      output: allOf([
+        contains('> foo 2.0.0 (was 1.0.0)'),
+        contains('Would have changed 1 dependency.'),
+      ]),
+      error: contains('Unable to satisfy `pubspec.yaml` using `pubspec.lock`.'),
+      exitCode: DATA,
+    );
+  });
+
+  test("Refuses to get if hash on server doesn't correspond to lockfile",
+      () async {
+    final server = await servePackages();
+    server.serveContentHashes = true;
+    server.serve('foo', '1.0.0');
+    await appDir({'foo': '^1.0.0'}).create();
+    await pubGet();
+    server.serve('foo', '1.0.0', contents: [
+      file('README.md', 'Including this will change the content-hash.'),
+    ]);
+    // Deleting the version-listing cache will cause it to be refetched, and the
+    // error will happen.
+    File(p.join(globalServer.cachingPath, '.cache', 'foo-versions.json'))
+        .deleteSync();
+    await pubGet(
+      args: ['--enforce-lockfile'],
+      output: allOf(
+        contains('~ foo 1.0.0 (was 1.0.0)'),
+        contains('Would have changed 1 dependency.'),
+      ),
+      error: allOf(
+        contains('Cached version of foo-1.0.0 has wrong hash - redownloading.'),
+        contains(
+          'The existing content-hash from pubspec.lock doesn\'t match contents for:',
+        ),
+        contains(
+          ' * foo-1.0.0 from "${server.url}"',
+        ),
+        contains(
+          'Unable to satisfy `pubspec.yaml` using `pubspec.lock`.',
+        ),
+      ),
+      exitCode: DATA,
+    );
+  });
+
+  test(
+      'Refuses to get if archive on legacy server doesn\'t have hash corresponding to lockfile',
+      () async {
+    final server = await servePackages();
+    server.serveContentHashes = false;
+    server.serve('foo', '1.0.0');
+    await appDir({'foo': '^1.0.0'}).create();
+    await pubGet();
+    await runPub(args: ['cache', 'clean', '-f']);
+    server.serve('foo', '1.0.0', contents: [
+      file('README.md', 'Including this will change the content-hash.'),
+    ]);
+
+    await pubGet(
+      args: ['--enforce-lockfile'],
+      output: allOf(
+        contains('~ foo 1.0.0 (was 1.0.0)'),
+        contains('Would have changed 1 dependency.'),
+      ),
+      error: allOf(
+        contains('''
+The existing content-hash from pubspec.lock doesn't match contents for:
+ * foo-1.0.0 from "${server.url}"'''),
+        contains('Unable to satisfy `pubspec.yaml` using `pubspec.lock`.'),
+      ),
+      exitCode: DATA,
+    );
+  });
+}
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 f48d6ba..642f8fd 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
@@ -247,6 +247,7 @@
    | For details on how manage the `PUB_CACHE`, see:
    | https://dart.dev/go/pub-cache
 MSG : + foo 1.0.0
+MSG : Changed 1 dependency!
 IO  : Writing $N characters to text file pubspec.lock.
 FINE: Contents:
    | # Generated by pub
@@ -262,7 +263,6 @@
    |   version: "1.0.0"
    | sdks:
    |   dart: ">=0.1.2 <1.0.0"
-MSG : Changed 1 dependency!
 IO  : Writing $N characters to text file .dart_tool/package_config.json.
 FINE: Contents:
    | {
diff --git a/test/testdata/goldens/help_test/pub get --help.txt b/test/testdata/goldens/help_test/pub get --help.txt
index 74648a2..45d987b 100644
--- a/test/testdata/goldens/help_test/pub get --help.txt
+++ b/test/testdata/goldens/help_test/pub get --help.txt
@@ -5,12 +5,14 @@
 Get the current package's dependencies.
 
 Usage: pub get <subcommand> [arguments...]
--h, --help               Print this usage information.
-    --[no-]offline       Use cached packages instead of accessing the network.
--n, --dry-run            Report what dependencies would change but don't change
-                         any.
-    --[no-]precompile    Build executables in immediate dependencies.
--C, --directory=<dir>    Run this in the directory<dir>.
+-h, --help                Print this usage information.
+    --[no-]offline        Use cached packages instead of accessing the network.
+-n, --dry-run             Report what dependencies would change but don't change
+                          any.
+    --enforce-lockfile    Enforce pubspec.lock. Fail resolution if pubspec.lock
+                          does not satisfy pubspec.yaml
+    --[no-]precompile     Build executables in immediate dependencies.
+-C, --directory=<dir>     Run this in the directory<dir>.
 
 Run "pub help" to see global options.
 See https://dart.dev/tools/pub/cmd/pub-get for detailed documentation.