Merge changes Ifbc1e3ed,I78c15e01

* changes:
  Migrate CommentSender to use the new diff cache
  Migrate Prolog Environment to use the new diff cache
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 2a019ca..3da69df 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -477,6 +477,11 @@
 `+refs/heads/sandbox/${username}/*+`. If you do, it's also recommended
 you grant the users the push force permission to be able to clean up
 stale branches.
+If link:config-gerrit.html#auth.userNameCaseInsensitive[auth.userNameCaseInsensitive]
+is enabled, the `${username}` is still case sensitive and will use
+the capitalization used during account creation. This is done, since
+git branches are case sensitive, so that sandbox branches containing
+`${username}` are still reachable by the users.
 
 [[category_delete]]
 === Delete Reference
diff --git a/Documentation/cmd-set-account.txt b/Documentation/cmd-set-account.txt
index 6808e017..02eaf83 100644
--- a/Documentation/cmd-set-account.txt
+++ b/Documentation/cmd-set-account.txt
@@ -14,7 +14,8 @@
   [--delete-ssh-key - | <KEY> | ALL]
   [--generate-http-password]
   [--http-password <PASSWORD>]
-  [--clear-http-password] <USER>
+  [--clear-http-password]
+  [--delete-external-id <EXTERNALID>] <USER>
 --
 
 == DESCRIPTION
@@ -106,6 +107,13 @@
 --clear-http-password::
     Clear the HTTP password for the user account.
 
+--delete-external-id::
+    Delete an external ID from a user's account if it exists.
+    If the external ID provided is 'ALL', all associated
+    external IDs are deleted from this account.
+    May be supplied more than once to remove multiple external
+    IDs from an account in a single command execution.
+
 == EXAMPLES
 Add an email and SSH key to `watcher`'s account:
 
diff --git a/Documentation/config-accounts.txt b/Documentation/config-accounts.txt
index 9b99960..7a7cef2 100644
--- a/Documentation/config-accounts.txt
+++ b/Documentation/config-accounts.txt
@@ -3,8 +3,7 @@
 
 == Overview
 
-Starting from 2.15 Gerrit accounts are fully stored in
-link:note-db.html[NoteDb].
+Gerrit accounts are stored in link:note-db.html[NoteDb].
 
 The account data consists of a sequence number (account ID), account
 properties (full name, display name, preferred email, registration
@@ -298,6 +297,13 @@
 This ensures that an external ID is used only once (e.g. an external ID can
 never be assigned to multiple accounts at a point in time).
 
+By default, the SHA-1 sum is computed preserving the case of the external ID. If
+auth.userNameCaseInsensitive` is set to `true`, the SHA-1 sum of external IDs
+in the `gerrit:` and `username:` schemes are computed from the all lowercase
+external ID. This enables case insensitive username handling. The case of the
+external ID is however preserved by using the original capitalization in the
+note content.
+
 The following commands show how to find the SHA-1 of an external ID:
 
 ----
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 32867b6..9e73f6f 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -652,6 +652,32 @@
 +
 By default this is set to false.
 
+[[auth.userNameCaseInsensitive]]auth.userNameCaseInsensitive::
++
+If set the username will be handled case insensitively but case preserving,
+i.e. a user can login with `johndoe` or `JohnDoe` for the same account
+created for `JohnDoe`. The form of the username used during account creation
+will be used wherever the username is displayed. Sandbox branches created
+for a user can also only be created for this original form.
++
+Note, that this does not work for all existing accounts, if they were
+not originally created with all lowercase, since the note keys of the
+external IDs will not match the new scheme. For more details refer to
+the link:config-accounts.html#external-ids[External ID documentation].
++
+Gerrit provides the
+link:pgm-ChangeExternalIdCaseSensitivity.html[ChangeExternalIdCaseSensitivity tool]
+to migrate existing accounts to match the new scheme.
++
+Naturally, if there were two accounts only different in capitalization,
+e.g. `johndoe` and `JohnDoe`, the account `JohnDoe` will not be able
+to authenticate anymore after setting this option. If such duplicate
+accounts exist the migration tool will fail, since the newly computed
+note name would be identical and thus conflict. These duplicates thus
+have to be deleted manually by deleting the respective external ID.
++
+Default is false.
+
 [[auth.enableRunAs]]auth.enableRunAs::
 +
 If true HTTP REST APIs will accept the `X-Gerrit-RunAs` HTTP request
@@ -4454,8 +4480,7 @@
 [[retry.retryWithTraceOnFailure]]retry.retryWithTraceOnFailure::
 +
 Whether Gerrit should automatically retry operations on failure with tracing
-enabled. The automatically generated traces can help with debugging. Please
-note that only some of the REST endpoints support automatic retry.
+enabled. The automatically generated traces can help with debugging.
 +
 By default this is set to false.
 
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 992d459..fa2b78c 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -74,9 +74,9 @@
 manifest fields:
 
 ----
-  Implementation-Title: Example plugin showing examples
-  Implementation-Version: 1.0
-  Implementation-Vendor: Example, Inc.
+Implementation-Title: Example plugin showing examples
+Implementation-Version: 1.0
+Implementation-Vendor: Example, Inc.
 ----
 
 === ApiType
@@ -88,7 +88,7 @@
 loading a plugin that needs the plugin API.
 
 ----
-  Gerrit-ApiType: plugin
+Gerrit-ApiType: plugin
 ----
 
 === Explicit Registration
@@ -103,9 +103,9 @@
 `@Listen` and `@Export("")` annotations.
 
 ----
-  Gerrit-Module:     tld.example.project.CoreModuleClassName
-  Gerrit-SshModule:  tld.example.project.SshModuleClassName
-  Gerrit-HttpModule: tld.example.project.HttpModuleClassName
+Gerrit-Module:     tld.example.project.CoreModuleClassName
+Gerrit-SshModule:  tld.example.project.SshModuleClassName
+Gerrit-HttpModule: tld.example.project.HttpModuleClassName
 ----
 
 === Batch runtime
@@ -120,7 +120,7 @@
 offline reindexing task.
 
 ----
-  Gerrit-BatchModule: tld.example.project.CoreModuleClassName
+Gerrit-BatchModule: tld.example.project.CoreModuleClassName
 ----
 
 In this runtime, only the module designated by `Gerrit-BatchModule` is
@@ -132,7 +132,7 @@
 A plugin can optionally provide its own plugin name.
 
 ----
-  Gerrit-PluginName: replication
+Gerrit-PluginName: replication
 ----
 
 This is useful for plugins that contribute plugin-owned capabilities that
@@ -218,7 +218,7 @@
 with no down time.
 
 ----
-  Gerrit-ReloadMode: restart
+Gerrit-ReloadMode: restart
 ----
 
 In either mode ('restart' or 'reload') any plugin or extension can
@@ -261,7 +261,7 @@
 credentials and possibly verify connectivity to validate them.
 
 ----
-  Gerrit-InitStep: tld.example.project.MyInitStep
+Gerrit-InitStep: tld.example.project.MyInitStep
 ----
 
 MyInitStep needs to follow the standard Gerrit InitStep syntax
@@ -278,37 +278,37 @@
 
 [source,java]
 ----
-  public class MyInitStep implements InitStep {
-    private final String pluginName;
-    private final ConsoleUI ui;
-    private final AllProjectsConfig allProjectsConfig;
+public class MyInitStep implements InitStep {
+  private final String pluginName;
+  private final ConsoleUI ui;
+  private final AllProjectsConfig allProjectsConfig;
 
-    @Inject
-    public MyInitStep(@PluginName String pluginName, ConsoleUI ui,
-        AllProjectsConfig allProjectsConfig) {
-      this.pluginName = pluginName;
-      this.ui = ui;
-      this.allProjectsConfig = allProjectsConfig;
-    }
-
-    @Override
-    public void run() throws Exception {
-    }
-
-    @Override
-    public void postRun() throws Exception {
-      ui.message("\n");
-      ui.header(pluginName + " Integration");
-      boolean enabled = ui.yesno(true, "By default enabled for all projects");
-      Config cfg = allProjectsConfig.load().getConfig();
-      if (enabled) {
-        cfg.setBoolean("plugin", pluginName, "enabled", enabled);
-      } else {
-        cfg.unset("plugin", pluginName, "enabled");
-      }
-      allProjectsConfig.save(pluginName, "Initialize " + pluginName + " Integration");
-    }
+  @Inject
+  public MyInitStep(@PluginName String pluginName, ConsoleUI ui,
+      AllProjectsConfig allProjectsConfig) {
+    this.pluginName = pluginName;
+    this.ui = ui;
+    this.allProjectsConfig = allProjectsConfig;
   }
+
+  @Override
+  public void run() throws Exception {
+  }
+
+  @Override
+  public void postRun() throws Exception {
+    ui.message("\n");
+    ui.header(pluginName + " Integration");
+    boolean enabled = ui.yesno(true, "By default enabled for all projects");
+    Config cfg = allProjectsConfig.load().getConfig();
+    if (enabled) {
+      cfg.setBoolean("plugin", pluginName, "enabled", enabled);
+    } else {
+      cfg.unset("plugin", pluginName, "enabled");
+    }
+    allProjectsConfig.save(pluginName, "Initialize " + pluginName + " Integration");
+  }
+}
 ----
 
 Bear in mind that the Plugin's InitStep class will be loaded but
@@ -706,9 +706,12 @@
 in between, leading to the final operator name.  An example
 registration looks like this:
 
-    bind(ChangeOperatorFactory.class)
-       .annotatedWith(Exports.named("sample"))
-       .to(SampleOperator.class);
+[source,java]
+----
+bind(ChangeOperatorFactory.class)
+  .annotatedWith(Exports.named("sample"))
+  .to(SampleOperator.class);
+----
 
 If this is registered in the `myplugin` plugin, then the resulting
 operator will be named `sample_myplugin`.
@@ -736,7 +739,7 @@
 ----
 
 [[search_operands]]
-=== Search Operands ===
+== Search Operands
 
 Plugins can define new search operands to extend change searching.
 Plugin methods implementing search operands (returning a
@@ -748,31 +751,33 @@
 a module's `configure()` method in the plugin.
 
 The new operand, when used in a search would appear as:
-  operatorName:operandName_pluginName
+  `operatorName:operandName_pluginName`
 
 A sample `ChangeHasOperandFactory` class implementing, and registering, a
 new `has:sample_pluginName` operand is shown below:
 
-====
-  public class SampleHasOperand implements ChangeHasOperandFactory {
-    public static class Module extends AbstractModule {
-      @Override
-      protected void configure() {
-        bind(ChangeHasOperandFactory.class)
-            .annotatedWith(Exports.named("sample")
-            .to(SampleHasOperand.class);
-      }
-    }
-
+[source, java]
+----
+public class SampleHasOperand implements ChangeHasOperandFactory {
+  public static class Module extends AbstractModule {
     @Override
-    public Predicate<ChangeData> create(ChangeQueryBuilder builder)
-        throws QueryParseException {
-      return new HasSamplePredicate();
+    protected void configure() {
+      bind(ChangeHasOperandFactory.class)
+          .annotatedWith(Exports.named("sample")
+          .to(SampleHasOperand.class);
     }
-====
+  }
+
+  @Override
+  public Predicate<ChangeData> create(ChangeQueryBuilder builder)
+      throws QueryParseException {
+    return new HasSamplePredicate();
+  }
+}
+----
 
 [[command_options]]
-=== Command Options ===
+== Command Options
 
 Plugins can provide additional options for each of the gerrit ssh and the
 REST API commands by implementing the DynamicBean interface and registering
@@ -801,6 +806,7 @@
       logger.atSevere().log("Say Hello in the Log %s", arg);
     }
   }
+}
 ----
 
 To provide additional Guice bindings for options to a command in another classloader, bind a
@@ -815,21 +821,21 @@
 
 [source, java]
 ----
-  bind(DynamicOptions.DynamicBean.class)
-      .annotatedWith(Exports.named(
-          "com.google.gerrit.plugins.otherplugin.command"))
-      .to(MyOptionsModulesClassNamesProvider.class);
+bind(DynamicOptions.DynamicBean.class)
+    .annotatedWith(Exports.named(
+        "com.google.gerrit.plugins.otherplugin.command"))
+    .to(MyOptionsModulesClassNamesProvider.class);
 
-  static class MyOptionsModulesClassNamesProvider implements DynamicOptions.ModulesClassNamesProvider {
-    {@literal @}Override
-    public String getClassName() {
-      return "com.googlesource.gerrit.plugins.myplugin.CommandOptions";
-    }
-    {@literal @}Override
-    public Iterable<String> getModulesClassNames()() {
-      return "com.googlesource.gerrit.plugins.myplugin.MyOptionsModule";
-    }
+static class MyOptionsModulesClassNamesProvider implements DynamicOptions.ModulesClassNamesProvider {
+  @Override
+  public String getClassName() {
+    return "com.googlesource.gerrit.plugins.myplugin.CommandOptions";
   }
+  @Override
+  public Iterable<String> getModulesClassNames()() {
+    return "com.googlesource.gerrit.plugins.myplugin.MyOptionsModule";
+  }
+}
 ----
 
 === Calling Command Options ===
@@ -891,7 +897,7 @@
 ----
 
 [[query_attributes]]
-=== Change Attributes ===
+== Change Attributes
 
 ==== ChangePluginDefinedInfoFactory
 
@@ -967,13 +973,9 @@
 }
 ----
 
-Example
+Example:
 ----
-
-ssh -p 29418 localhost gerrit query --myplugin-name--all "change:1" --format json
-
-Output:
-
+$ ssh -p 29418 localhost gerrit query --myplugin-name--all "change:1" --format json
 {
    "url" : "http://localhost:8080/1",
    "plugins" : [
@@ -986,10 +988,7 @@
     ...
 }
 
-curl http://localhost:8080/changes/1?myplugin-name--all
-
-Output:
-
+$ curl http://localhost:8080/changes/1?myplugin-name--all
 {
   "_number": 1,
   ...
@@ -1133,8 +1132,8 @@
 `plugin.helloworld` subsection:
 
 ----
-  [plugin "helloworld"]
-    enabled = true
+[plugin "helloworld"]
+  enabled = true
 ----
 
 Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
@@ -1337,7 +1336,7 @@
 Here and example of ref-updated JSON event payload with `instanceId`:
 
 [source,json]
----
+----
 {
   "submitter": {
     "name": "Administrator",
@@ -1354,7 +1353,7 @@
   "eventCreatedOn": 1588849085,
   "instanceId": "instance1"
 }
----
+----
 
 [[capabilities]]
 == Plugin Owned Capabilities
@@ -1681,11 +1680,11 @@
 can be accessed from any REST client, i. e.:
 
 ----
-  curl -X POST -H "Content-Type: application/json" \
+$ curl -X POST -H "Content-Type: application/json" \
     -d '{message: "François", french: true}' \
     --user joe:secret \
     http://host:port/a/changes/1/revisions/1/cookbook~say-hello
-  "Bonjour François from change 1, patch set 1!"
+"Bonjour François from change 1, patch set 1!"
 ----
 
 A special case is to bind an endpoint without a view name.  This is
@@ -1782,7 +1781,6 @@
 [source,java]
 ----
 public class MyTopMenuExtension implements TopMenu {
-
   @Override
   public List<MenuEntry> getEntries() {
     return Lists.newArrayList(
@@ -1799,7 +1797,6 @@
 [source,java]
 ----
 public class MyTopMenuExtension implements TopMenu {
-
   @Override
   public List<MenuEntry> getEntries() {
     return Lists.newArrayList(
@@ -1817,17 +1814,17 @@
 specific requests and add an menu item for this:
 
 [source,java]
----
-  new MenuItem("My Screen", "/plugins/myplugin/project/${projectName}");
----
+----
+new MenuItem("My Screen", "/plugins/myplugin/project/${projectName}");
+----
 
 This also enables plugins to provide menu items for project aware
 screens:
 
 [source,java]
----
-  new MenuItem("My Screen", "/x/my-screen/for/${projectName}");
----
+----
+new MenuItem("My Screen", "/x/my-screen/for/${projectName}");
+----
 
 If no Guice modules are declared in the manifest, the top menu extension may use
 auto-registration by providing an `@Listen` annotation:
@@ -2041,7 +2038,6 @@
 bind(AccountExternalIdCreator.class)
   .annotatedWith(UniqueAnnotations.create())
   .to(MyExternalIdCreator.class);
-}
 ----
 
 [[download-commands]]
@@ -2108,7 +2104,6 @@
 
 @Listen
 public class MyWeblinkPlugin implements PatchSetWebLink {
-
   private String name = "MyLink";
   private String placeHolderUrlProjectCommit = "http://my.tool.com/project=%s/commit=%s";
   private String imageUrl = "http://placehold.it/16x16.gif";
@@ -2189,7 +2184,6 @@
 import com.google.inject.servlet.ServletModule;
 
 public class HttpModule extends ServletModule {
-
   @Override
   protected void configureServlets() {
     serveRegex(URL_REGEX).with(LfsApiServlet.class);
@@ -2200,7 +2194,7 @@
 import org.eclipse.jgit.lfs.server.s3.S3Repository;
 
 public class S3LargeFileRepository extends S3Repository {
-...
+  ...
 }
 ----
 
@@ -2250,8 +2244,8 @@
 file. For example:
 
 ----
-  [plugin "my-plugin"]
-    metricsPrefix = my-metrics
+[plugin "my-plugin"]
+  metricsPrefix = my-metrics
 ----
 
 will cause the metrics to be recorded under `my-metrics/${metric-name}`.
@@ -2274,6 +2268,7 @@
 implementation, e.g. one that supports cluster setup with multiple
 primary Gerrit nodes handling write operations.
 
+[source,java]
 ----
 DynamicItem.bind(binder(), AccountPatchReviewStore.class)
     .to(MultiMasterAccountPatchReviewStore.class);
@@ -2521,6 +2516,26 @@
 }
 ----
 
+
+[[account-tag]]
+== Account Tag Plugins
+
+Gerrit provides an extension point that enables Plugins to supply additional
+tags on an account.
+
+[source, java]
+----
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.server.account.AccountTagProvider;
+import java.util.List;
+
+public class MyPlugin implements AccountTagProvider {
+  public List<String> getTags(Account.Id id) {
+    // Implement your logic here
+  }
+}
+----
+
 [[ssh-command-creation-interception]]
 == SSH Command Creation Interception
 
@@ -2536,6 +2551,8 @@
   @Override
   public String intercept(String in) {
     return pluginName + " mycommand";
+  }
+}
 ----
 
 [[ssh-command-execution-interception]]
@@ -2569,7 +2586,8 @@
 And then declare it in your SSH module:
 [source, java]
 ----
-  DynamicSet.bind(binder(), SshExecuteCommandInterceptor.class).to(SshExecuteCommandInterceptorImpl.class);
+DynamicSet.bind(binder(), SshExecuteCommandInterceptor.class)
+  .to(SshExecuteCommandInterceptorImpl.class);
 ----
 
 [[pre-submit-evaluator]]
diff --git a/Documentation/dev-stars.txt b/Documentation/dev-stars.txt
index a83ad44..764e326 100644
--- a/Documentation/dev-stars.txt
+++ b/Documentation/dev-stars.txt
@@ -29,9 +29,6 @@
 There are link:rest-api-accounts.html#default-star-endpoints[
 additional REST endpoints] for the link:#default-star[default star].
 
-Only the link:#default-star[default star] is shown in the WebUI and
-can be updated from there. Other stars do not show up in the WebUI.
-
 [[default-star]]
 == Default Star
 
@@ -61,36 +58,11 @@
 
 The ignore star is represented by the special star label 'ignore'.
 
-[[reviewed-star]]
-== Reviewed Star
-
-If the "reviewed/<patchset_id>"-star is set by a user, and <patchset_id>
-matches the current patch set, the change is always reported as "reviewed"
-in the ChangeInfo.
-
-This allows users to "de-highlight" changes in a dashboard until a new
-patchset has been uploaded.
-
-[[unreviewed-star]]
-== Unreviewed Star
-
-If the "unreviewed/<patchset_id>"-star is set by a user, and <patchset_id>
-matches the current patch set, the change is always reported as "unreviewed"
-in the ChangeInfo.
-
-This allows users to "highlight" changes in a dashboard.
-
 [[query-stars]]
 == Query Stars
 
 There are several query operators to find changes with stars:
 
-* link:user-search.html#star[star:<LABEL>]:
-  Matches any change that was starred by the current user with the
-  label `<LABEL>`.
-* link:user-search.html#has-stars[has:stars]:
-  Matches any change that was starred by the current user with any
-  label.
 * link:user-search.html#is-starred[is:starred] /
   link:user-search.html#has-star[has:star]:
   Matches any change that was starred by the current user with the
diff --git a/Documentation/index.txt b/Documentation/index.txt
index dc94b14..782a6a9 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -77,6 +77,7 @@
 . link:pgm-index.html[Server Side Administrative Tools]
 . link:repository-maintenance.html[Repository Maintenance]
 . link:user-request-tracing.html[Request Tracing]
+. link:user-request-cancellation-and-deadlines.html[Request Cancellation and Deadlines]
 . link:note-db.html[NoteDb]
 . link:config-accounts.html[Accounts on NoteDb]
 . link:config-groups.html[Groups on NoteDb]
diff --git a/Documentation/js_licenses.txt b/Documentation/js_licenses.txt
index 3953737..ab79c8f 100644
--- a/Documentation/js_licenses.txt
+++ b/Documentation/js_licenses.txt
@@ -252,6 +252,7 @@
 
 * @types/resemblejs
 * @types/resize-observer-browser
+* @types/trusted-types
 
 [[DefinitelyTyped_license]]
 ----
@@ -280,6 +281,48 @@
 ----
 
 
+[[Lit]]
+Lit
+
+* @lit/reactive-element
+* lit
+* lit-element
+* lit-html
+
+[[Lit_license]]
+----
+BSD 3-Clause License
+
+Copyright (c) 2017 Google LLC. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----
+
+
 [[Polymer-2014]]
 Polymer-2014
 
@@ -1141,84 +1184,6 @@
 ----
 
 
-[[lit-element]]
-lit-element
-
-* lit-element
-
-[[lit-element_license]]
-----
-BSD 3-Clause License
-
-Copyright (c) 2017, The Polymer Authors. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice, this
-  list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice,
-  this list of conditions and the following disclaimer in the documentation
-  and/or other materials provided with the distribution.
-
-* Neither the name of the copyright holder nor the names of its
-  contributors may be used to endorse or promote products derived from
-  this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-----
-
-
-[[lit-html]]
-lit-html
-
-* lit-html
-
-[[lit-html_license]]
-----
-BSD 3-Clause License
-
-Copyright (c) 2017, The Polymer Authors. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice, this
-  list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice,
-  this list of conditions and the following disclaimer in the documentation
-  and/or other materials provided with the distribution.
-
-* Neither the name of the copyright holder nor the names of its
-  contributors may be used to endorse or promote products derived from
-  this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-----
-
-
 [[page]]
 page
 
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index 476751c..86ff904 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -3211,6 +3211,7 @@
 
 * @types/resemblejs
 * @types/resize-observer-browser
+* @types/trusted-types
 
 [[DefinitelyTyped_license]]
 ----
@@ -3239,6 +3240,48 @@
 ----
 
 
+[[Lit]]
+Lit
+
+* @lit/reactive-element
+* lit
+* lit-element
+* lit-html
+
+[[Lit_license]]
+----
+BSD 3-Clause License
+
+Copyright (c) 2017 Google LLC. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----
+
+
 [[Polymer-2014]]
 Polymer-2014
 
@@ -4100,84 +4143,6 @@
 ----
 
 
-[[lit-element]]
-lit-element
-
-* lit-element
-
-[[lit-element_license]]
-----
-BSD 3-Clause License
-
-Copyright (c) 2017, The Polymer Authors. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice, this
-  list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice,
-  this list of conditions and the following disclaimer in the documentation
-  and/or other materials provided with the distribution.
-
-* Neither the name of the copyright holder nor the names of its
-  contributors may be used to endorse or promote products derived from
-  this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-----
-
-
-[[lit-html]]
-lit-html
-
-* lit-html
-
-[[lit-html_license]]
-----
-BSD 3-Clause License
-
-Copyright (c) 2017, The Polymer Authors. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice, this
-  list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice,
-  this list of conditions and the following disclaimer in the documentation
-  and/or other materials provided with the distribution.
-
-* Neither the name of the copyright holder nor the names of its
-  contributors may be used to endorse or promote products derived from
-  this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-----
-
-
 [[page]]
 page
 
diff --git a/Documentation/metrics.txt b/Documentation/metrics.txt
index 03be71e..8f36cfb 100644
--- a/Documentation/metrics.txt
+++ b/Documentation/metrics.txt
@@ -22,11 +22,14 @@
 * `action/auto_retry_count`: Number of automatic retries with tracing
 * `action/failures_on_auto_retry_count`: Number of failures on auto retry
 
+[[cancellations]]
 === Cancellations
 
 * `cancellation/advisory_deadline_count`: Exceeded advisory deadlines by request
 * `cancellation/cancelled_requests_count`: Number of request cancellations by
   request
+* `cancellation/receive_timeout_count`: Number of requests that are cancelled
+  because link:config.html#receive.timeout[receive.timout] is exceeded
 
 === Pushes
 
@@ -74,6 +77,8 @@
 * `caches/disk_cached`: Disk entries used by persistent cache.
 * `caches/disk_hit_ratio`: Disk hit ratio for persistent cache.
 * `caches/refresh_count`: The number of refreshes per cache with an indicator if a reload was necessary.
+* `caches/diff/timeouts`: The number of git file diff computations that resulted in timeouts.
+* `caches/diff/legacy/timeouts`: The number of git file diff computations (using the legacy cache) that resulted in timeouts.
 
 Cache disk metrics are expensive to compute on larger installations and are not
 computed by default. They can be enabled via the
diff --git a/Documentation/pgm-ChangeExternalIdCaseSensitivity.txt b/Documentation/pgm-ChangeExternalIdCaseSensitivity.txt
new file mode 100644
index 0000000..1fb4b97
--- /dev/null
+++ b/Documentation/pgm-ChangeExternalIdCaseSensitivity.txt
@@ -0,0 +1,71 @@
+= ChangeExternalIdCaseSensitivity
+
+== NAME
+ChangeExternalIdCaseSensitivity - Convert `username` and `gerrit`
+external IDs to be handled case insensitively
+
+== SYNOPSIS
+[verse]
+--
+_java_ -jar gerrit.war _ChangeExternalIdCaseSensitivity_
+  -d <SITE_PATH>
+  [--batch]
+  [--dryrun]
+--
+
+== DESCRIPTION
+Convert `username` and `gerrit` external IDs to be handled case
+insensitively or case sensitively. This is done by recomputing
+the name of the note from the sha1 sum of the all lowercase
+external ID key or of the key with its original capitalization
+respectively.
+
+The tool uses the `auth.userNameCaseInsensitive` option to determine,
+whether the migration should be performed to case insensitive or case sensitive
+usernames, i.e. if the option is set to `false`, migration will be performed to
+make external IDs case insensitive and if set to `true` to case sensitive.
+
+== OPTIONS
+
+-d::
+--site-path::
+	Path of the Gerrit site
+
+--batch::
+    No user interaction is required. The tool won't ask for confirmation before migrating.
+
+--dryrun::
+    Whether to perform the conversion without persisting it.
+
+== CONTEXT
+This command can only be run offline with direct access to the server's
+site.
+
+== EXAMPLES
+To convert the external IDs to be case insensitive:
+
+----
+    $ git config -f $SITE/etc/gerrit.config --get auth.userNameCaseInsensitive
+    > false
+    $ java -jar gerrit.war ChangeExternalIdCaseSensitivity -d site_path
+----
+
+To convert the external IDs to be case sensitive again:
+
+----
+    $ git config -f $SITE/etc/gerrit.config --get auth.userNameCaseInsensitive
+    > true
+    $ java -jar gerrit.war ChangeExternalIdCaseSensitivity -d site_path
+----
+
+
+== SEE ALSO
+
+* Configuration parameter link:config-gerrit.html#auth.userNameCaseInsensitive[auth.userNameCaseInsensitive]
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/pgm-index.txt b/Documentation/pgm-index.txt
index dde0231..8f4cbda 100644
--- a/Documentation/pgm-index.txt
+++ b/Documentation/pgm-index.txt
@@ -38,6 +38,9 @@
 link:pgm-LocalUsernamesToLowerCase.html[LocalUsernamesToLowerCase]::
 	Convert the local username of every account to lower case.
 
+link:pgm-ChangeExternalIdCaseSensitivity.html[ChangeExternalIdCaseSensitivity]::
+    Convert external IDs to be case insensitive.
+
 link:pgm-MigrateAccountPatchReviewDb.html[MigrateAccountPatchReviewDb]::
 	Migrates AccountPatchReviewDb from one database backend to another.
 
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 0caebfc..a613c7e 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -1779,7 +1779,16 @@
 
 Only external ids belonging to the caller may be deleted. Users that have
 link:access-control.html#capability_modifyAccount[Modify Account] can delete
-external ids that belong to other accounts.
+external ids that belong to other accounts. External ids in the 'username:'
+scheme can only be deleted by users that have
+link:access-control.html#capability_administrateServer[Administrate Server]
+or both
+link:access-control.html#capability_maintainServer[Maintain Server] and
+link:access-control.html#capability__modifyAccount[Modify Account]
+since the user may not be able to login anymore, after the removal of the
+external id with scheme 'username:'. Users cannot delete their own external id
+with scheme 'username:' in order to prevent they can lock themselves out
+since they may not be able to login anymore.
 
 .Request
 ----
@@ -2001,7 +2010,7 @@
 --
 
 Star a change with the default label. Changes starred with the default
-label are returned for the search query `is:starred` or `starredby:USER`
+label are returned for the search query `is:starred` or `has:star`
 and automatically notify the user whenever updates are made to the
 change.
 
@@ -2033,131 +2042,6 @@
   HTTP/1.1 204 No Content
 ----
 
-[[star-endpoints]]
-== Star Endpoints
-
-[[get-starred-changes]]
-=== Get Starred Changes
---
-'GET /accounts/link:#account-id[\{account-id\}]/stars.changes'
---
-
-Gets the changes that were starred with any label by the identified
-user account. This URL endpoint is functionally identical to the
-changes query `GET /changes/?q=has:stars`. The result is a list of
-link:rest-api-changes.html#change-info[ChangeInfo] entities.
-
-.Request
-----
-  GET /a/accounts/self/stars.changes
-----
-
-.Response
-----
-  HTTP/1.1 200 OK
-  Content-Disposition: attachment
-  Content-Type: application/json; charset=UTF-8
-
-  )]}'
-  [
-    {
-      "id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940",
-      "project": "myProject",
-      "branch": "master",
-      "change_id": "I8473b95934b5732ac55d26311a706c9c2bde9940",
-      "subject": "Implementing Feature X",
-      "status": "NEW",
-      "created": "2013-02-01 09:59:32.126000000",
-      "updated": "2013-02-21 11:16:36.775000000",
-      "stars": [
-        "ignore",
-        "risky"
-      ],
-      "mergeable": true,
-      "submittable": false,
-      "insertions": 145,
-      "deletions": 12,
-      "_number": 3965,
-      "owner": {
-        "name": "John Doe"
-      }
-    }
-  ]
-----
-
-[[get-stars]]
-=== Get Star Labels From Change
---
-'GET /accounts/link:#account-id[\{account-id\}]/stars.changes/link:rest-api-changes.html#change-id[\{change-id\}]'
---
-
-Get star labels from a change.
-
-.Request
-----
-  GET /a/accounts/self/stars.changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940 HTTP/1.0
-----
-
-As response the star labels that the user applied on the change are
-returned. The labels are lexicographically sorted.
-
-.Response
-----
-  HTTP/1.1 200 OK
-  Content-Disposition: attachment
-  Content-Type: application/json; charset=UTF-8
-
-  )]}'
-  [
-    "blue",
-    "green",
-    "red"
-  ]
-----
-
-[[set-stars]]
-=== Update Star Labels On Change
---
-'POST /accounts/link:#account-id[\{account-id\}]/stars.changes/link:rest-api-changes.html#change-id[\{change-id\}]'
---
-
-Update star labels on a change. The star labels to be added/removed
-must be specified in the request body as link:#stars-input[StarsInput]
-entity. Starred changes are returned for the search query `has:stars`.
-
-.Request
-----
-  POST /a/accounts/self/stars.changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940 HTTP/1.0
-  Content-Type: application/json; charset=UTF-8
-
-  {
-    "add": [
-      "blue",
-      "red"
-    ],
-    "remove": [
-      "yellow"
-    ]
-  }
-----
-
-As response the star labels that the user applied on the change are
-returned. The labels are lexicographically sorted.
-
-.Response
-----
-  HTTP/1.1 200 OK
-  Content-Disposition: attachment
-  Content-Type: application/json; charset=UTF-8
-
-  )]}'
-  [
-    "blue",
-    "green",
-    "red"
-  ]
-----
-
 [[ids]]
 == IDs
 
@@ -2917,18 +2801,6 @@
 |`valid`         ||Whether the SSH key is valid.
 |=============================
 
-[[stars-input]]
-=== StarsInput
-The `StarsInput` entity contains star labels that should be added to
-or removed from a change.
-
-[options="header",cols="1,^1,5"]
-|========================
-|Field Name ||Description
-|`add`      |optional|List of labels to add to the change.
-|`remove`   |optional|List of labels to remove from the change.
-|========================
-
 [[username-input]]
 === UsernameInput
 The `UsernameInput` entity contains information for setting the
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index b376a74..2b6796b 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -2561,42 +2561,6 @@
   PUT /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/unignore HTTP/1.0
 ----
 
-[[mark-as-reviewed]]
-=== Mark as Reviewed
---
-'PUT /changes/link:#change-id[\{change-id\}]/reviewed'
---
-
-Marks a change as reviewed.
-
-This allows users to "de-highlight" changes in their dashboard until a new
-patch set is uploaded.
-
-This differs from the link:#ignore[ignore] endpoint, which will mute
-emails and hide the change from dashboard completely until it is
-link:#unignore[unignored] again.
-
-
-.Request
-----
-  PUT /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/reviewed HTTP/1.0
-----
-
-[[mark-as-unreviewed]]
-=== Mark as Unreviewed
---
-'PUT /changes/link:#change-id[\{change-id\}]/unreviewed'
---
-
-Marks a change as unreviewed.
-
-This allows users to "highlight" changes in their dashboard
-
-.Request
-----
-  PUT /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/unreviewed HTTP/1.0
-----
-
 [[get-hashtags]]
 === Get Hashtags
 --
@@ -6512,7 +6476,9 @@
 |`unresolved_comment_count`  |optional|
 Number of unresolved inline comment threads across all patch sets. Not set if
 the current change index doesn't have the data.
-|`_number`            ||The legacy numeric ID of the change.
+|`_number`            ||
+The numeric ID of the change. (The underscore is just a relict of a prior
+attempt to deprecate the numeric ID.)
 |`owner`              ||
 The owner of the change as an link:rest-api-accounts.html#account-info[
 AccountInfo] entity.
@@ -8198,6 +8164,10 @@
 |`status`||
 Status describing the result of evaluating the submit requirement. The status
 is one of (`SATISFIED`, `UNSATISFIED`, `OVERRIDDEN`, `NOT_APPLICABLE`).
+|`is_legacy`||
+If true, this submit requirement result was created from a legacy
+link:#submit-record[SubmitRecord]. Otherwise, it was created by evaluating a
+submit requirement.
 |`applicability_expression_result`|optional|
 A link:#submit-requirement-expression-info[SubmitRequirementExpressionInfo]
 containing the result of evaluating the applicability expression. Not set if the
diff --git a/Documentation/user-request-cancellation-and-deadlines.txt b/Documentation/user-request-cancellation-and-deadlines.txt
new file mode 100644
index 0000000..b368d6a
--- /dev/null
+++ b/Documentation/user-request-cancellation-and-deadlines.txt
@@ -0,0 +1,184 @@
+:linkattrs:
+= Request Cancellation and Deadlines
+
+[[motivation]]
+== Motivation
+
+Protect the Gerrit service by aborting requests that were cancelled or for which
+the deadline has exceeded. If these requests are not aborted, it can happen that
+too many of these requests are accumulated so that the server runs out of
+resources (e.g. threads).
+
+[[request-cancellation]]
+== Request Cancellation
+
+If a user cancels a request by disconnecting, ideally Gerrit should detect this
+and abort the request execution to avoid doing unnecessary work. If nobody is
+waiting for the response, Gerrit shouldn't spend resources to compute it.
+
+Detecting cancelled requests is not easily possible with all protocols that a
+client may use. At the moment Gerrit only detects request cancellations for git
+pushes, but not for other request types (in particular cancelled requests are
+not detected for REST calls over HTTP, SSH commands and git clone/fetch).
+
+[[server-side-deadlines]]
+== Server-side deadlines
+
+To limit the maximal execution time for requests, administrators can [configure
+server-side deadlines](config-gerrit.html#deadline.id). If a server-side
+deadline is exceeded by a matching request, the request is automatically
+aborted. In this case the client gets a proper error message informing the user
+about the exceeded deadline.
+
+Clients may override server-side deadlines by setting a
+[deadline](#client-provided-deadline) on the request. This means, if a request
+fails due to an exceeded server-side deadline, the client may repeat the request
+with a higher deadline or no deadline (deadline = 0) to get unblocked.
+
+Server-side deadlines are meant to protect the Gerrit service against resource
+exhaustion due to performence issues with a particular request. E.g. imagine a
+situation where requests for a certain REST endpoint are very slow. If more and
+more of such requests get stuck and are not being aborted, the Gerrit service
+may run out of threads, causing an outage for the entire Gerrit service.
+Server-side deadlines may prevent this because the slow requests get aborted
+after the deadline is exceeded, and hence the server resources are freed up.
+
+In some cases server-side deadlines may also lead to a better user experience,
+as it's better to tell the user that there is a performance issue, that prevents
+the execution of the request, than letting them wait indefinitely.
+
+Finally server-side deadlines can help ops engineers to detect performance
+issues more reliably and more quicky. For this alerts may be setup that are
+based on the [cancellation metrics](metrics.html#cancellations).
+
+[[receive-timeout]]
+=== Receive Timeout
+
+For git pushes it is possible to configure a [hard
+timeout](config-gerrit.html#receive.timeout). In contrast to server-side
+deadlines, this timeout is not overridable by [client-provided
+deadlines](#client-provided-deadlines).
+
+[[client-provided-deadlines]]
+== Client-provided deadlines
+
+Clients can set a deadline on requests to limit the maximal execution time that
+they are willing to wait for a response. If the request doesn't finish within
+this deadline the request is aborted and the client receives an error, with a
+message telling them that the deadline has been exceeded.
+
+How to set a deadline on a request depends on the request type:
+
+[options="header",cols="1,6"]
+|=======================
+|Request Type   |How to set a deadline?
+|REST over HTTP |Set the [X-Gerrit-Deadline header](rest-api.html#deadline).
+|SSH command    |Set the [deadline option](cmd-index.html#deadline).
+|git push       |Set the [deadline push option](user-upload.html#deadline).
+|git clone/fetch|Not supported.
+|=======================
+
+[[override-server-side-deadline]]
+=== Override server-side deadline
+
+By setting a deadline on a request it is possible to override any [server-side
+deadline](#server-side-deadline), e.g. in order to increase it. Setting the
+deadline to `0` disables any server-side deadline. This allows clients to get
+unblocked if a request has previously failed due to an exceeded deadline.
+
+[NOTE]
+It is stronly discouraged for clients to permanently override [server-side
+deadlines](#server-side-deadlines] with a higher deadline or to permanently
+disable them by always setting the deadline to `0`. If this becomes necessary
+the caller should get in touch with the Gerrit administrators to increase the
+server-side deadlines or resolve the performance issue in another way.
+
+[NOTE]
+It's not possible for clients to override the [receive
+timeout](#receive-timeout) that is enforced on git push.
+
+[[faqs]]
+== FAQs
+
+[[deadline-exceeded-what-to-do]]
+=== My request failed due to an execeeded deadline, what can I do?
+
+To get unblocked, you may repeat the request with deadlines disabled. To do this
+set the deadline to `0` on the request as explained
+[above](#override-server-side-deadline).
+
+If doing this becomes required frequently, please get in touch with the Gerrit
+administrators in order to investigate the performance issue and increase the
+server-side deadline if necessary.
+
+[NOTE]
+Setting deadlines for requests that are done from the Gerrit web UI is not
+possible. If exceeded deadlines occur frequently here, please get in touch with
+the Gerrit administrators in order to investigate the performance issue.
+
+[[push-fails-due-to-exceeded-deadline-but-cannot-be-overridden]]
+=== My git push fails due to an exceeded deadline and I cannot override the deadline, what can I do?
+
+As explained [above](#receive-timeout) a configured receive timeout cannot be
+overridden by clients. If pushes fail due to this timeout, get in touch with the
+Gerrit administrators in order to investigate the performance issue and increase
+the receive timeout if necessary.
+
+[[when-are-requests-aborted]]
+=== How quickly does a request get aborted when it is cancelled or a deadline is exceeded?
+
+In order to know if a request should be aborted, Gerrit needs to explicitly
+check whether the request is cancelled or whether a deadline is exceeded.
+Gerrit does this check at the beginning and end of all performance critical
+steps and sub-steps. This means, the request is only aborted the next time such
+a step starts or finishes, which can also be never (e.g. if the request is stuck
+inside of a step).
+
+[NOTE]
+Technically the check whether a request should be aborted is done whenever the
+execution time of an operation or sub-step is captured, either by a timer
+metric or a `TraceTimer` ('TraceTimer` is the class that logs the execution time
+when the request is being [traced](user-request-tracing.html)).
+
+[[how-are-requests-aborted]]
+=== How does Gerrit abort requests?
+
+The exact response that is returned to the client depends on the request type
+and the cancellation reason:
+
+[options="header",cols="1,3,3"]
+|=======================
+|Request Type   |Cancellation Reason|Response
+|REST over HTTP |Client Disconnected|The response is '499 Client Closed Request'.
+|               |Server-side deadline exceeded|The response is '408 Server Deadline Exceeded'.
+|               |Client-provided deadline exceeded|The response is '408 Client Provided Deadline Exceeded'.
+|SSH command    |Client Disconnected|The error message is 'Client Closed Request'.
+|               |Server-side deadline exceeded|The error message is 'Server Deadline Exceeded'.
+|               |Client-provided deadline exceeded|The error message is 'Client Provided Deadline Exceeded'.
+|git push       |Client Disconnected|The error status is 'Client Closed Request'.
+|               |Server-side deadline exceeded|The error status is 'Server Deadline Exceeded'.
+|               |Client-provided deadline exceeded|The error status is 'Client Provided Deadline Exceeded'.
+|git clone/fetch|Not supported.
+|=======================
+
+This means clients always get a proper error message telling the user why the
+request has been aborted.
+
+Errors due to aborted requests are usually not counted as internal server errors,
+but the [cancellation metrics](metrics.html#cancellations) may be used to setup
+alerting for performance issues.
+
+[NOTE]
+During a request, cancellations can occur at any time. This means for non-atomic
+operations, it can happen that the operation is cancelled after some steps have
+already been successfully performed and before all steps have been executed,
+potentially leaving behind an inconsistent state (same as when a request fails
+due to an error). However for important steps, such a NoteDb updates that span
+multiple repositories, Gerrit ensures that they are not torn by cancellations.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/user-request-tracing.txt b/Documentation/user-request-tracing.txt
index e684b85..303242df 100644
--- a/Documentation/user-request-tracing.txt
+++ b/Documentation/user-request-tracing.txt
@@ -148,3 +148,10 @@
 * permission checks (e.g. which rule is responsible for a deny)
 * timer metrics
 * all other logs
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 7f80c5b..2ebe6bd 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -337,8 +337,9 @@
 +
 Slash ('/') is used path separator.
 +
-More examples:
-* `-file:^path/.*` - changes that do not modify files from `path/`,
+*More examples:*
+
+* `-file:^path/.*` - changes that do not modify files from `path/`.
 * `file:{^~(path/.*)}` - changes that modify files not from `path/` (but may
 contain files from `path/`).
 
@@ -405,6 +406,8 @@
 +
 'star:star' is the same as 'has:star' and 'is:starred'.
 
+Only "ignore" and "star" are supported labels.
+
 [[has]]
 has:draft::
 +
@@ -416,11 +419,6 @@
 Same as 'is:starred' and 'star:star', true if the change has been
 starred by the current user with the default label.
 
-[[has-stars]]
-has:stars::
-+
-True if the change has been starred by the current user with any label.
-
 has:edit::
 +
 True if the change has inline edit created by the current user.
@@ -529,6 +527,16 @@
 +
 True if the change is a merge commit.
 
+[[cherrypick]]
+is:cherrypick::
++
+True if the change is a cherrypick of an another change.
+
+This is limited to changes which are cherrypicked using REST API
+or WebUI only. It is not able to identify changes which are
+cherry-picked locally using the git cherry-pick command and then
+pushed to Gerrit.
+
 [[status]]
 status:open, status:pending, status:new::
 +
@@ -608,6 +616,22 @@
 only applies to the top-level status; individual label statuses can be
 searched link:#labels[by label].
 
+[[rule]]
+rule:'SUBMIT_RULE_NAME'::
++
+Changes where 'SUBMIT_RULE_NAME' returns a submit record with status in {OK,
+FORCED}. This means that the submit rule has passed and is not blocking the
+change submission. 'SUBMIT_RULE_NAME' should be in the form of
+'$plugin_name~$rule_name'. For gerrit core rules, 'SUBMIT_RULE_NAME' should
+be in the form of '$rule_name' (example: `DefaultSubmitRule`), or
+'gerrit~$rule_name' (example: `gerrit~DefaultSubmitRule`).
+
+rule:'SUBMIT_RULE_NAME'='STATUS'::
++
+Changes where 'SUBMIT_RULE_NAME' returns a submit record with status equals to
+'STATUS'. The status can be any of the statuses that are documented for the
+`status` field of link:rest-api-changes.html#submit-record[SubmitRecord].
+
 [[unresolved]]
 unresolved:'RELATION''NUMBER'::
 +
@@ -740,6 +764,15 @@
 The special "owner" parameter corresponds to the change owner.  Matches
 all changes that have a +2 vote from the change owner.
 
+`label:Code-Review=+2,user=non_uploader`::
+`label:Code-Review=ok,user=non_uploader`::
+`label:Code-Review=+2,non_uploader`::
+`label:Code-Review=ok,non_uploader`::
++
+The special "non_uploader" parameter corresponds to any user who's not the
+uploader of the latest patchset. Matches all changes that have a +2 vote from
+a non upoader.
+
 `label:Code-Review=+1,group=ldap/linux.workflow`::
 +
 Matches changes with a +1 code review where the reviewer is in the
@@ -787,24 +820,6 @@
 Magical internal flag to prove the current user has access to read
 the change.  This flag is always added to any query.
 
-starredby:'USER'::
-+
-Matches changes that have been starred by 'USER' with the default label.
-The special case `starredby:self` applies to the caller.
-
-watchedby:'USER'::
-+
-Matches changes that 'USER' has configured watch filters for.
-The special case `watchedby:self` applies to the caller.
-
-draftby:'USER'::
-+
-Matches changes that 'USER' has left unpublished draft comments on.
-Since the drafts are unpublished, it is not possible to see the
-draft text, or even how many drafts there are. The special case
-of `draftby:self` will find changes where the caller has created
-a draft comment.
-
 [[limit]]
 limit:'CNT'::
 +
diff --git a/java/com/google/gerrit/acceptance/AccountCreator.java b/java/com/google/gerrit/acceptance/AccountCreator.java
index aa13339..c67991d 100644
--- a/java/com/google/gerrit/acceptance/AccountCreator.java
+++ b/java/com/google/gerrit/acceptance/AccountCreator.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.account.ServiceUserClassifier;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
 import com.google.gerrit.server.group.db.GroupDelta;
 import com.google.gerrit.server.group.db.GroupsUpdate;
 import com.google.gerrit.server.notedb.Sequences;
@@ -52,18 +53,21 @@
   private final Provider<AccountsUpdate> accountsUpdateProvider;
   private final GroupCache groupCache;
   private final Provider<GroupsUpdate> groupsUpdateProvider;
+  private final ExternalIdFactory externalIdFactory;
 
   @Inject
   AccountCreator(
       Sequences sequences,
       @ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider,
       GroupCache groupCache,
-      @ServerInitiated Provider<GroupsUpdate> groupsUpdateProvider) {
+      @ServerInitiated Provider<GroupsUpdate> groupsUpdateProvider,
+      ExternalIdFactory externalIdFactory) {
     accounts = new HashMap<>();
     this.sequences = sequences;
     this.accountsUpdateProvider = accountsUpdateProvider;
     this.groupCache = groupCache;
     this.groupsUpdateProvider = groupsUpdateProvider;
+    this.externalIdFactory = externalIdFactory;
   }
 
   public synchronized TestAccount create(
@@ -84,11 +88,11 @@
     String httpPass = null;
     if (username != null) {
       httpPass = "http-pass";
-      extIds.add(ExternalId.createUsername(username, id, httpPass));
+      extIds.add(externalIdFactory.createUsername(username, id, httpPass));
     }
 
     if (email != null) {
-      extIds.add(ExternalId.createEmail(id, email));
+      extIds.add(externalIdFactory.createEmail(id, email));
     }
 
     accountsUpdateProvider
diff --git a/java/com/google/gerrit/acceptance/FakeSubmitRule.java b/java/com/google/gerrit/acceptance/FakeSubmitRule.java
new file mode 100644
index 0000000..d53456f
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/FakeSubmitRule.java
@@ -0,0 +1,43 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance;
+
+import com.google.gerrit.entities.SubmitRecord;
+import com.google.gerrit.entities.SubmitRecord.Status;
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.rules.SubmitRule;
+import com.google.inject.AbstractModule;
+import com.google.inject.Singleton;
+import java.util.Optional;
+
+/** Fake submit rule that returns OK if the change contains one or more hashtags. */
+@Singleton
+public class FakeSubmitRule implements SubmitRule {
+  public static class Module extends AbstractModule {
+    @Override
+    public void configure() {
+      bind(SubmitRule.class).annotatedWith(Exports.named("Fake")).to(FakeSubmitRule.class);
+    }
+  }
+
+  @Override
+  public Optional<SubmitRecord> evaluate(ChangeData cd) {
+    SubmitRecord record = new SubmitRecord();
+    record.status = cd.hashtags().isEmpty() ? Status.NOT_READY : Status.OK;
+    record.ruleName = FakeSubmitRule.class.getSimpleName();
+    return Optional.of(record);
+  }
+}
diff --git a/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java b/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
index e4b0eea..373246a 100644
--- a/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
+++ b/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
@@ -21,6 +21,8 @@
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.server.config.AllProjectsConfigProvider;
+import com.google.gerrit.server.config.FileBasedAllProjectsConfigProvider;
 import com.google.gerrit.server.config.FileBasedGlobalPluginConfigProvider;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.GlobalPluginConfigProvider;
@@ -57,6 +59,7 @@
   protected void configure() {
     bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(cfg);
     bind(GlobalPluginConfigProvider.class).to(FileBasedGlobalPluginConfigProvider.class);
+    bind(AllProjectsConfigProvider.class).to(FileBasedAllProjectsConfigProvider.class);
     bind(Path.class).annotatedWith(SitePath.class).toInstance(sitePath);
 
     if (repoManager != null) {
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
index 3763f9a..c6457a4 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
@@ -27,6 +27,7 @@
 import com.google.gerrit.server.account.AccountsUpdate;
 import com.google.gerrit.server.account.AccountsUpdate.ConfigureDeltaFromState;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
 import com.google.gerrit.server.notedb.Sequences;
 import com.google.inject.Inject;
 import java.io.IOException;
@@ -44,13 +45,18 @@
   private final Accounts accounts;
   private final AccountsUpdate accountsUpdate;
   private final Sequences seq;
+  private final ExternalIdFactory externalIdFactory;
 
   @Inject
   public AccountOperationsImpl(
-      Accounts accounts, @ServerInitiated AccountsUpdate accountsUpdate, Sequences seq) {
+      Accounts accounts,
+      @ServerInitiated AccountsUpdate accountsUpdate,
+      Sequences seq,
+      ExternalIdFactory externalIdFactory) {
     this.accounts = accounts;
     this.accountsUpdate = accountsUpdate;
     this.seq = seq;
+    this.externalIdFactory = externalIdFactory;
   }
 
   @Override
@@ -72,7 +78,7 @@
     return createdAccount.account().id();
   }
 
-  private static void initAccountDelta(
+  private void initAccountDelta(
       AccountDelta.Builder builder, TestAccountCreation accountCreation, Account.Id accountId) {
     accountCreation.fullname().ifPresent(builder::setFullName);
     accountCreation.preferredEmail().ifPresent(e -> setPreferredEmail(builder, accountId, e));
@@ -84,19 +90,19 @@
         .secondaryEmails()
         .forEach(
             secondaryEmail ->
-                builder.addExternalId(ExternalId.createEmail(accountId, secondaryEmail)));
+                builder.addExternalId(externalIdFactory.createEmail(accountId, secondaryEmail)));
   }
 
-  private static void setPreferredEmail(
+  private void setPreferredEmail(
       AccountDelta.Builder builder, Account.Id accountId, String preferredEmail) {
     builder
         .setPreferredEmail(preferredEmail)
-        .addExternalId(ExternalId.createEmail(accountId, preferredEmail));
+        .addExternalId(externalIdFactory.createEmail(accountId, preferredEmail));
   }
 
-  private static void setUsername(
+  private void setUsername(
       AccountDelta.Builder builder, Account.Id accountId, String username, String httpPassword) {
-    builder.addExternalId(ExternalId.createUsername(username, accountId, httpPassword));
+    builder.addExternalId(externalIdFactory.createUsername(username, accountId, httpPassword));
   }
 
   private class PerAccountOperationsImpl implements PerAccountOperations {
@@ -202,14 +208,14 @@
               .collect(toImmutableSet()));
       builder.addExternalIds(
           newSecondaryEmails.stream()
-              .map(secondaryEmail -> ExternalId.createEmail(accountId, secondaryEmail))
+              .map(secondaryEmail -> externalIdFactory.createEmail(accountId, secondaryEmail))
               .collect(toImmutableSet()));
       if (accountUpdate.preferredEmail().isPresent()) {
         builder.addExternalId(
-            ExternalId.createEmail(accountId, accountUpdate.preferredEmail().get()));
+            externalIdFactory.createEmail(accountId, accountUpdate.preferredEmail().get()));
       } else if (accountState.account().preferredEmail() != null) {
         builder.addExternalId(
-            ExternalId.createEmail(accountId, accountState.account().preferredEmail()));
+            externalIdFactory.createEmail(accountId, accountState.account().preferredEmail()));
       }
     }
 
diff --git a/java/com/google/gerrit/auth/ldap/LdapRealm.java b/java/com/google/gerrit/auth/ldap/LdapRealm.java
index 9305914..9a9f309 100644
--- a/java/com/google/gerrit/auth/ldap/LdapRealm.java
+++ b/java/com/google/gerrit/auth/ldap/LdapRealm.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.server.account.EmailExpander;
 import com.google.gerrit.server.account.GroupBackends;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.auth.AuthenticationUnavailableException;
 import com.google.gerrit.server.auth.NoSuchUserException;
@@ -346,10 +347,12 @@
 
   static class UserLoader extends CacheLoader<String, Optional<Account.Id>> {
     private final ExternalIds externalIds;
+    private final ExternalIdKeyFactory externalIdKeyFactory;
 
     @Inject
-    UserLoader(ExternalIds externalIds) {
+    UserLoader(ExternalIds externalIds, ExternalIdKeyFactory externalIdKeyFactory) {
       this.externalIds = externalIds;
+      this.externalIdKeyFactory = externalIdKeyFactory;
     }
 
     @Override
@@ -358,7 +361,7 @@
           TraceContext.newTimer(
               "Loading account for username", Metadata.builder().username(username).build())) {
         return externalIds
-            .get(ExternalId.Key.create(SCHEME_GERRIT, username))
+            .get(externalIdKeyFactory.create(SCHEME_GERRIT, username))
             .map(ExternalId::accountId);
       }
     }
diff --git a/java/com/google/gerrit/entities/Patch.java b/java/com/google/gerrit/entities/Patch.java
index 856765b..2d28046 100644
--- a/java/com/google/gerrit/entities/Patch.java
+++ b/java/com/google/gerrit/entities/Patch.java
@@ -78,25 +78,27 @@
     public abstract String fileName();
   }
 
-  /** Type of modification made to the file path. */
+  /**
+   * Type of modification made to the file path. Ordering of values matters (used by diff cache).
+   */
   public enum ChangeType implements CodedEnum {
     /** Path is being created/introduced by this patch. */
     ADDED('A'),
 
-    /** Path already exists, and has updated content. */
-    MODIFIED('M'),
-
-    /** Path existed, but is being removed by this patch. */
-    DELETED('D'),
-
     /** Path existed at the source but was moved. */
     RENAMED('R'),
 
+    /** Path already exists, and has updated content. */
+    MODIFIED('M'),
+
     /** Path was copied from the source. */
     COPIED('C'),
 
     /** Sufficient amount of content changed to claim the file was rewritten. */
-    REWRITE('W');
+    REWRITE('W'),
+
+    /** Path existed, but is being removed by this patch. */
+    DELETED('D');
 
     private final char code;
 
diff --git a/java/com/google/gerrit/entities/SubmitRecord.java b/java/com/google/gerrit/entities/SubmitRecord.java
index 860997f..95ad9f8 100644
--- a/java/com/google/gerrit/entities/SubmitRecord.java
+++ b/java/com/google/gerrit/entities/SubmitRecord.java
@@ -68,6 +68,8 @@
     }
   }
 
+  // Name of the rule that created this submit record, formatted as '$pluginName~$ruleName'
+  public String ruleName;
   public Status status;
   public List<Label> labels;
   public List<LegacySubmitRequirement> requirements;
@@ -158,6 +160,7 @@
    */
   public SubmitRecord deepCopy() {
     SubmitRecord copy = new SubmitRecord();
+    copy.ruleName = ruleName;
     copy.status = status;
     copy.errorMessage = errorMessage;
     if (labels != null) {
diff --git a/java/com/google/gerrit/entities/SubmitRequirementResult.java b/java/com/google/gerrit/entities/SubmitRequirementResult.java
index e1d5f39..4f2f2f6 100644
--- a/java/com/google/gerrit/entities/SubmitRequirementResult.java
+++ b/java/com/google/gerrit/entities/SubmitRequirementResult.java
@@ -41,6 +41,12 @@
   /** SHA-1 of the patchset commit ID for which the submit requirement was evaluated. */
   public abstract ObjectId patchSetCommitId();
 
+  /**
+   * Whether this result was created from a legacy {@link SubmitRecord}, or by evaluating a {@link
+   * SubmitRequirement}.
+   */
+  public abstract boolean legacy();
+
   @Memoized
   public Status status() {
     if (assertError(submittabilityExpressionResult())
@@ -109,6 +115,8 @@
 
     public abstract Builder patchSetCommitId(ObjectId value);
 
+    public abstract Builder legacy(boolean value);
+
     public abstract SubmitRequirementResult build();
   }
 
diff --git a/java/com/google/gerrit/extensions/api/accounts/AccountApi.java b/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
index 6df9889..9c9c282 100644
--- a/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
+++ b/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.extensions.api.accounts;
 
-import com.google.gerrit.extensions.api.changes.StarsInput;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.client.EditPreferencesInfo;
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
@@ -23,7 +22,6 @@
 import com.google.gerrit.extensions.common.AccountExternalIdInfo;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.AgreementInfo;
-import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.EmailInfo;
 import com.google.gerrit.extensions.common.GpgKeyInfo;
 import com.google.gerrit.extensions.common.GroupInfo;
@@ -32,7 +30,6 @@
 import com.google.gerrit.extensions.restapi.RestApiException;
 import java.util.List;
 import java.util.Map;
-import java.util.SortedSet;
 
 public interface AccountApi {
   AccountInfo get() throws RestApiException;
@@ -67,12 +64,6 @@
 
   void unstarChange(String changeId) throws RestApiException;
 
-  void setStars(String changeId, StarsInput input) throws RestApiException;
-
-  SortedSet<String> getStars(String changeId) throws RestApiException;
-
-  List<ChangeInfo> getStarredChanges() throws RestApiException;
-
   List<GroupInfo> getGroups() throws RestApiException;
 
   List<EmailInfo> getEmails() throws RestApiException;
@@ -221,21 +212,6 @@
     }
 
     @Override
-    public void setStars(String changeId, StarsInput input) throws RestApiException {
-      throw new NotImplementedException();
-    }
-
-    @Override
-    public SortedSet<String> getStars(String changeId) throws RestApiException {
-      throw new NotImplementedException();
-    }
-
-    @Override
-    public List<ChangeInfo> getStarredChanges() throws RestApiException {
-      throw new NotImplementedException();
-    }
-
-    @Override
     public List<GroupInfo> getGroups() throws RestApiException {
       throw new NotImplementedException();
     }
diff --git a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index 04f5bd2..ab98aa6 100644
--- a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -140,14 +140,6 @@
   boolean ignored() throws RestApiException;
 
   /**
-   * Mark this change as reviewed/unreviewed.
-   *
-   * @param reviewed flag to decide if this change should be marked as reviewed ({@code true}) or
-   *     unreviewed ({@code false})
-   */
-  void markAsReviewed(boolean reviewed) throws RestApiException;
-
-  /**
    * Create a new change that reverts this change.
    *
    * @see Changes#id(int)
@@ -814,11 +806,6 @@
     }
 
     @Override
-    public void markAsReviewed(boolean reviewed) throws RestApiException {
-      throw new NotImplementedException();
-    }
-
-    @Override
     public PureRevertInfo pureRevert() throws RestApiException {
       throw new NotImplementedException();
     }
diff --git a/java/com/google/gerrit/extensions/api/changes/StarsInput.java b/java/com/google/gerrit/extensions/api/changes/StarsInput.java
deleted file mode 100644
index 1207d27..0000000
--- a/java/com/google/gerrit/extensions/api/changes/StarsInput.java
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (C) 2016 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.extensions.api.changes;
-
-import java.util.Set;
-
-public class StarsInput {
-  public Set<String> add;
-  public Set<String> remove;
-
-  public StarsInput() {}
-
-  public StarsInput(Set<String> add) {
-    this.add = add;
-  }
-
-  public StarsInput(Set<String> add, Set<String> remove) {
-    this.add = add;
-    this.remove = remove;
-  }
-}
diff --git a/java/com/google/gerrit/extensions/common/AccountInfo.java b/java/com/google/gerrit/extensions/common/AccountInfo.java
index 60ba18d..4701e86 100644
--- a/java/com/google/gerrit/extensions/common/AccountInfo.java
+++ b/java/com/google/gerrit/extensions/common/AccountInfo.java
@@ -27,10 +27,13 @@
  * are defined in {@link AccountDetailInfo}.
  */
 public class AccountInfo {
-  /** Tags are additional properties of an account. */
-  public enum Tag {
+  /**
+   * Tags are additional properties of an account. These are just tags known to Gerrit core. Plugins
+   * may define their own.
+   */
+  public static final class Tags {
     /** Tag indicating that this account is a service user. */
-    SERVICE_USER
+    public static final String SERVICE_USER = "SERVICE_USER";
   }
 
   /** The numeric ID of the account. */
@@ -74,7 +77,7 @@
   public Boolean inactive;
 
   /** Tags, such as whether this account is a service user. */
-  public List<Tag> tags;
+  public List<String> tags;
 
   public AccountInfo(Integer id) {
     this._accountId = id;
diff --git a/java/com/google/gerrit/extensions/common/SubmitRequirementResultInfo.java b/java/com/google/gerrit/extensions/common/SubmitRequirementResultInfo.java
index 685e81a..d17da0a 100644
--- a/java/com/google/gerrit/extensions/common/SubmitRequirementResultInfo.java
+++ b/java/com/google/gerrit/extensions/common/SubmitRequirementResultInfo.java
@@ -47,6 +47,9 @@
   /** Overall result (status) of evaluating this submit requirement. */
   public Status status;
 
+  /** Whether this result was created from a legacy submit record. */
+  public boolean isLegacy;
+
   /** Result of evaluating the applicability expression. */
   public SubmitRequirementExpressionInfo applicabilityExpressionResult;
 
diff --git a/java/com/google/gerrit/extensions/restapi/PreconditionFailedException.java b/java/com/google/gerrit/extensions/restapi/PreconditionFailedException.java
index b8c9d38..86b821b 100644
--- a/java/com/google/gerrit/extensions/restapi/PreconditionFailedException.java
+++ b/java/com/google/gerrit/extensions/restapi/PreconditionFailedException.java
@@ -22,4 +22,12 @@
   public PreconditionFailedException(String msg) {
     super(msg);
   }
+
+  /**
+   * @param msg message to return to the client describing the error.
+   * @param cause cause of this exception.
+   */
+  public PreconditionFailedException(String msg, Throwable cause) {
+    super(msg, cause);
+  }
 }
diff --git a/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java b/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
index 9477cb6..71dff97 100644
--- a/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
+++ b/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.UrlFormatter;
 import com.google.gerrit.server.query.account.InternalAccountQuery;
@@ -62,17 +63,20 @@
     private final IdentifiedUser.GenericFactory userFactory;
     private final int maxTrustDepth;
     private final ImmutableMap<Long, Fingerprint> trusted;
+    private final ExternalIdKeyFactory externalIdKeyFactory;
 
     @Inject
     Factory(
         @GerritServerConfig Config cfg,
         Provider<InternalAccountQuery> accountQueryProvider,
         IdentifiedUser.GenericFactory userFactory,
-        DynamicItem<UrlFormatter> urlFormatter) {
+        DynamicItem<UrlFormatter> urlFormatter,
+        ExternalIdKeyFactory externalIdKeyFactory) {
       this.accountQueryProvider = accountQueryProvider;
       this.urlFormatter = urlFormatter;
       this.userFactory = userFactory;
       this.maxTrustDepth = cfg.getInt("receive", null, "maxTrustDepth", 0);
+      this.externalIdKeyFactory = externalIdKeyFactory;
 
       String[] strs = cfg.getStringList("receive", null, "trustedKey");
       if (strs.length != 0) {
@@ -103,6 +107,7 @@
   private final Provider<InternalAccountQuery> accountQueryProvider;
   private final DynamicItem<UrlFormatter> urlFormatter;
   private final IdentifiedUser.GenericFactory userFactory;
+  private final ExternalIdKeyFactory externalIdKeyFactory;
 
   private IdentifiedUser expectedUser;
 
@@ -113,6 +118,7 @@
     if (factory.trusted != null) {
       enableTrust(factory.maxTrustDepth, factory.trusted);
     }
+    this.externalIdKeyFactory = factory.externalIdKeyFactory;
   }
 
   /**
@@ -247,7 +253,8 @@
     return sb.toString();
   }
 
-  static ExternalId.Key toExtIdKey(PGPPublicKey key) {
-    return ExternalId.Key.create(SCHEME_GPGKEY, BaseEncoding.base16().encode(key.getFingerprint()));
+  ExternalId.Key toExtIdKey(PGPPublicKey key) {
+    return externalIdKeyFactory.create(
+        SCHEME_GPGKEY, BaseEncoding.base16().encode(key.getFingerprint()));
   }
 }
diff --git a/java/com/google/gerrit/gpg/server/DeleteGpgKey.java b/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
index 1be37f5..e0c921d 100644
--- a/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
+++ b/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.server.UserInitiated;
 import com.google.gerrit.server.account.AccountsUpdate;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.mail.send.DeleteKeySender;
 import com.google.inject.Inject;
@@ -53,6 +54,7 @@
   private final Provider<AccountsUpdate> accountsUpdateProvider;
   private final ExternalIds externalIds;
   private final DeleteKeySender.Factory deleteKeySenderFactory;
+  private final ExternalIdKeyFactory externalIdKeyFactory;
 
   @Inject
   DeleteGpgKey(
@@ -60,12 +62,14 @@
       Provider<PublicKeyStore> storeProvider,
       @UserInitiated Provider<AccountsUpdate> accountsUpdateProvider,
       ExternalIds externalIds,
-      DeleteKeySender.Factory deleteKeySenderFactory) {
+      DeleteKeySender.Factory deleteKeySenderFactory,
+      ExternalIdKeyFactory externalIdKeyFactory) {
     this.serverIdent = serverIdent;
     this.storeProvider = storeProvider;
     this.accountsUpdateProvider = accountsUpdateProvider;
     this.externalIds = externalIds;
     this.deleteKeySenderFactory = deleteKeySenderFactory;
+    this.externalIdKeyFactory = externalIdKeyFactory;
   }
 
   @Override
@@ -73,7 +77,8 @@
       throws RestApiException, PGPException, IOException, ConfigInvalidException {
     PGPPublicKey key = rsrc.getKeyRing().getPublicKey();
     String fingerprint = BaseEncoding.base16().encode(key.getFingerprint());
-    Optional<ExternalId> extId = externalIds.get(ExternalId.Key.create(SCHEME_GPGKEY, fingerprint));
+    Optional<ExternalId> extId =
+        externalIds.get(externalIdKeyFactory.create(SCHEME_GPGKEY, fingerprint));
     if (!extId.isPresent()) {
       throw new ResourceNotFoundException(fingerprint);
     }
diff --git a/java/com/google/gerrit/gpg/server/PostGpgKeys.java b/java/com/google/gerrit/gpg/server/PostGpgKeys.java
index 1b5e06a..d46b344 100644
--- a/java/com/google/gerrit/gpg/server/PostGpgKeys.java
+++ b/java/com/google/gerrit/gpg/server/PostGpgKeys.java
@@ -53,6 +53,8 @@
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.AccountsUpdate;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.mail.send.AddKeySender;
 import com.google.gerrit.server.mail.send.DeleteKeySender;
@@ -93,6 +95,8 @@
   private final ExternalIds externalIds;
   private final Provider<AccountsUpdate> accountsUpdateProvider;
   private final RetryHelper retryHelper;
+  private final ExternalIdFactory externalIdFactory;
+  private final ExternalIdKeyFactory externalIdKeyFactory;
 
   @Inject
   PostGpgKeys(
@@ -105,7 +109,9 @@
       Provider<InternalAccountQuery> accountQueryProvider,
       ExternalIds externalIds,
       @UserInitiated Provider<AccountsUpdate> accountsUpdateProvider,
-      RetryHelper retryHelper) {
+      RetryHelper retryHelper,
+      ExternalIdFactory externalIdFactory,
+      ExternalIdKeyFactory externalIdKeyFactory) {
     this.serverIdent = serverIdent;
     this.self = self;
     this.storeProvider = storeProvider;
@@ -116,6 +122,8 @@
     this.externalIds = externalIds;
     this.accountsUpdateProvider = accountsUpdateProvider;
     this.retryHelper = retryHelper;
+    this.externalIdFactory = externalIdFactory;
+    this.externalIdKeyFactory = externalIdKeyFactory;
   }
 
   @Override
@@ -140,7 +148,7 @@
             throw new ResourceConflictException("GPG key already associated with another account");
           }
         } else {
-          newExtIds.add(ExternalId.create(extIdKey, rsrc.getUser().getAccountId()));
+          newExtIds.add(externalIdFactory.create(extIdKey, rsrc.getUser().getAccountId()));
         }
       }
 
@@ -287,7 +295,7 @@
   }
 
   private ExternalId.Key toExtIdKey(byte[] fp) {
-    return ExternalId.Key.create(SCHEME_GPGKEY, BaseEncoding.base16().encode(fp));
+    return externalIdKeyFactory.create(SCHEME_GPGKEY, BaseEncoding.base16().encode(fp));
   }
 
   private Account getAccountByExternalId(ExternalId.Key extIdKey) {
diff --git a/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java b/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
index de989ac..a421139 100644
--- a/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
+++ b/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
@@ -76,17 +76,23 @@
   private final AccountCache accountCache;
   private final AccountManager accountManager;
   private final AuthConfig authConfig;
+  private final AuthRequest.Factory authRequestFactory;
+  private final PasswordVerifier passwordVerifier;
 
   @Inject
   ProjectBasicAuthFilter(
       DynamicItem<WebSession> session,
       AccountCache accountCache,
       AccountManager accountManager,
-      AuthConfig authConfig) {
+      AuthConfig authConfig,
+      AuthRequest.Factory authRequestFactory,
+      PasswordVerifier passwordVerifier) {
     this.session = session;
     this.accountCache = accountCache;
     this.accountManager = accountManager;
     this.authConfig = authConfig;
+    this.authRequestFactory = authRequestFactory;
+    this.passwordVerifier = passwordVerifier;
   }
 
   @Override
@@ -155,7 +161,7 @@
     GitBasicAuthPolicy gitBasicAuthPolicy = authConfig.getGitBasicAuthPolicy();
     if (gitBasicAuthPolicy == GitBasicAuthPolicy.HTTP
         || gitBasicAuthPolicy == GitBasicAuthPolicy.HTTP_LDAP) {
-      if (PasswordVerifier.checkPassword(who.externalIds(), username, password)) {
+      if (passwordVerifier.checkPassword(who.externalIds(), username, password)) {
         logger.atFine().log(
             "HTTP:%s %s username/password authentication succeeded",
             req.getMethod(), req.getRequestURI());
@@ -167,7 +173,7 @@
       return failAuthentication(rsp, username, req);
     }
 
-    AuthRequest whoAuth = AuthRequest.forUser(username);
+    AuthRequest whoAuth = authRequestFactory.createForUser(username);
     whoAuth.setPassword(password);
 
     try {
@@ -177,7 +183,7 @@
           "HTTP:%s %s Realm authentication succeeded", req.getMethod(), req.getRequestURI());
       return true;
     } catch (NoSuchUserException e) {
-      if (PasswordVerifier.checkPassword(who.externalIds(), username, password)) {
+      if (passwordVerifier.checkPassword(who.externalIds(), username, password)) {
         return succeedAuthentication(who, null);
       }
       logger.atWarning().withCause(e).log(authenticationFailedMsg(username, req));
diff --git a/java/com/google/gerrit/httpd/ProjectOAuthFilter.java b/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
index dab36c4..fa53053 100644
--- a/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
+++ b/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
@@ -76,6 +76,7 @@
   private final AccountManager accountManager;
   private final String gitOAuthProvider;
   private final boolean userNameToLowerCase;
+  private final AuthRequest.Factory authRequestFactory;
 
   private String defaultAuthPlugin;
   private String defaultAuthProvider;
@@ -86,13 +87,15 @@
       DynamicMap<OAuthLoginProvider> pluginsProvider,
       AccountCache accountCache,
       AccountManager accountManager,
-      @GerritServerConfig Config gerritConfig) {
+      @GerritServerConfig Config gerritConfig,
+      AuthRequest.Factory authRequestFactory) {
     this.session = session;
     this.loginProviders = pluginsProvider;
     this.accountCache = accountCache;
     this.accountManager = accountManager;
     this.gitOAuthProvider = gerritConfig.getString("auth", null, "gitOAuthProvider");
     this.userNameToLowerCase = gerritConfig.getBoolean("auth", null, "userNameToLowerCase", false);
+    this.authRequestFactory = authRequestFactory;
   }
 
   @Override
@@ -162,7 +165,7 @@
     }
 
     Account account = who.get().account();
-    AuthRequest authRequest = AuthRequest.forExternalUser(authInfo.username);
+    AuthRequest authRequest = authRequestFactory.createForExternalUser(authInfo.username);
     authRequest.setEmailAddress(account.preferredEmail());
     authRequest.setDisplayName(account.fullName());
     authRequest.setPassword(authInfo.tokenOrSecret);
diff --git a/java/com/google/gerrit/httpd/WebModule.java b/java/com/google/gerrit/httpd/WebModule.java
index e416075..0645aac 100644
--- a/java/com/google/gerrit/httpd/WebModule.java
+++ b/java/com/google/gerrit/httpd/WebModule.java
@@ -75,6 +75,10 @@
     listener().toInstance(registerInParentInjectors());
 
     install(UniversalWebLoginFilter.module());
+
+    // Static injection was unfortunately the best solution in this place. However, it is to be
+    // avoided if possible.
+    requestStaticInjection(WebSessionManager.Val.class);
   }
 
   private void installAuthModule() {
diff --git a/java/com/google/gerrit/httpd/WebSessionManager.java b/java/com/google/gerrit/httpd/WebSessionManager.java
index c0900ec..87bf3a6 100644
--- a/java/com/google/gerrit/httpd/WebSessionManager.java
+++ b/java/com/google/gerrit/httpd/WebSessionManager.java
@@ -32,6 +32,7 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.inject.Inject;
@@ -186,6 +187,8 @@
   public static final class Val implements Serializable {
     static final long serialVersionUID = 2L;
 
+    @Inject private static transient ExternalIdKeyFactory externalIdKeyFactory;
+
     private transient Account.Id accountId;
     private transient long refreshCookieAt;
     private transient boolean persistentCookie;
@@ -295,7 +298,7 @@
             persistentCookie = readVarInt32(in) != 0;
             continue;
           case 4:
-            externalId = ExternalId.Key.parse(readString(in));
+            externalId = externalIdKeyFactory.parse(readString(in));
             continue;
           case 5:
             sessionId = readString(in);
diff --git a/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
index 97bb44b..2f760f0 100644
--- a/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
+++ b/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -31,7 +31,7 @@
 import com.google.gerrit.server.account.Accounts;
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.AuthResult;
-import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.query.account.InternalAccountQuery;
 import com.google.gerrit.util.http.CacheHeaders;
 import com.google.inject.Inject;
@@ -62,6 +62,8 @@
   private final AccountManager accountManager;
   private final SiteHeaderFooter headers;
   private final Provider<InternalAccountQuery> queryProvider;
+  private final ExternalIdKeyFactory externalIdKeyFactory;
+  private final AuthRequest.Factory authRequestFactory;
 
   @Inject
   BecomeAnyAccountLoginServlet(
@@ -70,13 +72,17 @@
       AccountCache ac,
       AccountManager am,
       SiteHeaderFooter shf,
-      Provider<InternalAccountQuery> qp) {
+      Provider<InternalAccountQuery> qp,
+      ExternalIdKeyFactory eikf,
+      AuthRequest.Factory arf) {
     webSession = ws;
     accounts = a;
     accountCache = ac;
     accountManager = am;
     headers = shf;
     queryProvider = qp;
+    externalIdKeyFactory = eikf;
+    authRequestFactory = arf;
   }
 
   @Override
@@ -220,7 +226,8 @@
   private AuthResult create() throws IOException {
     try {
       return accountManager.authenticate(
-          new AuthRequest(ExternalId.Key.create(SCHEME_UUID, UUID.randomUUID().toString())));
+          authRequestFactory.create(
+              externalIdKeyFactory.create(SCHEME_UUID, UUID.randomUUID().toString())));
     } catch (AccountException e) {
       getServletContext().log("cannot create new account", e);
       return null;
diff --git a/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java b/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
index e20c9b9..acb3282 100644
--- a/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
+++ b/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.httpd.RemoteUserUtil;
 import com.google.gerrit.httpd.WebSession;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.util.http.CacheHeaders;
 import com.google.gerrit.util.http.RequestUtil;
@@ -64,10 +65,16 @@
   private final String emailHeader;
   private final String externalIdHeader;
   private final boolean userNameToLowerCase;
+  private final ExternalIdKeyFactory externalIdKeyFactory;
 
   @Inject
-  HttpAuthFilter(DynamicItem<WebSession> webSession, AuthConfig authConfig) throws IOException {
+  HttpAuthFilter(
+      DynamicItem<WebSession> webSession,
+      AuthConfig authConfig,
+      ExternalIdKeyFactory externalIdKeyFactory)
+      throws IOException {
     this.sessionProvider = webSession;
+    this.externalIdKeyFactory = externalIdKeyFactory;
 
     final String pageName = "LoginRedirect.html";
     final String doc = HtmlDomUtil.readFile(getClass(), pageName);
@@ -124,9 +131,9 @@
     return false;
   }
 
-  private static boolean correctUser(String user, WebSession session) {
+  private boolean correctUser(String user, WebSession session) {
     Optional<ExternalId.Key> id = session.getUser().getLastLoginExternalIdKey();
-    return id.map(i -> i.equals(ExternalId.Key.create(SCHEME_GERRIT, user))).orElse(false);
+    return id.map(i -> i.equals(externalIdKeyFactory.create(SCHEME_GERRIT, user))).orElse(false);
   }
 
   String getRemoteUser(HttpServletRequest req) {
diff --git a/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java b/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
index 1b7e477..53f33b5 100644
--- a/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
+++ b/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
@@ -28,7 +28,7 @@
 import com.google.gerrit.server.account.AccountManager;
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.AuthResult;
-import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.util.http.CacheHeaders;
 import com.google.inject.Inject;
@@ -61,6 +61,8 @@
   private final AccountManager accountManager;
   private final HttpAuthFilter authFilter;
   private final AuthConfig authConfig;
+  private final ExternalIdKeyFactory externalIdKeyFactory;
+  private final AuthRequest.Factory authRequestFactory;
 
   @Inject
   HttpLoginServlet(
@@ -68,12 +70,16 @@
       final CanonicalWebUrl urlProvider,
       final AccountManager accountManager,
       final HttpAuthFilter authFilter,
-      final AuthConfig authConfig) {
+      final AuthConfig authConfig,
+      final ExternalIdKeyFactory externalIdKeyFactory,
+      final AuthRequest.Factory authRequestFactory) {
     this.webSession = webSession;
     this.urlProvider = urlProvider;
     this.accountManager = accountManager;
     this.authFilter = authFilter;
     this.authConfig = authConfig;
+    this.externalIdKeyFactory = externalIdKeyFactory;
+    this.authRequestFactory = authRequestFactory;
   }
 
   @Override
@@ -109,7 +115,7 @@
       return;
     }
 
-    final AuthRequest areq = AuthRequest.forUser(user);
+    final AuthRequest areq = authRequestFactory.createForUser(user);
     areq.setDisplayName(authFilter.getRemoteDisplayname(req));
     areq.setEmailAddress(authFilter.getRemoteEmail(req));
     final AuthResult arsp;
@@ -154,7 +160,7 @@
       throws AccountException, IOException, ConfigInvalidException {
     accountManager.updateLink(
         arsp.getAccountId(),
-        new AuthRequest(ExternalId.Key.create(SCHEME_EXTERNAL, remoteAuthToken)));
+        authRequestFactory.create(externalIdKeyFactory.create(SCHEME_EXTERNAL, remoteAuthToken)));
   }
 
   private void replace(Document doc, String name, String value) {
diff --git a/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java b/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java
index 40807c0..820c7a2 100644
--- a/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java
+++ b/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java
@@ -42,12 +42,16 @@
 
   private final DynamicItem<WebSession> webSession;
   private final AccountManager accountManager;
+  private final AuthRequest.Factory authRequestFactory;
 
   @Inject
   HttpsClientSslCertAuthFilter(
-      final DynamicItem<WebSession> webSession, AccountManager accountManager) {
+      final DynamicItem<WebSession> webSession,
+      AccountManager accountManager,
+      final AuthRequest.Factory authRequestFactory) {
     this.webSession = webSession;
     this.accountManager = accountManager;
+    this.authRequestFactory = authRequestFactory;
   }
 
   @Override
@@ -70,7 +74,7 @@
     } else {
       throw new ServletException("Couldn't extract username from your certificate");
     }
-    final AuthRequest areq = AuthRequest.forUser(userName);
+    final AuthRequest areq = authRequestFactory.createForUser(userName);
     final AuthResult arsp;
     try {
       arsp = accountManager.authenticate(areq);
diff --git a/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java b/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
index a09866e..6caa760 100644
--- a/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
+++ b/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
@@ -56,17 +56,20 @@
   private final DynamicItem<WebSession> webSession;
   private final CanonicalWebUrl urlProvider;
   private final SiteHeaderFooter headers;
+  private final AuthRequest.Factory authRequestFactory;
 
   @Inject
   LdapLoginServlet(
       AccountManager accountManager,
       DynamicItem<WebSession> webSession,
       CanonicalWebUrl urlProvider,
-      SiteHeaderFooter headers) {
+      SiteHeaderFooter headers,
+      AuthRequest.Factory authRequestFactory) {
     this.accountManager = accountManager;
     this.webSession = webSession;
     this.urlProvider = urlProvider;
     this.headers = headers;
+    this.authRequestFactory = authRequestFactory;
   }
 
   private void sendForm(
@@ -115,7 +118,7 @@
       return;
     }
 
-    AuthRequest areq = AuthRequest.forUser(username);
+    AuthRequest areq = authRequestFactory.createForUser(username);
     areq.setPassword(password);
 
     AuthResult ares;
diff --git a/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java b/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
index 70ed79b..a3f8fbda 100644
--- a/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
+++ b/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
@@ -35,7 +35,7 @@
 import com.google.gerrit.server.account.AccountManager;
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.AuthResult;
-import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.servlet.SessionScoped;
@@ -65,6 +65,8 @@
   private Account.Id accountId;
   private String redirectToken;
   private boolean linkMode;
+  private final ExternalIdKeyFactory externalIdKeyFactory;
+  private final AuthRequest.Factory authRequestFactory;
 
   @Inject
   OAuthSession(
@@ -72,13 +74,17 @@
       Provider<IdentifiedUser> identifiedUser,
       AccountManager accountManager,
       CanonicalWebUrl urlProvider,
-      OAuthTokenCache tokenCache) {
+      OAuthTokenCache tokenCache,
+      ExternalIdKeyFactory externalIdKeyFactory,
+      AuthRequest.Factory authRequestFactory) {
     this.state = generateRandomState();
     this.identifiedUser = identifiedUser;
     this.webSession = webSession;
     this.accountManager = accountManager;
     this.urlProvider = urlProvider;
     this.tokenCache = tokenCache;
+    this.externalIdKeyFactory = externalIdKeyFactory;
+    this.authRequestFactory = authRequestFactory;
   }
 
   boolean isLoggedIn() {
@@ -126,7 +132,7 @@
 
   private void authenticateAndRedirect(
       HttpServletRequest req, HttpServletResponse rsp, OAuthToken token) throws IOException {
-    AuthRequest areq = new AuthRequest(ExternalId.Key.parse(user.getExternalId()));
+    AuthRequest areq = authRequestFactory.create(externalIdKeyFactory.parse(user.getExternalId()));
     AuthResult arsp;
     try {
       String claimedIdentifier = user.getClaimedIdentity();
diff --git a/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java b/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java
index b987c68..df0062c 100644
--- a/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java
+++ b/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java
@@ -32,8 +32,9 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountException;
 import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.AuthResult;
-import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.servlet.SessionScoped;
@@ -63,18 +64,24 @@
   private OAuthUserInfo user;
   private String redirectToken;
   private boolean linkMode;
+  private final ExternalIdKeyFactory externalIdKeyFactory;
+  private final AuthRequest.Factory authRequestFactory;
 
   @Inject
   OAuthSessionOverOpenID(
       DynamicItem<WebSession> webSession,
       Provider<IdentifiedUser> identifiedUser,
       AccountManager accountManager,
-      CanonicalWebUrl urlProvider) {
+      CanonicalWebUrl urlProvider,
+      ExternalIdKeyFactory externalIdKeyFactory,
+      AuthRequest.Factory authRequestFactory) {
     this.state = generateRandomState();
     this.webSession = webSession;
     this.identifiedUser = identifiedUser;
     this.accountManager = accountManager;
     this.urlProvider = urlProvider;
+    this.externalIdKeyFactory = externalIdKeyFactory;
+    this.authRequestFactory = authRequestFactory;
   }
 
   boolean isLoggedIn() {
@@ -117,8 +124,7 @@
   private void authenticateAndRedirect(HttpServletRequest req, HttpServletResponse rsp)
       throws IOException {
     com.google.gerrit.server.account.AuthRequest areq =
-        new com.google.gerrit.server.account.AuthRequest(
-            ExternalId.Key.parse(user.getExternalId()));
+        authRequestFactory.create(externalIdKeyFactory.parse(user.getExternalId()));
     AuthResult arsp;
     try {
       String claimedIdentifier = user.getClaimedIdentity();
diff --git a/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java b/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
index b685011..cf3562f 100644
--- a/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
+++ b/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
@@ -28,7 +28,7 @@
 import com.google.gerrit.server.UrlEncoded;
 import com.google.gerrit.server.account.AccountException;
 import com.google.gerrit.server.account.AccountManager;
-import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.auth.openid.OpenIdProviderPattern;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.config.ConfigUtil;
@@ -92,6 +92,8 @@
   private final ConsumerManager manager;
   private final List<OpenIdProviderPattern> allowedOpenIDs;
   private final List<String> openIdDomains;
+  private final ExternalIdKeyFactory externalIdKeyFactory;
+  private final com.google.gerrit.server.account.AuthRequest.Factory authRequestFactory;
 
   /** Maximum age, in seconds, before forcing re-authentication of account. */
   private final int papeMaxAuthAge;
@@ -104,7 +106,9 @@
       @GerritServerConfig Config config,
       AuthConfig ac,
       AccountManager am,
-      ProxyProperties proxyProperties) {
+      ProxyProperties proxyProperties,
+      ExternalIdKeyFactory externalIdKeyFactory,
+      com.google.gerrit.server.account.AuthRequest.Factory authRequestFactory) {
 
     if (proxyProperties.getProxyUrl() != null) {
       final org.openid4java.util.ProxyProperties proxy = new org.openid4java.util.ProxyProperties();
@@ -132,6 +136,8 @@
                 "maxOpenIdSessionAge",
                 -1,
                 TimeUnit.SECONDS);
+    this.externalIdKeyFactory = externalIdKeyFactory;
+    this.authRequestFactory = authRequestFactory;
   }
 
   @SuppressWarnings("unchecked")
@@ -310,7 +316,7 @@
     }
 
     final com.google.gerrit.server.account.AuthRequest areq =
-        new com.google.gerrit.server.account.AuthRequest(ExternalId.Key.parse(openidIdentifier));
+        authRequestFactory.create(externalIdKeyFactory.parse(openidIdentifier));
 
     if (sregRsp != null) {
       areq.setDisplayName(sregRsp.getAttributeValue("fullname"));
@@ -388,8 +394,7 @@
         // was missing due to a bug in Gerrit. Link the claimed.
         //
         final com.google.gerrit.server.account.AuthRequest linkReq =
-            new com.google.gerrit.server.account.AuthRequest(
-                ExternalId.Key.parse(claimedIdentifier));
+            authRequestFactory.create(externalIdKeyFactory.parse(claimedIdentifier));
         linkReq.setDisplayName(areq.getDisplayName());
         linkReq.setEmailAddress(areq.getEmailAddress());
         accountManager.link(actualId.get(), linkReq);
@@ -425,8 +430,7 @@
           webSession.get().login(arsp, remember);
           if (arsp.isNew() && claimedIdentifier != null) {
             final com.google.gerrit.server.account.AuthRequest linkReq =
-                new com.google.gerrit.server.account.AuthRequest(
-                    ExternalId.Key.parse(claimedIdentifier));
+                authRequestFactory.create(externalIdKeyFactory.parse(claimedIdentifier));
             linkReq.setDisplayName(areq.getDisplayName());
             linkReq.setEmailAddress(areq.getEmailAddress());
             accountManager.link(arsp.getAccountId(), linkReq);
diff --git a/java/com/google/gerrit/httpd/restapi/ParameterParser.java b/java/com/google/gerrit/httpd/restapi/ParameterParser.java
index 3ab409e..315c9c8 100644
--- a/java/com/google/gerrit/httpd/restapi/ParameterParser.java
+++ b/java/com/google/gerrit/httpd/restapi/ParameterParser.java
@@ -56,9 +56,18 @@
 
 public class ParameterParser {
   public static final String TRACE_PARAMETER = "trace";
+  public static final String EXPERIMENT_PARAMETER = "experiment";
 
   private static final ImmutableSet<String> RESERVED_KEYS =
-      ImmutableSet.of("pp", "prettyPrint", "strict", "callback", "alt", "fields", TRACE_PARAMETER);
+      ImmutableSet.of(
+          "pp",
+          "prettyPrint",
+          "strict",
+          "callback",
+          "alt",
+          "fields",
+          TRACE_PARAMETER,
+          EXPERIMENT_PARAMETER);
 
   @AutoValue
   public abstract static class QueryParams {
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index c65d0b5..3c39ea1 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -359,15 +359,11 @@
     ViewData viewData = null;
 
     try (TraceContext traceContext = enableTracing(req, res)) {
-      List<IdString> path = splitPath(req);
-      RequestInfo requestInfo = createRequestInfo(traceContext, requestUri(req), path);
+      String requestUri = requestUri(req);
 
-      try (RequestStateContext requestStateContext =
-              RequestStateContext.open()
-                  .addRequestStateProvider(
-                      globals.deadlineCheckerFactory.create(
-                          requestInfo, req.getHeader(X_GERRIT_DEADLINE)));
-          PerThreadCache ignored = PerThreadCache.create()) {
+      try (PerThreadCache ignored = PerThreadCache.create()) {
+        List<IdString> path = splitPath(req);
+        RequestInfo requestInfo = createRequestInfo(traceContext, requestUri, path);
         globals.requestListeners.runEach(l -> l.onRequest(requestInfo));
 
         // It's important that the PerformanceLogContext is closed before the response is sent to
@@ -375,8 +371,13 @@
         // plugins happens before the client sees the response. This is needed for being able to
         // test performance logging from an acceptance test (see
         // TraceIT#performanceLoggingForRestCall()).
-        try (PerformanceLogContext performanceLogContext =
-            new PerformanceLogContext(globals.config, globals.performanceLoggers)) {
+        try (RequestStateContext requestStateContext =
+                RequestStateContext.open()
+                    .addRequestStateProvider(
+                        globals.deadlineCheckerFactory.create(
+                            requestInfo, req.getHeader(X_GERRIT_DEADLINE)));
+            PerformanceLogContext performanceLogContext =
+                new PerformanceLogContext(globals.config, globals.performanceLoggers)) {
           traceRequestData(req);
 
           if (isCorsPreflight(req)) {
@@ -741,7 +742,8 @@
         if (requestCancelledException.isPresent()) {
           RequestStateProvider.Reason cancellationReason =
               requestCancelledException.get().getCancellationReason();
-          globals.cancellationMetrics.countCancelledRequest(requestInfo, cancellationReason);
+          globals.cancellationMetrics.countCancelledRequest(
+              RequestInfo.RequestType.REST, requestUri, cancellationReason);
           statusCode = getCancellationStatusCode(cancellationReason);
           responseBytes =
               replyError(
@@ -1815,6 +1817,10 @@
     logger.atFinest().log(
         "Received REST request: %s %s (parameters: %s)",
         req.getMethod(), req.getRequestURI(), getParameterNames(req));
+    Optional.ofNullable(req.getHeader(X_GERRIT_DEADLINE))
+        .ifPresent(
+            clientProvidedDeadline ->
+                logger.atFine().log("%s = %s", X_GERRIT_DEADLINE, clientProvidedDeadline));
     logger.atFinest().log("Calling user: %s", globals.currentUser.get().getLoggableName());
     logger.atFinest().log(
         "Groups: %s", lazy(() -> globals.currentUser.get().getEffectiveGroups().getKnownGroups()));
diff --git a/java/com/google/gerrit/mail/HtmlParser.java b/java/com/google/gerrit/mail/HtmlParser.java
index ba73bdd..97fe06e 100644
--- a/java/com/google/gerrit/mail/HtmlParser.java
+++ b/java/com/google/gerrit/mail/HtmlParser.java
@@ -143,7 +143,7 @@
           if (!Strings.isNullOrEmpty(content)) {
             ParserUtil.appendOrAddNewComment(
                 new MailComment(
-                    content, null, null, MailComment.CommentType.CHANGE_MESSAGE, isLink),
+                    content, null, null, MailComment.CommentType.PATCHSET_LEVEL, isLink),
                 parsedComments);
           }
         } else if (lastEncounteredComment == null) {
diff --git a/java/com/google/gerrit/mail/MailComment.java b/java/com/google/gerrit/mail/MailComment.java
index 3e7da10..cea856c 100644
--- a/java/com/google/gerrit/mail/MailComment.java
+++ b/java/com/google/gerrit/mail/MailComment.java
@@ -20,7 +20,7 @@
 /** A comment parsed from inbound email */
 public class MailComment {
   public enum CommentType {
-    CHANGE_MESSAGE,
+    PATCHSET_LEVEL,
     FILE_COMMENT,
     INLINE_COMMENT
   }
diff --git a/java/com/google/gerrit/mail/TextParser.java b/java/com/google/gerrit/mail/TextParser.java
index a33c66f..95ab10c 100644
--- a/java/com/google/gerrit/mail/TextParser.java
+++ b/java/com/google/gerrit/mail/TextParser.java
@@ -77,7 +77,7 @@
         // This is not a comment, try to advance the file/comment pointers and
         // add previous comment to list if applicable
         if (currentComment != null) {
-          if (currentComment.type == MailComment.CommentType.CHANGE_MESSAGE) {
+          if (currentComment.type == MailComment.CommentType.PATCHSET_LEVEL) {
             currentComment.message = ParserUtil.trimQuotation(currentComment.message);
           }
           if (!Strings.isNullOrEmpty(currentComment.message)) {
@@ -115,7 +115,7 @@
           if (lastEncounteredComment == null) {
             if (lastEncounteredFileName == null) {
               // Change message
-              currentComment.type = MailComment.CommentType.CHANGE_MESSAGE;
+              currentComment.type = MailComment.CommentType.PATCHSET_LEVEL;
             } else {
               // File comment not sent in reply to another comment
               currentComment.type = MailComment.CommentType.FILE_COMMENT;
diff --git a/java/com/google/gerrit/metrics/BUILD b/java/com/google/gerrit/metrics/BUILD
index 3cc056b..0cb0275 100644
--- a/java/com/google/gerrit/metrics/BUILD
+++ b/java/com/google/gerrit/metrics/BUILD
@@ -8,6 +8,7 @@
         "//java/com/google/gerrit/common:server",
         "//java/com/google/gerrit/extensions:api",
         "//java/com/google/gerrit/lifecycle",
+        "//java/com/google/gerrit/server/cancellation",
         "//java/com/google/gerrit/server/logging",
         "//lib:guava",
         "//lib:jgit",
diff --git a/java/com/google/gerrit/metrics/Timer0.java b/java/com/google/gerrit/metrics/Timer0.java
index d0033a4..2617431 100644
--- a/java/com/google/gerrit/metrics/Timer0.java
+++ b/java/com/google/gerrit/metrics/Timer0.java
@@ -18,6 +18,7 @@
 
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.server.cancellation.RequestStateContext;
 import com.google.gerrit.server.logging.LoggingContext;
 import com.google.gerrit.server.logging.PerformanceLogRecord;
 import java.util.concurrent.TimeUnit;
@@ -60,6 +61,7 @@
    * @return timer context
    */
   public Context start() {
+    RequestStateContext.abortIfCancelled();
     return new Context(this);
   }
 
@@ -75,6 +77,7 @@
         .addPerformanceLogRecord(() -> PerformanceLogRecord.create(name, durationMs));
     logger.atFinest().log("%s took %dms", name, durationMs);
     doRecord(value, unit);
+    RequestStateContext.abortIfCancelled();
   }
 
   /**
diff --git a/java/com/google/gerrit/metrics/Timer1.java b/java/com/google/gerrit/metrics/Timer1.java
index a8fb1a2..319f3e0 100644
--- a/java/com/google/gerrit/metrics/Timer1.java
+++ b/java/com/google/gerrit/metrics/Timer1.java
@@ -18,6 +18,7 @@
 
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.server.cancellation.RequestStateContext;
 import com.google.gerrit.server.logging.LoggingContext;
 import com.google.gerrit.server.logging.Metadata;
 import com.google.gerrit.server.logging.PerformanceLogRecord;
@@ -68,6 +69,7 @@
    * @return timer context
    */
   public Context<F1> start(F1 fieldValue) {
+    RequestStateContext.abortIfCancelled();
     return new Context<>(this, fieldValue);
   }
 
@@ -90,6 +92,7 @@
 
     logger.atFinest().log("%s (%s = %s) took %dms", name, field.name(), fieldValue, durationMs);
     doRecord(fieldValue, value, unit);
+    RequestStateContext.abortIfCancelled();
   }
 
   /**
diff --git a/java/com/google/gerrit/metrics/Timer2.java b/java/com/google/gerrit/metrics/Timer2.java
index 8a4a793..8ae76a2 100644
--- a/java/com/google/gerrit/metrics/Timer2.java
+++ b/java/com/google/gerrit/metrics/Timer2.java
@@ -18,6 +18,7 @@
 
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.server.cancellation.RequestStateContext;
 import com.google.gerrit.server.logging.LoggingContext;
 import com.google.gerrit.server.logging.Metadata;
 import com.google.gerrit.server.logging.PerformanceLogRecord;
@@ -74,6 +75,7 @@
    * @return timer context
    */
   public Context<F1, F2> start(F1 fieldValue1, F2 fieldValue2) {
+    RequestStateContext.abortIfCancelled();
     return new Context<>(this, fieldValue1, fieldValue2);
   }
 
@@ -100,6 +102,7 @@
         "%s (%s = %s, %s = %s) took %dms",
         name, field1.name(), fieldValue1, field2.name(), fieldValue2, durationMs);
     doRecord(fieldValue1, fieldValue2, value, unit);
+    RequestStateContext.abortIfCancelled();
   }
 
   /**
diff --git a/java/com/google/gerrit/metrics/Timer3.java b/java/com/google/gerrit/metrics/Timer3.java
index 2044da6..9df963a 100644
--- a/java/com/google/gerrit/metrics/Timer3.java
+++ b/java/com/google/gerrit/metrics/Timer3.java
@@ -18,6 +18,7 @@
 
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.server.cancellation.RequestStateContext;
 import com.google.gerrit.server.logging.LoggingContext;
 import com.google.gerrit.server.logging.Metadata;
 import com.google.gerrit.server.logging.PerformanceLogRecord;
@@ -80,6 +81,7 @@
    * @return timer context
    */
   public Context<F1, F2, F3> start(F1 fieldValue1, F2 fieldValue2, F3 fieldValue3) {
+    RequestStateContext.abortIfCancelled();
     return new Context<>(this, fieldValue1, fieldValue2, fieldValue3);
   }
 
@@ -116,6 +118,7 @@
         fieldValue3,
         durationMs);
     doRecord(fieldValue1, fieldValue2, fieldValue3, value, unit);
+    RequestStateContext.abortIfCancelled();
   }
 
   /**
diff --git a/java/com/google/gerrit/pgm/ChangeExternalIdCaseSensitivity.java b/java/com/google/gerrit/pgm/ChangeExternalIdCaseSensitivity.java
new file mode 100644
index 0000000..fbc6065
--- /dev/null
+++ b/java/com/google/gerrit/pgm/ChangeExternalIdCaseSensitivity.java
@@ -0,0 +1,194 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm;
+
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GERRIT;
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.exceptions.DuplicateKeyException;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.util.SiteProgram;
+import com.google.gerrit.server.account.externalids.DisabledExternalIdCache;
+import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
+import com.google.gerrit.server.account.externalids.ExternalIdNotes;
+import com.google.gerrit.server.account.externalids.ExternalIdUpsertPreprocessor;
+import com.google.gerrit.server.account.externalids.ExternalIds;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.meta.MetaDataUpdate;
+import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
+import com.google.gerrit.server.schema.NoteDbSchemaVersionCheck;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Provider;
+import java.io.IOException;
+import java.util.Collection;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.TextProgressMonitor;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.kohsuke.args4j.Option;
+
+/**
+ * Changes the case sensitivity of `username:` and `gerrit:` external IDs by recomputing the SHA-1
+ * sums used as note names.
+ */
+public class ChangeExternalIdCaseSensitivity extends SiteProgram {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  @Option(name = "--batch", usage = "Don't ask for confirmation before migrating.")
+  private boolean batch;
+
+  @Option(name = "--dryrun", usage = "Do a dryrun of the migration.")
+  private boolean dryrun;
+
+  private final LifecycleManager manager = new LifecycleManager();
+  private final TextProgressMonitor monitor = new TextProgressMonitor();
+
+  private Config globalConfig;
+  private boolean isUserNameCaseInsensitive;
+  private ConsoleUI ui;
+
+  @Inject private GitRepositoryManager repoManager;
+  @Inject private AllUsersName allUsersName;
+  @Inject private Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory;
+  @Inject private ExternalIdNotes.FactoryNoReindex externalIdNotesFactory;
+  @Inject private ExternalIds externalIds;
+  @Inject private ExternalIdFactory externalIdFactory;
+
+  @Override
+  public int run() throws Exception {
+    mustHaveValidSite();
+    ui = ConsoleUI.getInstance(batch);
+
+    Injector dbInjector = createDbInjector();
+    manager.add(dbInjector, dbInjector.createChildInjector(NoteDbSchemaVersionCheck.module()));
+    dbInjector
+        .createChildInjector(
+            new FactoryModule() {
+              @Override
+              protected void configure() {
+                bind(GitReferenceUpdated.class).toInstance(GitReferenceUpdated.DISABLED);
+                factory(MetaDataUpdate.InternalFactory.class);
+                DynamicMap.mapOf(binder(), ExternalIdUpsertPreprocessor.class);
+
+                // The ChangeExternalIdCaseSensitivity program needs to access all external IDs only
+                // once to update them. After the update they are not accessed again. Hence the
+                // LocalUsernamesToLowerCase program doesn't benefit from caching external IDs and
+                // the external ID cache can be disabled.
+                install(DisabledExternalIdCache.module());
+              }
+            })
+        .injectMembers(this);
+    globalConfig = dbInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
+
+    this.isUserNameCaseInsensitive =
+        globalConfig.getBoolean("auth", "userNameCaseInsensitive", false);
+
+    String message =
+        "auth.userNameCaseInsensitive is set to %b. "
+            + "External IDs will be migrated to be case %ssensitive. Continue?";
+    if (!ui.yesno(
+        true, message, isUserNameCaseInsensitive, isUserNameCaseInsensitive ? "" : "in")) {
+      return 0;
+    }
+
+    Collection<ExternalId> todo = externalIds.all();
+    monitor.beginTask("Converting external ID note names", todo.size());
+
+    manager.start();
+    try {
+      try (Repository repo = repoManager.openRepository(allUsersName)) {
+        ExternalIdNotes extIdNotes = externalIdNotesFactory.load(repo);
+        for (ExternalId extId : todo) {
+          recomputeExternalIdNoteId(extIdNotes, extId);
+          monitor.update(1);
+        }
+        if (!dryrun) {
+          try (MetaDataUpdate metaDataUpdate =
+              metaDataUpdateServerFactory.get().create(allUsersName)) {
+            metaDataUpdate.setMessage(
+                String.format(
+                    "Migration to case %ssensitive usernames",
+                    isUserNameCaseInsensitive ? "" : "in"));
+            extIdNotes.commit(metaDataUpdate);
+          }
+        }
+      }
+    } finally {
+      manager.stop();
+      monitor.endTask();
+    }
+
+    int exitCode;
+    if (!dryrun) {
+      updateGerritConfig();
+
+      exitCode = reindexAccounts();
+    } else {
+      exitCode = 0;
+    }
+    return exitCode;
+  }
+
+  private void recomputeExternalIdNoteId(ExternalIdNotes extIdNotes, ExternalId extId)
+      throws DuplicateKeyException, IOException {
+    if (extId.isScheme(SCHEME_GERRIT) || extId.isScheme(SCHEME_USERNAME)) {
+      ExternalId.Key updatedKey =
+          ExternalId.Key.create(extId.key().scheme(), extId.key().id(), !isUserNameCaseInsensitive);
+      if (!extId.key().sha1().getName().equals(updatedKey.sha1().getName())) {
+        logger.atInfo().log("Converting note name of external ID: %s", extId.key());
+        ExternalId updatedExtId =
+            externalIdFactory.create(
+                updatedKey, extId.accountId(), extId.email(), extId.password(), extId.blobId());
+        extIdNotes.replace(extId, updatedExtId);
+      }
+    }
+  }
+
+  private void updateGerritConfig() throws IOException, ConfigInvalidException {
+    logger.atInfo().log("Setting auth.userNameCaseInsensitive to true in gerrit.config.");
+    FileBasedConfig config =
+        new FileBasedConfig(
+            globalConfig, getSitePath().resolve("etc/gerrit.config").toFile(), FS.DETECTED);
+    config.load();
+    config.setBoolean("auth", null, "userNameCaseInsensitive", !isUserNameCaseInsensitive);
+    config.save();
+  }
+
+  private int reindexAccounts() throws Exception {
+    monitor.beginTask("Reindex accounts", ProgressMonitor.UNKNOWN);
+    String[] reindexArgs = {
+      "--site-path", getSitePath().toString(), "--index", AccountSchemaDefinitions.NAME
+    };
+    logger.atInfo().log(
+        "Migration complete, reindexing accounts with: reindex %s", String.join(" ", reindexArgs));
+    Reindex reindexPgm = new Reindex();
+    int exitCode = reindexPgm.main(reindexArgs);
+    monitor.endTask();
+    return exitCode;
+  }
+}
diff --git a/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java b/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
index 8e2f70f..f651994 100644
--- a/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
+++ b/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.pgm.util.SiteProgram;
 import com.google.gerrit.server.account.externalids.DisabledExternalIdCache;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
 import com.google.gerrit.server.account.externalids.ExternalIdNotes;
 import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.config.AllUsersName;
@@ -52,6 +53,7 @@
   @Inject private AllUsersName allUsersName;
   @Inject private Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory;
   @Inject private ExternalIdNotes.FactoryNoReindex externalIdNotesFactory;
+  @Inject private ExternalIdFactory externalIdFactory;
   @Inject private ExternalIds externalIds;
 
   @Override
@@ -105,7 +107,7 @@
       String localUserLowerCase = localUser.toLowerCase(Locale.US);
       if (!localUser.equals(localUserLowerCase)) {
         ExternalId extIdLowerCase =
-            ExternalId.create(
+            externalIdFactory.create(
                 SCHEME_GERRIT,
                 localUserLowerCase,
                 extId.accountId(),
diff --git a/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java b/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java
index 9519653..e2a1f04 100644
--- a/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java
+++ b/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.pgm.init.api.InitFlags;
 import com.google.gerrit.server.GerritPersonIdentProvider;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
 import com.google.gerrit.server.account.externalids.ExternalIdNotes;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.SitePaths;
@@ -39,12 +40,18 @@
   private final InitFlags flags;
   private final SitePaths site;
   private final AllUsersName allUsers;
+  private final ExternalIdFactory externalIdFactory;
 
   @Inject
-  public ExternalIdsOnInit(InitFlags flags, SitePaths site, AllUsersNameOnInitProvider allUsers) {
+  public ExternalIdsOnInit(
+      InitFlags flags,
+      SitePaths site,
+      AllUsersNameOnInitProvider allUsers,
+      ExternalIdFactory externalIdFactory) {
     this.flags = flags;
     this.site = site;
     this.allUsers = new AllUsersName(allUsers.get());
+    this.externalIdFactory = externalIdFactory;
   }
 
   public synchronized void insert(String commitMessage, Collection<ExternalId> extIds)
@@ -52,7 +59,8 @@
     File path = getPath();
     if (path != null) {
       try (Repository allUsersRepo = new FileRepository(path)) {
-        ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(allUsers, allUsersRepo);
+        ExternalIdNotes extIdNotes =
+            ExternalIdNotes.loadNoCacheUpdate(allUsers, allUsersRepo, externalIdFactory);
         extIdNotes.insert(extIds);
         try (MetaDataUpdate metaDataUpdate =
             new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsers, allUsersRepo)) {
diff --git a/java/com/google/gerrit/pgm/init/InitAdminUser.java b/java/com/google/gerrit/pgm/init/InitAdminUser.java
index 2e32066..d6a0133 100644
--- a/java/com/google/gerrit/pgm/init/InitAdminUser.java
+++ b/java/com/google/gerrit/pgm/init/InitAdminUser.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.server.account.AccountSshKey;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
 import com.google.gerrit.server.index.account.AccountIndex;
 import com.google.gerrit.server.index.account.AccountIndexCollection;
 import com.google.gerrit.server.index.group.GroupIndex;
@@ -52,6 +53,7 @@
   private final ExternalIdsOnInit externalIds;
   private final SequencesOnInit sequencesOnInit;
   private final GroupsOnInit groupsOnInit;
+  private final ExternalIdFactory externalIdFactory;
   private AccountIndexCollection accountIndexCollection;
   private GroupIndexCollection groupIndexCollection;
 
@@ -63,7 +65,8 @@
       VersionedAuthorizedKeysOnInit.Factory authorizedKeysFactory,
       ExternalIdsOnInit externalIds,
       SequencesOnInit sequencesOnInit,
-      GroupsOnInit groupsOnInit) {
+      GroupsOnInit groupsOnInit,
+      ExternalIdFactory externalIdFactory) {
     this.flags = flags;
     this.ui = ui;
     this.accounts = accounts;
@@ -71,6 +74,7 @@
     this.externalIds = externalIds;
     this.sequencesOnInit = sequencesOnInit;
     this.groupsOnInit = groupsOnInit;
+    this.externalIdFactory = externalIdFactory;
   }
 
   @Override
@@ -107,10 +111,10 @@
         String email = readEmail(sshKey);
 
         List<ExternalId> extIds = new ArrayList<>(2);
-        extIds.add(ExternalId.createUsername(username, id, httpPassword));
+        extIds.add(externalIdFactory.createUsername(username, id, httpPassword));
 
         if (email != null) {
-          extIds.add(ExternalId.createEmail(id, email));
+          extIds.add(externalIdFactory.createEmail(id, email));
         }
         externalIds.insert("Add external IDs for initial admin user", extIds);
 
diff --git a/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java b/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java
index 5ca239e..abd7d43 100644
--- a/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java
+++ b/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java
@@ -15,15 +15,16 @@
 package com.google.gerrit.pgm.init.api;
 
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.server.config.AllProjectsConfigProvider;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.project.GroupList;
 import com.google.gerrit.server.project.ProjectConfig;
 import com.google.inject.Inject;
 import java.io.IOException;
+import java.util.Optional;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.Config;
@@ -34,16 +35,18 @@
 public class AllProjectsConfig extends VersionedMetaDataOnInit {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
-  @Nullable private final StoredConfig baseConfig;
+  private final Optional<StoredConfig> baseConfig;
   private Config cfg;
   private GroupList groupList;
 
   @Inject
-  AllProjectsConfig(AllProjectsNameOnInitProvider allProjects, SitePaths site, InitFlags flags) {
+  AllProjectsConfig(
+      AllProjectsNameOnInitProvider allProjects,
+      AllProjectsConfigProvider allProjectsConfigProvider,
+      SitePaths site,
+      InitFlags flags) {
     super(flags, site, allProjects.get(), RefNames.REFS_CONFIG);
-    this.baseConfig =
-        ProjectConfig.Factory.getBaseConfig(
-            site, new AllProjectsName(allProjects.get()), Project.nameKey(allProjects.get()));
+    this.baseConfig = allProjectsConfigProvider.get(new AllProjectsName(allProjects.get()));
   }
 
   public Config getConfig() {
@@ -62,8 +65,8 @@
 
   @Override
   protected void onLoad() throws IOException, ConfigInvalidException {
-    if (baseConfig != null) {
-      baseConfig.load();
+    if (baseConfig.isPresent()) {
+      baseConfig.get().load();
     }
     groupList = readGroupList();
     cfg = readConfig(ProjectConfig.PROJECT_CONFIG, baseConfig);
diff --git a/java/com/google/gerrit/pgm/init/api/GitRepositoryManagerOnInit.java b/java/com/google/gerrit/pgm/init/api/GitRepositoryManagerOnInit.java
index a937c4b..8e69eb9 100644
--- a/java/com/google/gerrit/pgm/init/api/GitRepositoryManagerOnInit.java
+++ b/java/com/google/gerrit/pgm/init/api/GitRepositoryManagerOnInit.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.pgm.init.api;
 
 import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.Project.NameKey;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
@@ -41,6 +42,18 @@
   }
 
   @Override
+  public Status getRepositoryStatus(NameKey name) {
+    try {
+      openRepository(name);
+    } catch (RepositoryNotFoundException e) {
+      return Status.NON_EXISTENT;
+    } catch (IOException e) {
+      return Status.UNAVAILABLE;
+    }
+    return Status.ACTIVE;
+  }
+
+  @Override
   public Repository openRepository(Project.NameKey name)
       throws RepositoryNotFoundException, IOException {
     return new FileRepository(getPath(name));
diff --git a/java/com/google/gerrit/pgm/util/BatchProgramModule.java b/java/com/google/gerrit/pgm/util/BatchProgramModule.java
index 4db657d..188f30b 100644
--- a/java/com/google/gerrit/pgm/util/BatchProgramModule.java
+++ b/java/com/google/gerrit/pgm/util/BatchProgramModule.java
@@ -39,7 +39,7 @@
 import com.google.gerrit.server.account.GroupIncludeCacheImpl;
 import com.google.gerrit.server.account.Realm;
 import com.google.gerrit.server.account.ServiceUserClassifierImpl;
-import com.google.gerrit.server.account.externalids.ExternalIdModule;
+import com.google.gerrit.server.account.externalids.ExternalIdCacheModule;
 import com.google.gerrit.server.approval.ApprovalCacheImpl;
 import com.google.gerrit.server.cache.CacheRemovalListener;
 import com.google.gerrit.server.cache.h2.H2CacheModule;
@@ -79,6 +79,7 @@
 import com.google.gerrit.server.project.CommitResource;
 import com.google.gerrit.server.project.ProjectCacheImpl;
 import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.SubmitRequirementsEvaluatorImpl;
 import com.google.gerrit.server.project.SubmitRuleEvaluator;
 import com.google.gerrit.server.query.approval.ApprovalModule;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -171,7 +172,7 @@
     modules.add(new DefaultPermissionBackendModule());
     modules.add(new DefaultMemoryCacheModule());
     modules.add(new H2CacheModule());
-    modules.add(new ExternalIdModule());
+    modules.add(new ExternalIdCacheModule());
     modules.add(new GroupModule());
     modules.add(new NoteDbModule());
     modules.add(AccountCacheImpl.module());
@@ -188,6 +189,7 @@
     modules.add(TagCache.module());
     modules.add(PureRevertCache.module());
     modules.add(new ApprovalModule());
+    modules.add(SubmitRequirementsEvaluatorImpl.module());
     factory(CapabilityCollection.Factory.class);
     factory(ChangeData.AssistedFactory.class);
     factory(ChangeIsVisibleToPredicate.Factory.class);
diff --git a/java/com/google/gerrit/server/CancellationMetrics.java b/java/com/google/gerrit/server/CancellationMetrics.java
index 9a1ac9c..2d0b878 100644
--- a/java/com/google/gerrit/server/CancellationMetrics.java
+++ b/java/com/google/gerrit/server/CancellationMetrics.java
@@ -20,6 +20,7 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Splitter;
 import com.google.gerrit.common.UsedAt;
+import com.google.gerrit.metrics.Counter1;
 import com.google.gerrit.metrics.Counter3;
 import com.google.gerrit.metrics.Description;
 import com.google.gerrit.metrics.Field;
@@ -34,6 +35,7 @@
 public class CancellationMetrics {
   private final Counter3<String, String, String> advisoryDeadlineCount;
   private final Counter3<String, String, RequestStateProvider.Reason> cancelledRequestsCount;
+  private final Counter1<String> receiveTimeoutCount;
 
   @Inject
   CancellationMetrics(MetricMaker metrics) {
@@ -71,6 +73,16 @@
                     Metadata.Builder::cancellationReason)
                 .description("The reason why the request was cancelled.")
                 .build());
+
+    this.receiveTimeoutCount =
+        metrics.newCounter(
+            "cancellation/receive_timeout_count",
+            new Description(
+                    "Number of requests that are cancelled because receive.timout is exceeded")
+                .setRate(),
+            Field.ofString("cancellation_type", (metadataBuilder, resolveAllUsers) -> {})
+                .description("The cancellation type (graceful or forceful).")
+                .build());
   }
 
   public void countAdvisoryDeadline(RequestInfo requestInfo, String deadlineId) {
@@ -88,6 +100,14 @@
         cancellationReason);
   }
 
+  public void countCancelledRequest(
+      RequestInfo.RequestType requestType,
+      String requestUri,
+      RequestStateProvider.Reason cancellationReason) {
+    cancelledRequestsCount.increment(
+        requestType.name(), CancellationMetrics.redactRequestUri(requestUri), cancellationReason);
+  }
+
   @UsedAt(UsedAt.Project.GOOGLE)
   public void countCancelledRequest(
       String requestType,
@@ -96,6 +116,14 @@
     cancelledRequestsCount.increment(requestType, redactedRequestUri, cancellationReason);
   }
 
+  public void countGracefulReceiveTimeout() {
+    receiveTimeoutCount.increment("graceful");
+  }
+
+  public void countForcefulReceiveTimeout() {
+    receiveTimeoutCount.increment("forceful");
+  }
+
   /**
    * Redacts resource IDs from the given request URI.
    *
@@ -110,7 +138,8 @@
   @VisibleForTesting
   static String redactRequestUri(String requestUri) {
     requireNonNull(requestUri, "requestUri");
-    checkState(!requestUri.startsWith("/a"), "request URI must not start with '/a'");
+    checkState(
+        !requestUri.startsWith("/a/"), "request URI must not start with '/a/': %s", requestUri);
 
     StringBuilder redactedRequestUri = new StringBuilder();
 
diff --git a/java/com/google/gerrit/server/DeadlineChecker.java b/java/com/google/gerrit/server/DeadlineChecker.java
index 7b231f9..5662e50 100644
--- a/java/com/google/gerrit/server/DeadlineChecker.java
+++ b/java/com/google/gerrit/server/DeadlineChecker.java
@@ -14,14 +14,14 @@
 
 package com.google.gerrit.server;
 
-import static com.google.common.collect.ImmutableList.toImmutableList;
 import static java.util.Comparator.comparing;
+import static java.util.Objects.requireNonNull;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.MINUTES;
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.stream.Collectors.toMap;
 
 import com.google.auto.value.AutoValue;
-import com.google.common.base.Function;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.flogger.FluentLogger;
@@ -32,31 +32,43 @@
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
+import java.util.HashSet;
+import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
 import org.eclipse.jgit.lib.Config;
 
-/** {@link RequestStateProvider} that checks whether a client provided deadline is exceeded. */
+/**
+ * {@link RequestStateProvider} that checks whether a client provided deadline is exceeded.
+ *
+ * <p>Should be registered at most once per request.
+ */
 public class DeadlineChecker implements RequestStateProvider {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   private static String SECTION_DEADLINE = "deadline";
 
   /**
-   * Formatter to format a timeout as {@code timeout=<TIMEOUT><TIME_UNIT>}.
+   * Creates a formatter that formats a timeout as {@code <TIMEOUT_NAME>=<TIMEOUT><TIME_UNIT>}.
    *
    * <p>If the timeout is 1 minute or greater, minutes is used as a time unit. Otherwise
    * milliseconds is just as a time unit.
+   *
+   * @param timeoutName the name of the timeout
    */
-  public static Function<Long, String> TIMEOUT_FORMATTER =
-      timeout -> {
-        String formattedTimeout = MILLISECONDS.convert(timeout, NANOSECONDS) + "ms";
-        long timeoutInMinutes = MINUTES.convert(timeout, NANOSECONDS);
-        if (timeoutInMinutes > 0) {
-          formattedTimeout = timeoutInMinutes + "m";
-        }
-        return String.format("timeout=%s", formattedTimeout);
-      };
+  public static Function<Long, String> getTimeoutFormatter(String timeoutName) {
+    requireNonNull(timeoutName, "timeoutName");
+    return timeout -> {
+      String formattedTimeout = MILLISECONDS.convert(timeout, NANOSECONDS) + "ms";
+      long timeoutInMinutes = MINUTES.convert(timeout, NANOSECONDS);
+      if (timeoutInMinutes > 0) {
+        formattedTimeout = timeoutInMinutes + "m";
+      }
+      return String.format("%s=%s", timeoutName, formattedTimeout);
+    };
+  }
 
   public interface Factory {
     DeadlineChecker create(RequestInfo requestInfo, @Nullable String clientProvidedTimeoutValue)
@@ -68,8 +80,13 @@
   }
 
   private final CancellationMetrics cancellationsMetrics;
+
+  /** The start time of the request in nanoseconds. */
+  private final long start;
+
   private final RequestInfo requestInfo;
   private final RequestStateProvider.Reason cancellationReason;
+  private final String timeoutName;
 
   /**
    * Timeout in nanoseconds after which the request should be aborted.
@@ -93,10 +110,10 @@
    * <p>If any of these deadlines is exceeded the request is not be aborted. Instead the {@code
    * cancellation/advisory_deadline_count} metric is incremented and a log is written.
    */
-  private final ImmutableList<ServerDeadline> advisoryDeadlines;
+  private final Map<String, ServerDeadline> advisoryDeadlines;
 
   /**
-   * Creates a {@code ClientProvidedDeadlineChecker}.
+   * Creates a {@link DeadlineChecker}.
    *
    * <p>No deadline is enforced if the client provided deadline value is {@code null} or {@code 0}.
    *
@@ -123,7 +140,7 @@
   }
 
   /**
-   * Creates a {@code ClientProvidedDeadlineChecker}.
+   * Creates a {@link DeadlineChecker}.
    *
    * <p>No deadline is enforced if the client provided deadline value is {@code null} or {@code 0}.
    *
@@ -144,6 +161,7 @@
       @Assisted @Nullable String clientProvidedTimeoutValue)
       throws InvalidDeadlineException {
     this.cancellationsMetrics = cancellationsMetrics;
+    this.start = start;
     this.requestInfo = requestInfo;
 
     ImmutableList<RequestConfig> deadlineConfigs =
@@ -152,6 +170,26 @@
     Optional<ServerDeadline> serverSideDeadline =
         getServerSideDeadline(deadlineConfigs, requestInfo);
     Optional<Long> clientedProvidedTimeout = parseTimeout(clientProvidedTimeoutValue);
+    logDeadlines(serverSideDeadline, clientedProvidedTimeout);
+
+    this.cancellationReason =
+        clientedProvidedTimeout.isPresent()
+            ? RequestStateProvider.Reason.CLIENT_PROVIDED_DEADLINE_EXCEEDED
+            : RequestStateProvider.Reason.SERVER_DEADLINE_EXCEEDED;
+    this.timeoutName =
+        clientedProvidedTimeout
+            .map(clientTimeout -> "client.timeout")
+            .orElse(
+                serverSideDeadline
+                    .map(serverDeadline -> serverDeadline.id() + ".timeout")
+                    .orElse("timeout"));
+    this.timeout =
+        clientedProvidedTimeout.orElse(serverSideDeadline.map(ServerDeadline::timeout).orElse(0L));
+    this.deadline = timeout > 0 ? Optional.of(start + timeout) : Optional.empty();
+  }
+
+  private void logDeadlines(
+      Optional<ServerDeadline> serverSideDeadline, Optional<Long> clientedProvidedTimeout) {
     if (serverSideDeadline.isPresent()) {
       if (clientedProvidedTimeout.isPresent()) {
         logger.atFine().log(
@@ -167,14 +205,11 @@
             TimeUnit.MILLISECONDS.convert(
                 serverSideDeadline.get().timeout(), TimeUnit.NANOSECONDS));
       }
+    } else if (clientedProvidedTimeout.isPresent()) {
+      logger.atFine().log(
+          "applying client provided deadline (timeout = %sms)",
+          TimeUnit.MILLISECONDS.convert(clientedProvidedTimeout.get(), TimeUnit.NANOSECONDS));
     }
-    this.cancellationReason =
-        clientedProvidedTimeout.isPresent()
-            ? RequestStateProvider.Reason.CLIENT_PROVIDED_DEADLINE_EXCEEDED
-            : RequestStateProvider.Reason.SERVER_DEADLINE_EXCEEDED;
-    this.timeout =
-        clientedProvidedTimeout.orElse(serverSideDeadline.map(ServerDeadline::timeout).orElse(0L));
-    this.deadline = timeout > 0 ? Optional.of(start + timeout) : Optional.empty();
   }
 
   private Optional<ServerDeadline> getServerSideDeadline(
@@ -189,32 +224,40 @@
         .findFirst();
   }
 
-  private ImmutableList<ServerDeadline> getAdvisoryDeadlines(
+  private Map<String, ServerDeadline> getAdvisoryDeadlines(
       ImmutableList<RequestConfig> deadlineConfigs, RequestInfo requestInfo) {
     return deadlineConfigs.stream()
         .filter(deadlineConfig -> deadlineConfig.matches(requestInfo))
         .map(ServerDeadline::readFrom)
         .filter(ServerDeadline::hasTimeout)
         .filter(ServerDeadline::isAdvisory)
-        .collect(toImmutableList());
+        .collect(toMap(ServerDeadline::id, Function.identity()));
   }
 
   @Override
   public void checkIfCancelled(OnCancelled onCancelled) {
     long now = System.nanoTime();
 
-    advisoryDeadlines.forEach(
-        advisoryDeadline -> {
-          if (now > advisoryDeadline.timeout()) {
-            logger.atWarning().log(
-                "advisory deadline %s exceeded (%s)",
-                advisoryDeadline.id(), TIMEOUT_FORMATTER.apply(advisoryDeadline.timeout()));
-            cancellationsMetrics.countAdvisoryDeadline(requestInfo, advisoryDeadline.id());
-          }
-        });
+    Set<String> exceededAdvisoryDeadlines = new HashSet<>();
+    advisoryDeadlines
+        .values()
+        .forEach(
+            advisoryDeadline -> {
+              if (now > start + advisoryDeadline.timeout()) {
+                exceededAdvisoryDeadlines.add(advisoryDeadline.id());
+                logger.atFine().log(
+                    "advisory deadline exceeded (%s)",
+                    getTimeoutFormatter(advisoryDeadline.id() + ".timeout")
+                        .apply(advisoryDeadline.timeout()));
+                cancellationsMetrics.countAdvisoryDeadline(requestInfo, advisoryDeadline.id());
+              }
+            });
+    // remove advisory deadlines which have already been reported as exceeded so that they don't get
+    // reported again for this request
+    exceededAdvisoryDeadlines.forEach(advisoryDeadlines::remove);
 
     if (deadline.isPresent() && now > deadline.get()) {
-      onCancelled.onCancel(cancellationReason, TIMEOUT_FORMATTER.apply(timeout));
+      onCancelled.onCancel(cancellationReason, getTimeoutFormatter(timeoutName).apply(timeout));
     }
   }
 
diff --git a/java/com/google/gerrit/server/RequestInfo.java b/java/com/google/gerrit/server/RequestInfo.java
index f369239..053b3ac 100644
--- a/java/com/google/gerrit/server/RequestInfo.java
+++ b/java/com/google/gerrit/server/RequestInfo.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server;
 
 import com.google.auto.value.AutoValue;
+import com.google.gerrit.common.UsedAt;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.server.logging.TraceContext;
 import java.util.Optional;
@@ -69,10 +70,12 @@
 
   public static RequestInfo.Builder builder(
       RequestType requestType, CurrentUser callingUser, TraceContext traceContext) {
-    return new AutoValue_RequestInfo.Builder()
-        .requestType(requestType)
-        .callingUser(callingUser)
-        .traceContext(traceContext);
+    return builder().requestType(requestType).callingUser(callingUser).traceContext(traceContext);
+  }
+
+  @UsedAt(UsedAt.Project.GOOGLE)
+  public static RequestInfo.Builder builder() {
+    return new AutoValue_RequestInfo.Builder();
   }
 
   @AutoValue.Builder
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/server/StarredChangesUtil.java
index 93cf0de..5dcbd01 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/server/StarredChangesUtil.java
@@ -27,7 +27,6 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
-import com.google.common.collect.Sets;
 import com.google.common.flogger.FluentLogger;
 import com.google.common.primitives.Ints;
 import com.google.gerrit.common.Nullable;
@@ -57,7 +56,6 @@
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Optional;
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
@@ -161,8 +159,6 @@
 
   public static final String DEFAULT_LABEL = "star";
   public static final String IGNORE_LABEL = "ignore";
-  public static final String REVIEWED_LABEL = "reviewed";
-  public static final String UNREVIEWED_LABEL = "unreviewed";
   public static final ImmutableSortedSet<String> DEFAULT_LABELS =
       ImmutableSortedSet.of(DEFAULT_LABEL);
 
@@ -350,40 +346,6 @@
     return isIgnoredBy(rsrc.getChange().getId(), rsrc.getUser().asIdentifiedUser().getAccountId());
   }
 
-  private static String getReviewedLabel(Change change) {
-    return getReviewedLabel(change.currentPatchSetId().get());
-  }
-
-  private static String getReviewedLabel(int ps) {
-    return REVIEWED_LABEL + "/" + ps;
-  }
-
-  private static String getUnreviewedLabel(Change change) {
-    return getUnreviewedLabel(change.currentPatchSetId().get());
-  }
-
-  private static String getUnreviewedLabel(int ps) {
-    return UNREVIEWED_LABEL + "/" + ps;
-  }
-
-  public void markAsReviewed(ChangeResource rsrc) throws IllegalLabelException {
-    star(
-        rsrc.getUser().asIdentifiedUser().getAccountId(),
-        rsrc.getProject(),
-        rsrc.getChange().getId(),
-        ImmutableSet.of(getReviewedLabel(rsrc.getChange())),
-        ImmutableSet.of(getUnreviewedLabel(rsrc.getChange())));
-  }
-
-  public void markAsUnreviewed(ChangeResource rsrc) throws IllegalLabelException {
-    star(
-        rsrc.getUser().asIdentifiedUser().getAccountId(),
-        rsrc.getProject(),
-        rsrc.getChange().getId(),
-        ImmutableSet.of(getUnreviewedLabel(rsrc.getChange())),
-        ImmutableSet.of(getReviewedLabel(rsrc.getChange())));
-  }
-
   public static StarRef readLabels(Repository repo, String refName) throws IOException {
     try (TraceTimer traceTimer =
         TraceContext.newTimer(
@@ -422,23 +384,6 @@
     if (labels.containsAll(ImmutableSet.of(DEFAULT_LABEL, IGNORE_LABEL))) {
       throw new MutuallyExclusiveLabelsException(DEFAULT_LABEL, IGNORE_LABEL);
     }
-
-    Set<Integer> reviewedPatchSets = getStarredPatchSets(labels, REVIEWED_LABEL);
-    Set<Integer> unreviewedPatchSets = getStarredPatchSets(labels, UNREVIEWED_LABEL);
-    Optional<Integer> ps =
-        Sets.intersection(reviewedPatchSets, unreviewedPatchSets).stream().findFirst();
-    if (ps.isPresent()) {
-      throw new MutuallyExclusiveLabelsException(
-          getReviewedLabel(ps.get()), getUnreviewedLabel(ps.get()));
-    }
-  }
-
-  public static Set<Integer> getStarredPatchSets(Set<String> labels, String label) {
-    return labels.stream()
-        .filter(l -> l.startsWith(label + "/"))
-        .filter(l -> Ints.tryParse(l.substring(label.length() + 1)) != null)
-        .map(l -> Integer.valueOf(l.substring(label.length() + 1)))
-        .collect(toSet());
   }
 
   private static void validateLabels(Collection<String> labels) throws InvalidLabelsException {
diff --git a/java/com/google/gerrit/server/account/AccountCacheImpl.java b/java/com/google/gerrit/server/account/AccountCacheImpl.java
index 93e04880..093af68 100644
--- a/java/com/google/gerrit/server/account/AccountCacheImpl.java
+++ b/java/com/google/gerrit/server/account/AccountCacheImpl.java
@@ -24,7 +24,7 @@
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gerrit.server.config.AllUsersName;
@@ -45,7 +45,6 @@
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
-import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 
@@ -77,6 +76,7 @@
   private final GitRepositoryManager repoManager;
   private final AllUsersName allUsersName;
   private final DefaultPreferencesCache defaultPreferenceCache;
+  private final ExternalIdKeyFactory externalIdKeyFactory;
 
   @Inject
   AccountCacheImpl(
@@ -85,12 +85,14 @@
           LoadingCache<CachedAccountDetails.Key, CachedAccountDetails> accountDetailsCache,
       GitRepositoryManager repoManager,
       AllUsersName allUsersName,
-      DefaultPreferencesCache defaultPreferenceCache) {
+      DefaultPreferencesCache defaultPreferenceCache,
+      ExternalIdKeyFactory externalIdKeyFactory) {
     this.externalIds = externalIds;
     this.accountDetailsCache = accountDetailsCache;
     this.repoManager = repoManager;
     this.allUsersName = allUsersName;
     this.defaultPreferenceCache = defaultPreferenceCache;
+    this.externalIdKeyFactory = externalIdKeyFactory;
   }
 
   @Override
@@ -141,10 +143,10 @@
   public Optional<AccountState> getByUsername(String username) {
     try {
       return externalIds
-          .get(ExternalId.Key.create(SCHEME_USERNAME, username))
+          .get(externalIdKeyFactory.create(SCHEME_USERNAME, username))
           .map(e -> get(e.accountId()))
           .orElseGet(Optional::empty);
-    } catch (IOException | ConfigInvalidException e) {
+    } catch (IOException e) {
       logger.atWarning().withCause(e).log("Cannot load AccountState for username %s", username);
       return Optional.empty();
     }
diff --git a/java/com/google/gerrit/server/account/AccountManager.java b/java/com/google/gerrit/server/account/AccountManager.java
index 2152e1e..987e7e3 100644
--- a/java/com/google/gerrit/server/account/AccountManager.java
+++ b/java/com/google/gerrit/server/account/AccountManager.java
@@ -36,6 +36,8 @@
 import com.google.gerrit.server.ServerInitiated;
 import com.google.gerrit.server.account.externalids.DuplicateExternalIdKeyException;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.auth.NoSuchUserException;
 import com.google.gerrit.server.config.GerritServerConfig;
@@ -77,6 +79,8 @@
   private final GroupsUpdate.Factory groupsUpdateFactory;
   private final boolean autoUpdateAccountActiveStatus;
   private final SetInactiveFlag setInactiveFlag;
+  private final ExternalIdFactory externalIdFactory;
+  private final ExternalIdKeyFactory externalIdKeyFactory;
 
   @VisibleForTesting
   @Inject
@@ -92,7 +96,9 @@
       ProjectCache projectCache,
       ExternalIds externalIds,
       GroupsUpdate.Factory groupsUpdateFactory,
-      SetInactiveFlag setInactiveFlag) {
+      SetInactiveFlag setInactiveFlag,
+      ExternalIdFactory externalIdFactory,
+      ExternalIdKeyFactory externalIdKeyFactory) {
     this.sequences = sequences;
     this.accounts = accounts;
     this.accountsUpdateProvider = accountsUpdateProvider;
@@ -108,13 +114,15 @@
     this.autoUpdateAccountActiveStatus =
         cfg.getBoolean("auth", "autoUpdateAccountActiveStatus", false);
     this.setInactiveFlag = setInactiveFlag;
+    this.externalIdFactory = externalIdFactory;
+    this.externalIdKeyFactory = externalIdKeyFactory;
   }
 
   /** @return user identified by this external identity string */
   public Optional<Account.Id> lookup(String externalId) throws AccountException {
     try {
-      return externalIds.get(ExternalId.Key.parse(externalId)).map(ExternalId::accountId);
-    } catch (IOException | ConfigInvalidException e) {
+      return externalIds.get(externalIdKeyFactory.parse(externalId)).map(ExternalId::accountId);
+    } catch (IOException e) {
       throw new AccountException("Cannot lookup account " + externalId, e);
     }
   }
@@ -229,7 +237,7 @@
     String oldEmail = extId.email();
     if (newEmail != null && !newEmail.equals(oldEmail)) {
       ExternalId extIdWithNewEmail =
-          ExternalId.create(extId.key(), extId.accountId(), newEmail, extId.password());
+          externalIdFactory.create(extId.key(), extId.accountId(), newEmail, extId.password());
       checkEmailNotUsed(extId.accountId(), extIdWithNewEmail);
       accountUpdates.add(u -> u.replaceExternalId(extId, extIdWithNewEmail));
 
@@ -273,7 +281,7 @@
     logger.atFine().log("Assigning new Id %s to account", newId);
 
     ExternalId extId =
-        ExternalId.createWithEmail(who.getExternalIdKey(), newId, who.getEmailAddress());
+        externalIdFactory.createWithEmail(who.getExternalIdKey(), newId, who.getEmailAddress());
     logger.atFine().log("Created external Id: %s", extId);
     checkEmailNotUsed(newId, extId);
     ExternalId userNameExtId =
@@ -348,7 +356,7 @@
               "Cannot assign user name \"%s\" to account %s; name does not conform.",
               username, accountId));
     }
-    return ExternalId.create(SCHEME_USERNAME, username, accountId);
+    return externalIdFactory.create(SCHEME_USERNAME, username, accountId);
   }
 
   private void checkEmailNotUsed(Account.Id accountId, ExternalId extIdToBeCreated)
@@ -414,7 +422,7 @@
       update(who, extId);
     } else {
       ExternalId newExtId =
-          ExternalId.createWithEmail(who.getExternalIdKey(), to, who.getEmailAddress());
+          externalIdFactory.createWithEmail(who.getExternalIdKey(), to, who.getEmailAddress());
       checkEmailNotUsed(to, newExtId);
       accountsUpdateProvider
           .get()
diff --git a/java/com/google/gerrit/server/account/AccountModule.java b/java/com/google/gerrit/server/account/AccountModule.java
new file mode 100644
index 0000000..c1305cf
--- /dev/null
+++ b/java/com/google/gerrit/server/account/AccountModule.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import com.google.inject.AbstractModule;
+
+public class AccountModule extends AbstractModule {
+  @Override
+  protected void configure() {
+    bind(AuthRequest.Factory.class);
+  }
+}
diff --git a/java/com/google/gerrit/server/account/AccountTagProvider.java b/java/com/google/gerrit/server/account/AccountTagProvider.java
new file mode 100644
index 0000000..ddb1331
--- /dev/null
+++ b/java/com/google/gerrit/server/account/AccountTagProvider.java
@@ -0,0 +1,14 @@
+package com.google.gerrit.server.account;
+
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import java.util.List;
+
+/**
+ * An extension point for plugins to define their own account tags in addition to the ones defined
+ * at {@link com.google.gerrit.extensions.common.AccountInfo.Tags}.
+ */
+@ExtensionPoint
+public interface AccountTagProvider {
+  List<String> getTags(Account.Id id);
+}
diff --git a/java/com/google/gerrit/server/account/AuthRequest.java b/java/com/google/gerrit/server/account/AuthRequest.java
index ddb54a6..50ed532 100644
--- a/java/com/google/gerrit/server/account/AuthRequest.java
+++ b/java/com/google/gerrit/server/account/AuthRequest.java
@@ -21,6 +21,9 @@
 import com.google.common.base.Strings;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import java.util.Optional;
 
 /**
@@ -32,31 +35,52 @@
  * not all OpenID providers return them, and not all non-OpenID systems can use them.
  */
 public class AuthRequest {
-  /** Create a request for a local username, such as from LDAP. */
-  public static AuthRequest forUser(String username) {
-    AuthRequest r = new AuthRequest(ExternalId.Key.create(SCHEME_GERRIT, username));
-    r.setUserName(username);
-    return r;
+  @Singleton
+  public static class Factory {
+    private final ExternalIdKeyFactory externalIdKeyFactory;
+
+    @Inject
+    public Factory(ExternalIdKeyFactory externalIdKeyFactory) {
+      this.externalIdKeyFactory = externalIdKeyFactory;
+    }
+
+    public AuthRequest create(ExternalId.Key externalIdKey) {
+      return new AuthRequest(externalIdKey, externalIdKeyFactory);
+    }
+
+    /** Create a request for a local username, such as from LDAP. */
+    public AuthRequest createForUser(String username) {
+      AuthRequest r =
+          new AuthRequest(
+              externalIdKeyFactory.create(SCHEME_GERRIT, username), externalIdKeyFactory);
+      r.setUserName(username);
+      return r;
+    }
+
+    /** Create a request for an external username. */
+    public AuthRequest createForExternalUser(String username) {
+      AuthRequest r =
+          new AuthRequest(
+              externalIdKeyFactory.create(SCHEME_EXTERNAL, username), externalIdKeyFactory);
+      r.setUserName(username);
+      return r;
+    }
+
+    /**
+     * Create a request for an email address registration.
+     *
+     * <p>This type of request should be used only to attach a new email address to an existing user
+     * account.
+     */
+    public AuthRequest createForEmail(String email) {
+      AuthRequest r =
+          new AuthRequest(externalIdKeyFactory.create(SCHEME_MAILTO, email), externalIdKeyFactory);
+      r.setEmailAddress(email);
+      return r;
+    }
   }
 
-  /** Create a request for an external username. */
-  public static AuthRequest forExternalUser(String username) {
-    AuthRequest r = new AuthRequest(ExternalId.Key.create(SCHEME_EXTERNAL, username));
-    r.setUserName(username);
-    return r;
-  }
-
-  /**
-   * Create a request for an email address registration.
-   *
-   * <p>This type of request should be used only to attach a new email address to an existing user
-   * account.
-   */
-  public static AuthRequest forEmail(String email) {
-    AuthRequest r = new AuthRequest(ExternalId.Key.create(SCHEME_MAILTO, email));
-    r.setEmailAddress(email);
-    return r;
-  }
+  private final ExternalIdKeyFactory externalIdKeyFactory;
 
   private ExternalId.Key externalId;
   private String password;
@@ -69,8 +93,9 @@
   private boolean authProvidesAccountActiveStatus;
   private boolean active;
 
-  public AuthRequest(ExternalId.Key externalId) {
+  private AuthRequest(ExternalId.Key externalId, ExternalIdKeyFactory externalIdKeyFactory) {
     this.externalId = externalId;
+    this.externalIdKeyFactory = externalIdKeyFactory;
   }
 
   public ExternalId.Key getExternalIdKey() {
@@ -86,7 +111,7 @@
 
   public void setLocalUser(String localUser) {
     if (externalId.isScheme(SCHEME_GERRIT)) {
-      externalId = ExternalId.Key.create(SCHEME_GERRIT, localUser);
+      externalId = externalIdKeyFactory.create(SCHEME_GERRIT, localUser);
     }
   }
 
diff --git a/java/com/google/gerrit/server/account/InternalAccountDirectory.java b/java/com/google/gerrit/server/account/InternalAccountDirectory.java
index 13b71cf..9d684ac 100644
--- a/java/com/google/gerrit/server/account/InternalAccountDirectory.java
+++ b/java/com/google/gerrit/server/account/InternalAccountDirectory.java
@@ -14,17 +14,19 @@
 
 package com.google.gerrit.server.account;
 
+import static com.google.common.collect.Streams.stream;
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toSet;
+import static java.util.stream.Stream.concat;
 
 import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
-import com.google.common.collect.Streams;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.AccountInfo.Tags;
 import com.google.gerrit.extensions.common.AvatarInfo;
 import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
@@ -45,6 +47,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Stream;
 
 @Singleton
 public class InternalAccountDirectory extends AccountDirectory {
@@ -63,6 +66,7 @@
   private final Provider<CurrentUser> self;
   private final PermissionBackend permissionBackend;
   private final ServiceUserClassifier serviceUserClassifier;
+  private final DynamicMap<AccountTagProvider> accountTagProviders;
 
   @Inject
   InternalAccountDirectory(
@@ -71,13 +75,15 @@
       IdentifiedUser.GenericFactory userFactory,
       Provider<CurrentUser> self,
       PermissionBackend permissionBackend,
-      ServiceUserClassifier serviceUserClassifier) {
+      ServiceUserClassifier serviceUserClassifier,
+      DynamicMap<AccountTagProvider> accountTagProviders) {
     this.accountCache = accountCache;
     this.avatar = avatar;
     this.userFactory = userFactory;
     this.self = self;
     this.permissionBackend = permissionBackend;
     this.serviceUserClassifier = serviceUserClassifier;
+    this.accountTagProviders = accountTagProviders;
   }
 
   @Override
@@ -102,7 +108,7 @@
 
     Set<FillOptions> fillOptionsWithoutSecondaryEmails =
         Sets.difference(options, EnumSet.of(FillOptions.SECONDARY_EMAILS));
-    Set<Account.Id> ids = Streams.stream(in).map(a -> Account.id(a._accountId)).collect(toSet());
+    Set<Account.Id> ids = stream(in).map(a -> Account.id(a._accountId)).collect(toSet());
     Map<Account.Id, AccountState> accountStates = accountCache.get(ids);
     for (AccountInfo info : in) {
       Account.Id id = Account.id(info._accountId);
@@ -160,10 +166,10 @@
     }
 
     if (options.contains(FillOptions.TAGS)) {
-      info.tags =
-          serviceUserClassifier.isServiceUser(account.id())
-              ? ImmutableList.of(AccountInfo.Tag.SERVICE_USER)
-              : null;
+      List<String> tags = getTags(account.id());
+      if (!tags.isEmpty()) {
+        info.tags = tags;
+      }
     }
 
     if (options.contains(FillOptions.AVATARS)) {
@@ -194,6 +200,15 @@
         .collect(toList());
   }
 
+  private List<String> getTags(Account.Id id) {
+    Stream<String> tagsFromProviders =
+        stream(accountTagProviders.iterator())
+            .flatMap(accountTagProvider -> accountTagProvider.get().getTags(id).stream());
+    Stream<String> tagsFromServiceUserClassifier =
+        serviceUserClassifier.isServiceUser(id) ? Stream.of(Tags.SERVICE_USER) : Stream.empty();
+    return concat(tagsFromProviders, tagsFromServiceUserClassifier).collect(toList());
+  }
+
   private static void addAvatar(
       AvatarProvider provider, AccountInfo account, IdentifiedUser user, int size) {
     String url = provider.getUrl(user, size);
diff --git a/java/com/google/gerrit/server/account/externalids/AllExternalIds.java b/java/com/google/gerrit/server/account/externalids/AllExternalIds.java
index f1fc5cb..e718bcb 100644
--- a/java/com/google/gerrit/server/account/externalids/AllExternalIds.java
+++ b/java/com/google/gerrit/server/account/externalids/AllExternalIds.java
@@ -68,7 +68,8 @@
       ExternalIdProto.Builder b =
           ExternalIdProto.newBuilder()
               .setKey(externalId.key().get())
-              .setAccountId(externalId.accountId().get());
+              .setAccountId(externalId.accountId().get())
+              .setIsCaseInsensitive(externalId.isCaseInsensitive());
       if (externalId.email() != null) {
         b.setEmail(externalId.email());
       }
@@ -91,7 +92,7 @@
 
     private static ExternalId toExternalId(ObjectIdConverter idConverter, ExternalIdProto proto) {
       return ExternalId.create(
-          ExternalId.Key.parse(proto.getKey()),
+          ExternalId.Key.parse(proto.getKey(), proto.getIsCaseInsensitive()),
           Account.id(proto.getAccountId()),
           // ExternalId treats null and empty strings the same, so no need to distinguish here.
           proto.getEmail(),
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalId.java b/java/com/google/gerrit/server/account/externalids/ExternalId.java
index 268812c..bbee1b2 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalId.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalId.java
@@ -17,35 +17,28 @@
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.ImmutableSet.toImmutableSet;
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Objects.requireNonNull;
 
 import com.google.auto.value.AutoValue;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.flogger.FluentLogger;
 import com.google.common.hash.Hashing;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.extensions.client.AuthType;
 import com.google.gerrit.git.ObjectIds;
-import com.google.gerrit.server.account.HashedPassword;
 import java.io.Serializable;
 import java.util.Collection;
+import java.util.Locale;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.Set;
 import java.util.regex.Pattern;
 import java.util.stream.Stream;
-import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
 
 @AutoValue
 public abstract class ExternalId implements Serializable {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
   // If these regular expressions are modified the same modifications should be done to the
   // corresponding regular expressions in the
   // com.google.gerrit.client.account.UsernameField class.
@@ -106,10 +99,10 @@
 
   private static final long serialVersionUID = 1L;
 
-  private static final String EXTERNAL_ID_SECTION = "externalId";
-  private static final String ACCOUNT_ID_KEY = "accountId";
-  private static final String EMAIL_KEY = "email";
-  private static final String PASSWORD_KEY = "password";
+  static final String EXTERNAL_ID_SECTION = "externalId";
+  static final String ACCOUNT_ID_KEY = "accountId";
+  static final String EMAIL_KEY = "email";
+  static final String PASSWORD_KEY = "password";
 
   /**
    * Scheme used for {@link AuthType#LDAP}, {@link AuthType#CLIENT_SSL_CERT_LDAP}, {@link
@@ -118,6 +111,8 @@
    * <p>The name {@code gerrit:} was a very poor choice.
    *
    * <p>Scheme names must not contain colons (':').
+   *
+   * <p>Will be handled case insensitive, if auth.userNameCaseInsensitive = true.
    */
   public static final String SCHEME_GERRIT = "gerrit";
 
@@ -127,7 +122,11 @@
   /** Scheme used to represent only an email address. */
   public static final String SCHEME_MAILTO = "mailto";
 
-  /** Scheme for the username used to authenticate an account, e.g. over SSH. */
+  /**
+   * Scheme for the username used to authenticate an account, e.g. over SSH.
+   *
+   * <p>Will be handled case insensitive, if auth.userNameCaseInsensitive = true.
+   */
   public static final String SCHEME_USERNAME = "username";
 
   /** Scheme used for GPG public keys. */
@@ -154,10 +153,12 @@
      *
      * @param scheme the scheme name, must not contain colons (':'), can be {@code null}
      * @param id the external ID, must not contain colons (':')
+     * @param isCaseInsensitive whether the external ID key is matched case insensitively
      * @return the created external ID key
      */
-    public static Key create(@Nullable String scheme, String id) {
-      return new AutoValue_ExternalId_Key(Strings.emptyToNull(scheme), id);
+    @VisibleForTesting
+    public static Key create(@Nullable String scheme, String id, boolean isCaseInsensitive) {
+      return new AutoValue_ExternalId_Key(Strings.emptyToNull(scheme), id, isCaseInsensitive);
     }
 
     /**
@@ -165,18 +166,21 @@
      *
      * @return the parsed external ID key
      */
-    public static Key parse(String externalId) {
+    @VisibleForTesting
+    public static Key parse(String externalId, boolean isCaseInsensitive) {
       int c = externalId.indexOf(':');
       if (c < 1 || c >= externalId.length() - 1) {
-        return create(null, externalId);
+        return create(null, externalId, isCaseInsensitive);
       }
-      return create(externalId.substring(0, c), externalId.substring(c + 1));
+      return create(externalId.substring(0, c), externalId.substring(c + 1), isCaseInsensitive);
     }
 
     public abstract @Nullable String scheme();
 
     public abstract String id();
 
+    public abstract boolean isCaseInsensitive();
+
     public boolean isScheme(String scheme) {
       return scheme.equals(scheme());
     }
@@ -187,7 +191,8 @@
      */
     @SuppressWarnings("deprecation") // Use Hashing.sha1 for compatibility.
     public ObjectId sha1() {
-      return ObjectId.fromRaw(Hashing.sha1().hashString(get(), UTF_8).asBytes());
+      String keyString = isCaseInsensitive() ? get().toLowerCase(Locale.US) : get();
+      return ObjectId.fromRaw(Hashing.sha1().hashString(keyString, UTF_8).asBytes());
     }
 
     /**
@@ -209,100 +214,26 @@
       return get();
     }
 
+    @Override
+    public final boolean equals(Object obj) {
+      if (!(obj instanceof ExternalId.Key)) {
+        return false;
+      }
+      ExternalId.Key o = (ExternalId.Key) obj;
+
+      return sha1().equals(o.sha1());
+    }
+
+    @Override
+    public final int hashCode() {
+      return Objects.hash(sha1());
+    }
+
     public static ImmutableSet<ExternalId.Key> from(Collection<ExternalId> extIds) {
       return extIds.stream().map(ExternalId::key).collect(toImmutableSet());
     }
   }
 
-  /**
-   * Creates an external ID.
-   *
-   * @param scheme the scheme name, must not contain colons (':')
-   * @param id the external ID, must not contain colons (':')
-   * @param accountId the ID of the account to which the external ID belongs
-   * @return the created external ID
-   */
-  public static ExternalId create(String scheme, String id, Account.Id accountId) {
-    return create(Key.create(scheme, id), accountId, null, null);
-  }
-
-  /**
-   * Creates an external ID.
-   *
-   * @param scheme the scheme name, must not contain colons (':')
-   * @param id the external ID, must not contain colons (':')
-   * @param accountId the ID of the account to which the external ID belongs
-   * @param email the email of the external ID, may be {@code null}
-   * @param hashedPassword the hashed password of the external ID, may be {@code null}
-   * @return the created external ID
-   */
-  public static ExternalId create(
-      String scheme,
-      String id,
-      Account.Id accountId,
-      @Nullable String email,
-      @Nullable String hashedPassword) {
-    return create(Key.create(scheme, id), accountId, email, hashedPassword);
-  }
-
-  public static ExternalId create(Key key, Account.Id accountId) {
-    return create(key, accountId, null, null);
-  }
-
-  public static ExternalId create(
-      Key key, Account.Id accountId, @Nullable String email, @Nullable String hashedPassword) {
-    return create(
-        key, accountId, Strings.emptyToNull(email), Strings.emptyToNull(hashedPassword), null);
-  }
-
-  public static ExternalId createWithPassword(
-      Key key, Account.Id accountId, @Nullable String email, @Nullable String plainPassword) {
-    plainPassword = Strings.emptyToNull(plainPassword);
-    String hashedPassword =
-        plainPassword != null ? HashedPassword.fromPassword(plainPassword).encode() : null;
-    return create(key, accountId, email, hashedPassword);
-  }
-
-  /**
-   * Create a external ID for a username (scheme "username").
-   *
-   * @param id the external ID, must not contain colons (':')
-   * @param accountId the ID of the account to which the external ID belongs
-   * @param plainPassword the plain HTTP password, may be {@code null}
-   * @return the created external ID
-   */
-  public static ExternalId createUsername(
-      String id, Account.Id accountId, @Nullable String plainPassword) {
-    return createWithPassword(Key.create(SCHEME_USERNAME, id), accountId, null, plainPassword);
-  }
-
-  /**
-   * Creates an external ID with an email.
-   *
-   * @param scheme the scheme name, must not contain colons (':')
-   * @param id the external ID, must not contain colons (':')
-   * @param accountId the ID of the account to which the external ID belongs
-   * @param email the email of the external ID, may be {@code null}
-   * @return the created external ID
-   */
-  public static ExternalId createWithEmail(
-      String scheme, String id, Account.Id accountId, @Nullable String email) {
-    return createWithEmail(Key.create(scheme, id), accountId, email);
-  }
-
-  public static ExternalId createWithEmail(Key key, Account.Id accountId, @Nullable String email) {
-    return create(key, accountId, Strings.emptyToNull(email), null);
-  }
-
-  public static ExternalId createEmail(Account.Id accountId, String email) {
-    return createWithEmail(SCHEME_MAILTO, email, accountId, requireNonNull(email));
-  }
-
-  static ExternalId create(ExternalId extId, @Nullable ObjectId blobId) {
-    return new AutoValue_ExternalId(
-        extId.key(), extId.accountId(), extId.email(), extId.password(), blobId);
-  }
-
   @VisibleForTesting
   public static ExternalId create(
       Key key,
@@ -311,111 +242,20 @@
       @Nullable String hashedPassword,
       @Nullable ObjectId blobId) {
     return new AutoValue_ExternalId(
-        key, accountId, Strings.emptyToNull(email), Strings.emptyToNull(hashedPassword), blobId);
-  }
-
-  /**
-   * Parses an external ID from a byte array that contain the external ID as an Git config file
-   * text.
-   *
-   * <p>The Git config must have exactly one externalId subsection with an accountId and optionally
-   * email and password:
-   *
-   * <pre>
-   * [externalId "username:jdoe"]
-   *   accountId = 1003407
-   *   email = jdoe@example.com
-   *   password = bcrypt:4:LCbmSBDivK/hhGVQMfkDpA==:XcWn0pKYSVU/UJgOvhidkEtmqCp6oKB7
-   * </pre>
-   */
-  public static ExternalId parse(String noteId, byte[] raw, ObjectId blobId)
-      throws ConfigInvalidException {
-    requireNonNull(blobId);
-
-    Config externalIdConfig = new Config();
-    try {
-      externalIdConfig.fromText(new String(raw, UTF_8));
-    } catch (ConfigInvalidException e) {
-      throw invalidConfig(noteId, e.getMessage());
-    }
-
-    Set<String> externalIdKeys = externalIdConfig.getSubsections(EXTERNAL_ID_SECTION);
-    if (externalIdKeys.size() != 1) {
-      throw invalidConfig(
-          noteId,
-          String.format(
-              "Expected exactly 1 '%s' section, found %d",
-              EXTERNAL_ID_SECTION, externalIdKeys.size()));
-    }
-
-    String externalIdKeyStr = Iterables.getOnlyElement(externalIdKeys);
-    Key externalIdKey = Key.parse(externalIdKeyStr);
-    if (externalIdKey == null) {
-      throw invalidConfig(noteId, String.format("External ID %s is invalid", externalIdKeyStr));
-    }
-
-    if (!externalIdKey.sha1().getName().equals(noteId)) {
-      throw invalidConfig(
-          noteId,
-          String.format(
-              "SHA1 of external ID '%s' does not match note ID '%s'", externalIdKeyStr, noteId));
-    }
-
-    String email = externalIdConfig.getString(EXTERNAL_ID_SECTION, externalIdKeyStr, EMAIL_KEY);
-    String password =
-        externalIdConfig.getString(EXTERNAL_ID_SECTION, externalIdKeyStr, PASSWORD_KEY);
-    int accountId = readAccountId(noteId, externalIdConfig, externalIdKeyStr);
-
-    return create(
-        externalIdKey,
-        Account.id(accountId),
+        key,
+        accountId,
+        key.isCaseInsensitive(),
         Strings.emptyToNull(email),
-        Strings.emptyToNull(password),
+        Strings.emptyToNull(hashedPassword),
         blobId);
   }
 
-  private static int readAccountId(String noteId, Config externalIdConfig, String externalIdKeyStr)
-      throws ConfigInvalidException {
-    String accountIdStr =
-        externalIdConfig.getString(EXTERNAL_ID_SECTION, externalIdKeyStr, ACCOUNT_ID_KEY);
-    if (accountIdStr == null) {
-      throw invalidConfig(
-          noteId,
-          String.format(
-              "Value for '%s.%s.%s' is missing, expected account ID",
-              EXTERNAL_ID_SECTION, externalIdKeyStr, ACCOUNT_ID_KEY));
-    }
-
-    try {
-      int accountId =
-          externalIdConfig.getInt(EXTERNAL_ID_SECTION, externalIdKeyStr, ACCOUNT_ID_KEY, -1);
-      if (accountId < 0) {
-        throw invalidConfig(
-            noteId,
-            String.format(
-                "Value %s for '%s.%s.%s' is invalid, expected account ID",
-                accountIdStr, EXTERNAL_ID_SECTION, externalIdKeyStr, ACCOUNT_ID_KEY));
-      }
-      return accountId;
-    } catch (IllegalArgumentException e) {
-      String msg =
-          String.format(
-              "Value %s for '%s.%s.%s' is invalid, expected account ID",
-              accountIdStr, EXTERNAL_ID_SECTION, externalIdKeyStr, ACCOUNT_ID_KEY);
-      logger.atSevere().withCause(e).log(msg);
-      throw invalidConfig(noteId, msg);
-    }
-  }
-
-  private static ConfigInvalidException invalidConfig(String noteId, String message) {
-    return new ConfigInvalidException(
-        String.format("Invalid external ID config for note '%s': %s", noteId, message));
-  }
-
   public abstract Key key();
 
   public abstract Account.Id accountId();
 
+  public abstract boolean isCaseInsensitive();
+
   public abstract @Nullable String email();
 
   public abstract @Nullable String password();
@@ -456,13 +296,14 @@
     ExternalId o = (ExternalId) obj;
     return Objects.equals(key(), o.key())
         && Objects.equals(accountId(), o.accountId())
+        && Objects.equals(isCaseInsensitive(), o.isCaseInsensitive())
         && Objects.equals(email(), o.email())
         && Objects.equals(password(), o.password());
   }
 
   @Override
   public final int hashCode() {
-    return Objects.hash(key(), accountId(), email(), password());
+    return Objects.hash(key(), accountId(), isCaseInsensitive(), email(), password());
   }
 
   /**
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdCacheLoader.java b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheLoader.java
index 902c18b..5d81a25 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdCacheLoader.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheLoader.java
@@ -73,6 +73,7 @@
   private final Timer0 reloadDifferential;
   private final boolean enablePartialReloads;
   private final boolean isPersistentCache;
+  private final ExternalIdFactory externalIdFactory;
 
   @Inject
   ExternalIdCacheLoader(
@@ -82,7 +83,8 @@
       @Named(ExternalIdCacheImpl.CACHE_NAME)
           Provider<Cache<ObjectId, AllExternalIds>> externalIdCache,
       MetricMaker metricMaker,
-      @GerritServerConfig Config config) {
+      @GerritServerConfig Config config,
+      ExternalIdFactory externalIdFactory) {
     this.externalIdReader = externalIdReader;
     this.externalIdCache = externalIdCache;
     this.gitRepositoryManager = gitRepositoryManager;
@@ -105,6 +107,7 @@
         config.getBoolean("cache", ExternalIdCacheImpl.CACHE_NAME, "enablePartialReloads", true);
     this.isPersistentCache =
         config.getInt("cache", ExternalIdCacheImpl.CACHE_NAME, "diskLimit", 0) > 0;
+    this.externalIdFactory = externalIdFactory;
   }
 
   @Override
@@ -216,7 +219,7 @@
    * @param additions map of name to blob ID for each external ID that should be added
    * @param removals set of name {@link ObjectId}s that should be removed
    */
-  private static AllExternalIds buildAllExternalIds(
+  private AllExternalIds buildAllExternalIds(
       Repository repo,
       AllExternalIds oldExternalIds,
       Map<ObjectId, ObjectId> additions,
@@ -245,7 +248,7 @@
         ExternalId parsedExternalId;
         try {
           parsedExternalId =
-              ExternalId.parse(
+              externalIdFactory.parse(
                   nameToBlob.getKey().name(),
                   reader.open(nameToBlob.getValue()).getCachedBytes(),
                   nameToBlob.getValue());
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdCacheModule.java b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheModule.java
new file mode 100644
index 0000000..f0ad1b2
--- /dev/null
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheModule.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account.externalids;
+
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.cache.serialize.ObjectIdCacheSerializer;
+import com.google.inject.TypeLiteral;
+import java.time.Duration;
+import org.eclipse.jgit.lib.ObjectId;
+
+public class ExternalIdCacheModule extends CacheModule {
+  @Override
+  protected void configure() {
+    persist(ExternalIdCacheImpl.CACHE_NAME, ObjectId.class, new TypeLiteral<AllExternalIds>() {})
+        // The cached data is potentially pretty large and we are always only interested
+        // in the latest value. However, due to a race condition, it is possible for different
+        // threads to observe different values of the meta ref, and hence request different keys
+        // from the cache. Extend the cache size by 1 to cover this case, but expire the extra
+        // object after a short period of time, since it may be a potentially large amount of
+        // memory.
+        // When loading a new value because the primary data advanced, we want to leverage the old
+        // cache state to recompute only what changed. This doesn't affect cache size though as
+        // Guava calls the loader first and evicts later on.
+        .maximumWeight(2)
+        .expireFromMemoryAfterAccess(Duration.ofMinutes(1))
+        .loader(ExternalIdCacheLoader.class)
+        .diskLimit(-1)
+        .version(1)
+        .keySerializer(ObjectIdCacheSerializer.INSTANCE)
+        .valueSerializer(AllExternalIds.Serializer.INSTANCE);
+
+    bind(ExternalIdCacheImpl.class);
+    bind(ExternalIdCache.class).to(ExternalIdCacheImpl.class);
+  }
+}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdFactory.java b/java/com/google/gerrit/server/account/externalids/ExternalIdFactory.java
new file mode 100644
index 0000000..0c96f58
--- /dev/null
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdFactory.java
@@ -0,0 +1,314 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account.externalids;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.server.account.HashedPassword;
+import com.google.gerrit.server.account.externalids.ExternalId.Key;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.Set;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+
+@Singleton
+public class ExternalIdFactory {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+  private final ExternalIdKeyFactory externalIdKeyFactory;
+
+  @Inject
+  public ExternalIdFactory(ExternalIdKeyFactory externalIdKeyFactory) {
+    this.externalIdKeyFactory = externalIdKeyFactory;
+  }
+
+  /**
+   * Creates an external ID.
+   *
+   * @param scheme the scheme name, must not contain colons (':'). E.g. {@link
+   *     ExternalId#SCHEME_USERNAME}.
+   * @param id the external ID, must not contain colons (':')
+   * @param accountId the ID of the account to which the external ID belongs
+   * @return the created external ID
+   */
+  public ExternalId create(String scheme, String id, Account.Id accountId) {
+    return create(externalIdKeyFactory.create(scheme, id), accountId, null, null);
+  }
+
+  /**
+   * Creates an external ID.
+   *
+   * @param scheme the scheme name, must not contain colons (':'). E.g. {@link
+   *     ExternalId#SCHEME_USERNAME}.
+   * @param id the external ID, must not contain colons (':')
+   * @param accountId the ID of the account to which the external ID belongs
+   * @param email the email of the external ID, may be {@code null}
+   * @param hashedPassword the hashed password of the external ID, may be {@code null}
+   * @return the created external ID
+   */
+  public ExternalId create(
+      String scheme,
+      String id,
+      Account.Id accountId,
+      @Nullable String email,
+      @Nullable String hashedPassword) {
+    return create(externalIdKeyFactory.create(scheme, id), accountId, email, hashedPassword);
+  }
+
+  /**
+   * Creates an external ID.
+   *
+   * @param key the external Id key
+   * @param accountId the ID of the account to which the external ID belongs
+   * @return the created external ID
+   */
+  public ExternalId create(Key key, Account.Id accountId) {
+    return create(key, accountId, null, null);
+  }
+
+  /**
+   * Creates an external ID.
+   *
+   * @param key the external Id key
+   * @param accountId the ID of the account to which the external ID belongs
+   * @param email the email of the external ID, may be {@code null}
+   * @param hashedPassword the hashed password of the external ID, may be {@code null}
+   * @return the created external ID
+   */
+  public ExternalId create(
+      Key key, Account.Id accountId, @Nullable String email, @Nullable String hashedPassword) {
+    return create(
+        key, accountId, Strings.emptyToNull(email), Strings.emptyToNull(hashedPassword), null);
+  }
+
+  /**
+   * Creates an external ID adding a hashed password computed from a plain password.
+   *
+   * @param key the external Id key
+   * @param accountId the ID of the account to which the external ID belongs
+   * @param email the email of the external ID, may be {@code null}
+   * @param plainPassword the plain HTTP password, may be {@code null}
+   * @return the created external ID
+   */
+  public ExternalId createWithPassword(
+      Key key, Account.Id accountId, @Nullable String email, @Nullable String plainPassword) {
+    plainPassword = Strings.emptyToNull(plainPassword);
+    String hashedPassword =
+        plainPassword != null ? HashedPassword.fromPassword(plainPassword).encode() : null;
+    return create(key, accountId, email, hashedPassword);
+  }
+
+  /**
+   * Create a external ID for a username (scheme "username").
+   *
+   * @param id the external ID, must not contain colons (':')
+   * @param accountId the ID of the account to which the external ID belongs
+   * @param plainPassword the plain HTTP password, may be {@code null}
+   * @return the created external ID
+   */
+  public ExternalId createUsername(
+      String id, Account.Id accountId, @Nullable String plainPassword) {
+    return createWithPassword(
+        externalIdKeyFactory.create(ExternalId.SCHEME_USERNAME, id),
+        accountId,
+        null,
+        plainPassword);
+  }
+
+  /**
+   * Creates an external ID with an email.
+   *
+   * @param scheme the scheme name, must not contain colons (':'). E.g. {@link
+   *     ExternalId#SCHEME_USERNAME}.
+   * @param id the external ID, must not contain colons (':')
+   * @param accountId the ID of the account to which the external ID belongs
+   * @param email the email of the external ID, may be {@code null}
+   * @return the created external ID
+   */
+  public ExternalId createWithEmail(
+      String scheme, String id, Account.Id accountId, @Nullable String email) {
+    return createWithEmail(externalIdKeyFactory.create(scheme, id), accountId, email);
+  }
+
+  /**
+   * Creates an external ID with an email.
+   *
+   * @param key the external Id key
+   * @param accountId the ID of the account to which the external ID belongs
+   * @param email the email of the external ID, may be {@code null}
+   * @return the created external ID
+   */
+  public ExternalId createWithEmail(Key key, Account.Id accountId, @Nullable String email) {
+    return create(key, accountId, Strings.emptyToNull(email), null);
+  }
+
+  /**
+   * Creates an external ID using the `mailto`-scheme.
+   *
+   * @param accountId the ID of the account to which the external ID belongs
+   * @param email the email of the external ID, may be {@code null}
+   * @return the created external ID
+   */
+  public ExternalId createEmail(Account.Id accountId, String email) {
+    return createWithEmail(ExternalId.SCHEME_MAILTO, email, accountId, requireNonNull(email));
+  }
+
+  ExternalId create(ExternalId extId, @Nullable ObjectId blobId) {
+    return create(extId.key(), extId.accountId(), extId.email(), extId.password(), blobId);
+  }
+
+  /**
+   * Creates an external ID.
+   *
+   * @param key the external Id key
+   * @param accountId the ID of the account to which the external ID belongs
+   * @param email the email of the external ID, may be {@code null}
+   * @param hashedPassword the hashed password of the external ID, may be {@code null}
+   * @param blobId the ID of the note blob in the external IDs branch that stores this external ID.
+   *     {@code null} if the external ID was created in code and is not yet stored in Git.
+   * @return the created external ID
+   */
+  public ExternalId create(
+      Key key,
+      Account.Id accountId,
+      @Nullable String email,
+      @Nullable String hashedPassword,
+      @Nullable ObjectId blobId) {
+    return ExternalId.create(
+        key, accountId, Strings.emptyToNull(email), Strings.emptyToNull(hashedPassword), blobId);
+  }
+
+  /**
+   * Parses an external ID from a byte array that contains the external ID as a Git config file
+   * text.
+   *
+   * <p>The Git config must have exactly one externalId subsection with an accountId and optionally
+   * email and password:
+   *
+   * <pre>
+   * [externalId "username:jdoe"]
+   *   accountId = 1003407
+   *   email = jdoe@example.com
+   *   password = bcrypt:4:LCbmSBDivK/hhGVQMfkDpA==:XcWn0pKYSVU/UJgOvhidkEtmqCp6oKB7
+   * </pre>
+   *
+   * @param noteId the SHA-1 sum of the external ID used as the note's ID
+   * @param raw a byte array that contains the external ID as a Git config file text.
+   * @param blobId the ID of the note blob in the external IDs branch that stores this external ID.
+   *     {@code null} if the external ID was created in code and is not yet stored in Git.
+   * @return the parsed external ID
+   */
+  public ExternalId parse(String noteId, byte[] raw, ObjectId blobId)
+      throws ConfigInvalidException {
+    requireNonNull(blobId);
+
+    Config externalIdConfig = new Config();
+    try {
+      externalIdConfig.fromText(new String(raw, UTF_8));
+    } catch (ConfigInvalidException e) {
+      throw invalidConfig(noteId, e.getMessage());
+    }
+
+    Set<String> externalIdKeys = externalIdConfig.getSubsections(ExternalId.EXTERNAL_ID_SECTION);
+    if (externalIdKeys.size() != 1) {
+      throw invalidConfig(
+          noteId,
+          String.format(
+              "Expected exactly 1 '%s' section, found %d",
+              ExternalId.EXTERNAL_ID_SECTION, externalIdKeys.size()));
+    }
+
+    String externalIdKeyStr = Iterables.getOnlyElement(externalIdKeys);
+    Key externalIdKey = externalIdKeyFactory.parse(externalIdKeyStr);
+    if (externalIdKey == null) {
+      throw invalidConfig(noteId, String.format("External ID %s is invalid", externalIdKeyStr));
+    }
+
+    if (!externalIdKey.sha1().getName().equals(noteId)) {
+      throw invalidConfig(
+          noteId,
+          String.format(
+              "SHA1 of external ID '%s' does not match note ID '%s'", externalIdKeyStr, noteId));
+    }
+
+    String email =
+        externalIdConfig.getString(
+            ExternalId.EXTERNAL_ID_SECTION, externalIdKeyStr, ExternalId.EMAIL_KEY);
+    String password =
+        externalIdConfig.getString(
+            ExternalId.EXTERNAL_ID_SECTION, externalIdKeyStr, ExternalId.PASSWORD_KEY);
+    int accountId = readAccountId(noteId, externalIdConfig, externalIdKeyStr);
+
+    return create(
+        externalIdKey,
+        Account.id(accountId),
+        Strings.emptyToNull(email),
+        Strings.emptyToNull(password),
+        blobId);
+  }
+
+  private static int readAccountId(String noteId, Config externalIdConfig, String externalIdKeyStr)
+      throws ConfigInvalidException {
+    String accountIdStr =
+        externalIdConfig.getString(
+            ExternalId.EXTERNAL_ID_SECTION, externalIdKeyStr, ExternalId.ACCOUNT_ID_KEY);
+    if (accountIdStr == null) {
+      throw invalidConfig(
+          noteId,
+          String.format(
+              "Value for '%s.%s.%s' is missing, expected account ID",
+              ExternalId.EXTERNAL_ID_SECTION, externalIdKeyStr, ExternalId.ACCOUNT_ID_KEY));
+    }
+
+    try {
+      int accountId =
+          externalIdConfig.getInt(
+              ExternalId.EXTERNAL_ID_SECTION, externalIdKeyStr, ExternalId.ACCOUNT_ID_KEY, -1);
+      if (accountId < 0) {
+        throw invalidConfig(
+            noteId,
+            String.format(
+                "Value %s for '%s.%s.%s' is invalid, expected account ID",
+                accountIdStr,
+                ExternalId.EXTERNAL_ID_SECTION,
+                externalIdKeyStr,
+                ExternalId.ACCOUNT_ID_KEY));
+      }
+      return accountId;
+    } catch (IllegalArgumentException e) {
+      String msg =
+          String.format(
+              "Value %s for '%s.%s.%s' is invalid, expected account ID",
+              accountIdStr,
+              ExternalId.EXTERNAL_ID_SECTION,
+              externalIdKeyStr,
+              ExternalId.ACCOUNT_ID_KEY);
+      logger.atSevere().withCause(e).log(msg);
+      throw invalidConfig(noteId, msg);
+    }
+  }
+
+  private static ConfigInvalidException invalidConfig(String noteId, String message) {
+    return new ConfigInvalidException(
+        String.format("Invalid external ID config for note '%s': %s", noteId, message));
+  }
+}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdKeyFactory.java b/java/com/google/gerrit/server/account/externalids/ExternalIdKeyFactory.java
new file mode 100644
index 0000000..37c2604
--- /dev/null
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdKeyFactory.java
@@ -0,0 +1,61 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account.externalids;
+
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.server.config.AuthConfig;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class ExternalIdKeyFactory {
+  private final boolean isUserNameCaseInsensitive;
+
+  @Inject
+  public ExternalIdKeyFactory(AuthConfig authConfig) {
+    this.isUserNameCaseInsensitive = authConfig.isUserNameCaseInsensitive();
+  }
+
+  /**
+   * Creates an external ID key.
+   *
+   * @param scheme the scheme name, must not contain colons (':'). E.g. {@link
+   *     ExternalId#SCHEME_USERNAME}.
+   * @param id the external ID, must not contain colons (':')
+   * @return the created external ID key
+   */
+  public ExternalId.Key create(@Nullable String scheme, String id) {
+    if (scheme != null
+        && (scheme.equals(ExternalId.SCHEME_USERNAME) || scheme.equals(ExternalId.SCHEME_GERRIT))) {
+      return ExternalId.Key.create(scheme, id, isUserNameCaseInsensitive);
+    }
+
+    return ExternalId.Key.create(scheme, id, false);
+  }
+
+  /**
+   * Parses an external ID key from its String representation
+   *
+   * @param externalId String representation of external ID key (e.g. username:johndoe)
+   * @return the external Id key object
+   */
+  public ExternalId.Key parse(String externalId) {
+    int c = externalId.indexOf(':');
+    if (c < 1 || c >= externalId.length() - 1) {
+      return create(null, externalId);
+    }
+    return create(externalId.substring(0, c), externalId.substring(c + 1));
+  }
+}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdModule.java b/java/com/google/gerrit/server/account/externalids/ExternalIdModule.java
index 3e5d7b8..da7b357 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdModule.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdModule.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,34 +14,13 @@
 
 package com.google.gerrit.server.account.externalids;
 
-import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.serialize.ObjectIdCacheSerializer;
-import com.google.inject.TypeLiteral;
-import java.time.Duration;
-import org.eclipse.jgit.lib.ObjectId;
+import com.google.inject.AbstractModule;
 
-public class ExternalIdModule extends CacheModule {
+public class ExternalIdModule extends AbstractModule {
   @Override
   protected void configure() {
-    persist(ExternalIdCacheImpl.CACHE_NAME, ObjectId.class, new TypeLiteral<AllExternalIds>() {})
-        // The cached data is potentially pretty large and we are always only interested
-        // in the latest value. However, due to a race condition, it is possible for different
-        // threads to observe different values of the meta ref, and hence request different keys
-        // from the cache. Extend the cache size by 1 to cover this case, but expire the extra
-        // object after a short period of time, since it may be a potentially large amount of
-        // memory.
-        // When loading a new value because the primary data advanced, we want to leverage the old
-        // cache state to recompute only what changed. This doesn't affect cache size though as
-        // Guava calls the loader first and evicts later on.
-        .maximumWeight(2)
-        .expireFromMemoryAfterAccess(Duration.ofMinutes(1))
-        .loader(ExternalIdCacheLoader.class)
-        .diskLimit(-1)
-        .version(1)
-        .keySerializer(ObjectIdCacheSerializer.INSTANCE)
-        .valueSerializer(AllExternalIds.Serializer.INSTANCE);
-
-    bind(ExternalIdCacheImpl.class);
-    bind(ExternalIdCache.class).to(ExternalIdCacheImpl.class);
+    bind(ExternalIdFactory.class);
+    bind(ExternalIdKeyFactory.class);
+    bind(PasswordVerifier.class);
   }
 }
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
index 435a524..2b9c00a9 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
@@ -99,16 +99,19 @@
     protected final MetricMaker metricMaker;
     protected final AllUsersName allUsersName;
     protected final DynamicMap<ExternalIdUpsertPreprocessor> upsertPreprocessors;
+    protected final ExternalIdFactory externalIdFactory;
 
     protected ExternalIdNotesLoader(
         ExternalIdCache externalIdCache,
         MetricMaker metricMaker,
         AllUsersName allUsersName,
-        DynamicMap<ExternalIdUpsertPreprocessor> upsertPreprocessors) {
+        DynamicMap<ExternalIdUpsertPreprocessor> upsertPreprocessors,
+        ExternalIdFactory externalIdFactory) {
       this.externalIdCache = externalIdCache;
       this.metricMaker = metricMaker;
       this.allUsersName = allUsersName;
       this.upsertPreprocessors = upsertPreprocessors;
+      this.externalIdFactory = externalIdFactory;
     }
 
     /**
@@ -199,22 +202,25 @@
         Provider<AccountIndexer> accountIndexer,
         MetricMaker metricMaker,
         AllUsersName allUsersName,
-        DynamicMap<ExternalIdUpsertPreprocessor> upsertPreprocessors) {
-      super(externalIdCache, metricMaker, allUsersName, upsertPreprocessors);
+        DynamicMap<ExternalIdUpsertPreprocessor> upsertPreprocessors,
+        ExternalIdFactory externalIdFactory) {
+      super(externalIdCache, metricMaker, allUsersName, upsertPreprocessors, externalIdFactory);
       this.accountIndexer = accountIndexer;
     }
 
     @Override
     public ExternalIdNotes load(Repository allUsersRepo)
         throws IOException, ConfigInvalidException {
-      return new ExternalIdNotes(metricMaker, allUsersName, allUsersRepo, upsertPreprocessors)
+      return new ExternalIdNotes(
+              metricMaker, allUsersName, allUsersRepo, upsertPreprocessors, externalIdFactory)
           .load();
     }
 
     @Override
     public ExternalIdNotes load(Repository allUsersRepo, @Nullable ObjectId rev)
         throws IOException, ConfigInvalidException {
-      return new ExternalIdNotes(metricMaker, allUsersName, allUsersRepo, upsertPreprocessors)
+      return new ExternalIdNotes(
+              metricMaker, allUsersName, allUsersRepo, upsertPreprocessors, externalIdFactory)
           .load(rev);
     }
 
@@ -232,14 +238,16 @@
         ExternalIdCache externalIdCache,
         MetricMaker metricMaker,
         AllUsersName allUsersName,
-        DynamicMap<ExternalIdUpsertPreprocessor> upsertPreprocessors) {
-      super(externalIdCache, metricMaker, allUsersName, upsertPreprocessors);
+        DynamicMap<ExternalIdUpsertPreprocessor> upsertPreprocessors,
+        ExternalIdFactory externalIdFactory) {
+      super(externalIdCache, metricMaker, allUsersName, upsertPreprocessors, externalIdFactory);
     }
 
     @Override
     public ExternalIdNotes load(Repository allUsersRepo)
         throws IOException, ConfigInvalidException {
-      return new ExternalIdNotes(metricMaker, allUsersName, allUsersRepo, upsertPreprocessors)
+      return new ExternalIdNotes(
+              metricMaker, allUsersName, allUsersRepo, upsertPreprocessors, externalIdFactory)
           .setNoReindex()
           .load();
     }
@@ -247,7 +255,8 @@
     @Override
     public ExternalIdNotes load(Repository allUsersRepo, @Nullable ObjectId rev)
         throws IOException, ConfigInvalidException {
-      return new ExternalIdNotes(metricMaker, allUsersName, allUsersRepo, upsertPreprocessors)
+      return new ExternalIdNotes(
+              metricMaker, allUsersName, allUsersRepo, upsertPreprocessors, externalIdFactory)
           .setNoReindex()
           .load(rev);
     }
@@ -269,10 +278,17 @@
    * @return read-only {@link ExternalIdNotes} instance
    */
   public static ExternalIdNotes loadReadOnly(
-      AllUsersName allUsersName, Repository allUsersRepo, @Nullable ObjectId rev)
+      AllUsersName allUsersName,
+      Repository allUsersRepo,
+      @Nullable ObjectId rev,
+      ExternalIdFactory externalIdFactory)
       throws IOException, ConfigInvalidException {
     return new ExternalIdNotes(
-            new DisabledMetricMaker(), allUsersName, allUsersRepo, DynamicMap.emptyMap())
+            new DisabledMetricMaker(),
+            allUsersName,
+            allUsersRepo,
+            DynamicMap.emptyMap(),
+            externalIdFactory)
         .setReadOnly()
         .setNoCacheUpdate()
         .setNoReindex()
@@ -290,10 +306,14 @@
    * @return {@link ExternalIdNotes} instance that doesn't updates caches on save
    */
   public static ExternalIdNotes loadNoCacheUpdate(
-      AllUsersName allUsersName, Repository allUsersRepo)
+      AllUsersName allUsersName, Repository allUsersRepo, ExternalIdFactory externalIdFactory)
       throws IOException, ConfigInvalidException {
     return new ExternalIdNotes(
-            new DisabledMetricMaker(), allUsersName, allUsersRepo, DynamicMap.emptyMap())
+            new DisabledMetricMaker(),
+            allUsersName,
+            allUsersRepo,
+            DynamicMap.emptyMap(),
+            externalIdFactory)
         .setNoCacheUpdate()
         .setNoReindex()
         .load();
@@ -304,6 +324,7 @@
   private final Repository repo;
   private final DynamicMap<ExternalIdUpsertPreprocessor> upsertPreprocessors;
   private final CallerFinder callerFinder;
+  private final ExternalIdFactory externalIdFactory;
 
   private NoteMap noteMap;
   private ObjectId oldRev;
@@ -334,7 +355,8 @@
       MetricMaker metricMaker,
       AllUsersName allUsersName,
       Repository allUsersRepo,
-      DynamicMap<ExternalIdUpsertPreprocessor> upsertPreprocessors) {
+      DynamicMap<ExternalIdUpsertPreprocessor> upsertPreprocessors,
+      ExternalIdFactory externalIdFactory) {
     this.updateCount =
         metricMaker.newCounter(
             "notedb/external_id_update_count",
@@ -355,6 +377,7 @@
             // 3. direct callers
             .addTarget(ExternalIdNotes.class)
             .build();
+    this.externalIdFactory = externalIdFactory;
   }
 
   public ExternalIdNotes setAfterReadRevision(Runnable afterReadRevision) {
@@ -434,7 +457,7 @@
     try (RevWalk rw = new RevWalk(repo)) {
       ObjectId noteDataId = noteMap.get(noteId);
       byte[] raw = readNoteData(rw, noteDataId);
-      return Optional.of(ExternalId.parse(noteId.name(), raw, noteDataId));
+      return Optional.of(externalIdFactory.parse(noteId.name(), raw, noteDataId));
     }
   }
 
@@ -468,7 +491,7 @@
       for (Note note : noteMap) {
         byte[] raw = readNoteData(rw, note.getData());
         try {
-          b.add(ExternalId.parse(note.getName(), raw, note.getData()));
+          b.add(externalIdFactory.parse(note.getName(), raw, note.getData()));
         } catch (ConfigInvalidException | RuntimeException e) {
           logger.atSevere().withCause(e).log(
               "Ignoring invalid external ID note %s", note.getName());
@@ -840,8 +863,7 @@
    *
    * <p>If the external ID already exists, it is overwritten.
    */
-  private static ExternalId upsert(
-      RevWalk rw, ObjectInserter ins, NoteMap noteMap, ExternalId extId)
+  private ExternalId upsert(RevWalk rw, ObjectInserter ins, NoteMap noteMap, ExternalId extId)
       throws IOException, ConfigInvalidException {
     ObjectId noteId = extId.key().sha1();
     Config c = new Config();
@@ -859,7 +881,7 @@
     byte[] raw = c.toText().getBytes(UTF_8);
     ObjectId noteData = ins.insert(OBJ_BLOB, raw);
     noteMap.set(noteId, noteData);
-    return ExternalId.create(extId, noteData);
+    return externalIdFactory.create(extId, noteData);
   }
 
   /**
@@ -868,7 +890,7 @@
    * @throws IllegalStateException is thrown if there is an existing external ID that has the same
    *     key, but otherwise doesn't match the specified external ID.
    */
-  private static void remove(RevWalk rw, NoteMap noteMap, ExternalId extId)
+  private void remove(RevWalk rw, NoteMap noteMap, ExternalId extId)
       throws IOException, ConfigInvalidException {
     ObjectId noteId = extId.key().sha1();
     if (!noteMap.contains(noteId)) {
@@ -877,7 +899,7 @@
 
     ObjectId noteDataId = noteMap.get(noteId);
     byte[] raw = readNoteData(rw, noteDataId);
-    ExternalId actualExtId = ExternalId.parse(noteId.name(), raw, noteDataId);
+    ExternalId actualExtId = externalIdFactory.parse(noteId.name(), raw, noteDataId);
     checkState(
         extId.equals(actualExtId),
         "external id %s should be removed, but it doesn't match the actual external id %s",
@@ -894,8 +916,7 @@
    * @return the external ID that was removed, {@code null} if no external ID with the specified key
    *     exists
    */
-  @Nullable
-  private static ExternalId remove(
+  private ExternalId remove(
       RevWalk rw, NoteMap noteMap, ExternalId.Key extIdKey, Account.Id expectedAccountId)
       throws IOException, ConfigInvalidException {
     ObjectId noteId = extIdKey.sha1();
@@ -905,7 +926,7 @@
 
     ObjectId noteDataId = noteMap.get(noteId);
     byte[] raw = readNoteData(rw, noteDataId);
-    ExternalId extId = ExternalId.parse(noteId.name(), raw, noteDataId);
+    ExternalId extId = externalIdFactory.parse(noteId.name(), raw, noteDataId);
     if (expectedAccountId != null) {
       checkState(
           expectedAccountId.equals(extId.accountId()),
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java b/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
index f2505fa..0d715ae 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
@@ -69,10 +69,14 @@
   private boolean failOnLoad = false;
   private final Timer0 readAllLatency;
   private final Timer0 readSingleLatency;
+  private final ExternalIdFactory externalIdFactory;
 
   @Inject
   ExternalIdReader(
-      GitRepositoryManager repoManager, AllUsersName allUsersName, MetricMaker metricMaker) {
+      GitRepositoryManager repoManager,
+      AllUsersName allUsersName,
+      MetricMaker metricMaker,
+      ExternalIdFactory externalIdFactory) {
     this.repoManager = repoManager;
     this.allUsersName = allUsersName;
     this.readAllLatency =
@@ -87,6 +91,7 @@
             new Description("Latency for reading a single external ID from NoteDb.")
                 .setCumulative()
                 .setUnit(Units.MILLISECONDS));
+    this.externalIdFactory = externalIdFactory;
   }
 
   @VisibleForTesting
@@ -106,7 +111,7 @@
 
     try (Timer0.Context ctx = readAllLatency.start();
         Repository repo = repoManager.openRepository(allUsersName)) {
-      return ExternalIdNotes.loadReadOnly(allUsersName, repo, null).all();
+      return ExternalIdNotes.loadReadOnly(allUsersName, repo, null, externalIdFactory).all();
     }
   }
 
@@ -125,7 +130,7 @@
 
     try (Timer0.Context ctx = readAllLatency.start();
         Repository repo = repoManager.openRepository(allUsersName)) {
-      return ExternalIdNotes.loadReadOnly(allUsersName, repo, rev).all();
+      return ExternalIdNotes.loadReadOnly(allUsersName, repo, rev, externalIdFactory).all();
     }
   }
 
@@ -135,7 +140,7 @@
 
     try (Timer0.Context ctx = readSingleLatency.start();
         Repository repo = repoManager.openRepository(allUsersName)) {
-      return ExternalIdNotes.loadReadOnly(allUsersName, repo, null).get(key);
+      return ExternalIdNotes.loadReadOnly(allUsersName, repo, null, externalIdFactory).get(key);
     }
   }
 
@@ -146,7 +151,7 @@
 
     try (Timer0.Context ctx = readSingleLatency.start();
         Repository repo = repoManager.openRepository(allUsersName)) {
-      return ExternalIdNotes.loadReadOnly(allUsersName, repo, rev).get(key);
+      return ExternalIdNotes.loadReadOnly(allUsersName, repo, rev, externalIdFactory).get(key);
     }
   }
 
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIds.java b/java/com/google/gerrit/server/account/externalids/ExternalIds.java
index 7b0dc57..4e1e524 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIds.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIds.java
@@ -53,7 +53,7 @@
   }
 
   /** Returns the specified external ID. */
-  public Optional<ExternalId> get(ExternalId.Key key) throws IOException, ConfigInvalidException {
+  public Optional<ExternalId> get(ExternalId.Key key) throws IOException {
     return externalIdCache.byKey(key);
   }
 
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java b/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java
index 6a4da09..cf0e5d3 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java
@@ -43,29 +43,32 @@
   private final AllUsersName allUsers;
   private final AccountCache accountCache;
   private final OutgoingEmailValidator validator;
+  private final ExternalIdFactory externalIdFactory;
 
   @Inject
   ExternalIdsConsistencyChecker(
       GitRepositoryManager repoManager,
       AllUsersName allUsers,
       AccountCache accountCache,
-      OutgoingEmailValidator validator) {
+      OutgoingEmailValidator validator,
+      ExternalIdFactory externalIdFactory) {
     this.repoManager = repoManager;
     this.allUsers = allUsers;
     this.accountCache = accountCache;
     this.validator = validator;
+    this.externalIdFactory = externalIdFactory;
   }
 
   public List<ConsistencyProblemInfo> check() throws IOException, ConfigInvalidException {
     try (Repository repo = repoManager.openRepository(allUsers)) {
-      return check(ExternalIdNotes.loadReadOnly(allUsers, repo, null));
+      return check(ExternalIdNotes.loadReadOnly(allUsers, repo, null, externalIdFactory));
     }
   }
 
   public List<ConsistencyProblemInfo> check(ObjectId rev)
       throws IOException, ConfigInvalidException {
     try (Repository repo = repoManager.openRepository(allUsers)) {
-      return check(ExternalIdNotes.loadReadOnly(allUsers, repo, rev));
+      return check(ExternalIdNotes.loadReadOnly(allUsers, repo, rev, externalIdFactory));
     }
   }
 
@@ -79,7 +82,7 @@
       for (Note note : noteMap) {
         byte[] raw = ExternalIdNotes.readNoteData(rw, note.getData());
         try {
-          ExternalId extId = ExternalId.parse(note.getName(), raw, note.getData());
+          ExternalId extId = externalIdFactory.parse(note.getName(), raw, note.getData());
           problems.addAll(validateExternalId(extId));
 
           if (extId.email() != null) {
diff --git a/java/com/google/gerrit/server/account/externalids/PasswordVerifier.java b/java/com/google/gerrit/server/account/externalids/PasswordVerifier.java
index 3f2f774..33443c1 100644
--- a/java/com/google/gerrit/server/account/externalids/PasswordVerifier.java
+++ b/java/com/google/gerrit/server/account/externalids/PasswordVerifier.java
@@ -20,21 +20,30 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.server.account.HashedPassword;
+import com.google.inject.Inject;
 import java.util.Collection;
 
 /** Checks if a given username and password match a user's external IDs. */
 public class PasswordVerifier {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
+  private final ExternalIdKeyFactory externalIdKeyFactory;
+
+  @Inject
+  public PasswordVerifier(ExternalIdKeyFactory externalIdKeyFactory) {
+    this.externalIdKeyFactory = externalIdKeyFactory;
+  }
+
   /** Returns {@code true} if there is an external ID matching both the username and password. */
-  public static boolean checkPassword(
+  public boolean checkPassword(
       Collection<ExternalId> externalIds, String username, @Nullable String password) {
     if (password == null) {
       return false;
     }
     for (ExternalId id : externalIds) {
       // Only process the "username:$USER" entry, which is unique.
-      if (!id.isScheme(SCHEME_USERNAME) || !username.equals(id.key().id())) {
+      if (!id.isScheme(SCHEME_USERNAME)
+          || !id.key().equals(externalIdKeyFactory.create(SCHEME_USERNAME, username))) {
         continue;
       }
 
diff --git a/java/com/google/gerrit/server/account/externalids/testing/ExternalIdTestUtil.java b/java/com/google/gerrit/server/account/externalids/testing/ExternalIdTestUtil.java
index b8040f7..a42afc3 100644
--- a/java/com/google/gerrit/server/account/externalids/testing/ExternalIdTestUtil.java
+++ b/java/com/google/gerrit/server/account/externalids/testing/ExternalIdTestUtil.java
@@ -45,7 +45,9 @@
         rw,
         ident,
         (ins, noteMap) -> {
-          ExternalId extId = ExternalId.create(ExternalId.Key.parse(externalId), accountId);
+          ExternalId extId =
+              ExternalId.create(
+                  ExternalId.Key.parse(externalId, false), accountId, null, null, null);
           ObjectId noteId = extId.key().sha1();
           Config c = new Config();
           extId.writeToConfig(c);
@@ -65,8 +67,10 @@
         rw,
         ident,
         (ins, noteMap) -> {
-          ExternalId extId = ExternalId.create(ExternalId.Key.parse(externalId), accountId);
-          ObjectId noteId = ExternalId.Key.parse(externalId + "x").sha1();
+          ExternalId extId =
+              ExternalId.create(
+                  ExternalId.Key.parse(externalId, false), accountId, null, null, null);
+          ObjectId noteId = ExternalId.Key.parse(externalId + "x", false).sha1();
           Config c = new Config();
           extId.writeToConfig(c);
           byte[] raw = c.toText().getBytes(UTF_8);
@@ -83,7 +87,7 @@
         rw,
         ident,
         (ins, noteMap) -> {
-          ObjectId noteId = ExternalId.Key.parse(externalId).sha1();
+          ObjectId noteId = ExternalId.Key.parse(externalId, false).sha1();
           byte[] raw = "bad-config".getBytes(UTF_8);
           ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
           noteMap.set(noteId, dataBlob);
@@ -98,7 +102,7 @@
         rw,
         ident,
         (ins, noteMap) -> {
-          ObjectId noteId = ExternalId.Key.parse(externalId).sha1();
+          ObjectId noteId = ExternalId.Key.parse(externalId, false).sha1();
           byte[] raw = "".getBytes(UTF_8);
           ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
           noteMap.set(noteId, dataBlob);
diff --git a/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java b/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
index 1eee10f..b23782f 100644
--- a/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
+++ b/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
@@ -28,7 +28,6 @@
 import com.google.gerrit.extensions.api.accounts.GpgKeyApi;
 import com.google.gerrit.extensions.api.accounts.SshKeyInput;
 import com.google.gerrit.extensions.api.accounts.StatusInput;
-import com.google.gerrit.extensions.api.changes.StarsInput;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.client.EditPreferencesInfo;
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
@@ -37,7 +36,6 @@
 import com.google.gerrit.extensions.common.AccountExternalIdInfo;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.AgreementInfo;
-import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.EmailInfo;
 import com.google.gerrit.extensions.common.GpgKeyInfo;
 import com.google.gerrit.extensions.common.GroupInfo;
@@ -86,13 +84,11 @@
 import com.google.gerrit.server.restapi.account.SetPreferences;
 import com.google.gerrit.server.restapi.account.SshKeys;
 import com.google.gerrit.server.restapi.account.StarredChanges;
-import com.google.gerrit.server.restapi.account.Stars;
 import com.google.gerrit.server.restapi.change.ChangesCollection;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 import java.util.List;
 import java.util.Map;
-import java.util.SortedSet;
 
 public class AccountApiImpl implements AccountApi {
   interface Factory {
@@ -115,9 +111,6 @@
   private final DeleteWatchedProjects deleteWatchedProjects;
   private final StarredChanges.Create starredChangesCreate;
   private final StarredChanges.Delete starredChangesDelete;
-  private final Stars stars;
-  private final Stars.Get starsGet;
-  private final Stars.Post starsPost;
   private final GetEmails getEmails;
   private final CreateEmail createEmail;
   private final DeleteEmail deleteEmail;
@@ -159,9 +152,6 @@
       DeleteWatchedProjects deleteWatchedProjects,
       StarredChanges.Create starredChangesCreate,
       StarredChanges.Delete starredChangesDelete,
-      Stars stars,
-      Stars.Get starsGet,
-      Stars.Post starsPost,
       GetEmails getEmails,
       CreateEmail createEmail,
       DeleteEmail deleteEmail,
@@ -202,9 +192,6 @@
     this.deleteWatchedProjects = deleteWatchedProjects;
     this.starredChangesCreate = starredChangesCreate;
     this.starredChangesDelete = starredChangesDelete;
-    this.stars = stars;
-    this.starsGet = starsGet;
-    this.starsPost = starsPost;
     this.getEmails = getEmails;
     this.createEmail = createEmail;
     this.deleteEmail = deleteEmail;
@@ -384,35 +371,6 @@
   }
 
   @Override
-  public void setStars(String changeId, StarsInput input) throws RestApiException {
-    try {
-      AccountResource.Star rsrc = stars.parse(account, IdString.fromUrl(changeId));
-      starsPost.apply(rsrc, input);
-    } catch (Exception e) {
-      throw asRestApiException("Cannot post stars", e);
-    }
-  }
-
-  @Override
-  public SortedSet<String> getStars(String changeId) throws RestApiException {
-    try {
-      AccountResource.Star rsrc = stars.parse(account, IdString.fromUrl(changeId));
-      return starsGet.apply(rsrc).value();
-    } catch (Exception e) {
-      throw asRestApiException("Cannot get stars", e);
-    }
-  }
-
-  @Override
-  public List<ChangeInfo> getStarredChanges() throws RestApiException {
-    try {
-      return stars.list().apply(account).value();
-    } catch (Exception e) {
-      throw asRestApiException("Cannot get starred changes", e);
-    }
-  }
-
-  @Override
   public List<GroupInfo> getGroups() throws RestApiException {
     try {
       return getGroups.apply(account).value();
diff --git a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index 6a26f53..cbaf49e 100644
--- a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -91,8 +91,6 @@
 import com.google.gerrit.server.restapi.change.ListChangeDrafts;
 import com.google.gerrit.server.restapi.change.ListChangeRobotComments;
 import com.google.gerrit.server.restapi.change.ListReviewers;
-import com.google.gerrit.server.restapi.change.MarkAsReviewed;
-import com.google.gerrit.server.restapi.change.MarkAsUnreviewed;
 import com.google.gerrit.server.restapi.change.Move;
 import com.google.gerrit.server.restapi.change.PostHashtags;
 import com.google.gerrit.server.restapi.change.PostPrivate;
@@ -172,8 +170,6 @@
   private final DeletePrivate deletePrivate;
   private final Ignore ignore;
   private final Unignore unignore;
-  private final MarkAsReviewed markAsReviewed;
-  private final MarkAsUnreviewed markAsUnreviewed;
   private final SetWorkInProgress setWip;
   private final SetReadyForReview setReady;
   private final PutMessage putMessage;
@@ -228,8 +224,6 @@
       DeletePrivate deletePrivate,
       Ignore ignore,
       Unignore unignore,
-      MarkAsReviewed markAsReviewed,
-      MarkAsUnreviewed markAsUnreviewed,
       SetWorkInProgress setWip,
       SetReadyForReview setReady,
       PutMessage putMessage,
@@ -282,8 +276,6 @@
     this.deletePrivate = deletePrivate;
     this.ignore = ignore;
     this.unignore = unignore;
-    this.markAsReviewed = markAsReviewed;
-    this.markAsUnreviewed = markAsUnreviewed;
     this.setWip = setWip;
     this.setReady = setReady;
     this.putMessage = putMessage;
@@ -749,22 +741,6 @@
   }
 
   @Override
-  public void markAsReviewed(boolean reviewed) throws RestApiException {
-    // TODO(dborowitz): Convert to RetryingRestModifyView. Needs to plumb BatchUpdate.Factory into
-    // StarredChangesUtil.
-    try {
-      if (reviewed) {
-        markAsReviewed.apply(change, new Input());
-      } else {
-        markAsUnreviewed.apply(change, new Input());
-      }
-    } catch (StorageException | IllegalLabelException e) {
-      throw asRestApiException(
-          "Cannot mark change as " + (reviewed ? "reviewed" : "unreviewed"), e);
-    }
-  }
-
-  @Override
   public PureRevertInfo pureRevert() throws RestApiException {
     return pureRevert(null);
   }
diff --git a/java/com/google/gerrit/server/approval/ApprovalCacheImpl.java b/java/com/google/gerrit/server/approval/ApprovalCacheImpl.java
index 93099eb..a01931c 100644
--- a/java/com/google/gerrit/server/approval/ApprovalCacheImpl.java
+++ b/java/com/google/gerrit/server/approval/ApprovalCacheImpl.java
@@ -49,7 +49,7 @@
                 CACHE_NAME,
                 Cache.PatchSetApprovalsKeyProto.class,
                 Cache.AllPatchSetApprovalsProto.class)
-            .version(1)
+            .version(2)
             .loader(Loader.class)
             .keySerializer(new ProtobufSerializer<>(Cache.PatchSetApprovalsKeyProto.parser()))
             .valueSerializer(new ProtobufSerializer<>(Cache.AllPatchSetApprovalsProto.parser()));
diff --git a/java/com/google/gerrit/server/args4j/AccountIdHandler.java b/java/com/google/gerrit/server/args4j/AccountIdHandler.java
index 73a970b..5df4d28 100644
--- a/java/com/google/gerrit/server/args4j/AccountIdHandler.java
+++ b/java/com/google/gerrit/server/args4j/AccountIdHandler.java
@@ -44,12 +44,14 @@
   private final AccountResolver accountResolver;
   private final AccountManager accountManager;
   private final AuthType authType;
+  private final AuthRequest.Factory authRequestFactory;
 
   @Inject
   public AccountIdHandler(
       AccountResolver accountResolver,
       AccountManager accountManager,
       AuthConfig authConfig,
+      AuthRequest.Factory authRequestFactory,
       @Assisted CmdLineParser parser,
       @Assisted OptionDef option,
       @Assisted Setter<Account.Id> setter) {
@@ -57,6 +59,7 @@
     this.accountResolver = accountResolver;
     this.accountManager = accountManager;
     this.authType = authConfig.getAuthType();
+    this.authRequestFactory = authRequestFactory;
   }
 
   @Override
@@ -105,7 +108,7 @@
     }
 
     try {
-      AuthRequest req = AuthRequest.forUser(user);
+      AuthRequest req = authRequestFactory.createForUser(user);
       req.setSkipAuthentication(true);
       return accountManager.authenticate(req).getAccountId();
     } catch (AccountException e) {
diff --git a/java/com/google/gerrit/server/auth/InternalAuthBackend.java b/java/com/google/gerrit/server/auth/InternalAuthBackend.java
index 2f8886b..ce536f6 100644
--- a/java/com/google/gerrit/server/auth/InternalAuthBackend.java
+++ b/java/com/google/gerrit/server/auth/InternalAuthBackend.java
@@ -26,11 +26,14 @@
 public class InternalAuthBackend implements AuthBackend {
   private final AccountCache accountCache;
   private final AuthConfig authConfig;
+  private final PasswordVerifier passwordVerifier;
 
   @Inject
-  InternalAuthBackend(AccountCache accountCache, AuthConfig authConfig) {
+  InternalAuthBackend(
+      AccountCache accountCache, AuthConfig authConfig, PasswordVerifier passwordVerifier) {
     this.accountCache = accountCache;
     this.authConfig = authConfig;
+    this.passwordVerifier = passwordVerifier;
   }
 
   @Override
@@ -63,7 +66,7 @@
               + ": account inactive or not provisioned in Gerrit");
     }
 
-    if (!PasswordVerifier.checkPassword(who.externalIds(), username, req.getPassword().get())) {
+    if (!passwordVerifier.checkPassword(who.externalIds(), username, req.getPassword().get())) {
       throw new InvalidCredentialsException();
     }
     return new AuthUser(AuthUser.UUID.create(username), username);
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 89069e2..5efcf59 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -98,6 +98,7 @@
 import com.google.gerrit.server.StarredChangesUtil;
 import com.google.gerrit.server.account.AccountInfoComparator;
 import com.google.gerrit.server.account.AccountLoader;
+import com.google.gerrit.server.cancellation.RequestCancelledException;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.TrackingFooters;
 import com.google.gerrit.server.index.change.ChangeField;
@@ -401,6 +402,7 @@
         submitRequirementExpressionToInfo(
             req.submittabilityExpression(), result.submittabilityExpressionResult());
     info.status = SubmitRequirementResultInfo.Status.valueOf(result.status().toString());
+    info.isLegacy = result.legacy();
     return info;
   }
 
@@ -509,6 +511,11 @@
             cache.put(Change.id(info._number), info);
           }
         } catch (RuntimeException e) {
+          Optional<RequestCancelledException> requestCancelledException =
+              RequestCancelledException.getFromCausalChain(e);
+          if (requestCancelledException.isPresent()) {
+            throw e;
+          }
           logger.atWarning().withCause(e).log(
               "Omitting corrupt change %s from results", cd.getId());
         }
diff --git a/java/com/google/gerrit/server/config/AllProjectsConfigProvider.java b/java/com/google/gerrit/server/config/AllProjectsConfigProvider.java
new file mode 100644
index 0000000..27ae41f
--- /dev/null
+++ b/java/com/google/gerrit/server/config/AllProjectsConfigProvider.java
@@ -0,0 +1,8 @@
+package com.google.gerrit.server.config;
+
+import java.util.Optional;
+import org.eclipse.jgit.lib.StoredConfig;
+
+public interface AllProjectsConfigProvider {
+  Optional<StoredConfig> get(AllProjectsName allProjectsName);
+}
diff --git a/java/com/google/gerrit/server/config/AuthConfig.java b/java/com/google/gerrit/server/config/AuthConfig.java
index de57d04..2ac551d 100644
--- a/java/com/google/gerrit/server/config/AuthConfig.java
+++ b/java/com/google/gerrit/server/config/AuthConfig.java
@@ -64,6 +64,7 @@
   private final boolean cookieSecure;
   private final SignedToken emailReg;
   private final boolean allowRegisterNewEmail;
+  private final boolean userNameCaseInsensitive;
   private GitBasicAuthPolicy gitBasicAuthPolicy;
 
   @Inject
@@ -95,6 +96,7 @@
     useContributorAgreements = cfg.getBoolean("auth", "contributoragreements", false);
     userNameToLowerCase = cfg.getBoolean("auth", "userNameToLowerCase", false);
     allowRegisterNewEmail = cfg.getBoolean("auth", "allowRegisterNewEmail", true);
+    userNameCaseInsensitive = cfg.getBoolean("auth", "userNameCaseInsensitive", false);
 
     if (gitBasicAuthPolicy == GitBasicAuthPolicy.HTTP_LDAP
         && authType != AuthType.LDAP
@@ -237,6 +239,11 @@
     return userNameToLowerCase;
   }
 
+  /** Whether user name should be matched case insenitive */
+  public boolean isUserNameCaseInsensitive() {
+    return userNameCaseInsensitive;
+  }
+
   public GitBasicAuthPolicy getGitBasicAuthPolicy() {
     return gitBasicAuthPolicy;
   }
diff --git a/java/com/google/gerrit/server/config/FileBasedAllProjectsConfigProvider.java b/java/com/google/gerrit/server/config/FileBasedAllProjectsConfigProvider.java
new file mode 100644
index 0000000..ebb0e50
--- /dev/null
+++ b/java/com/google/gerrit/server/config/FileBasedAllProjectsConfigProvider.java
@@ -0,0 +1,33 @@
+package com.google.gerrit.server.config;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.server.project.ProjectConfig;
+import com.google.inject.Inject;
+import java.util.Optional;
+import javax.inject.Singleton;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+
+@Singleton
+public class FileBasedAllProjectsConfigProvider implements AllProjectsConfigProvider {
+  private final SitePaths sitePaths;
+
+  @VisibleForTesting
+  @Inject
+  public FileBasedAllProjectsConfigProvider(SitePaths sitePaths) {
+    this.sitePaths = sitePaths;
+  }
+
+  @Override
+  public Optional<StoredConfig> get(AllProjectsName allProjectsName) {
+    return Optional.of(
+        new FileBasedConfig(
+            sitePaths
+                .etc_dir
+                .resolve(allProjectsName.get())
+                .resolve(ProjectConfig.PROJECT_CONFIG)
+                .toFile(),
+            FS.DETECTED));
+  }
+}
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 6a98473..ef1c0ae 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -92,6 +92,8 @@
 import com.google.gerrit.server.account.AccountDeactivator;
 import com.google.gerrit.server.account.AccountExternalIdCreator;
 import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AccountModule;
+import com.google.gerrit.server.account.AccountTagProvider;
 import com.google.gerrit.server.account.AccountVisibilityProvider;
 import com.google.gerrit.server.account.CapabilityCollection;
 import com.google.gerrit.server.account.EmailExpander;
@@ -100,6 +102,7 @@
 import com.google.gerrit.server.account.GroupIncludeCacheImpl;
 import com.google.gerrit.server.account.ServiceUserClassifierImpl;
 import com.google.gerrit.server.account.VersionedAuthorizedKeys;
+import com.google.gerrit.server.account.externalids.ExternalIdCacheModule;
 import com.google.gerrit.server.account.externalids.ExternalIdModule;
 import com.google.gerrit.server.account.externalids.ExternalIdUpsertPreprocessor;
 import com.google.gerrit.server.approval.ApprovalCacheImpl;
@@ -183,6 +186,7 @@
 import com.google.gerrit.server.project.ProjectCacheImpl;
 import com.google.gerrit.server.project.ProjectNameLockManager;
 import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.SubmitRequirementsEvaluatorImpl;
 import com.google.gerrit.server.project.SubmitRuleEvaluator;
 import com.google.gerrit.server.query.approval.ApprovalModule;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -261,10 +265,13 @@
     install(TagCache.module());
     install(PureRevertCache.module());
     install(CommentContextCacheImpl.module());
+    install(SubmitRequirementsEvaluatorImpl.module());
 
     install(new AccessControlModule());
+    install(new AccountModule());
     install(new CmdLineParserModule());
     install(new EmailModule());
+    install(new ExternalIdCacheModule());
     install(new ExternalIdModule());
     install(new GitModule());
     install(new GroupDbModule());
@@ -431,6 +438,7 @@
     DynamicSet.bind(binder(), ExceptionHook.class).to(ExceptionHookImpl.class);
     DynamicSet.setOf(binder(), MailSoyTemplateProvider.class);
     DynamicSet.setOf(binder(), OnPostReview.class);
+    DynamicMap.mapOf(binder(), AccountTagProvider.class);
 
     DynamicMap.mapOf(binder(), MailFilter.class);
     bind(MailFilter.class).annotatedWith(Exports.named("ListMailFilter")).to(ListMailFilter.class);
diff --git a/java/com/google/gerrit/server/config/GerritServerConfigModule.java b/java/com/google/gerrit/server/config/GerritServerConfigModule.java
index da85834..8ddcdac 100644
--- a/java/com/google/gerrit/server/config/GerritServerConfigModule.java
+++ b/java/com/google/gerrit/server/config/GerritServerConfigModule.java
@@ -66,6 +66,7 @@
     bind(Config.class)
         .annotatedWith(GerritServerConfig.class)
         .toProvider(GerritServerConfigProvider.class);
+    bind(AllProjectsConfigProvider.class).to(FileBasedAllProjectsConfigProvider.class);
     bind(GlobalPluginConfigProvider.class).to(FileBasedGlobalPluginConfigProvider.class);
     bind(SecureStore.class).toProvider(SecureStoreProvider.class).in(SINGLETON);
     bind(Boolean.class)
diff --git a/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java b/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java
index 65662ba..5e9ce97 100644
--- a/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java
+++ b/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java
@@ -32,6 +32,14 @@
   public static final String GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_PUSH_CANCELLATION =
       "GerritBackendRequestFeature__enable_push_cencallation";
 
+  /**
+   * Allow legacy {@link com.google.gerrit.entities.SubmitRecord}s to be converted and returned as
+   * submit requirements by the {@link
+   * com.google.gerrit.server.project.SubmitRequirementsEvaluator}.
+   */
+  public static final String GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_LEGACY_SUBMIT_REQUIREMENTS =
+      "GerritBackendRequestFeature__enable_legacy_submit_requirements";
+
   /** Features, enabled by default in the current release. */
   public static final ImmutableSet<String> DEFAULT_ENABLED_FEATURES =
       ImmutableSet.of(UI_FEATURE_PATCHSET_COMMENTS);
diff --git a/java/com/google/gerrit/server/git/DelegateRefDatabase.java b/java/com/google/gerrit/server/git/DelegateRefDatabase.java
index decae05..bc5dd00 100644
--- a/java/com/google/gerrit/server/git/DelegateRefDatabase.java
+++ b/java/com/google/gerrit/server/git/DelegateRefDatabase.java
@@ -15,10 +15,12 @@
 package com.google.gerrit.server.git;
 
 import java.io.IOException;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefDatabase;
@@ -34,7 +36,7 @@
 
   private Repository delegate;
 
-  DelegateRefDatabase(Repository delegate) {
+  public DelegateRefDatabase(Repository delegate) {
     this.delegate = delegate;
   }
 
@@ -49,11 +51,21 @@
   }
 
   @Override
+  public boolean hasVersioning() {
+    return delegate.getRefDatabase().hasVersioning();
+  }
+
+  @Override
   public boolean isNameConflicting(String name) throws IOException {
     return delegate.getRefDatabase().isNameConflicting(name);
   }
 
   @Override
+  public Collection<String> getConflictingNames(String name) throws IOException {
+    return delegate.getRefDatabase().getConflictingNames(name);
+  }
+
+  @Override
   public RefUpdate newUpdate(String name, boolean detach) throws IOException {
     return delegate.getRefDatabase().newUpdate(name, detach);
   }
@@ -64,10 +76,35 @@
   }
 
   @Override
+  public BatchRefUpdate newBatchUpdate() {
+    return delegate.getRefDatabase().newBatchUpdate();
+  }
+
+  @Override
+  public boolean performsAtomicTransactions() {
+    return delegate.getRefDatabase().performsAtomicTransactions();
+  }
+
+  @Override
   public Ref exactRef(String name) throws IOException {
     return delegate.getRefDatabase().exactRef(name);
   }
 
+  @Override
+  public Map<String, Ref> exactRef(String... refs) throws IOException {
+    return delegate.getRefDatabase().exactRef(refs);
+  }
+
+  @Override
+  public Ref firstExactRef(String... refs) throws IOException {
+    return delegate.getRefDatabase().firstExactRef(refs);
+  }
+
+  @Override
+  public List<Ref> getRefs() throws IOException {
+    return delegate.getRefDatabase().getRefs();
+  }
+
   @SuppressWarnings("deprecation")
   @Override
   public Map<String, Ref> getRefs(String prefix) throws IOException {
@@ -75,12 +112,38 @@
   }
 
   @Override
+  public List<Ref> getRefsByPrefix(String prefix) throws IOException {
+    return delegate.getRefDatabase().getRefsByPrefix(prefix);
+  }
+
+  @Override
+  public List<Ref> getRefsByPrefixWithExclusions(String include, Set<String> excludes)
+      throws IOException {
+    return delegate.getRefDatabase().getRefsByPrefixWithExclusions(include, excludes);
+  }
+
+  @Override
+  public List<Ref> getRefsByPrefix(String... prefixes) throws IOException {
+    return delegate.getRefDatabase().getRefsByPrefix(prefixes);
+  }
+
+  @Override
   @NonNull
   public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException {
     return delegate.getRefDatabase().getTipsWithSha1(id);
   }
 
   @Override
+  public boolean hasFastTipsWithSha1() throws IOException {
+    return delegate.getRefDatabase().hasFastTipsWithSha1();
+  }
+
+  @Override
+  public boolean hasRefs() throws IOException {
+    return delegate.getRefDatabase().hasRefs();
+  }
+
+  @Override
   public List<Ref> getAdditionalRefs() throws IOException {
     return delegate.getRefDatabase().getAdditionalRefs();
   }
@@ -90,7 +153,12 @@
     return delegate.getRefDatabase().peel(ref);
   }
 
-  Repository getDelegate() {
+  @Override
+  public void refresh() {
+    delegate.getRefDatabase().refresh();
+  }
+
+  protected Repository getDelegate() {
     return delegate;
   }
 }
diff --git a/java/com/google/gerrit/server/git/GitRepositoryManager.java b/java/com/google/gerrit/server/git/GitRepositoryManager.java
index e4d0696..8142089a 100644
--- a/java/com/google/gerrit/server/git/GitRepositoryManager.java
+++ b/java/com/google/gerrit/server/git/GitRepositoryManager.java
@@ -30,6 +30,23 @@
  */
 @ImplementedBy(value = LocalDiskRepositoryManager.class)
 public interface GitRepositoryManager {
+
+  /** Status of the repository. */
+  enum Status {
+    /** Repository exists and is available on host. */
+    ACTIVE,
+    /** Repository does not exist. */
+    NON_EXISTENT,
+    /**
+     * Repository might exist but can not be opened. This can for example be the case when the
+     * repository is pending deletion / the caller does not have permissions / repository is broken.
+     */
+    UNAVAILABLE;
+  }
+
+  /** Get {@link Status} of the repository by name. */
+  Status getRepositoryStatus(Project.NameKey name);
+
   /**
    * Get (or open) a repository by name.
    *
@@ -47,13 +64,14 @@
    * @param name the repository name, relative to the base directory.
    * @return the cached Repository instance. Caller must call {@code close()} when done to decrement
    *     the resource handle.
+   * @throws RepositoryExistsException repository exists.
    * @throws RepositoryCaseMismatchException the name collides with an existing repository name, but
    *     only in case of a character within the name.
    * @throws RepositoryNotFoundException the name is invalid.
    * @throws IOException the repository cannot be created.
    */
   Repository createRepository(Project.NameKey name)
-      throws RepositoryCaseMismatchException, RepositoryNotFoundException, IOException;
+      throws RepositoryNotFoundException, RepositoryExistsException, IOException;
 
   /** @return set of all known projects, sorted by natural NameKey order. */
   SortedSet<Project.NameKey> list();
diff --git a/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java b/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
index 10220d8..1dc5c16 100644
--- a/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
+++ b/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
@@ -16,6 +16,7 @@
 
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.Project.NameKey;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.lifecycle.LifecycleModule;
@@ -128,6 +129,29 @@
   }
 
   @Override
+  public Status getRepositoryStatus(NameKey name) {
+    if (isUnreasonableName(name)) {
+      return Status.NON_EXISTENT;
+    }
+    Path path = getBasePath(name);
+    File dir = FileKey.resolve(path.resolve(name.get()).toFile(), FS.DETECTED);
+    if (dir == null) {
+      return Status.NON_EXISTENT;
+    }
+    Repository repo;
+    try {
+      // Try to open with mustExist, so that it does not attempt to create a repository.
+      repo = RepositoryCache.open(FileKey.lenient(dir, FS.DETECTED), /*mustExist=*/ true);
+    } catch (RepositoryNotFoundException e) {
+      return Status.NON_EXISTENT;
+    } catch (IOException e) {
+      return Status.UNAVAILABLE;
+    }
+    // If object database does not exist, the repository is unusable
+    return repo.getObjectDatabase().exists() ? Status.ACTIVE : Status.UNAVAILABLE;
+  }
+
+  @Override
   public Repository openRepository(Project.NameKey name) throws RepositoryNotFoundException {
     return openRepository(getBasePath(name), name);
   }
@@ -147,7 +171,7 @@
 
   @Override
   public Repository createRepository(Project.NameKey name)
-      throws RepositoryNotFoundException, RepositoryCaseMismatchException, IOException {
+      throws RepositoryNotFoundException, RepositoryExistsException, IOException {
     if (isUnreasonableName(name)) {
       throw new RepositoryNotFoundException("Invalid name: " + name);
     }
@@ -162,8 +186,7 @@
       if (!onDiskName.equals(name)) {
         throw new RepositoryCaseMismatchException(name);
       }
-
-      throw new IllegalStateException("Repository already exists: " + name);
+      throw new RepositoryExistsException(name);
     }
 
     // It doesn't exist under any of the standard permutations
diff --git a/java/com/google/gerrit/server/git/MultiProgressMonitor.java b/java/com/google/gerrit/server/git/MultiProgressMonitor.java
index 397519f..3faafd1 100644
--- a/java/com/google/gerrit/server/git/MultiProgressMonitor.java
+++ b/java/com/google/gerrit/server/git/MultiProgressMonitor.java
@@ -14,13 +14,14 @@
 
 package com.google.gerrit.server.git;
 
-import static com.google.gerrit.server.DeadlineChecker.TIMEOUT_FORMATTER;
+import static com.google.gerrit.server.DeadlineChecker.getTimeoutFormatter;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 
 import com.google.common.base.Strings;
 import com.google.common.flogger.FluentLogger;
 import com.google.common.util.concurrent.UncheckedExecutionException;
+import com.google.gerrit.server.CancellationMetrics;
 import com.google.gerrit.server.cancellation.RequestStateProvider;
 import com.google.gerrit.server.experiments.ExperimentFeatures;
 import com.google.gerrit.server.experiments.ExperimentFeaturesConstants;
@@ -167,6 +168,7 @@
         TimeUnit maxIntervalUnit);
   }
 
+  private final CancellationMetrics cancellationMetrics;
   private final ExperimentFeatures experimentFeatures;
   private final OutputStream out;
   private final TaskKind taskKind;
@@ -177,6 +179,7 @@
   private boolean done;
   private boolean clientDisconnected;
   private boolean deadlineExceeded;
+  private boolean forcefulTermination;
   private Optional<Long> timeout = Optional.empty();
 
   private final long maxIntervalNanos;
@@ -189,11 +192,12 @@
    */
   @AssistedInject
   private MultiProgressMonitor(
+      CancellationMetrics cancellationMetrics,
       ExperimentFeatures experimentFeatures,
       @Assisted OutputStream out,
       @Assisted TaskKind taskKind,
       @Assisted String taskName) {
-    this(experimentFeatures, out, taskKind, taskName, 500, MILLISECONDS);
+    this(cancellationMetrics, experimentFeatures, out, taskKind, taskName, 500, MILLISECONDS);
   }
 
   /**
@@ -206,12 +210,14 @@
    */
   @AssistedInject
   private MultiProgressMonitor(
+      CancellationMetrics cancellationMetrics,
       ExperimentFeatures experimentFeatures,
       @Assisted OutputStream out,
       @Assisted TaskKind taskKind,
       @Assisted String taskName,
       @Assisted long maxIntervalTime,
       @Assisted TimeUnit maxIntervalUnit) {
+    this.cancellationMetrics = cancellationMetrics;
     this.experimentFeatures = experimentFeatures;
     this.out = out;
     this.taskKind = taskKind;
@@ -290,12 +296,14 @@
         long now = System.nanoTime();
 
         if (deadline > 0 && now > deadline) {
-          logger.atFine().log(
-              "deadline exceeded after %sms, signaling cancellation (timeout=%sms, task=%s(%s))",
-              MILLISECONDS.convert(now - overallStart, NANOSECONDS),
-              MILLISECONDS.convert(now - deadline, NANOSECONDS),
-              taskKind,
-              taskName);
+          if (!deadlineExceeded) {
+            logger.atFine().log(
+                "deadline exceeded after %sms, signaling cancellation (timeout=%sms, task=%s(%s))",
+                MILLISECONDS.convert(now - overallStart, NANOSECONDS),
+                MILLISECONDS.convert(now - deadline, NANOSECONDS),
+                taskKind,
+                taskName);
+          }
           deadlineExceeded = true;
 
           // After setting deadlineExceeded = true give the cancellationNanos to react to the
@@ -303,6 +311,7 @@
           if (now > deadline + cancellationNanos) {
             // The worker didn't react to the cancellation, cancel it forcefully by an interrupt.
             workerFuture.cancel(true);
+            forcefulTermination = true;
             if (workerFuture.isCancelled()) {
               logger.atWarning().log(
                   "MultiProgressMonitor worker killed after %sms, cancelled (timeout=%sms, task=%s(%s))",
@@ -310,6 +319,9 @@
                   MILLISECONDS.convert(now - deadline, NANOSECONDS),
                   taskKind,
                   taskName);
+              if (taskKind == TaskKind.RECEIVE_COMMITS) {
+                cancellationMetrics.countForcefulReceiveTimeout();
+              }
             }
             break;
           }
@@ -330,6 +342,9 @@
           end();
         }
       }
+      if (deadlineExceeded && !forcefulTermination && taskKind == TaskKind.RECEIVE_COMMITS) {
+        cancellationMetrics.countGracefulReceiveTimeout();
+      }
       sendDone();
     }
 
@@ -465,7 +480,12 @@
     } else if (deadlineExceeded) {
       onCancelled.onCancel(
           RequestStateProvider.Reason.SERVER_DEADLINE_EXCEEDED,
-          timeout.map(TIMEOUT_FORMATTER).orElse(null));
+          timeout
+              .map(
+                  taskKind == TaskKind.RECEIVE_COMMITS
+                      ? getTimeoutFormatter("receive.timeout")
+                      : getTimeoutFormatter("timeout"))
+              .orElse(null));
     }
   }
 }
diff --git a/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java b/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
index b7dc2b3..99a66f8 100644
--- a/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
+++ b/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -33,8 +34,10 @@
 import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefRename;
@@ -61,6 +64,11 @@
   }
 
   @Override
+  public Collection<String> getConflictingNames(String name) throws IOException {
+    throw new UnsupportedOperationException("PermissionAwareReadOnlyRefDatabase is read-only");
+  }
+
+  @Override
   public RefUpdate newUpdate(String name, boolean detach) {
     throw new UnsupportedOperationException("PermissionAwareReadOnlyRefDatabase is read-only");
   }
@@ -71,6 +79,11 @@
   }
 
   @Override
+  public BatchRefUpdate newBatchUpdate() {
+    throw new UnsupportedOperationException("PermissionAwareReadOnlyRefDatabase is read-only");
+  }
+
+  @Override
   public Ref exactRef(String name) throws IOException {
     Ref ref = getDelegate().getRefDatabase().exactRef(name);
     if (ref == null) {
@@ -148,6 +161,25 @@
   }
 
   @Override
+  public List<Ref> getRefsByPrefixWithExclusions(String include, Set<String> excludes)
+      throws IOException {
+    Stream<Ref> refs = getRefs(include).values().stream();
+    for (String exclude : excludes) {
+      refs = refs.filter(r -> !r.getName().startsWith(exclude));
+    }
+    return Collections.unmodifiableList(refs.collect(Collectors.toList()));
+  }
+
+  @Override
+  public List<Ref> getRefsByPrefix(String... prefixes) throws IOException {
+    List<Ref> result = new ArrayList<>();
+    for (String prefix : prefixes) {
+      result.addAll(getRefsByPrefix(prefix));
+    }
+    return Collections.unmodifiableList(result);
+  }
+
+  @Override
   @NonNull
   public Map<String, Ref> exactRef(String... refs) throws IOException {
     Map<String, Ref> result = new HashMap<>(refs.length);
@@ -173,6 +205,11 @@
   }
 
   @Override
+  public List<Ref> getRefs() throws IOException {
+    return getRefsByPrefix(ALL);
+  }
+
+  @Override
   @NonNull
   public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException {
     Set<Ref> unfiltered = super.getTipsWithSha1(id);
@@ -184,4 +221,9 @@
     }
     return result;
   }
+
+  @Override
+  public boolean hasRefs() throws IOException {
+    return !getRefs().isEmpty();
+  }
 }
diff --git a/java/com/google/gerrit/server/git/RepositoryCaseMismatchException.java b/java/com/google/gerrit/server/git/RepositoryCaseMismatchException.java
index 8535cd2..9e10c67 100644
--- a/java/com/google/gerrit/server/git/RepositoryCaseMismatchException.java
+++ b/java/com/google/gerrit/server/git/RepositoryCaseMismatchException.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.git;
 
 import com.google.gerrit.entities.Project;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
 
 /**
  * This exception is thrown if a project cannot be created because a project with the same name in a
@@ -23,12 +22,12 @@
  * (e.g. Windows), because in this case the name for the git repository in the file system is
  * already occupied by the existing project.
  */
-public class RepositoryCaseMismatchException extends RepositoryNotFoundException {
+public class RepositoryCaseMismatchException extends RepositoryExistsException {
 
   private static final long serialVersionUID = 1L;
 
   /** @param projectName name of the project that cannot be created */
   public RepositoryCaseMismatchException(Project.NameKey projectName) {
-    super("Name occupied in other case. Project " + projectName.get() + " cannot be created.");
+    super(projectName, "Name occupied in other case.");
   }
 }
diff --git a/java/com/google/gerrit/server/git/RepositoryExistsException.java b/java/com/google/gerrit/server/git/RepositoryExistsException.java
new file mode 100644
index 0000000..563b078
--- /dev/null
+++ b/java/com/google/gerrit/server/git/RepositoryExistsException.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.gerrit.entities.Project;
+import java.io.IOException;
+
+/** Thrown when trying to create a repository that exist. */
+public class RepositoryExistsException extends IOException {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * @param projectName name of the project that cannot be created
+   * @param reason reason why the project cannot be created
+   */
+  public RepositoryExistsException(Project.NameKey projectName, String reason) {
+    super(
+        String.format("Repository %s exists and cannot be created. %s", projectName.get(), reason));
+  }
+
+  /** @param projectName name of the project that cannot be created */
+  public RepositoryExistsException(Project.NameKey projectName) {
+    super(String.format("Repository %s exists and cannot be created.", projectName.get()));
+  }
+}
diff --git a/java/com/google/gerrit/server/git/meta/VersionedMetaData.java b/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
index feb038a..999ed4e 100644
--- a/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
+++ b/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
@@ -35,6 +35,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.dircache.DirCacheEditor;
@@ -461,12 +462,12 @@
   }
 
   protected Config readConfig(String fileName) throws IOException, ConfigInvalidException {
-    return readConfig(fileName, null);
+    return readConfig(fileName, Optional.empty());
   }
 
-  protected Config readConfig(String fileName, Config baseConfig)
+  protected Config readConfig(String fileName, Optional<? extends Config> baseConfig)
       throws IOException, ConfigInvalidException {
-    Config rc = new Config(baseConfig);
+    Config rc = new Config(baseConfig.isPresent() ? baseConfig.get() : null);
     String text = readUTF8(fileName);
     if (!text.isEmpty()) {
       try {
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 1e77cc4..42fc916 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -637,10 +637,10 @@
                 tracePushOption.isPresent(),
                 tracePushOption.orElse(null),
                 (tagName, traceId) -> addMessage(tagName + ": " + traceId));
-        TraceTimer traceTimer =
-            newTimer("processCommands", Metadata.builder().resourceCount(commandCount));
         PerformanceLogContext performanceLogContext =
-            new PerformanceLogContext(config, performanceLoggers)) {
+            new PerformanceLogContext(config, performanceLoggers);
+        TraceTimer traceTimer =
+            newTimer("processCommands", Metadata.builder().resourceCount(commandCount))) {
       RequestInfo requestInfo =
           RequestInfo.builder(RequestInfo.RequestType.GIT_RECEIVE, user, traceContext)
               .project(project.getNameKey())
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 5caceef..02ec5ea 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -598,9 +598,9 @@
 
   /** List of labels on the current patch set including change owner votes. */
   public static final FieldDef<ChangeData, Iterable<String>> LABEL =
-      exact("label2").buildRepeatable(cd -> getLabels(cd, true));
+      exact("label2").buildRepeatable(cd -> getLabels(cd));
 
-  private static Iterable<String> getLabels(ChangeData cd, boolean owners) {
+  private static Iterable<String> getLabels(ChangeData cd) {
     Set<String> allApprovals = new HashSet<>();
     Set<String> distinctApprovals = new HashSet<>();
     for (PatchSetApproval a : cd.currentApprovals()) {
@@ -608,12 +608,19 @@
         allApprovals.add(formatLabel(a.label(), a.value(), a.accountId()));
         Optional<LabelType> labelType = cd.getLabelTypes().byLabel(a.labelId());
         allApprovals.addAll(getMaxMinAnyLabels(a.label(), a.value(), labelType, a.accountId()));
-        if (owners && cd.change().getOwner().equals(a.accountId())) {
+        if (cd.change().getOwner().equals(a.accountId())) {
           allApprovals.add(formatLabel(a.label(), a.value(), ChangeQueryBuilder.OWNER_ACCOUNT_ID));
           allApprovals.addAll(
               getMaxMinAnyLabels(
                   a.label(), a.value(), labelType, ChangeQueryBuilder.OWNER_ACCOUNT_ID));
         }
+        if (!cd.currentPatchSet().uploader().equals(a.accountId())) {
+          allApprovals.add(
+              formatLabel(a.label(), a.value(), ChangeQueryBuilder.NON_UPLOADER_ACCOUNT_ID));
+          allApprovals.addAll(
+              getMaxMinAnyLabels(
+                  a.label(), a.value(), labelType, ChangeQueryBuilder.NON_UPLOADER_ACCOUNT_ID));
+        }
         distinctApprovals.add(formatLabel(a.label(), a.value()));
         distinctApprovals.addAll(getMaxMinAnyLabels(a.label(), a.value(), labelType, null));
       }
@@ -735,6 +742,8 @@
   private static String formatAccount(Account.Id accountId) {
     if (ChangeQueryBuilder.OWNER_ACCOUNT_ID.equals(accountId)) {
       return ChangeQueryBuilder.ARG_ID_OWNER;
+    } else if (ChangeQueryBuilder.NON_UPLOADER_ACCOUNT_ID.equals(accountId)) {
+      return ChangeQueryBuilder.ARG_ID_NON_UPLOADER;
     }
     return Integer.toString(accountId.get());
   }
@@ -796,6 +805,12 @@
                 return m ? "1" : "0";
               });
 
+  /** Whether the change is a cherry pick of another change. */
+  public static final FieldDef<ChangeData, String> CHERRY_PICK =
+      exact(ChangeQueryBuilder.FIELD_CHERRYPICK)
+          .stored()
+          .build(cd -> cd.change().getCherryPickOf() != null ? "1" : "0");
+
   /** The number of inserted lines in this change. */
   public static final FieldDef<ChangeData, Integer> ADDED =
       intRange(ChangeQueryBuilder.FIELD_ADDED)
@@ -919,6 +934,19 @@
   public static final SubmitRuleOptions SUBMIT_RULE_OPTIONS_STRICT =
       SubmitRuleOptions.builder().build();
 
+  /** All submit rules results in the form of "$ruleName,$status". */
+  public static final FieldDef<ChangeData, Iterable<String>> SUBMIT_RULE_RESULT =
+      exact("submit_rule_result")
+          .buildRepeatable(
+              cd -> {
+                List<String> result = new ArrayList<>();
+                List<SubmitRecord> submitRecords = cd.submitRecords(SUBMIT_RULE_OPTIONS_STRICT);
+                for (SubmitRecord record : submitRecords) {
+                  result.add(record.ruleName + "=" + record.status.name());
+                }
+                return result;
+              });
+
   /**
    * JSON type for storing SubmitRecords.
    *
@@ -938,12 +966,14 @@
       @Deprecated Map<String, String> data;
     }
 
+    String ruleName;
     SubmitRecord.Status status;
     List<StoredLabel> labels;
     List<StoredRequirement> requirements;
     String errorMessage;
 
     public StoredSubmitRecord(SubmitRecord rec) {
+      this.ruleName = rec.ruleName;
       this.status = rec.status;
       this.errorMessage = rec.errorMessage;
       if (rec.labels != null) {
@@ -975,6 +1005,7 @@
 
     public SubmitRecord toSubmitRecord() {
       SubmitRecord rec = new SubmitRecord();
+      rec.ruleName = ruleName;
       rec.status = status;
       rec.errorMessage = errorMessage;
       if (labels != null) {
diff --git a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index bee0c35..a758377 100644
--- a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -154,10 +154,29 @@
   @Deprecated static final Schema<ChangeData> V64 = schema(V63, false);
 
   /** Added new field for submit requirements. */
+  @Deprecated
   static final Schema<ChangeData> V65 =
       new Schema.Builder<ChangeData>().add(V64).add(ChangeField.STORED_SUBMIT_REQUIREMENTS).build();
 
   /**
+   * The computation of {@link ChangeField#LABEL} has changed: We added the non_uploader arg to the
+   * label field.
+   */
+  @Deprecated static final Schema<ChangeData> V66 = schema(V65, false);
+
+  /** Updated submit records: store the rule name that created the submit record. */
+  @Deprecated static final Schema<ChangeData> V67 = schema(V66, false);
+
+  /** Added new field {@link ChangeField#SUBMIT_RULE_RESULT}. */
+  @Deprecated
+  static final Schema<ChangeData> V68 =
+      new Schema.Builder<ChangeData>().add(V67).add(ChangeField.SUBMIT_RULE_RESULT).build();
+
+  /** Added new field {@link ChangeField#CHERRY_PICK}. */
+  static final Schema<ChangeData> V69 =
+      new Schema.Builder<ChangeData>().add(V68).add(ChangeField.CHERRY_PICK).build();
+
+  /**
    * Name of the change index to be used when contacting index backends or loading configurations.
    */
   public static final String NAME = "changes";
diff --git a/java/com/google/gerrit/server/mail/EmailTokenVerifier.java b/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
index 2ff5fc3..ead4c06 100644
--- a/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
+++ b/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
@@ -56,10 +56,13 @@
   class ParsedToken {
     private final Account.Id accountId;
     private final String emailAddress;
+    private final AuthRequest.Factory authRequestFactory;
 
-    public ParsedToken(Account.Id accountId, String emailAddress) {
+    public ParsedToken(
+        Account.Id accountId, String emailAddress, AuthRequest.Factory authRequestFactory) {
       this.accountId = accountId;
       this.emailAddress = emailAddress;
+      this.authRequestFactory = authRequestFactory;
     }
 
     public Account.Id getAccountId() {
@@ -71,7 +74,7 @@
     }
 
     public AuthRequest toAuthRequest() {
-      return AuthRequest.forEmail(getEmailAddress());
+      return authRequestFactory.createForEmail(getEmailAddress());
     }
 
     @Override
diff --git a/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java b/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
index 77be665..bdfaf6d 100644
--- a/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
+++ b/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
@@ -19,6 +19,7 @@
 
 import com.google.common.io.BaseEncoding;
 import com.google.gerrit.entities.Account;
+import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.mail.send.RegisterNewEmailSender;
 import com.google.inject.AbstractModule;
@@ -31,6 +32,7 @@
 @Singleton
 public class SignedTokenEmailTokenVerifier implements EmailTokenVerifier {
   private final SignedToken emailRegistrationToken;
+  private final AuthRequest.Factory authRequestFactory;
 
   public static class Module extends AbstractModule {
     @Override
@@ -40,8 +42,9 @@
   }
 
   @Inject
-  SignedTokenEmailTokenVerifier(AuthConfig config) {
+  SignedTokenEmailTokenVerifier(AuthConfig config, AuthRequest.Factory authRequestFactory) {
     emailRegistrationToken = config.getEmailRegistrationToken();
+    this.authRequestFactory = authRequestFactory;
   }
 
   @Override
@@ -77,7 +80,7 @@
     }
     Account.Id id = Account.Id.tryParse(matcher.group(1)).orElseThrow(InvalidTokenException::new);
     String newEmail = matcher.group(2);
-    return new ParsedToken(id, newEmail);
+    return new ParsedToken(id, newEmail, authRequestFactory);
   }
 
   private void checkEmailRegistrationToken() {
diff --git a/java/com/google/gerrit/server/mail/receive/MailProcessor.java b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
index d805e39..e98647d 100644
--- a/java/com/google/gerrit/server/mail/receive/MailProcessor.java
+++ b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.mail.receive;
 
+import static com.google.gerrit.entities.Patch.PATCHSET_LEVEL;
 import static java.util.stream.Collectors.toList;
 
 import com.google.common.base.Strings;
@@ -91,7 +92,7 @@
   private static final ImmutableMap<MailComment.CommentType, CommentForValidation.CommentType>
       MAIL_COMMENT_TYPE_TO_VALIDATION_TYPE =
           ImmutableMap.of(
-              MailComment.CommentType.CHANGE_MESSAGE,
+              MailComment.CommentType.PATCHSET_LEVEL,
                   CommentForValidation.CommentType.CHANGE_MESSAGE,
               MailComment.CommentType.FILE_COMMENT, CommentForValidation.CommentType.FILE_COMMENT,
               MailComment.CommentType.INLINE_COMMENT,
@@ -342,9 +343,6 @@
           changeMessagesUtil.setChangeMessage(ctx.getUpdate(psId), generateChangeMessage(), tag);
       comments = new ArrayList<>();
       for (MailComment c : parsedComments) {
-        if (c.getType() == MailComment.CommentType.CHANGE_MESSAGE) {
-          continue;
-        }
         comments.add(
             persistentCommentFromMailComment(ctx, c, targetPatchSetForComment(ctx, c, patchSet)));
       }
@@ -359,7 +357,7 @@
     @Override
     public void postUpdate(PostUpdateContext ctx) throws Exception {
       String patchSetComment = null;
-      if (parsedComments.get(0).getType() == MailComment.CommentType.CHANGE_MESSAGE) {
+      if (parsedComments.get(0).getType() == MailComment.CommentType.PATCHSET_LEVEL) {
         patchSetComment = parsedComments.get(0).getMessage();
       }
       // Send email notifications
@@ -396,15 +394,7 @@
 
     private String generateChangeMessage() {
       String changeMsg = "Patch Set " + psId.get() + ":";
-      if (parsedComments.get(0).getType() == MailComment.CommentType.CHANGE_MESSAGE) {
-        // Add a blank line after Patch Set to follow the default format
-        if (parsedComments.size() > 1) {
-          changeMsg += "\n\n" + numComments(parsedComments.size() - 1);
-        }
-        changeMsg += "\n\n" + parsedComments.get(0).getMessage();
-      } else {
-        changeMsg += "\n\n" + numComments(parsedComments.size());
-      }
+      changeMsg += "\n\n" + numComments(parsedComments.size());
       return changeMsg;
     }
 
@@ -424,7 +414,11 @@
       // The patch set that this comment is based on is different if this
       // comment was sent in reply to a comment on a previous patch set.
       Side side;
-      if (mailComment.getInReplyTo() != null) {
+      if (mailComment.getType() == MailComment.CommentType.PATCHSET_LEVEL) {
+        fileName = PATCHSET_LEVEL;
+        // Patchset comments do not have side.
+        side = Side.REVISION;
+      } else if (mailComment.getInReplyTo() != null) {
         fileName = mailComment.getInReplyTo().key.filename;
         side = Side.fromShort(mailComment.getInReplyTo().side);
       } else {
diff --git a/java/com/google/gerrit/server/mail/send/EmailArguments.java b/java/com/google/gerrit/server/mail/send/EmailArguments.java
index c5d74cc..96effc1 100644
--- a/java/com/google/gerrit/server/mail/send/EmailArguments.java
+++ b/java/com/google/gerrit/server/mail/send/EmailArguments.java
@@ -43,6 +43,7 @@
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 import com.google.gerrit.server.ssh.SshAdvertisedAddresses;
+import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.validators.OutgoingEmailValidationListener;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -86,7 +87,6 @@
   final AllProjectsName allProjectsName;
   final List<String> sshAddresses;
   final SitePaths site;
-
   final Provider<ChangeQueryBuilder> queryBuilder;
   final ChangeData.Factory changeDataFactory;
   final Provider<SoySauce> soySauce;
@@ -97,6 +97,7 @@
   final boolean addInstanceNameInSubject;
   final Provider<String> instanceNameProvider;
   final Provider<CurrentUser> currentUserProvider;
+  final RetryHelper retryHelper;
 
   @Inject
   EmailArguments(
@@ -129,7 +130,8 @@
       OutgoingEmailValidator validator,
       @GerritInstanceName Provider<String> instanceNameProvider,
       @GerritServerConfig Config cfg,
-      Provider<CurrentUser> currentUserProvider) {
+      Provider<CurrentUser> currentUserProvider,
+      RetryHelper retryHelper) {
     this.server = server;
     this.projectCache = projectCache;
     this.permissionBackend = permissionBackend;
@@ -158,8 +160,8 @@
     this.accountQueryProvider = accountQueryProvider;
     this.validator = validator;
     this.instanceNameProvider = instanceNameProvider;
-
     this.addInstanceNameInSubject = cfg.getBoolean("sendemail", "addInstanceNameInSubject", false);
     this.currentUserProvider = currentUserProvider;
+    this.retryHelper = retryHelper;
   }
 }
diff --git a/java/com/google/gerrit/server/mail/send/ModifyReviewerSender.java b/java/com/google/gerrit/server/mail/send/ModifyReviewerSender.java
index dcf3b6c..b187f9c 100644
--- a/java/com/google/gerrit/server/mail/send/ModifyReviewerSender.java
+++ b/java/com/google/gerrit/server/mail/send/ModifyReviewerSender.java
@@ -37,5 +37,6 @@
     super.init();
 
     ccExistingReviewers();
+    removeUsersThatIgnoredTheChange();
   }
 }
diff --git a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
index ddcc0cf..8547336 100644
--- a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
+++ b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
@@ -18,6 +18,7 @@
 import static com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy.DISABLED;
 import static java.util.Objects.requireNonNull;
 
+import com.google.common.base.Throwables;
 import com.google.common.collect.Sets;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
@@ -34,6 +35,7 @@
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.change.NotifyResolver;
 import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.update.RetryableAction.ActionType;
 import com.google.gerrit.server.validators.OutgoingEmailValidationListener;
 import com.google.gerrit.server.validators.ValidationException;
 import com.google.template.soy.jbcsrc.api.SoySauce;
@@ -99,6 +101,25 @@
    * @throws EmailException
    */
   public void send() throws EmailException {
+    try {
+      args.retryHelper
+          .action(
+              ActionType.SEND_EMAIL,
+              "sendEmail",
+              () -> {
+                sendImpl();
+                return null;
+              })
+          .retryWithTrace(Exception.class::isInstance)
+          .call();
+    } catch (Exception e) {
+      Throwables.throwIfUnchecked(e);
+      Throwables.throwIfInstanceOf(e, EmailException.class);
+      throw new EmailException("sending email failed", e);
+    }
+  }
+
+  private void sendImpl() throws EmailException {
     if (!args.emailSender.isEnabled()) {
       // Server has explicitly disabled email sending.
       //
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index 87ad646..f82349a 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -917,6 +917,11 @@
         }
       } else {
         checkFooter(rec != null, FOOTER_SUBMITTED_WITH, line);
+        if (line.startsWith("Rule-Name: ")) {
+          String ruleName = line.split(": ")[1];
+          rec.ruleName = ruleName;
+          continue;
+        }
         SubmitRecord.Label label = new SubmitRecord.Label();
         if (rec.labels == null) {
           rec.labels = new ArrayList<>();
diff --git a/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 971e0a8..004ad40 100644
--- a/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -730,7 +730,10 @@
           msg.append(' ').append(sanitizeFooter(rec.errorMessage));
         }
         msg.append('\n');
-
+        if (rec.ruleName != null) {
+          addFooter(msg, FOOTER_SUBMITTED_WITH).append("Rule-Name: ").append(rec.ruleName);
+          msg.append('\n');
+        }
         if (rec.labels != null) {
           for (SubmitRecord.Label label : rec.labels) {
             // Label names/values are safe to append without sanitizing.
@@ -745,7 +748,6 @@
             msg.append('\n');
           }
         }
-        // TODO(maximeg) We might want to list plugins that validated this submission.
       }
     }
 
diff --git a/java/com/google/gerrit/server/notedb/CommitRewriter.java b/java/com/google/gerrit/server/notedb/CommitRewriter.java
index 549bd0f..7d743dc 100644
--- a/java/com/google/gerrit/server/notedb/CommitRewriter.java
+++ b/java/com/google/gerrit/server/notedb/CommitRewriter.java
@@ -13,16 +13,17 @@
 // limitations under the License.
 package com.google.gerrit.server.notedb;
 
+import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_ASSIGNEE;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_ATTENTION;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_LABEL;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_REAL_USER;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMITTED_WITH;
-import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_TAG;
 import static com.google.gerrit.server.util.AccountTemplateUtil.ACCOUNT_TEMPLATE_PATTERN;
 import static com.google.gerrit.server.util.AccountTemplateUtil.ACCOUNT_TEMPLATE_REGEX;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -38,7 +39,6 @@
 import com.google.gerrit.entities.SubmitRecord;
 import com.google.gerrit.git.RefUpdateUtil;
 import com.google.gerrit.json.OutputFormat;
-import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.externalids.ExternalId;
@@ -128,7 +128,7 @@
      * Refs that were fixed by the run/ would be fixed if in --dry-run, together with their commit
      * history diff. Diff is empty if --output-diff is false.
      */
-    public Map<String, List<String>> fixedRefDiff = new HashMap<>();
+    public Map<String, List<CommitDiff>> fixedRefDiff = new HashMap<>();
 
     /**
      * Refs that still contain user data after the backfill run. Only filled if --verify-commits,
@@ -140,6 +140,20 @@
     public List<String> refsFailedToFix = new ArrayList<>();
   }
 
+  /** Diff result of a single commit rewrite */
+  @AutoValue
+  public abstract static class CommitDiff {
+    public static CommitDiff create(ObjectId oldSha1, String commitDiff) {
+      return new AutoValue_CommitRewriter_CommitDiff(oldSha1, commitDiff);
+    }
+
+    /** SHA1 of the overwritten commit */
+    public abstract ObjectId oldSha1();
+
+    /** Diff applied to the commit with {@link #oldSha1} */
+    public abstract String diff();
+  }
+
   public static final String DEFAULT_ACCOUNT_REPLACEMENT = "Gerrit Account";
 
   private static final Pattern NON_REPLACE_ACCOUNT_PATTERN =
@@ -156,10 +170,14 @@
       Pattern.compile("Assignee changed from: (.*) to: (.*)");
 
   private static final Pattern REMOVED_REVIEWER_PATTERN =
-      Pattern.compile("Removed (cc|reviewer) (.*) .*");
+      Pattern.compile("Removed (cc|reviewer) (.*)(\\.| with the following votes)");
 
   private static final Pattern REMOVED_VOTE_PATTERN = Pattern.compile("Removed (.*) by (.*)");
 
+  private static final String REMOVED_VOTES_CHANGE_MESSAGE_START = "Removed the following votes:";
+  private static final Pattern REMOVED_VOTES_CHANGE_MESSAGE_PATTERN =
+      Pattern.compile("\\* (.*) by (.*)");
+
   private static final Pattern REMOVED_CHANGE_MESSAGE_PATTERN =
       Pattern.compile("Change message removed by: (.*)(\nReason: .*)?");
 
@@ -168,10 +186,12 @@
 
   private static final Pattern ON_CODE_OWNER_ADD_REVIEWER_PATTERN =
       Pattern.compile("(.*) who was added as reviewer owns the following files");
-  private static final Pattern ON_CODE_OWNER_APPROVAL_PATTERN =
-      Pattern.compile("code-owner approved by (.*):");
-  private static final Pattern ON_CODE_OWNER_OVERRIDE_PATTERN =
-      Pattern.compile("code-owners submit requirement .* overridden by (.*):");
+  private static final String ON_CODE_OWNER_APPROVAL_REGEX = "code-owner approved by (.*):";
+  private static final String ON_CODE_OWNER_OVERRIDE_REGEX =
+      "code-owners submit requirement .* overridden by (.*)";
+
+  private static final Pattern ON_CODE_OWNER_REVIEW_PATTERN =
+      Pattern.compile(ON_CODE_OWNER_APPROVAL_REGEX + "|" + ON_CODE_OWNER_OVERRIDE_REGEX);
 
   private static final Pattern REPLY_BY_REASON_PATTERN =
       Pattern.compile("(.*) replied on the change");
@@ -179,6 +199,11 @@
       Pattern.compile("Added by (.*) using the hovercard menu");
   private static final Pattern REMOVED_BY_REASON_PATTERN =
       Pattern.compile("Removed by (.*) using the hovercard menu");
+  private static final Pattern REMOVED_BY_ICON_CLICK_REASON_PATTERN =
+      Pattern.compile("Removed by (.*) by clicking the attention icon");
+
+  /** Matches {@link Account#getNameEmail} */
+  private static final Pattern NAME_EMAIL_PATTERN = Pattern.compile("(.*) (\\<.*\\>|\\(.*\\))");
 
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
@@ -211,6 +236,8 @@
     try (RevWalk revWalk = new RevWalk(repo);
         ObjectInserter ins = newPackInserter(repo)) {
       BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
+      bru.setForceRefLog(true);
+      bru.setRefLogMessage(CommitRewriter.class.getName(), false);
       bru.setAllowNonFastForwards(true);
       for (Ref ref : repo.getRefDatabase().getRefsByPrefix(RefNames.REFS_CHANGES)) {
         Change.Id changeId = Change.Id.fromRef(ref.getName());
@@ -362,7 +389,8 @@
     ChangeFixProgress changeFixProgress = new ChangeFixProgress();
     while ((originalCommit = revWalk.next()) != null) {
 
-      changeFixProgress.updateAuthorId = parseIdent(originalCommit.getAuthorIdent());
+      changeFixProgress.updateAuthorId =
+          parseIdent(changeFixProgress, originalCommit.getAuthorIdent());
       PersonIdent fixedAuthorIdent;
       if (changeFixProgress.updateAuthorId.isPresent()) {
         fixedAuthorIdent =
@@ -378,8 +406,23 @@
               ? fixedCommitMessage.get()
               : originalCommit.getFullMessage();
       if (options.verifyCommits) {
-        changeFixProgress.isValidAfterFix &=
-            verifyCommit(commitMessage, fixedAuthorIdent, accountsInChange);
+        boolean isCommitValid = verifyCommit(commitMessage, fixedAuthorIdent, accountsInChange);
+        changeFixProgress.isValidAfterFix &= isCommitValid;
+        if (!isCommitValid) {
+          StringBuilder detailedVerificationStatus =
+              new StringBuilder(
+                  String.format(
+                      "Commit %s of ref %s failed verification after fix",
+                      originalCommit.getId(), ref));
+          detailedVerificationStatus.append("\nCommit body:\n");
+          detailedVerificationStatus.append(commitMessage);
+          if (fixedCommitMessage.isPresent()) {
+            detailedVerificationStatus.append("\n was fixed.\n");
+          }
+          detailedVerificationStatus.append("Commit author:\n");
+          detailedVerificationStatus.append(fixedAuthorIdent.toString());
+          logger.atWarning().log(detailedVerificationStatus.toString());
+        }
       }
       boolean needsFix =
           !fixedAuthorIdent.equals(originalCommit.getAuthorIdent())
@@ -411,7 +454,10 @@
             "Expected diff for commit %s of ref %s",
             originalCommit.getId(),
             ref.getName());
-        changeFixProgress.commitDiffs.add(diff);
+        changeFixProgress.commitDiffs.add(CommitDiff.create(originalCommit.getId(), diff));
+      } else if (needsFix) {
+        // Always output old commits SHA1
+        changeFixProgress.commitDiffs.add(CommitDiff.create(originalCommit.getId(), ""));
       }
     }
     return changeFixProgress;
@@ -478,37 +524,46 @@
   }
 
   private Optional<String> fixAssigneeChangeMessage(
-      Account.Id oldAssignee, Account.Id newAssignee, String originalChangeMessage) {
+      ChangeFixProgress changeFixProgress,
+      Optional<Account.Id> oldAssignee,
+      Optional<Account.Id> newAssignee,
+      String originalChangeMessage) {
     if (Strings.isNullOrEmpty(originalChangeMessage)) {
       return Optional.empty();
     }
 
     Matcher assigneeDeletedMatcher = ASSIGNEE_DELETED_PATTERN.matcher(originalChangeMessage);
     if (assigneeDeletedMatcher.matches()) {
-      if (!ACCOUNT_TEMPLATE_PATTERN.matcher(assigneeDeletedMatcher.group(1)).matches()) {
+      if (!NON_REPLACE_ACCOUNT_PATTERN.matcher(assigneeDeletedMatcher.group(1)).matches()) {
         return Optional.of(
-            "Assignee deleted: " + AccountTemplateUtil.getAccountTemplate(oldAssignee));
+            "Assignee deleted: "
+                + getPossibleAccountReplacement(
+                    changeFixProgress, oldAssignee, assigneeDeletedMatcher.group(1)));
       }
       return Optional.empty();
     }
 
     Matcher assigneeAddedMatcher = ASSIGNEE_ADDED_PATTERN.matcher(originalChangeMessage);
     if (assigneeAddedMatcher.matches()) {
-      if (!ACCOUNT_TEMPLATE_PATTERN.matcher(assigneeAddedMatcher.group(1)).matches()) {
+      if (!NON_REPLACE_ACCOUNT_PATTERN.matcher(assigneeAddedMatcher.group(1)).matches()) {
         return Optional.of(
-            "Assignee added: " + AccountTemplateUtil.getAccountTemplate(newAssignee));
+            "Assignee added: "
+                + getPossibleAccountReplacement(
+                    changeFixProgress, newAssignee, assigneeAddedMatcher.group(1)));
       }
       return Optional.empty();
     }
 
     Matcher assigneeChangedMatcher = ASSIGNEE_CHANGED_PATTERN.matcher(originalChangeMessage);
     if (assigneeChangedMatcher.matches()) {
-      if (!ACCOUNT_TEMPLATE_PATTERN.matcher(assigneeChangedMatcher.group(1)).matches()) {
+      if (!NON_REPLACE_ACCOUNT_PATTERN.matcher(assigneeChangedMatcher.group(1)).matches()) {
         return Optional.of(
             String.format(
                 "Assignee changed from: %s to: %s",
-                AccountTemplateUtil.getAccountTemplate(oldAssignee),
-                AccountTemplateUtil.getAccountTemplate(newAssignee)));
+                getPossibleAccountReplacement(
+                    changeFixProgress, oldAssignee, assigneeChangedMatcher.group(1)),
+                getPossibleAccountReplacement(
+                    changeFixProgress, newAssignee, assigneeChangedMatcher.group(2))));
       }
       return Optional.empty();
     }
@@ -520,7 +575,8 @@
       return Optional.empty();
     }
     Matcher matcher = REMOVED_REVIEWER_PATTERN.matcher(originalChangeMessage);
-    if (matcher.matches() && !ACCOUNT_TEMPLATE_PATTERN.matcher(matcher.group(2)).matches()) {
+
+    if (matcher.find() && !ACCOUNT_TEMPLATE_PATTERN.matcher(matcher.group(2)).matches()) {
       // Since we do not use change messages for reviewer updates on UI, it does not matter what we
       // rewrite it to.
       return Optional.of(originalChangeMessage.substring(0, matcher.end(1)));
@@ -529,24 +585,53 @@
   }
 
   private Optional<String> fixRemoveVoteChangeMessage(
-      Optional<Account.Id> reviewer, String originalChangeMessage) {
+      ChangeFixProgress changeFixProgress,
+      Optional<Account.Id> reviewer,
+      String originalChangeMessage) {
     if (Strings.isNullOrEmpty(originalChangeMessage)) {
       return Optional.empty();
     }
 
     Matcher matcher = REMOVED_VOTE_PATTERN.matcher(originalChangeMessage);
-    if (matcher.matches() && !ACCOUNT_TEMPLATE_PATTERN.matcher(matcher.group(2)).matches()) {
+    if (matcher.matches() && !NON_REPLACE_ACCOUNT_PATTERN.matcher(matcher.group(2)).matches()) {
       return Optional.of(
           String.format(
               "Removed %s by %s",
               matcher.group(1),
-              reviewer
-                  .map(AccountTemplateUtil::getAccountTemplate)
-                  .orElse(DEFAULT_ACCOUNT_REPLACEMENT)));
+              getPossibleAccountReplacement(
+                  changeFixProgress, reviewer, getNameFromNameEmail(matcher.group(2)))));
     }
     return Optional.empty();
   }
 
+  private Optional<String> fixRemoveVotesChangeMessage(
+      ChangeFixProgress changeFixProgress, String originalChangeMessage) {
+    if (Strings.isNullOrEmpty(originalChangeMessage)
+        || !originalChangeMessage.startsWith(REMOVED_VOTES_CHANGE_MESSAGE_START)) {
+      return Optional.empty();
+    }
+    String[] lines = originalChangeMessage.split("\\r?\\n");
+    StringBuilder fixedLines = new StringBuilder();
+    for (int i = 1; i < lines.length; i++) {
+      if (lines[i].isEmpty()) {
+        continue;
+      }
+      Matcher matcher = REMOVED_VOTES_CHANGE_MESSAGE_PATTERN.matcher(lines[i]);
+      if (matcher.matches() && !NON_REPLACE_ACCOUNT_PATTERN.matcher(matcher.group(2)).matches()) {
+        fixedLines.append(
+            String.format(
+                "* %s by %s\n",
+                matcher.group(1),
+                getPossibleAccountReplacement(
+                    changeFixProgress, Optional.empty(), getNameFromNameEmail(matcher.group(2)))));
+      }
+    }
+    if (fixedLines.length() == 0) {
+      return Optional.empty();
+    }
+    return Optional.of(REMOVED_VOTES_CHANGE_MESSAGE_START + "\n" + fixedLines);
+  }
+
   private Optional<String> fixDeleteChangeMessageCommitMessage(String originalChangeMessage) {
     if (Strings.isNullOrEmpty(originalChangeMessage)) {
       return Optional.empty();
@@ -589,43 +674,20 @@
 
     Matcher onAddReviewerMatcher = ON_CODE_OWNER_ADD_REVIEWER_PATTERN.matcher(originalMessage);
     if (!onAddReviewerMatcher.find()
-        || NON_REPLACE_ACCOUNT_PATTERN.matcher(onAddReviewerMatcher.group(1)).matches()) {
+        || NON_REPLACE_ACCOUNT_PATTERN
+            .matcher(normalizeOnCodeOwnerAddReviewerMatch(onAddReviewerMatcher.group(1)))
+            .matches()) {
       return Optional.empty();
     }
 
     // Pre fix, try to replace with something meaningful.
     // Retrieve reviewer accounts from cache and try to match by their name.
-    Map<Account.Id, AccountState> missingUserNameReviewers =
-        accountCache.get(
-            changeFixProgress.parsedReviewers.entrySet().stream()
-                .filter(entry -> entry.getValue().isEmpty())
-                .map(Map.Entry::getKey)
-                .collect(ImmutableSet.toImmutableSet()));
-    // TODO(mariasavtchouk): Adjust based on the dry run.
-    // We could just reset parsedReviewers here, because next message should only include the delta
-    changeFixProgress.parsedReviewers.putAll(
-        missingUserNameReviewers.entrySet().stream()
-            .collect(
-                ImmutableMap.toImmutableMap(
-                    Map.Entry::getKey, e -> e.getValue().account().getName())));
     onAddReviewerMatcher.reset();
     StringBuffer sb = new StringBuffer();
     while (onAddReviewerMatcher.find()) {
-      String reviewerName = onAddReviewerMatcher.group(1);
-      Set<Account.Id> possibleReplacements =
-          changeFixProgress.parsedReviewers.entrySet().stream()
-              .filter(reviewer -> reviewer.getValue().equals(reviewerName))
-              .map(Entry::getKey)
-              .collect(ImmutableSet.toImmutableSet());
-      String replacementName = DEFAULT_ACCOUNT_REPLACEMENT;
-      if (possibleReplacements.isEmpty()) {
-        logger.atWarning().log("Could not find reviewer account matching name %s", reviewerName);
-      } else if (possibleReplacements.size() > 1) {
-        logger.atWarning().log("Found multiple reviewer account matching name %s", reviewerName);
-      } else {
-        replacementName =
-            AccountTemplateUtil.getAccountTemplate(Iterables.getOnlyElement(possibleReplacements));
-      }
+      String reviewerName = normalizeOnCodeOwnerAddReviewerMatch(onAddReviewerMatcher.group(1));
+      String replacementName =
+          getPossibleAccountReplacement(changeFixProgress, Optional.empty(), reviewerName);
       onAddReviewerMatcher.appendReplacement(
           sb, replacementName + ", who was added as reviewer owns the following files");
     }
@@ -634,36 +696,42 @@
     return Optional.of(sb.toString());
   }
 
+  /**
+   * See {@link #ON_CODE_OWNER_ADD_REVIEWER_PATTERN}.
+   *
+   * <p>Some of the messages have format '{@link AccountTemplateUtil#ACCOUNT_TEMPLATE}, who...',
+   * while others '{@link AccountTemplateUtil#ACCOUNT_TEMPLATE} who...'.
+   *
+   * <p>Cut the trailing ',' from the match, so that valid patterns are not replaced.
+   */
+  private static String normalizeOnCodeOwnerAddReviewerMatch(String reviewerMatch) {
+    String reviewerName = reviewerMatch;
+    if (reviewerName.charAt(reviewerName.length() - 1) == ',') {
+      reviewerName = reviewerName.substring(0, reviewerName.length() - 1);
+    }
+    return reviewerName;
+  }
+
   private Optional<String> fixCodeOwnersOnReviewChangeMessage(
       Optional<Account.Id> reviewer, String originalMessage) {
     if (Strings.isNullOrEmpty(originalMessage)) {
       return Optional.empty();
     }
 
-    Matcher onCodeOwnerApprovalMatcher = ON_CODE_OWNER_APPROVAL_PATTERN.matcher(originalMessage);
-    if (onCodeOwnerApprovalMatcher.find()
-        && !ACCOUNT_TEMPLATE_PATTERN.matcher(onCodeOwnerApprovalMatcher.group(1)).matches()) {
-      return Optional.of(
-          originalMessage.replace(
-                  "approved by " + onCodeOwnerApprovalMatcher.group(1),
-                  "approved by "
-                      + reviewer
-                          .map(AccountTemplateUtil::getAccountTemplate)
-                          .orElse(DEFAULT_ACCOUNT_REPLACEMENT))
-              + "\n");
-    }
-
-    Matcher onCodeOwnerOverrideMatcher = ON_CODE_OWNER_OVERRIDE_PATTERN.matcher(originalMessage);
-    if (onCodeOwnerOverrideMatcher.find()
-        && !ACCOUNT_TEMPLATE_PATTERN.matcher(onCodeOwnerOverrideMatcher.group(1)).matches()) {
-      return Optional.of(
-          originalMessage.replace(
-                  "overridden by " + onCodeOwnerOverrideMatcher.group(1),
-                  "overridden by "
-                      + reviewer
-                          .map(AccountTemplateUtil::getAccountTemplate)
-                          .orElse(DEFAULT_ACCOUNT_REPLACEMENT))
-              + "\n");
+    Matcher onCodeOwnerReviewMatcher = ON_CODE_OWNER_REVIEW_PATTERN.matcher(originalMessage);
+    while (onCodeOwnerReviewMatcher.find()) {
+      String accountName =
+          firstNonNull(onCodeOwnerReviewMatcher.group(1), onCodeOwnerReviewMatcher.group(2));
+      if (!ACCOUNT_TEMPLATE_PATTERN.matcher(accountName).matches()) {
+        return Optional.of(
+            originalMessage.replace(
+                    "by " + accountName,
+                    "by "
+                        + reviewer
+                            .map(AccountTemplateUtil::getAccountTemplate)
+                            .orElse(DEFAULT_ACCOUNT_REPLACEMENT))
+                + "\n");
+      }
     }
 
     return Optional.empty();
@@ -694,6 +762,14 @@
 
       return Optional.of("Removed by someone using the hovercard menu");
     }
+
+    Matcher removedByIconClickReasonMatcher =
+        REMOVED_BY_ICON_CLICK_REASON_PATTERN.matcher(originalReason);
+    if (removedByIconClickReasonMatcher.matches()
+        && !OK_ACCOUNT_NAME_PATTERN.matcher(removedByIconClickReasonMatcher.group(1)).matches()) {
+
+      return Optional.of("Removed by someone by clicking the attention icon");
+    }
     return Optional.empty();
   }
 
@@ -733,22 +809,22 @@
     for (FooterLine fl : footerLines) {
       String footerKey = fl.getKey();
       String footerValue = fl.getValue();
-      if (footerKey.equalsIgnoreCase(FOOTER_TAG.getName())) {
-        if (footerValue.equals(ChangeMessagesUtil.TAG_MERGED) && !fixedChangeMessage.isPresent()) {
-          fixedChangeMessage = fixSubmitChangeMessage(originalChangeMessage);
-        }
-      } else if (footerKey.equalsIgnoreCase(FOOTER_ASSIGNEE.getName())) {
+      if (footerKey.equalsIgnoreCase(FOOTER_ASSIGNEE.getName())) {
         Account.Id oldAssignee = fixProgress.assigneeId;
         FixIdentResult fixedAssignee = null;
         if (footerValue.equals("")) {
           fixProgress.assigneeId = null;
         } else {
-          fixedAssignee = getFixedIdentString(footerValue);
+          fixedAssignee = getFixedIdentString(fixProgress, footerValue);
           fixProgress.assigneeId = fixedAssignee.accountId;
         }
         if (!fixedChangeMessage.isPresent()) {
           fixedChangeMessage =
-              fixAssigneeChangeMessage(oldAssignee, fixProgress.assigneeId, originalChangeMessage);
+              fixAssigneeChangeMessage(
+                  fixProgress,
+                  Optional.ofNullable(oldAssignee),
+                  Optional.ofNullable(fixProgress.assigneeId),
+                  originalChangeMessage);
         }
         if (fixedAssignee != null && fixedAssignee.fixedIdentString.isPresent()) {
           addFooter(footerLinesBuilder, footerKey, fixedAssignee.fixedIdentString.get());
@@ -760,15 +836,14 @@
         if (!fixedChangeMessage.isPresent()) {
           fixedChangeMessage = fixReviewerChangeMessage(originalChangeMessage);
         }
-        FixIdentResult fixedReviewer = getFixedIdentString(footerValue);
-        fixProgress.parsedReviewers.putIfAbsent(fixedReviewer.accountId, "");
+        FixIdentResult fixedReviewer = getFixedIdentString(fixProgress, footerValue);
         if (fixedReviewer.fixedIdentString.isPresent()) {
           addFooter(footerLinesBuilder, footerKey, fixedReviewer.fixedIdentString.get());
           anyFootersFixed = true;
           continue;
         }
       } else if (footerKey.equalsIgnoreCase(FOOTER_REAL_USER.getName())) {
-        FixIdentResult fixedRealUser = getFixedIdentString(footerValue);
+        FixIdentResult fixedRealUser = getFixedIdentString(fixProgress, footerValue);
         if (fixedRealUser.fixedIdentString.isPresent()) {
           addFooter(footerLinesBuilder, footerKey, fixedRealUser.fixedIdentString.get());
           anyFootersFixed = true;
@@ -779,11 +854,12 @@
         FixIdentResult fixedVoter = null;
         if (voterIdentStart > 0) {
           String originalIdentString = footerValue.substring(voterIdentStart + 1);
-          fixedVoter = getFixedIdentString(originalIdentString);
+          fixedVoter = getFixedIdentString(fixProgress, originalIdentString);
         }
         if (!fixedChangeMessage.isPresent()) {
           fixedChangeMessage =
               fixRemoveVoteChangeMessage(
+                  fixProgress,
                   fixedVoter == null
                       ? fixProgress.updateAuthorId
                       : Optional.of(fixedVoter.accountId),
@@ -803,7 +879,7 @@
         int voterIdentStart = StringUtils.ordinalIndexOf(footerValue, ": ", 2);
         if (voterIdentStart >= 0) {
           String originalIdentString = footerValue.substring(voterIdentStart + 2);
-          FixIdentResult fixedVoter = getFixedIdentString(originalIdentString);
+          FixIdentResult fixedVoter = getFixedIdentString(fixProgress, originalIdentString);
           if (fixedVoter.fixedIdentString.isPresent()) {
             String fixedLabelVote =
                 footerValue.substring(0, voterIdentStart)
@@ -819,7 +895,7 @@
         AttentionStatusInNoteDb originalAttentionSetUpdate =
             gson.fromJson(footerValue, AttentionStatusInNoteDb.class);
         FixIdentResult fixedAttentionAccount =
-            getFixedIdentString(originalAttentionSetUpdate.personIdent);
+            getFixedIdentString(fixProgress, originalAttentionSetUpdate.personIdent);
         Optional<String> fixedReason = fixAttentionSetReason(originalAttentionSetUpdate.reason);
         if (fixedAttentionAccount.fixedIdentString.isPresent() || fixedReason.isPresent()) {
           AttentionStatusInNoteDb fixedAttentionSetUpdate =
@@ -836,7 +912,27 @@
       }
       addFooter(footerLinesBuilder, footerKey, footerValue);
     }
-
+    // Some of the old commits are missing corresponding footers but still have change messages that
+    // need the fix. For such cases, try to guess or replace with the default string (see
+    // getPossibleAccountReplacement)
+    if (!fixedChangeMessage.isPresent()) {
+      fixedChangeMessage = fixReviewerChangeMessage(originalChangeMessage);
+    }
+    if (!fixedChangeMessage.isPresent()) {
+      fixedChangeMessage = fixRemoveVotesChangeMessage(fixProgress, originalChangeMessage);
+    }
+    if (!fixedChangeMessage.isPresent()) {
+      fixedChangeMessage =
+          fixRemoveVoteChangeMessage(fixProgress, Optional.empty(), originalChangeMessage);
+    }
+    if (!fixedChangeMessage.isPresent()) {
+      fixedChangeMessage =
+          fixAssigneeChangeMessage(
+              fixProgress, Optional.empty(), Optional.empty(), originalChangeMessage);
+    }
+    if (!fixedChangeMessage.isPresent()) {
+      fixedChangeMessage = fixSubmitChangeMessage(originalChangeMessage);
+    }
     if (!fixedChangeMessage.isPresent()) {
       fixedChangeMessage = fixDeleteChangeMessageCommitMessage(originalChangeMessage);
     }
@@ -872,9 +968,11 @@
     return sb;
   }
 
-  private Optional<Account.Id> parseIdent(PersonIdent ident) {
+  private Optional<Account.Id> parseIdent(ChangeFixProgress changeFixProgress, PersonIdent ident) {
     Optional<Account.Id> account = NoteDbUtil.parseIdent(ident);
-    if (!account.isPresent()) {
+    if (account.isPresent()) {
+      changeFixProgress.parsedAccounts.putIfAbsent(account.get(), "");
+    } else {
       logger.atWarning().log("Failed to parse id %s", ident);
     }
     return account;
@@ -896,20 +994,22 @@
    * Parses {@code originalIdentString} and applies the fix, so it does not contain user data, see
    * {@link ChangeNoteUtil#appendAccountIdIdentString}.
    *
+   * @param changeFixProgress see {@link ChangeFixProgress}
    * @param originalIdentString ident to apply the fix to.
    * @return {@link FixIdentResult}, with {@link FixIdentResult#accountId} parsed from {@code
    *     originalIdentString} and {@link FixIdentResult#fixedIdentString} if the fix was applied.
    * @throws ConfigInvalidException if could not parse {@link FixIdentResult#accountId} from {@code
    *     originalIdentString}
    */
-  private FixIdentResult getFixedIdentString(String originalIdentString)
+  private FixIdentResult getFixedIdentString(
+      ChangeFixProgress changeFixProgress, String originalIdentString)
       throws ConfigInvalidException {
     FixIdentResult fixIdentResult = new FixIdentResult();
     PersonIdent originalIdent = RawParseUtils.parsePersonIdent(originalIdentString);
     // Ident as String is saved in NoteDB footers, if this fails to parse, something is
     // wrong with the change and we better not touch it.
     fixIdentResult.accountId =
-        parseIdent(originalIdent)
+        parseIdent(changeFixProgress, originalIdent)
             .orElseThrow(
                 () -> new ConfigInvalidException("field to parse id: " + originalIdentString));
     String fixedIdentString =
@@ -922,6 +1022,59 @@
     return fixIdentResult;
   }
 
+  /** Extracts {@link Account#getName} from {@link Account#getNameEmail} */
+  private String getNameFromNameEmail(String nameEmail) {
+    Matcher nameEmailMatcher = NAME_EMAIL_PATTERN.matcher(nameEmail);
+    return nameEmailMatcher.matches() ? nameEmailMatcher.group(1) : nameEmail;
+  }
+
+  /**
+   * Returns replacement for {@code accountName}.
+   *
+   * <p>If {@code account} is known, replace with {@link AccountTemplateUtil#getAccountTemplate}.
+   * Otherwise, try to guess the correct replacement account for {@code accountName} among {@link
+   * ChangeFixProgress#parsedAccounts} that appeared in the change. If this fails {@link
+   * #DEFAULT_ACCOUNT_REPLACEMENT} is applied.
+   *
+   * @param changeFixProgress see {@link ChangeFixProgress}
+   * @param account account that should be used for replacement, if known
+   * @param accountName {@link Account#getName} to replace.
+   * @return replacement for {@code accountName}
+   */
+  private String getPossibleAccountReplacement(
+      ChangeFixProgress changeFixProgress, Optional<Account.Id> account, String accountName) {
+    if (account.isPresent()) {
+      return AccountTemplateUtil.getAccountTemplate(account.get());
+    }
+    // Retrieve reviewer accounts from cache and try to match by their name.
+    Map<Account.Id, AccountState> missingUserNameReviewers =
+        accountCache.get(
+            changeFixProgress.parsedAccounts.entrySet().stream()
+                .filter(entry -> entry.getValue().isEmpty())
+                .map(Map.Entry::getKey)
+                .collect(ImmutableSet.toImmutableSet()));
+    changeFixProgress.parsedAccounts.putAll(
+        missingUserNameReviewers.entrySet().stream()
+            .collect(
+                ImmutableMap.toImmutableMap(
+                    Map.Entry::getKey, e -> e.getValue().account().getName())));
+    Set<Account.Id> possibleReplacements =
+        changeFixProgress.parsedAccounts.entrySet().stream()
+            .filter(e -> e.getValue().equals(accountName))
+            .map(Entry::getKey)
+            .collect(ImmutableSet.toImmutableSet());
+    String replacementName = DEFAULT_ACCOUNT_REPLACEMENT;
+    if (possibleReplacements.isEmpty()) {
+      logger.atWarning().log("Could not find reviewer account matching name %s", accountName);
+    } else if (possibleReplacements.size() > 1) {
+      logger.atWarning().log("Found multiple reviewer account matching name %s", accountName);
+    } else {
+      replacementName =
+          AccountTemplateUtil.getAccountTemplate(Iterables.getOnlyElement(possibleReplacements));
+    }
+    return replacementName;
+  }
+
   /**
    * Cuts tree and parent lines from raw unparsed commit body, so they are not included in diff
    * comparison.
@@ -989,11 +1142,11 @@
     Optional<Account.Id> updateAuthorId = null;
 
     /**
-     * Reviewer accounts parsed so far together with their {@link Account#getName} extracted from
-     * {@link #accountCache} if needed by rewrite. Maps to empty string if was not requested from
-     * cache yet.
+     * Accounts parsed so far together with their {@link Account#getName} extracted from {@link
+     * #accountCache} if needed by rewrite. Maps to empty string if was not requested from cache
+     * yet.
      */
-    Map<Account.Id, String> parsedReviewers = new HashMap<>();
+    Map<Account.Id, String> parsedAccounts = new HashMap<>();
 
     /** Id of the current commit in rewriter walk. */
     ObjectId newTipId = null;
@@ -1006,6 +1159,6 @@
      */
     boolean isValidAfterFix = true;
 
-    List<String> commitDiffs = new ArrayList<>();
+    List<CommitDiff> commitDiffs = new ArrayList<>();
   }
 }
diff --git a/java/com/google/gerrit/server/notedb/StoreSubmitRequirementsOp.java b/java/com/google/gerrit/server/notedb/StoreSubmitRequirementsOp.java
index 4d99f93..57a3cd7 100644
--- a/java/com/google/gerrit/server/notedb/StoreSubmitRequirementsOp.java
+++ b/java/com/google/gerrit/server/notedb/StoreSubmitRequirementsOp.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.notedb;
 
-import com.google.gerrit.entities.Change;
 import com.google.gerrit.server.project.SubmitRequirementsEvaluator;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.update.BatchUpdateOp;
@@ -39,10 +38,19 @@
 
   @Override
   public boolean updateChange(ChangeContext ctx) throws Exception {
-    Change change = ctx.getChange();
-    ChangeData changeData = changeDataFactory.create(change);
-    ChangeUpdate update = ctx.getUpdate(change.currentPatchSetId());
-    update.putSubmitRequirementResults(evaluator.getResults(changeData).values());
+    // Create ChangeData using the project/change IDs instead of ctx.getChange(). We do that because
+    // for changes requiring a rebase before submission (e.g. if submit type = RebaseAlways), the
+    // RebaseOp inserts a new patchset that is visible here (via Change#getCurrentPatchset). If we
+    // then try to get ChangeData#currentPatchset it will return null, since it loads patchsets from
+    // NoteDb but tries to find the patchset with the ID of the one just inserted by the rebase op.
+    // Note that this implementation means that, in this case, submit requirement results will be
+    // stored in change notes of the pre last patchset commit. This is fine since submit requirement
+    // results should evaluate to the exact same results for both commits. Additionally, the
+    // pre-last commit is the one for which we displayed the submit requirement results of the last
+    // patchset to the user before it was merged.
+    ChangeData changeData = changeDataFactory.create(ctx.getProject(), ctx.getChange().getId());
+    ChangeUpdate update = ctx.getUpdate(ctx.getChange().currentPatchSetId());
+    update.putSubmitRequirementResults(evaluator.evaluateAllRequirements(changeData).values());
     return !changeData.submitRequirements().isEmpty();
   }
 }
diff --git a/java/com/google/gerrit/server/notedb/SubmitRequirementProtoConverter.java b/java/com/google/gerrit/server/notedb/SubmitRequirementProtoConverter.java
index 416b850..9bf56d8 100644
--- a/java/com/google/gerrit/server/notedb/SubmitRequirementProtoConverter.java
+++ b/java/com/google/gerrit/server/notedb/SubmitRequirementProtoConverter.java
@@ -40,6 +40,7 @@
     SubmitRequirementResultProto.Builder builder = SubmitRequirementResultProto.newBuilder();
     builder
         .setSubmitRequirement(SubmitRequirementSerializer.serialize(r.submitRequirement()))
+        .setLegacy(r.legacy())
         .setCommit(ObjectIdConverter.create().toByteString(r.patchSetCommitId()));
     if (r.applicabilityExpressionResult().isPresent()) {
       builder.setApplicabilityExpressionResult(
@@ -60,6 +61,7 @@
   public SubmitRequirementResult fromProto(SubmitRequirementResultProto proto) {
     SubmitRequirementResult.Builder builder =
         SubmitRequirementResult.builder()
+            .legacy(proto.getLegacy())
             .patchSetCommitId(ObjectIdConverter.create().fromByteString(proto.getCommit()))
             .submitRequirement(
                 SubmitRequirementSerializer.deserialize(proto.getSubmitRequirement()));
diff --git a/java/com/google/gerrit/server/patch/FilePathAdapter.java b/java/com/google/gerrit/server/patch/FilePathAdapter.java
index ccd1466..2c98f1a 100644
--- a/java/com/google/gerrit/server/patch/FilePathAdapter.java
+++ b/java/com/google/gerrit/server/patch/FilePathAdapter.java
@@ -35,11 +35,12 @@
       case DELETED:
       case ADDED:
       case MODIFIED:
-      case REWRITE:
         return null;
       case COPIED:
       case RENAMED:
         return oldName.get();
+      case REWRITE:
+        return oldName.isPresent() ? oldName.get() : null;
       default:
         throw new IllegalArgumentException("Unsupported type " + changeType);
     }
diff --git a/java/com/google/gerrit/server/patch/IntraLineLoader.java b/java/com/google/gerrit/server/patch/IntraLineLoader.java
index 29f5c2c..d6afa88 100644
--- a/java/com/google/gerrit/server/patch/IntraLineLoader.java
+++ b/java/com/google/gerrit/server/patch/IntraLineLoader.java
@@ -15,7 +15,6 @@
 
 package com.google.gerrit.server.patch;
 
-import com.google.common.base.Supplier;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -28,6 +27,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -35,6 +35,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 import org.eclipse.jgit.diff.Edit;
 import org.eclipse.jgit.diff.MyersDiff;
 import org.eclipse.jgit.lib.Config;
@@ -273,39 +274,71 @@
    *     otherwise.
    */
   private static boolean isValidTransformation(CharText lText, CharText rText, List<Edit> edits) {
-    Supplier<String> applyDeleteAndReplaceEditsToLeft =
-        () -> {
-          StringBuilder reconstructed = toStringBuilder(lText);
-          String right = toStringBuilder(rText).toString();
-          // Process edits right to left to avoid re-computation of indices when characters are
-          // removed.
-          for (int i = edits.size() - 1; i >= 0; i--) {
-            Edit edit = edits.get(i);
-            if (edit.getType() == Edit.Type.REPLACE) {
-              reconstructed.replace(
-                  edit.getBeginA(),
-                  edit.getEndA(),
-                  right.substring(edit.getBeginB(), edit.getEndB()));
-            } else if (edit.getType() == Edit.Type.DELETE) {
-              reconstructed.delete(edit.getBeginA(), edit.getEndA());
-            }
-          }
-          return reconstructed.toString();
-        };
-    Supplier<StringBuilder> removeInsertEditsFromRight =
-        () -> {
-          StringBuilder reconstructed = toStringBuilder(rText);
-          // Process edits right to left to avoid re-computation of indices when characters are
-          // removed.
-          for (int i = edits.size() - 1; i >= 0; i--) {
-            Edit edit = edits.get(i);
-            if (edit.getType() == Edit.Type.INSERT) {
-              reconstructed.delete(edit.getBeginB(), edit.getEndB());
-            }
-          }
-          return reconstructed;
-        };
-    return applyDeleteAndReplaceEditsToLeft.get().contentEquals(removeInsertEditsFromRight.get());
+    // Apply replace and delete edits to the left text
+    Optional<String> left =
+        applyEditsToString(
+            toStringBuilder(lText),
+            toStringBuilder(rText).toString(),
+            edits.stream()
+                .filter(e -> e.getType() == Edit.Type.REPLACE || e.getType() == Edit.Type.DELETE)
+                .collect(Collectors.toList()));
+    // Remove insert edits from the right text
+    Optional<String> right =
+        applyEditsToString(
+            toStringBuilder(rText),
+            null,
+            edits.stream()
+                .filter(e -> e.getType() == Edit.Type.INSERT)
+                .collect(Collectors.toList()));
+
+    return left.isPresent() && right.isPresent() && left.get().contentEquals(right.get());
+  }
+
+  /**
+   * Apply edits to the {@code target} string. Replace edits are applied to target and replaced with
+   * a substring from {@code from}. Delete edits are applied to target. Insert edits are removed
+   * from target.
+   *
+   * @return Optional containing the transformed string, or empty if the transformation fails (due
+   *     to index out of bounds).
+   */
+  private static Optional<String> applyEditsToString(
+      StringBuilder target, String from, List<Edit> edits) {
+    // Process edits right to left to avoid re-computation of indices when characters are removed.
+    try {
+      for (int i = edits.size() - 1; i >= 0; i--) {
+        Edit edit = edits.get(i);
+        if (edit.getType() == Edit.Type.REPLACE) {
+          boundaryCheck(target, edit.getBeginA(), edit.getEndA() - 1);
+          boundaryCheck(from, edit.getBeginB(), edit.getEndB() - 1);
+          target.replace(
+              edit.getBeginA(), edit.getEndA(), from.substring(edit.getBeginB(), edit.getEndB()));
+        } else if (edit.getType() == Edit.Type.DELETE) {
+          boundaryCheck(target, edit.getBeginA(), edit.getEndA() - 1);
+          target.delete(edit.getBeginA(), edit.getEndA());
+        } else if (edit.getType() == Edit.Type.INSERT) {
+          boundaryCheck(target, edit.getBeginB(), edit.getEndB() - 1);
+          target.delete(edit.getBeginB(), edit.getEndB());
+        }
+      }
+      return Optional.of(target.toString());
+    } catch (StringIndexOutOfBoundsException unused) {
+      return Optional.empty();
+    }
+  }
+
+  private static void boundaryCheck(StringBuilder s, int i1, int i2) {
+    if (i1 >= 0 && i2 >= 0 && i1 < s.length() && i2 < s.length()) {
+      return;
+    }
+    throw new StringIndexOutOfBoundsException();
+  }
+
+  private static void boundaryCheck(String s, int i1, int i2) {
+    if (i1 >= 0 && i2 >= 0 && i1 < s.length() && i2 < s.length()) {
+      return;
+    }
+    throw new StringIndexOutOfBoundsException();
   }
 
   private static StringBuilder toStringBuilder(CharText text) {
diff --git a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index 5998bba..0b08c4f 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -235,10 +235,10 @@
         return null;
       case DELETED:
       case MODIFIED:
-      case REWRITE:
         return entry.getNewName();
       case COPIED:
       case RENAMED:
+      case REWRITE:
       default:
         return entry.getOldName();
     }
diff --git a/java/com/google/gerrit/server/patch/PatchScriptFactory.java b/java/com/google/gerrit/server/patch/PatchScriptFactory.java
index fbb6559..6fa14e1 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptFactory.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptFactory.java
@@ -34,9 +34,10 @@
 import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.PatchSetUtil;
-import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.change.FileInfoJsonExperimentImpl;
 import com.google.gerrit.server.edit.ChangeEdit;
 import com.google.gerrit.server.edit.ChangeEditUtil;
+import com.google.gerrit.server.experiments.ExperimentFeatures;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.LargeObjectException;
 import com.google.gerrit.server.logging.Metadata;
@@ -56,17 +57,11 @@
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
 import java.io.IOException;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import org.apache.commons.lang.exception.ExceptionUtils;
 import org.eclipse.jgit.diff.Edit;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 
@@ -118,8 +113,6 @@
   private final PatchSetUtil psUtil;
   private final Provider<PatchScriptBuilder> builderFactory;
   private final PatchListCache patchListCache;
-  private final Metrics metrics;
-  private final ExecutorService executor;
 
   private final String fileName;
   @Nullable private final PatchSet.Id psa;
@@ -146,12 +139,10 @@
       Provider<PatchScriptBuilder> builderFactory,
       PatchListCache patchListCache,
       ChangeEditUtil editReader,
+      ExperimentFeatures experimentFeatures,
       PermissionBackend permissionBackend,
       ProjectCache projectCache,
       DiffOperations diffOperations,
-      Metrics metrics,
-      @DiffExecutor ExecutorService executor,
-      @GerritServerConfig Config cfg,
       @Assisted ChangeNotes notes,
       @Assisted String fileName,
       @Assisted("patchSetA") @Nullable PatchSet.Id patchSetA,
@@ -167,8 +158,6 @@
     this.permissionBackend = permissionBackend;
     this.projectCache = projectCache;
     this.diffOperations = diffOperations;
-    this.metrics = metrics;
-    this.executor = executor;
 
     this.fileName = fileName;
     this.psa = patchSetA;
@@ -177,7 +166,8 @@
     this.diffPrefs = diffPrefs;
     this.currentUser = currentUser;
 
-    this.runNewDiffCache = cfg.getBoolean("cache", "diff_cache", "runNewDiffCache_GetDiff", false);
+    this.runNewDiffCache =
+        experimentFeatures.isFeatureEnabled(FileInfoJsonExperimentImpl.NEW_DIFF_CACHE_FEATURE);
 
     changeId = patchSetB.changeId();
   }
@@ -189,12 +179,10 @@
       Provider<PatchScriptBuilder> builderFactory,
       PatchListCache patchListCache,
       ChangeEditUtil editReader,
+      ExperimentFeatures experimentFeatures,
       PermissionBackend permissionBackend,
       ProjectCache projectCache,
       DiffOperations diffOperations,
-      Metrics metrics,
-      @DiffExecutor ExecutorService executor,
-      @GerritServerConfig Config cfg,
       @Assisted ChangeNotes notes,
       @Assisted String fileName,
       @Assisted int parentNum,
@@ -210,8 +198,6 @@
     this.permissionBackend = permissionBackend;
     this.projectCache = projectCache;
     this.diffOperations = diffOperations;
-    this.metrics = metrics;
-    this.executor = executor;
 
     this.fileName = fileName;
     this.psa = null;
@@ -220,7 +206,8 @@
     this.diffPrefs = diffPrefs;
     this.currentUser = currentUser;
 
-    this.runNewDiffCache = cfg.getBoolean("cache", "diff_cache", "runNewDiffCache_GetDiff", false);
+    this.runNewDiffCache =
+        experimentFeatures.isFeatureEnabled(FileInfoJsonExperimentImpl.NEW_DIFF_CACHE_FEATURE);
 
     changeId = patchSetB.changeId();
     checkArgument(parentNum > 0, "parentNum must be > 0");
@@ -259,14 +246,9 @@
           }
           bId = edit.get().getEditCommit();
         }
-        if (runNewDiffCache) {
-          PatchScript patchScript = getPatchScriptWithNewDiffCache(git, aId, bId);
-          // TODO(ghareeb): remove the async run. This is temporarily used to keep sanity checking
-          // the results while rolling out the new diff cache.
-          runOldDiffCacheAsyncAndExportMetrics(git, aId, bId, patchScript);
-          return patchScript;
-        }
-        return getPatchScriptWithOldDiffCache(git, aId, bId);
+        return runNewDiffCache
+            ? getPatchScriptWithNewDiffCache(git, aId, bId)
+            : getPatchScriptWithOldDiffCache(git, aId, bId);
       } catch (PatchListNotAvailableException e) {
         throw new NoSuchChangeException(changeId, e);
       } catch (DiffNotAvailableException e) {
@@ -286,33 +268,6 @@
     }
   }
 
-  private void runOldDiffCacheAsyncAndExportMetrics(
-      Repository git, ObjectId aId, ObjectId bId, PatchScript expected) {
-    @SuppressWarnings("unused")
-    Future<?> possiblyIgnoredError =
-        executor.submit(
-            () -> {
-              try {
-                PatchScript patchScript = getPatchScriptWithOldDiffCache(git, aId, bId);
-                if (areEqualPatchscripts(patchScript, expected)) {
-                  metrics.diffs.increment(Metrics.MATCH);
-                } else {
-                  metrics.diffs.increment(Metrics.MISMATCH);
-                  logger.atWarning().atMostEvery(10, TimeUnit.SECONDS).log(
-                      "Mismatching diff for change %s, old commit ID: %s, new commit ID: %s, file name: %s.",
-                      changeId.toString(), aId, bId, fileName);
-                }
-              } catch (PatchListNotAvailableException | IOException e) {
-                metrics.diffs.increment(Metrics.ERROR);
-                logger.atSevere().atMostEvery(10, TimeUnit.SECONDS).log(
-                    String.format(
-                            "Error computing new diff for change %s, old commit ID: %s, new commit ID: %s.\n",
-                            changeId.toString(), aId, bId)
-                        + ExceptionUtils.getStackTrace(e));
-              }
-            });
-  }
-
   private PatchScript getPatchScriptWithOldDiffCache(Repository git, ObjectId aId, ObjectId bId)
       throws IOException, PatchListNotAvailableException {
     PatchScriptBuilder patchScriptBuilder = newBuilder();
@@ -332,61 +287,6 @@
     return newBuilder().toPatchScriptNew(git, fileDiffOutput);
   }
 
-  /**
-   * The comparison is not exhaustive but is using the most important fields. Comparing all fields
-   * will require some work in {@link PatchScript} to, e.g., convert it to autovalue. This
-   * comparison method shall give a strong signal that both patchscripts are almost identical.
-   */
-  private static boolean areEqualPatchscripts(PatchScript ps1, PatchScript ps2) {
-    boolean equal = true;
-    if (!ps1.getChangeType().equals(ps2.getChangeType())) {
-      equal = false;
-      logger.atWarning().log(
-          "Mismatching change type: old = %s, new = %s.", ps1.getChangeType(), ps2.getChangeType());
-    }
-    if (!ps1.getPatchHeader().equals(ps2.getPatchHeader())) {
-      equal = false;
-      logger.atWarning().log(
-          "Mismatching patch header: old = %s, new = %s.",
-          ps1.getPatchHeader(), ps2.getPatchHeader());
-    }
-    if (!Objects.equals(ps1.getOldName(), ps2.getOldName())) {
-      equal = false;
-      logger.atWarning().log(
-          "Mismatching old name: old = %s, new = %s.", ps1.getOldName(), ps2.getOldName());
-    }
-    if (!Objects.equals(ps1.getNewName(), ps2.getNewName())) {
-      equal = false;
-      logger.atWarning().log(
-          "Mismatching new name: old = %s, new = %s.", ps1.getNewName(), ps2.getNewName());
-    }
-    if (!ps1.getEdits().containsAll(ps2.getEdits())) {
-      equal = false;
-      logger.atWarning().log(
-          "Mismatching edits: old = %s, new = %s.", ps1.getEdits(), ps2.getEdits());
-    }
-    if (!ps2.getEdits().containsAll(ps1.getEdits())) {
-      equal = false;
-      logger.atWarning().log(
-          "Mismatching edits: old = %s, new = %s.", ps1.getEdits(), ps2.getEdits());
-    }
-    if (!ps1.getEditsDueToRebase().equals(ps2.getEditsDueToRebase())) {
-      equal = false;
-      logger.atWarning().log(
-          "Mismatching edits due to rebase: old = %s, new = %s.",
-          ps1.getEditsDueToRebase(), ps2.getEditsDueToRebase());
-    }
-    if (!ps1.getA().equals(ps2.getA())) {
-      equal = false;
-      logger.atWarning().log("Mismatching sparse file content in old commit.");
-    }
-    if (!ps1.getB().equals(ps2.getB())) {
-      equal = false;
-      logger.atWarning().log("Mismatching sparse file content in new commit.");
-    }
-    return equal;
-  }
-
   private Optional<ObjectId> getAId() {
     if (psa == null) {
       return Optional.empty();
diff --git a/java/com/google/gerrit/server/patch/diff/ModifiedFilesCacheImpl.java b/java/com/google/gerrit/server/patch/diff/ModifiedFilesCacheImpl.java
index e4fd728..460c2e2 100644
--- a/java/com/google/gerrit/server/patch/diff/ModifiedFilesCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/diff/ModifiedFilesCacheImpl.java
@@ -86,7 +86,7 @@
             .valueSerializer(GitModifiedFilesCacheImpl.ValueSerializer.INSTANCE)
             .maximumWeight(10 << 20)
             .weigher(ModifiedFilesWeigher.class)
-            .version(2)
+            .version(3)
             .loader(ModifiedFilesLoader.class);
       }
     };
@@ -208,27 +208,22 @@
     }
 
     /**
-     * Return the {@code modifiedFiles} input list while merging {@link ChangeType#ADDED} and {@link
-     * ChangeType#DELETED} entries for the same file into a single {@link ChangeType#REWRITE} entry.
+     * Return the {@code modifiedFiles} input list while merging rewritten entries.
      *
-     * <p>Background: In some cases, JGit returns two diff entries (ADDED + DELETED) for the same
-     * file path. This happens e.g. when a file's mode is changed between patchsets, for example
-     * converting a symlink file to a regular file. We identify this case and return a single
-     * modified file with changeType = {@link ChangeType#REWRITE}.
+     * <p>Background: In some cases, JGit returns two diff entries (ADDED/DELETED, RENAMED/DELETED,
+     * etc...) for the same file path. This happens e.g. when a file's mode is changed between
+     * patchsets, for example converting a symlink file to a regular file. We identify this case and
+     * return a single modified file with changeType = {@link ChangeType#REWRITE}.
      */
     private static List<ModifiedFile> mergeRewrittenEntries(List<ModifiedFile> modifiedFiles) {
       List<ModifiedFile> result = new ArrayList<>();
-
-      // Handle ADDED and DELETED entries separately.
       ListMultimap<String, ModifiedFile> byPath = ArrayListMultimap.create();
       modifiedFiles.stream()
-          .filter(ModifiedFilesLoader::isAddedOrDeleted)
           .forEach(
               f -> {
-                if (f.oldPath().isPresent()) {
+                if (f.changeType() == ChangeType.DELETED) {
                   byPath.get(f.oldPath().get()).add(f);
-                }
-                if (f.newPath().isPresent()) {
+                } else {
                   byPath.get(f.newPath().get()).add(f);
                 }
               });
@@ -236,31 +231,12 @@
         List<ModifiedFile> entries = byPath.get(path);
         if (entries.size() == 1) {
           result.add(entries.get(0));
-        } else if (entries.size() == 2) {
-          result.add(getAddedEntry(entries).toBuilder().changeType(ChangeType.REWRITE).build());
         } else {
-          // JGit error. Not expected to happen.
-          logger.atWarning().log(
-              "Found %d ADDED and DELETED entries for the same file path: %s."
-                  + " Adding the first entry only to the result.",
-              entries.size(), entries);
-          result.add(entries.get(0));
+          // More than one. Return a single REWRITE entry.
+          result.add(entries.get(0).toBuilder().changeType(ChangeType.REWRITE).build());
         }
       }
-
-      // Add the remaining non ADDED/DELETED entries to the result
-      modifiedFiles.stream().filter(f -> !isAddedOrDeleted(f)).forEach(result::add);
       return result;
     }
-
-    private static boolean isAddedOrDeleted(ModifiedFile f) {
-      return f.changeType() == ChangeType.ADDED || f.changeType() == ChangeType.DELETED;
-    }
-
-    private static ModifiedFile getAddedEntry(List<ModifiedFile> modifiedFiles) {
-      return modifiedFiles.get(0).changeType() == ChangeType.ADDED
-          ? modifiedFiles.get(0)
-          : modifiedFiles.get(1);
-    }
   }
 }
diff --git a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
index a67f221..92c3b39 100644
--- a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
@@ -97,7 +97,7 @@
         persist(DIFF, FileDiffCacheKey.class, FileDiffOutput.class)
             .maximumWeight(10 << 20)
             .weigher(FileDiffWeigher.class)
-            .version(7)
+            .version(8)
             .keySerializer(FileDiffCacheKey.Serializer.INSTANCE)
             .valueSerializer(FileDiffOutput.Serializer.INSTANCE)
             .loader(FileDiffLoader.class);
diff --git a/java/com/google/gerrit/server/patch/filediff/FileDiffOutput.java b/java/com/google/gerrit/server/patch/filediff/FileDiffOutput.java
index 3c6d746..242c1a4 100644
--- a/java/com/google/gerrit/server/patch/filediff/FileDiffOutput.java
+++ b/java/com/google/gerrit/server/patch/filediff/FileDiffOutput.java
@@ -37,7 +37,10 @@
 public abstract class FileDiffOutput implements Serializable {
   private static final long serialVersionUID = 1L;
 
-  /** The 20 bytes SHA-1 object ID of the old git commit used in the diff. */
+  /**
+   * The 20 bytes SHA-1 object ID of the old git commit used in the diff, or {@link
+   * ObjectId#zeroId()} if {@link #newCommitId()} was a root commit.
+   */
   public abstract ObjectId oldCommitId();
 
   /** The 20 bytes SHA-1 object ID of the new git commit used in the diff. */
@@ -130,9 +133,16 @@
         .build();
   }
 
+  /**
+   * Create a negative file diff. We use this to cache negative diffs for entries that result in
+   * timeouts.
+   */
   public static FileDiffOutput createNegative(
       String filePath, ObjectId oldCommitId, ObjectId newCommitId) {
-    return empty(filePath, oldCommitId, newCommitId).toBuilder().build();
+    return empty(filePath, oldCommitId, newCommitId)
+        .toBuilder()
+        .negative(Optional.of(true))
+        .build();
   }
 
   /** Returns true if this entity represents an unchanged file between two commits. */
diff --git a/java/com/google/gerrit/server/patch/filediff/PatchListLoader.java b/java/com/google/gerrit/server/patch/filediff/PatchListLoader.java
index 017e276..031f3db 100644
--- a/java/com/google/gerrit/server/patch/filediff/PatchListLoader.java
+++ b/java/com/google/gerrit/server/patch/filediff/PatchListLoader.java
@@ -35,6 +35,9 @@
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
+import com.google.gerrit.metrics.Counter0;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -52,6 +55,7 @@
 import com.google.gerrit.server.patch.Text;
 import com.google.gerrit.server.patch.filediff.EditTransformer.ContextAwareEdit;
 import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import com.google.inject.assistedinject.Assisted;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -99,12 +103,30 @@
     PatchListLoader create(PatchListKey key, Project.NameKey project);
   }
 
+  @Singleton
+  static class Metrics {
+    final Counter0 timeouts;
+
+    @Inject
+    Metrics(MetricMaker metricMaker) {
+      // TODO(ghareeb): Remove this metric from documentation once this class is deprecated.
+      timeouts =
+          metricMaker.newCounter(
+              "caches/diff/legacy/timeouts",
+              new Description(
+                      "Total number of git file diff computations that resulted in timeouts.")
+                  .setRate()
+                  .setUnit("count"));
+    }
+  }
+
   private final GitRepositoryManager repoManager;
   private final PatchListCache patchListCache;
   private final ThreeWayMergeStrategy mergeStrategy;
   private final ExecutorService diffExecutor;
   private final AutoMerger autoMerger;
   private final PatchListKey key;
+  private final Metrics metrics;
   private final Project.NameKey project;
   private final long timeoutMillis;
 
@@ -115,16 +137,18 @@
       @GerritServerConfig Config cfg,
       @DiffExecutor ExecutorService de,
       AutoMerger am,
+      Metrics metrics,
       @Assisted PatchListKey k,
       @Assisted Project.NameKey p) {
-    repoManager = mgr;
-    patchListCache = plc;
-    mergeStrategy = MergeUtil.getMergeStrategy(cfg);
-    diffExecutor = de;
-    autoMerger = am;
-    key = k;
-    project = p;
-    timeoutMillis =
+    this.repoManager = mgr;
+    this.patchListCache = plc;
+    this.mergeStrategy = MergeUtil.getMergeStrategy(cfg);
+    this.diffExecutor = de;
+    this.autoMerger = am;
+    this.metrics = metrics;
+    this.key = k;
+    this.project = p;
+    this.timeoutMillis =
         ConfigUtil.getTimeUnit(
             cfg,
             "cache",
@@ -529,6 +553,7 @@
     try {
       return result.get(timeoutMillis, TimeUnit.MILLISECONDS);
     } catch (InterruptedException | TimeoutException e) {
+      metrics.timeouts.increment();
       logger.atWarning().log(
           "%s ms timeout reached for Diff loader in project %s"
               + " on commit %s on path %s comparing %s..%s",
diff --git a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiff.java b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiff.java
index a502a46..2f23c8c 100644
--- a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiff.java
+++ b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiff.java
@@ -34,6 +34,7 @@
 import org.eclipse.jgit.diff.DiffEntry;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.patch.FileHeader;
 
 /**
@@ -120,7 +121,10 @@
   /** The file name at the new git tree identified by {@link #newId()} */
   public abstract Optional<String> newPath();
 
-  /** The 20 bytes SHA-1 object ID of the old git tree of the diff. */
+  /**
+   * The 20 bytes SHA-1 object ID of the old git tree of the diff, or {@link ObjectId#zeroId()} if
+   * {@link #newId()} was a root git tree (i.e. has no parents).
+   */
   public abstract AbbreviatedObjectId oldId();
 
   /** The 20 bytes SHA-1 object ID of the new git tree of the diff. */
diff --git a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java
index 77b8938..f293a64 100644
--- a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java
@@ -31,6 +31,9 @@
 import com.google.gerrit.entities.Patch;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
+import com.google.gerrit.metrics.Counter0;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
@@ -47,6 +50,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -91,6 +95,22 @@
     };
   }
 
+  @Singleton
+  static class Metrics {
+    final Counter0 timeouts;
+
+    @Inject
+    Metrics(MetricMaker metricMaker) {
+      timeouts =
+          metricMaker.newCounter(
+              "caches/diff/timeouts",
+              new Description(
+                      "Total number of git file diff computations that resulted in timeouts.")
+                  .setRate()
+                  .setUnit("count"));
+    }
+  }
+
   /** Enum for the supported diff algorithms for the file diff computation. */
   public enum DiffAlgorithm {
     HISTOGRAM_WITH_FALLBACK_MYERS,
@@ -110,9 +130,6 @@
 
   private final LoadingCache<GitFileDiffCacheKey, GitFileDiff> cache;
 
-  private static final ImmutableSet<Patch.ChangeType> ADDED_AND_DELETED =
-      ImmutableSet.of(Patch.ChangeType.ADDED, Patch.ChangeType.DELETED);
-
   @Inject
   public GitFileDiffCacheImpl(
       @Named(GIT_DIFF) LoadingCache<GitFileDiffCacheKey, GitFileDiff> cache) {
@@ -152,12 +169,14 @@
     private final GitRepositoryManager repoManager;
     private final ExecutorService diffExecutor;
     private final long timeoutMillis;
+    private final Metrics metrics;
 
     @Inject
     public Loader(
         @GerritServerConfig Config cfg,
         GitRepositoryManager repoManager,
-        @DiffExecutor ExecutorService de) {
+        @DiffExecutor ExecutorService de,
+        Metrics metrics) {
       this.repoManager = repoManager;
       this.diffExecutor = de;
       this.timeoutMillis =
@@ -168,6 +187,7 @@
               "timeout",
               TimeUnit.MILLISECONDS.convert(5, TimeUnit.SECONDS),
               TimeUnit.MILLISECONDS);
+      this.metrics = metrics;
     }
 
     @Override
@@ -346,6 +366,7 @@
         return GitFileDiff.create(diffEntry, fileHeader);
       } catch (InterruptedException | TimeoutException e) {
         // If timeout happens, create a negative result
+        metrics.timeouts.increment();
         return GitFileDiff.createNegative(
             AbbreviatedObjectId.fromObjectId(key.oldTree()),
             AbbreviatedObjectId.fromObjectId(key.newTree()),
@@ -360,12 +381,13 @@
   }
 
   /**
-   * Create a single {@link GitFileDiff} with {@link Patch.ChangeType} equals {@link
-   * Patch.ChangeType#REWRITE}, assuming the input list contains two entries with types {@link
-   * Patch.ChangeType#ADDED} and {@link Patch.ChangeType#DELETED}.
+   * Create a single {@link GitFileDiff} with {@link com.google.gerrit.entities.Patch.ChangeType}
+   * equals {@link com.google.gerrit.entities.Patch.ChangeType#REWRITE}, assuming the input list
+   * contains two entries.
    *
    * @param gitDiffs input list of exactly two {@link GitFileDiff} for same file path.
-   * @return a single {@link GitFileDiff} with change type equals {@link Patch.ChangeType#REWRITE}.
+   * @return a single {@link GitFileDiff} with change type equals {@link
+   *     com.google.gerrit.entities.Patch.ChangeType#REWRITE}.
    * @throws DiffNotAvailableException if input list contains git diffs with change types other than
    *     {ADDED, DELETED}. This is a JGit error.
    */
@@ -377,19 +399,9 @@
               "JGit error: found %d dff entries for same file path %s",
               gitDiffs.size(), gitDiffs.get(0).getDefaultPath()));
     }
-    if (!ImmutableSet.of(gitDiffs.get(0).changeType(), gitDiffs.get(1).changeType())
-        .equals(ADDED_AND_DELETED)) {
-      // This is an illegal state. JGit is not supposed to return this, so we throw an exception.
-      throw new DiffNotAvailableException(
-          String.format(
-              "JGit error: unexpected change types %s and %s for same file path %s",
-              gitDiffs.get(0).changeType(),
-              gitDiffs.get(1).changeType(),
-              gitDiffs.get(0).getDefaultPath()));
-    }
-    GitFileDiff addedEntry =
-        gitDiffs.get(0).changeType() == Patch.ChangeType.ADDED ? gitDiffs.get(0) : gitDiffs.get(1);
-    return addedEntry.toBuilder().changeType(Patch.ChangeType.REWRITE).build();
+    // Convert the first entry (prioritized according to change type enum order) to REWRITE
+    gitDiffs.sort(Comparator.comparingInt(o -> o.changeType().ordinal()));
+    return gitDiffs.get(0).toBuilder().changeType(Patch.ChangeType.REWRITE).build();
   }
 
   /** An entity representing the options affecting the diff computation. */
diff --git a/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index e69967c..2874a34 100644
--- a/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -51,9 +51,9 @@
 import com.google.gerrit.server.cache.serialize.ObjectIdConverter;
 import com.google.gerrit.server.cache.serialize.ProtobufSerializer;
 import com.google.gerrit.server.cache.serialize.entities.CachedProjectConfigSerializer;
+import com.google.gerrit.server.config.AllProjectsConfigProvider;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.logging.Metadata;
 import com.google.gerrit.server.logging.TraceContext;
@@ -78,8 +78,7 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.lib.StoredConfig;
 
 /**
  * Cache of project information, including access rights.
@@ -304,27 +303,22 @@
   /**
    * Returns a {@code MurMur128} hash of the contents of {@code etc/All-Projects-project.config}.
    */
-  public static byte[] allProjectsFileProjectConfigHash(
-      AllProjectsName allProjectsName, SitePaths sitePaths) {
+  public static byte[] allProjectsFileProjectConfigHash(Optional<StoredConfig> allProjectsConfig) {
     // Hash the contents of All-Projects-project.config
     // This is a way for administrators to orchestrate project.config changes across many Gerrit
     // instances.
     // When this file changes, we need to make sure we disregard persistently cached project
     // state.
-    FileBasedConfig fileBasedConfig =
-        new FileBasedConfig(
-            sitePaths
-                .etc_dir
-                .resolve(allProjectsName.get())
-                .resolve(ProjectConfig.PROJECT_CONFIG)
-                .toFile(),
-            FS.DETECTED);
+    if (!allProjectsConfig.isPresent()) {
+      // If the project.config file is not present, this is equal to an empty config file:
+      return Hashing.murmur3_128().hashString("", UTF_8).asBytes();
+    }
     try {
-      fileBasedConfig.load();
+      allProjectsConfig.get().load();
     } catch (IOException | ConfigInvalidException e) {
       throw new IllegalStateException(e);
     }
-    return Hashing.murmur3_128().hashString(fileBasedConfig.toText(), UTF_8).asBytes();
+    return Hashing.murmur3_128().hashString(allProjectsConfig.get().toText(), UTF_8).asBytes();
   }
 
   @Singleton
@@ -334,7 +328,7 @@
     private final ListeningExecutorService cacheRefreshExecutor;
     private final Counter2<String, Boolean> refreshCounter;
     private final AllProjectsName allProjectsName;
-    private final SitePaths sitePaths;
+    private final AllProjectsConfigProvider allProjectsConfigProvider;
 
     @Inject
     InMemoryLoader(
@@ -344,7 +338,7 @@
         @CacheRefreshExecutor ListeningExecutorService cacheRefreshExecutor,
         MetricMaker metricMaker,
         AllProjectsName allProjectsName,
-        SitePaths sitePaths) {
+        AllProjectsConfigProvider allProjectsConfigProvider) {
       this.persistedCache = persistedCache;
       this.repoManager = repoManager;
       this.cacheRefreshExecutor = cacheRefreshExecutor;
@@ -355,7 +349,7 @@
               Field.ofString("cache", Metadata.Builder::className).build(),
               Field.ofBoolean("outdated", Metadata.Builder::outdated).build());
       this.allProjectsName = allProjectsName;
-      this.sitePaths = sitePaths;
+      this.allProjectsConfigProvider = allProjectsConfigProvider;
     }
 
     @Override
@@ -369,7 +363,8 @@
             Cache.ProjectCacheKeyProto.newBuilder().setProject(key.get());
         Ref configRef = git.exactRef(RefNames.REFS_CONFIG);
         if (key.get().equals(allProjectsName.get())) {
-          byte[] fileHash = allProjectsFileProjectConfigHash(allProjectsName, sitePaths);
+          Optional<StoredConfig> allProjectsConfig = allProjectsConfigProvider.get(allProjectsName);
+          byte[] fileHash = allProjectsFileProjectConfigHash(allProjectsConfig);
           keyProto.setGlobalConfigRevision(ByteString.copyFrom(fileHash));
         }
         if (configRef != null) {
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index 0d710b9..a23bb39 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -64,10 +64,10 @@
 import com.google.gerrit.extensions.client.InheritableBoolean;
 import com.google.gerrit.extensions.client.ProjectState;
 import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.config.AllProjectsConfigProvider;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.PluginConfig;
-import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.git.ValidationError;
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
 import com.google.gerrit.server.git.meta.VersionedMetaData;
@@ -98,8 +98,6 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
 
 public class ProjectConfig extends VersionedMetaData implements ValidationError.Sink {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -203,28 +201,21 @@
   // ProjectCache, so this would retain lots more memory.
   @Singleton
   public static class Factory {
-    @Nullable
-    public static StoredConfig getBaseConfig(
-        SitePaths sitePaths, AllProjectsName allProjects, Project.NameKey projectName) {
-      return projectName.equals(allProjects)
-          // Delay loading till onLoad method.
-          ? new FileBasedConfig(
-              sitePaths.etc_dir.resolve(allProjects.get()).resolve(PROJECT_CONFIG).toFile(),
-              FS.DETECTED)
-          : null;
-    }
-
-    private final SitePaths sitePaths;
-    private final AllProjectsName allProjects;
+    private final AllProjectsName allProjectsName;
+    private final AllProjectsConfigProvider allProjectsConfigProvider;
 
     @Inject
-    Factory(SitePaths sitePaths, AllProjectsName allProjects) {
-      this.sitePaths = sitePaths;
-      this.allProjects = allProjects;
+    Factory(AllProjectsName allProjectsName, AllProjectsConfigProvider allProjectsConfigProvider) {
+      this.allProjectsName = allProjectsName;
+      this.allProjectsConfigProvider = allProjectsConfigProvider;
     }
 
     public ProjectConfig create(Project.NameKey projectName) {
-      return new ProjectConfig(projectName, getBaseConfig(sitePaths, allProjects, projectName));
+      return new ProjectConfig(
+          projectName,
+          projectName.equals(allProjectsName)
+              ? allProjectsConfigProvider.get(allProjectsName)
+              : Optional.empty());
     }
 
     public ProjectConfig read(MetaDataUpdate update) throws IOException, ConfigInvalidException {
@@ -249,7 +240,7 @@
     }
   }
 
-  private final StoredConfig baseConfig;
+  private final Optional<StoredConfig> baseConfig;
 
   private Project project;
   private AccountsSection accountsSection;
@@ -355,7 +346,7 @@
     requireNonNull(commentLinkSections.remove(name));
   }
 
-  private ProjectConfig(Project.NameKey projectName, @Nullable StoredConfig baseConfig) {
+  private ProjectConfig(Project.NameKey projectName, Optional<StoredConfig> baseConfig) {
     this.projectName = projectName;
     this.baseConfig = baseConfig;
   }
@@ -636,8 +627,8 @@
 
   @Override
   protected void onLoad() throws IOException, ConfigInvalidException {
-    if (baseConfig != null) {
-      baseConfig.load();
+    if (baseConfig.isPresent()) {
+      baseConfig.get().load();
     }
     readGroupList();
 
diff --git a/java/com/google/gerrit/server/project/ProjectCreator.java b/java/com/google/gerrit/server/project/ProjectCreator.java
index c382f04..4e778a4 100644
--- a/java/com/google/gerrit/server/project/ProjectCreator.java
+++ b/java/com/google/gerrit/server/project/ProjectCreator.java
@@ -39,7 +39,8 @@
 import com.google.gerrit.server.extensions.events.AbstractNoNotifyEvent;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.RepositoryCaseMismatchException;
+import com.google.gerrit.server.git.GitRepositoryManager.Status;
+import com.google.gerrit.server.git.RepositoryExistsException;
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
 import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.inject.Inject;
@@ -107,12 +108,9 @@
     final Project.NameKey nameKey = args.getProject();
     try {
       final String head = args.permissionsOnly ? RefNames.REFS_CONFIG : args.branch.get(0);
-      try (Repository repo = repoManager.openRepository(nameKey)) {
-        if (repo.getObjectDatabase().exists()) {
-          throw new ResourceConflictException("project \"" + nameKey + "\" exists");
-        }
-      } catch (RepositoryNotFoundException e) {
-        // It does not exist, safe to ignore.
+      Status status = repoManager.getRepositoryStatus(nameKey);
+      if (!status.equals(Status.NON_EXISTENT)) {
+        throw new RepositoryExistsException(nameKey, "Repository status: " + status);
       }
       try (Repository repo = repoManager.createRepository(nameKey)) {
         RefUpdate u = repo.updateRef(Constants.HEAD);
@@ -129,13 +127,11 @@
 
         return projectCache.get(nameKey).orElseThrow(illegalState(nameKey));
       }
-    } catch (RepositoryCaseMismatchException e) {
+    } catch (RepositoryExistsException e) {
       throw new ResourceConflictException(
           "Cannot create "
               + nameKey.get()
-              + " because the name is already occupied by another project."
-              + " The other project has the same name, only spelled in a"
-              + " different case.",
+              + " because the name is already occupied by another project.",
           e);
     } catch (RepositoryNotFoundException badName) {
       throw new BadRequestException("invalid project name: " + nameKey, badName);
diff --git a/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java b/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java
new file mode 100644
index 0000000..0383cdc
--- /dev/null
+++ b/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java
@@ -0,0 +1,187 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.project;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.MoreCollectors;
+import com.google.gerrit.entities.LabelType;
+import com.google.gerrit.entities.SubmitRecord;
+import com.google.gerrit.entities.SubmitRecord.Label;
+import com.google.gerrit.entities.SubmitRequirement;
+import com.google.gerrit.entities.SubmitRequirementExpression;
+import com.google.gerrit.entities.SubmitRequirementExpressionResult;
+import com.google.gerrit.entities.SubmitRequirementExpressionResult.Status;
+import com.google.gerrit.entities.SubmitRequirementResult;
+import java.util.List;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * Convert {@link com.google.gerrit.entities.SubmitRecord} entities to {@link
+ * com.google.gerrit.entities.SubmitRequirementResult}s.
+ */
+public class SubmitRequirementsAdapter {
+  private SubmitRequirementsAdapter() {}
+
+  public static List<SubmitRequirementResult> createResult(
+      SubmitRecord record, List<LabelType> labelTypes, ObjectId psCommitId) {
+    if (record.ruleName.equals("gerrit~DefaultSubmitRule")) {
+      return createFromDefaultSubmitRecord(record.labels, labelTypes, psCommitId);
+    }
+    return createFromCustomSubmitRecord(record, psCommitId);
+  }
+
+  private static List<SubmitRequirementResult> createFromDefaultSubmitRecord(
+      List<Label> labels, List<LabelType> labelTypes, ObjectId psCommitId) {
+    ImmutableList.Builder<SubmitRequirementResult> result = ImmutableList.builder();
+    for (Label label : labels) {
+      LabelType labelType = getLabelType(labelTypes, label.label);
+      if (!isBlocking(labelType)) {
+        continue;
+      }
+      ImmutableList<String> atoms = toExpressionAtomList(labelType);
+      SubmitRequirement.Builder req =
+          SubmitRequirement.builder()
+              .setName(label.label)
+              .setSubmittabilityExpression(toExpression(atoms))
+              .setAllowOverrideInChildProjects(labelType.isCanOverride());
+      result.add(
+          SubmitRequirementResult.builder()
+              .legacy(true)
+              .submitRequirement(req.build())
+              .submittabilityExpressionResult(
+                  createExpressionResult(toExpression(atoms), mapStatus(label), atoms))
+              .patchSetCommitId(psCommitId)
+              .build());
+    }
+    return result.build();
+  }
+
+  private static List<SubmitRequirementResult> createFromCustomSubmitRecord(
+      SubmitRecord record, ObjectId psCommitId) {
+    if (record.labels == null || record.labels.isEmpty()) {
+      SubmitRequirement sr =
+          SubmitRequirement.builder()
+              .setName(record.ruleName)
+              .setSubmittabilityExpression(
+                  SubmitRequirementExpression.create(String.format("rule:%s", record.ruleName)))
+              .setAllowOverrideInChildProjects(false)
+              .build();
+      return ImmutableList.of(
+          SubmitRequirementResult.builder()
+              .legacy(true)
+              .submitRequirement(sr)
+              .submittabilityExpressionResult(
+                  createExpressionResult(
+                      sr.submittabilityExpression(),
+                      mapStatus(record),
+                      ImmutableList.of(record.ruleName)))
+              .patchSetCommitId(psCommitId)
+              .build());
+    }
+    ImmutableList.Builder<SubmitRequirementResult> result = ImmutableList.builder();
+    for (Label label : record.labels) {
+      String expressionString = String.format("label:%s=%s", label.label, record.ruleName);
+      SubmitRequirement sr =
+          SubmitRequirement.builder()
+              .setName(label.label)
+              .setSubmittabilityExpression(SubmitRequirementExpression.create(expressionString))
+              .setAllowOverrideInChildProjects(false)
+              .build();
+      result.add(
+          SubmitRequirementResult.builder()
+              .legacy(true)
+              .submitRequirement(sr)
+              .submittabilityExpressionResult(
+                  createExpressionResult(
+                      sr.submittabilityExpression(),
+                      mapStatus(record),
+                      ImmutableList.of(expressionString)))
+              .patchSetCommitId(psCommitId)
+              .build());
+    }
+    return result.build();
+  }
+
+  private static boolean isBlocking(LabelType labelType) {
+    return labelType.getFunction().isBlock() || labelType.getFunction().isRequired();
+  }
+
+  private static SubmitRequirementExpression toExpression(List<String> atoms) {
+    return SubmitRequirementExpression.create(String.join(" ", atoms));
+  }
+
+  private static ImmutableList<String> toExpressionAtomList(LabelType lt) {
+    switch (lt.getFunction()) {
+      case MAX_WITH_BLOCK:
+        return ImmutableList.of(
+            String.format("label:%s=MAX", lt.getName()),
+            String.format("-label:%s=MIN", lt.getName()));
+      case ANY_WITH_BLOCK:
+        return ImmutableList.of(String.format(String.format("-label:%s=MIN", lt.getName())));
+      case MAX_NO_BLOCK:
+        return ImmutableList.of(String.format(String.format("label:%s=MAX", lt.getName())));
+      case NO_BLOCK:
+      case NO_OP:
+      case PATCH_SET_LOCK:
+      default:
+        return ImmutableList.of();
+    }
+  }
+
+  private static Status mapStatus(Label label) {
+    SubmitRequirementExpressionResult.Status status = Status.PASS;
+    switch (label.status) {
+      case OK:
+      case MAY:
+        status = Status.PASS;
+        break;
+      case REJECT:
+      case NEED:
+      case IMPOSSIBLE:
+        status = Status.FAIL;
+        break;
+    }
+    return status;
+  }
+
+  private static Status mapStatus(SubmitRecord submitRecord) {
+    switch (submitRecord.status) {
+      case OK:
+      case CLOSED:
+      case FORCED:
+        return Status.PASS;
+      case NOT_READY:
+        return Status.FAIL;
+      case RULE_ERROR:
+      default:
+        return Status.ERROR;
+    }
+  }
+
+  private static SubmitRequirementExpressionResult createExpressionResult(
+      SubmitRequirementExpression expression, Status status, ImmutableList<String> atoms) {
+    return SubmitRequirementExpressionResult.create(
+        expression,
+        status,
+        status == Status.PASS ? atoms : ImmutableList.of(),
+        status == Status.FAIL ? atoms : ImmutableList.of());
+  }
+
+  private static LabelType getLabelType(List<LabelType> labelTypes, String labelName) {
+    return labelTypes.stream()
+        .filter(lt -> lt.getName().equals(labelName))
+        .collect(MoreCollectors.onlyElement());
+  }
+}
diff --git a/java/com/google/gerrit/server/project/SubmitRequirementsEvaluator.java b/java/com/google/gerrit/server/project/SubmitRequirementsEvaluator.java
index fad90df..b3ac380 100644
--- a/java/com/google/gerrit/server/project/SubmitRequirementsEvaluator.java
+++ b/java/com/google/gerrit/server/project/SubmitRequirementsEvaluator.java
@@ -14,36 +14,27 @@
 
 package com.google.gerrit.server.project;
 
-import static com.google.gerrit.server.project.ProjectCache.illegalState;
-
-import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.entities.SubmitRequirement;
 import com.google.gerrit.entities.SubmitRequirementExpression;
 import com.google.gerrit.entities.SubmitRequirementExpressionResult;
-import com.google.gerrit.entities.SubmitRequirementExpressionResult.PredicateResult;
 import com.google.gerrit.entities.SubmitRequirementResult;
-import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.query.change.ChangeQueryBuilder;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
 import java.util.Map;
-import java.util.Optional;
 
-/** Evaluates submit requirements for different change data. */
-@Singleton
-public class SubmitRequirementsEvaluator {
-  private final Provider<ChangeQueryBuilder> changeQueryBuilderProvider;
-  private final ProjectCache projectCache;
+public interface SubmitRequirementsEvaluator {
+  /**
+   * Evaluate and return all submit requirement results for a change. Submit requirements are read
+   * from the project config of the project containing the change as well as parent projects.
+   */
+  Map<SubmitRequirement, SubmitRequirementResult> evaluateAllRequirements(ChangeData cd);
 
-  @Inject
-  private SubmitRequirementsEvaluator(
-      Provider<ChangeQueryBuilder> changeQueryBuilderProvider, ProjectCache projectCache) {
-    this.changeQueryBuilderProvider = changeQueryBuilderProvider;
-    this.projectCache = projectCache;
-  }
+  /** Evaluate a single {@link SubmitRequirement} using change data. */
+  SubmitRequirementResult evaluateRequirement(SubmitRequirement sr, ChangeData cd);
+
+  /** Evaluate a {@link SubmitRequirementExpression} using change data. */
+  SubmitRequirementExpressionResult evaluateExpression(
+      SubmitRequirementExpression expression, ChangeData changeData);
 
   /**
    * Validate a {@link SubmitRequirementExpression}. Callers who wish to validate submit
@@ -52,74 +43,5 @@
    * @param expression entity containing the expression string.
    * @throws QueryParseException the expression string contains invalid syntax and can't be parsed.
    */
-  public void validateExpression(SubmitRequirementExpression expression)
-      throws QueryParseException {
-    changeQueryBuilderProvider.get().parse(expression.expressionString());
-  }
-
-  /**
-   * Evaluate and return all submit requirements for a change. Submit requirements are retrieved for
-   * the project containing the change and parent projects as well.
-   */
-  public Map<SubmitRequirement, SubmitRequirementResult> getResults(ChangeData cd) {
-    ProjectState state = projectCache.get(cd.project()).orElseThrow(illegalState(cd.project()));
-    Map<String, SubmitRequirement> requirements = state.getSubmitRequirements();
-    ImmutableMap.Builder<SubmitRequirement, SubmitRequirementResult> result =
-        ImmutableMap.builderWithExpectedSize(requirements.size());
-    for (SubmitRequirement requirement : requirements.values()) {
-      result.put(requirement, evaluate(requirement, cd));
-    }
-    return result.build();
-  }
-
-  /** Evaluate a {@link SubmitRequirement} on a given {@link ChangeData}. */
-  public SubmitRequirementResult evaluate(SubmitRequirement sr, ChangeData cd) {
-    SubmitRequirementExpressionResult blockingResult =
-        evaluateExpression(sr.submittabilityExpression(), cd);
-
-    Optional<SubmitRequirementExpressionResult> applicabilityResult =
-        sr.applicabilityExpression().isPresent()
-            ? Optional.of(evaluateExpression(sr.applicabilityExpression().get(), cd))
-            : Optional.empty();
-
-    Optional<SubmitRequirementExpressionResult> overrideResult =
-        sr.overrideExpression().isPresent()
-            ? Optional.of(evaluateExpression(sr.overrideExpression().get(), cd))
-            : Optional.empty();
-
-    return SubmitRequirementResult.builder()
-        .submitRequirement(sr)
-        .patchSetCommitId(cd.currentPatchSet().commitId())
-        .submittabilityExpressionResult(blockingResult)
-        .applicabilityExpressionResult(applicabilityResult)
-        .overrideExpressionResult(overrideResult)
-        .build();
-  }
-
-  /** Evaluate a {@link SubmitRequirementExpression} using change data. */
-  public SubmitRequirementExpressionResult evaluateExpression(
-      SubmitRequirementExpression expression, ChangeData changeData) {
-    try {
-      Predicate<ChangeData> predicate =
-          changeQueryBuilderProvider.get().parse(expression.expressionString());
-      PredicateResult predicateResult = evaluatePredicateTree(predicate, changeData);
-      return SubmitRequirementExpressionResult.create(expression, predicateResult);
-    } catch (QueryParseException e) {
-      return SubmitRequirementExpressionResult.error(expression, e.getMessage());
-    }
-  }
-
-  /** Evaluate the predicate recursively using change data. */
-  private PredicateResult evaluatePredicateTree(
-      Predicate<ChangeData> predicate, ChangeData changeData) {
-    PredicateResult.Builder predicateResult =
-        PredicateResult.builder()
-            .predicateString(predicate.isLeaf() ? predicate.getPredicateString() : "")
-            .status(predicate.asMatchable().match(changeData));
-    predicate
-        .getChildren()
-        .forEach(
-            c -> predicateResult.addChildPredicateResult(evaluatePredicateTree(c, changeData)));
-    return predicateResult.build();
-  }
+  void validateExpression(SubmitRequirementExpression expression) throws QueryParseException;
 }
diff --git a/java/com/google/gerrit/server/project/SubmitRequirementsEvaluatorImpl.java b/java/com/google/gerrit/server/project/SubmitRequirementsEvaluatorImpl.java
new file mode 100644
index 0000000..9be50c7
--- /dev/null
+++ b/java/com/google/gerrit/server/project/SubmitRequirementsEvaluatorImpl.java
@@ -0,0 +1,173 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.project;
+
+import static com.google.gerrit.server.project.ProjectCache.illegalState;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.entities.LabelType;
+import com.google.gerrit.entities.SubmitRecord;
+import com.google.gerrit.entities.SubmitRequirement;
+import com.google.gerrit.entities.SubmitRequirementExpression;
+import com.google.gerrit.entities.SubmitRequirementExpressionResult;
+import com.google.gerrit.entities.SubmitRequirementExpressionResult.PredicateResult;
+import com.google.gerrit.entities.SubmitRequirementResult;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.server.experiments.ExperimentFeatures;
+import com.google.gerrit.server.experiments.ExperimentFeaturesConstants;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.Provider;
+import com.google.inject.Scopes;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.eclipse.jgit.lib.ObjectId;
+
+/** Evaluates submit requirements for different change data. */
+public class SubmitRequirementsEvaluatorImpl implements SubmitRequirementsEvaluator {
+
+  private final Provider<ChangeQueryBuilder> changeQueryBuilderProvider;
+  private final ProjectCache projectCache;
+  private final SubmitRuleEvaluator.Factory legacyEvaluator;
+  private final ExperimentFeatures experimentFeatures;
+
+  public static Module module() {
+    return new AbstractModule() {
+      @Override
+      protected void configure() {
+        bind(SubmitRequirementsEvaluator.class)
+            .to(SubmitRequirementsEvaluatorImpl.class)
+            .in(Scopes.SINGLETON);
+      }
+    };
+  }
+
+  @Inject
+  private SubmitRequirementsEvaluatorImpl(
+      Provider<ChangeQueryBuilder> changeQueryBuilderProvider,
+      ProjectCache projectCache,
+      SubmitRuleEvaluator.Factory legacyEvaluator,
+      ExperimentFeatures experimentFeatures) {
+    this.changeQueryBuilderProvider = changeQueryBuilderProvider;
+    this.projectCache = projectCache;
+    this.legacyEvaluator = legacyEvaluator;
+    this.experimentFeatures = experimentFeatures;
+  }
+
+  @Override
+  public void validateExpression(SubmitRequirementExpression expression)
+      throws QueryParseException {
+    changeQueryBuilderProvider.get().parse(expression.expressionString());
+  }
+
+  @Override
+  public Map<SubmitRequirement, SubmitRequirementResult> evaluateAllRequirements(ChangeData cd) {
+    Map<SubmitRequirement, SubmitRequirementResult> result = getRequirements(cd);
+    if (experimentFeatures.isFeatureEnabled(
+        ExperimentFeaturesConstants
+            .GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_LEGACY_SUBMIT_REQUIREMENTS)) {
+      result.putAll(getLegacyRequirements(cd));
+    }
+    return ImmutableMap.copyOf(result);
+  }
+
+  @Override
+  public SubmitRequirementResult evaluateRequirement(SubmitRequirement sr, ChangeData cd) {
+    SubmitRequirementExpressionResult blockingResult =
+        evaluateExpression(sr.submittabilityExpression(), cd);
+
+    Optional<SubmitRequirementExpressionResult> applicabilityResult =
+        sr.applicabilityExpression().isPresent()
+            ? Optional.of(evaluateExpression(sr.applicabilityExpression().get(), cd))
+            : Optional.empty();
+
+    Optional<SubmitRequirementExpressionResult> overrideResult =
+        sr.overrideExpression().isPresent()
+            ? Optional.of(evaluateExpression(sr.overrideExpression().get(), cd))
+            : Optional.empty();
+
+    return SubmitRequirementResult.builder()
+        .legacy(false)
+        .submitRequirement(sr)
+        .patchSetCommitId(cd.currentPatchSet().commitId())
+        .submittabilityExpressionResult(blockingResult)
+        .applicabilityExpressionResult(applicabilityResult)
+        .overrideExpressionResult(overrideResult)
+        .build();
+  }
+
+  @Override
+  public SubmitRequirementExpressionResult evaluateExpression(
+      SubmitRequirementExpression expression, ChangeData changeData) {
+    try {
+      Predicate<ChangeData> predicate =
+          changeQueryBuilderProvider.get().parse(expression.expressionString());
+      PredicateResult predicateResult = evaluatePredicateTree(predicate, changeData);
+      return SubmitRequirementExpressionResult.create(expression, predicateResult);
+    } catch (QueryParseException e) {
+      return SubmitRequirementExpressionResult.error(expression, e.getMessage());
+    }
+  }
+
+  /** Evaluate and return submit requirements stored in this project's config and its parents. */
+  private Map<SubmitRequirement, SubmitRequirementResult> getRequirements(ChangeData cd) {
+    ProjectState state = projectCache.get(cd.project()).orElseThrow(illegalState(cd.project()));
+    Map<String, SubmitRequirement> requirements = state.getSubmitRequirements();
+    Map<SubmitRequirement, SubmitRequirementResult> result = new HashMap<>();
+    for (SubmitRequirement requirement : requirements.values()) {
+      result.put(requirement, evaluateRequirement(requirement, cd));
+    }
+    return result;
+  }
+
+  /**
+   * Convert and return legacy submit records (created by label functions and other {@link
+   * com.google.gerrit.server.rules.SubmitRule}s to submit requirement results.
+   */
+  private Map<SubmitRequirement, SubmitRequirementResult> getLegacyRequirements(ChangeData cd) {
+    // We use SubmitRuleOptions.defaults() which does not recompute submit rules for closed changes.
+    // This doesn't have an effect since we never call this class (i.e. to evaluate submit
+    // requirements) for closed changes.
+    List<SubmitRecord> records = legacyEvaluator.create(SubmitRuleOptions.defaults()).evaluate(cd);
+    List<LabelType> labelTypes = cd.getLabelTypes().getLabelTypes();
+    ObjectId commitId = cd.currentPatchSet().commitId();
+    return records.stream()
+        .map(r -> SubmitRequirementsAdapter.createResult(r, labelTypes, commitId))
+        .flatMap(List::stream)
+        .collect(Collectors.toMap(sr -> sr.submitRequirement(), Function.identity()));
+  }
+
+  /** Evaluate the predicate recursively using change data. */
+  private PredicateResult evaluatePredicateTree(
+      Predicate<ChangeData> predicate, ChangeData changeData) {
+    PredicateResult.Builder predicateResult =
+        PredicateResult.builder()
+            .predicateString(predicate.isLeaf() ? predicate.getPredicateString() : "")
+            .status(predicate.asMatchable().match(changeData));
+    predicate
+        .getChildren()
+        .forEach(
+            c -> predicateResult.addChildPredicateResult(evaluatePredicateTree(c, changeData)));
+    return predicateResult.build();
+  }
+}
diff --git a/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java b/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
index 6345cdb..cb8b686 100644
--- a/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
+++ b/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
@@ -125,7 +125,17 @@
               projectState.hasPrologRules()
                   ? rule -> !(rule.get() instanceof DefaultSubmitRule)
                   : rule -> true)
-          .map(c -> c.call(s -> s.evaluate(cd)))
+          .map(
+              c ->
+                  c.call(
+                      s -> {
+                        Optional<SubmitRecord> evaluate = s.evaluate(cd);
+                        if (evaluate.isPresent()) {
+                          evaluate.get().ruleName =
+                              c.getPluginName() + "~" + s.getClass().getSimpleName();
+                        }
+                        return evaluate;
+                      }))
           .filter(Optional::isPresent)
           .map(Optional::get)
           .collect(toImmutableList());
diff --git a/java/com/google/gerrit/server/query/account/InternalAccountQuery.java b/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
index b7f8b45..1d67009 100644
--- a/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
+++ b/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
@@ -31,6 +31,7 @@
 import com.google.gerrit.index.query.InternalQuery;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.index.account.AccountField;
 import com.google.gerrit.server.index.account.AccountIndexCollection;
 import com.google.inject.Inject;
@@ -46,12 +47,16 @@
 public class InternalAccountQuery extends InternalQuery<AccountState, InternalAccountQuery> {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
+  private final ExternalIdKeyFactory externalIdKeyFactory;
+
   @Inject
   InternalAccountQuery(
       AccountQueryProcessor queryProcessor,
       AccountIndexCollection indexes,
-      IndexConfig indexConfig) {
+      IndexConfig indexConfig,
+      ExternalIdKeyFactory externalIdKeyFactory) {
     super(queryProcessor, indexes, indexConfig);
+    this.externalIdKeyFactory = externalIdKeyFactory;
   }
 
   public List<AccountState> byDefault(String query, boolean canSeeSecondaryEmails) {
@@ -59,7 +64,7 @@
   }
 
   public List<AccountState> byExternalId(String scheme, String id) {
-    return byExternalId(ExternalId.Key.create(scheme, id));
+    return byExternalId(externalIdKeyFactory.create(scheme, id));
   }
 
   public List<AccountState> byExternalId(ExternalId.Key externalId) {
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index 8b4e4c7..5b83dd5 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -951,7 +951,7 @@
             notes().getSubmitRequirementsResult().stream()
                 .collect(Collectors.toMap(r -> r.submitRequirement(), Function.identity()));
       } else {
-        submitRequirements = submitRequirementsEvaluator.getResults(this);
+        submitRequirements = submitRequirementsEvaluator.evaluateAllRequirements(this);
       }
     }
     return submitRequirements;
@@ -1102,19 +1102,6 @@
   }
 
   public boolean isReviewedBy(Account.Id accountId) {
-    Collection<String> stars = stars(accountId);
-
-    PatchSet ps = currentPatchSet();
-    if (ps != null) {
-      if (stars.contains(StarredChangesUtil.REVIEWED_LABEL + "/" + ps.number())) {
-        return true;
-      }
-
-      if (stars.contains(StarredChangesUtil.UNREVIEWED_LABEL + "/" + ps.number())) {
-        return false;
-      }
-    }
-
     return reviewedBy().contains(accountId);
   }
 
diff --git a/java/com/google/gerrit/server/query/change/ChangePredicates.java b/java/com/google/gerrit/server/query/change/ChangePredicates.java
index 044d276..4e638df 100644
--- a/java/com/google/gerrit/server/query/change/ChangePredicates.java
+++ b/java/com/google/gerrit/server/query/change/ChangePredicates.java
@@ -292,4 +292,15 @@
   public static Predicate<ChangeData> comment(String comment) {
     return new ChangeIndexPredicate(ChangeField.COMMENT, comment);
   }
+
+  /**
+   * Returns a predicate that matches with changes having a specific submit rule evaluating to a
+   * certain result. Value should be in the form of "$ruleName=$status" with $ruleName equals to
+   * '$plugin_name~$rule_name' and $rule_name equals to the name of the class that implements the
+   * {@link com.google.gerrit.server.rules.SubmitRule}. For gerrit core rules, $ruleName should be
+   * in the form of 'gerrit~$rule_name'.
+   */
+  public static Predicate<ChangeData> submitRuleStatus(String value) {
+    return new ChangeIndexPredicate(ChangeField.SUBMIT_RULE_RESULT, value);
+  }
 }
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index e244d6c..8525eb4 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -78,8 +78,10 @@
 import com.google.gerrit.server.notedb.ReviewerStateInternal;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.gerrit.server.project.ChildProjects;
 import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.rules.SubmitRule;
 import com.google.gerrit.server.submit.SubmitDryRun;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -87,6 +89,7 @@
 import com.google.inject.util.Providers;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -176,7 +179,6 @@
   public static final String FIELD_OWNERIN = "ownerin";
   public static final String FIELD_PARENTOF = "parentof";
   public static final String FIELD_PARENTPROJECT = "parentproject";
-  public static final String FIELD_PATH = "path";
   public static final String FIELD_PENDING_REVIEWER = "pendingreviewer";
   public static final String FIELD_PENDING_REVIEWER_BY_EMAIL = "pendingreviewerbyemail";
   public static final String FIELD_PRIVATE = "private";
@@ -184,11 +186,9 @@
   public static final String FIELD_PROJECTS = "projects";
   public static final String FIELD_REF = "ref";
   public static final String FIELD_REVIEWEDBY = "reviewedby";
-  public static final String FIELD_REVIEWER = "reviewer";
   public static final String FIELD_REVIEWERIN = "reviewerin";
   public static final String FIELD_STAR = "star";
   public static final String FIELD_STARBY = "starby";
-  public static final String FIELD_STARREDBY = "starredby";
   public static final String FIELD_STARTED = "started";
   public static final String FIELD_STATUS = "status";
   public static final String FIELD_SUBMISSIONID = "submissionid";
@@ -198,7 +198,7 @@
   public static final String FIELD_WATCHEDBY = "watchedby";
   public static final String FIELD_WIP = "wip";
   public static final String FIELD_REVERTOF = "revertof";
-  public static final String FIELD_CHERRY_PICK_OF = "cherrypickof";
+  public static final String FIELD_CHERRYPICK = "cherrypick";
   public static final String FIELD_CHERRY_PICK_OF_CHANGE = "cherrypickofchange";
   public static final String FIELD_CHERRY_PICK_OF_PATCHSET = "cherrypickofpatchset";
 
@@ -206,7 +206,9 @@
   public static final String ARG_ID_USER = "user";
   public static final String ARG_ID_GROUP = "group";
   public static final String ARG_ID_OWNER = "owner";
+  public static final String ARG_ID_NON_UPLOADER = "non_uploader";
   public static final Account.Id OWNER_ACCOUNT_ID = Account.id(0);
+  public static final Account.Id NON_UPLOADER_ACCOUNT_ID = Account.id(-1);
 
   public static final String OPERATOR_MERGED_BEFORE = "mergedbefore";
   public static final String OPERATOR_MERGED_AFTER = "mergedafter";
@@ -249,6 +251,7 @@
     final boolean indexMergeable;
     final boolean conflictsPredicateEnabled;
     final HasOperandAliasConfig hasOperandAliasConfig;
+    final PluginSetContext<SubmitRule> submitRules;
 
     private final Provider<CurrentUser> self;
 
@@ -283,7 +286,8 @@
         OperatorAliasConfig operatorAliasConfig,
         @GerritServerConfig Config gerritConfig,
         HasOperandAliasConfig hasOperandAliasConfig,
-        ChangeIsVisibleToPredicate.Factory changeIsVisbleToPredicateFactory) {
+        ChangeIsVisibleToPredicate.Factory changeIsVisbleToPredicateFactory,
+        PluginSetContext<SubmitRule> submitRules) {
       this(
           queryProvider,
           rewriter,
@@ -314,7 +318,8 @@
           MergeabilityComputationBehavior.fromConfig(gerritConfig).includeInIndex(),
           gerritConfig.getBoolean("change", null, "conflictsPredicateEnabled", true),
           hasOperandAliasConfig,
-          changeIsVisbleToPredicateFactory);
+          changeIsVisbleToPredicateFactory,
+          submitRules);
     }
 
     private Arguments(
@@ -347,7 +352,8 @@
         boolean indexMergeable,
         boolean conflictsPredicateEnabled,
         HasOperandAliasConfig hasOperandAliasConfig,
-        ChangeIsVisibleToPredicate.Factory changeIsVisbleToPredicateFactory) {
+        ChangeIsVisibleToPredicate.Factory changeIsVisbleToPredicateFactory,
+        PluginSetContext<SubmitRule> submitRules) {
       this.queryProvider = queryProvider;
       this.rewriter = rewriter;
       this.opFactories = opFactories;
@@ -378,6 +384,7 @@
       this.indexMergeable = indexMergeable;
       this.conflictsPredicateEnabled = conflictsPredicateEnabled;
       this.hasOperandAliasConfig = hasOperandAliasConfig;
+      this.submitRules = submitRules;
     }
 
     Arguments asUser(CurrentUser otherUser) {
@@ -411,7 +418,8 @@
           indexMergeable,
           conflictsPredicateEnabled,
           hasOperandAliasConfig,
-          changeIsVisbleToPredicateFactory);
+          changeIsVisbleToPredicateFactory,
+          submitRules);
     }
 
     Arguments asUser(Account.Id otherId) {
@@ -471,10 +479,6 @@
     hasOperandAliases = args.hasOperandAliasConfig.getChangeQueryHasOperandAliases();
   }
 
-  public Arguments getArgs() {
-    return args;
-  }
-
   public ChangeQueryBuilder asUser(CurrentUser user) {
     return new ChangeQueryBuilder(builderDef, args.asUser(user));
   }
@@ -567,18 +571,43 @@
   }
 
   @Operator
+  public Predicate<ChangeData> rule(String value) throws QueryParseException {
+    String ruleNameArg = value;
+    String statusArg = null;
+    String[] queryArgs = value.split("=");
+    if (queryArgs.length > 2) {
+      throw new QueryParseException(
+          "Invalid query arguments. Correct format is 'rule:<rule_name>=<status>' "
+              + "with <rule_name> in the form of <plugin>~<rule>. For Gerrit core rules, "
+              + "rule name should be specified either as gerrit~<rule> or <rule>.");
+    }
+    if (queryArgs.length == 2) {
+      ruleNameArg = queryArgs[0];
+      statusArg = queryArgs[1];
+    }
+
+    // If ruleName is not prefixed by the plugin name, add the "gerrit~" prefix to it.
+    if (!ruleNameArg.contains("~")) {
+      ruleNameArg = "gerrit~" + ruleNameArg;
+    }
+
+    return statusArg == null
+        ? Predicate.or(
+            Arrays.asList(
+                ChangePredicates.submitRuleStatus(ruleNameArg + "=" + SubmitRecord.Status.OK),
+                ChangePredicates.submitRuleStatus(ruleNameArg + "=" + SubmitRecord.Status.FORCED)))
+        : ChangePredicates.submitRuleStatus(ruleNameArg + "=" + statusArg);
+  }
+
+  @Operator
   public Predicate<ChangeData> has(String value) throws QueryParseException {
     value = hasOperandAliases.getOrDefault(value, value);
     if ("star".equalsIgnoreCase(value)) {
-      return starredby(self());
-    }
-
-    if ("stars".equalsIgnoreCase(value)) {
-      return new HasStarsPredicate(self());
+      return starredBySelf();
     }
 
     if ("draft".equalsIgnoreCase(value)) {
-      return draftby(self());
+      return draftBySelf();
     }
 
     if ("edit".equalsIgnoreCase(value)) {
@@ -604,11 +633,11 @@
   @Operator
   public Predicate<ChangeData> is(String value) throws QueryParseException {
     if ("starred".equalsIgnoreCase(value)) {
-      return starredby(self());
+      return starredBySelf();
     }
 
     if ("watched".equalsIgnoreCase(value)) {
-      return new IsWatchedByPredicate(args, false);
+      return new IsWatchedByPredicate(args);
     }
 
     if ("visible".equalsIgnoreCase(value)) {
@@ -679,7 +708,7 @@
     }
 
     if ("ignored".equalsIgnoreCase(value)) {
-      return star("ignore");
+      return ignoredBySelf();
     }
 
     if ("started".equalsIgnoreCase(value)) {
@@ -697,6 +726,14 @@
       throw new QueryParseException("'is:wip' operator is not supported by change index version");
     }
 
+    if ("cherrypick".equalsIgnoreCase(value)) {
+      if (args.getSchema().hasField(ChangeField.CHERRY_PICK)) {
+        return new BooleanPredicate(ChangeField.CHERRY_PICK);
+      }
+      throw new QueryParseException(
+          "'is:cherrypick' operator is not supported by change index version");
+    }
+
     // for plugins the value will be operandName_pluginName
     List<String> names = Lists.newArrayList(Splitter.on('_').split(value));
     if (names.size() == 2) {
@@ -940,6 +977,8 @@
         if (pair.getKey().equalsIgnoreCase(ARG_ID_USER)) {
           if (pair.getValue().equals(ARG_ID_OWNER)) {
             accounts = Collections.singleton(OWNER_ACCOUNT_ID);
+          } else if (pair.getValue().equals(ARG_ID_NON_UPLOADER)) {
+            accounts = Collections.singleton(NON_UPLOADER_ACCOUNT_ID);
           } else {
             accounts = parseAccount(pair.getValue());
           }
@@ -957,6 +996,8 @@
         try {
           if (value.equals(ARG_ID_OWNER)) {
             accounts = Collections.singleton(OWNER_ACCOUNT_ID);
+          } else if (value.equals(ARG_ID_NON_UPLOADER)) {
+            accounts = Collections.singleton(NON_UPLOADER_ACCOUNT_ID);
           } else {
             accounts = parseAccount(value);
           }
@@ -1011,65 +1052,25 @@
 
   @Operator
   public Predicate<ChangeData> star(String label) throws QueryParseException {
-    return new StarPredicate(self(), label);
-  }
-
-  @Operator
-  public Predicate<ChangeData> starredby(String who)
-      throws QueryParseException, IOException, ConfigInvalidException {
-    return starredby(parseAccount(who));
-  }
-
-  private Predicate<ChangeData> starredby(Set<Account.Id> who) {
-    List<Predicate<ChangeData>> p = Lists.newArrayListWithCapacity(who.size());
-    for (Account.Id id : who) {
-      p.add(starredby(id));
+    if ("ignore".equalsIgnoreCase(label)) {
+      return ignoredBySelf();
     }
-    return Predicate.or(p);
-  }
-
-  private Predicate<ChangeData> starredby(Account.Id who) {
-    return new StarPredicate(who, StarredChangesUtil.DEFAULT_LABEL);
-  }
-
-  @Operator
-  public Predicate<ChangeData> watchedby(String who)
-      throws QueryParseException, IOException, ConfigInvalidException {
-    Set<Account.Id> m = parseAccount(who);
-    List<IsWatchedByPredicate> p = Lists.newArrayListWithCapacity(m.size());
-
-    Account.Id callerId;
-    try {
-      CurrentUser caller = args.self.get();
-      callerId = caller.isIdentifiedUser() ? caller.getAccountId() : null;
-    } catch (ProvisionException e) {
-      callerId = null;
+    if ("star".equalsIgnoreCase(label)) {
+      return starredBySelf();
     }
-
-    for (Account.Id id : m) {
-      // Each child IsWatchedByPredicate includes a visibility filter for the
-      // corresponding user, to ensure that predicate subtree only returns
-      // changes visible to that user. The exception is if one of the users is
-      // the caller of this method, in which case visibility is already being
-      // checked at the top level.
-      p.add(new IsWatchedByPredicate(args.asUser(id), !id.equals(callerId)));
-    }
-    return Predicate.or(p);
+    throw new IllegalArgumentException();
   }
 
-  @Operator
-  public Predicate<ChangeData> draftby(String who)
-      throws QueryParseException, IOException, ConfigInvalidException {
-    Set<Account.Id> m = parseAccount(who);
-    List<Predicate<ChangeData>> p = Lists.newArrayListWithCapacity(m.size());
-    for (Account.Id id : m) {
-      p.add(draftby(id));
-    }
-    return Predicate.or(p);
+  private Predicate<ChangeData> ignoredBySelf() throws QueryParseException {
+    return new StarPredicate(self(), StarredChangesUtil.IGNORE_LABEL);
   }
 
-  private Predicate<ChangeData> draftby(Account.Id who) {
-    return ChangePredicates.draftBy(who);
+  private Predicate<ChangeData> starredBySelf() throws QueryParseException {
+    return new StarPredicate(self(), StarredChangesUtil.DEFAULT_LABEL);
+  }
+
+  private Predicate<ChangeData> draftBySelf() throws QueryParseException {
+    return ChangePredicates.draftBy(self());
   }
 
   @Operator
diff --git a/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java b/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
index ade615c..12efecb 100644
--- a/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
+++ b/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
@@ -108,16 +108,23 @@
       return false;
     }
 
-    if (account != null
-        && !account.equals(approver)
-        && !account.equals(ChangeQueryBuilder.OWNER_ACCOUNT_ID)) {
-      return false;
-    }
+    if (account != null) {
+      // case when account in query is numeric
+      if (!account.equals(approver) && !isMagicUser()) {
+        return false;
+      }
 
-    if (account != null
-        && account.equals(ChangeQueryBuilder.OWNER_ACCOUNT_ID)
-        && !cd.change().getOwner().equals(approver)) {
-      return false;
+      // case when account in query = owner
+      if (account.equals(ChangeQueryBuilder.OWNER_ACCOUNT_ID)
+          && !cd.change().getOwner().equals(approver)) {
+        return false;
+      }
+
+      // case when account in query = non_uploader
+      if (account.equals(ChangeQueryBuilder.NON_UPLOADER_ACCOUNT_ID)
+          && cd.currentPatchSet().uploader().equals(approver)) {
+        return false;
+      }
     }
 
     IdentifiedUser reviewer = userFactory.create(approver);
@@ -139,6 +146,11 @@
     }
   }
 
+  private boolean isMagicUser() {
+    return account.equals(ChangeQueryBuilder.OWNER_ACCOUNT_ID)
+        || account.equals(ChangeQueryBuilder.NON_UPLOADER_ACCOUNT_ID);
+  }
+
   @Override
   public int getCost() {
     return 1 + (group == null ? 0 : 1);
diff --git a/java/com/google/gerrit/server/query/change/HasStarsPredicate.java b/java/com/google/gerrit/server/query/change/HasStarsPredicate.java
deleted file mode 100644
index 6482a19..0000000
--- a/java/com/google/gerrit/server/query/change/HasStarsPredicate.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (C) 2016 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.query.change;
-
-import com.google.gerrit.entities.Account;
-import com.google.gerrit.server.index.change.ChangeField;
-
-public class HasStarsPredicate extends ChangeIndexPredicate {
-  protected final Account.Id accountId;
-
-  public HasStarsPredicate(Account.Id accountId) {
-    super(ChangeField.STARBY, accountId.toString());
-    this.accountId = accountId;
-  }
-
-  @Override
-  public boolean match(ChangeData cd) {
-    return cd.stars().containsKey(accountId);
-  }
-
-  @Override
-  public String toString() {
-    return ChangeQueryBuilder.FIELD_STARBY + ":" + accountId;
-  }
-}
diff --git a/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java b/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
index 3a43fd3..054a69e 100644
--- a/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
+++ b/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
@@ -36,14 +36,13 @@
 
   protected final CurrentUser user;
 
-  public IsWatchedByPredicate(ChangeQueryBuilder.Arguments args, boolean checkIsVisible)
-      throws QueryParseException {
-    super(filters(args, checkIsVisible));
+  public IsWatchedByPredicate(ChangeQueryBuilder.Arguments args) throws QueryParseException {
+    super(filters(args));
     this.user = args.getUser();
   }
 
-  protected static List<Predicate<ChangeData>> filters(
-      ChangeQueryBuilder.Arguments args, boolean checkIsVisible) throws QueryParseException {
+  protected static List<Predicate<ChangeData>> filters(ChangeQueryBuilder.Arguments args)
+      throws QueryParseException {
     List<Predicate<ChangeData>> r = new ArrayList<>();
     ChangeQueryBuilder builder = new ChangeQueryBuilder(args);
     for (ProjectWatchKey w : getWatches(args)) {
@@ -82,11 +81,8 @@
     }
     if (r.isEmpty()) {
       return ImmutableList.of(ChangeIndexPredicate.none());
-    } else if (checkIsVisible) {
-      return ImmutableList.of(or(r), builder.isVisible());
-    } else {
-      return ImmutableList.of(or(r));
     }
+    return ImmutableList.of(or(r));
   }
 
   protected static Collection<ProjectWatchKey> getWatches(ChangeQueryBuilder.Arguments args)
diff --git a/java/com/google/gerrit/server/restapi/account/CreateAccount.java b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
index baa2951..b4946c4 100644
--- a/java/com/google/gerrit/server/restapi/account/CreateAccount.java
+++ b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
@@ -44,6 +44,7 @@
 import com.google.gerrit.server.account.VersionedAuthorizedKeys;
 import com.google.gerrit.server.account.externalids.DuplicateExternalIdKeyException;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.group.GroupResolver;
 import com.google.gerrit.server.group.db.GroupDelta;
@@ -85,6 +86,7 @@
   private final Provider<GroupsUpdate> groupsUpdate;
   private final OutgoingEmailValidator validator;
   private final AuthConfig authConfig;
+  private final ExternalIdFactory externalIdFactory;
 
   @Inject
   CreateAccount(
@@ -97,7 +99,8 @@
       PluginSetContext<AccountExternalIdCreator> externalIdCreators,
       @UserInitiated Provider<GroupsUpdate> groupsUpdate,
       OutgoingEmailValidator validator,
-      AuthConfig authConfig) {
+      AuthConfig authConfig,
+      ExternalIdFactory externalIdFactory) {
     this.seq = seq;
     this.groupResolver = groupResolver;
     this.authorizedKeys = authorizedKeys;
@@ -108,6 +111,7 @@
     this.groupsUpdate = groupsUpdate;
     this.validator = validator;
     this.authConfig = authConfig;
+    this.externalIdFactory = externalIdFactory;
   }
 
   @Override
@@ -142,10 +146,10 @@
       if (!validator.isValid(input.email)) {
         throw new BadRequestException("invalid email address");
       }
-      extIds.add(ExternalId.createEmail(accountId, input.email));
+      extIds.add(externalIdFactory.createEmail(accountId, input.email));
     }
 
-    extIds.add(ExternalId.createUsername(username, accountId, input.httpPassword));
+    extIds.add(externalIdFactory.createUsername(username, accountId, input.httpPassword));
     externalIdCreators.runEach(c -> extIds.addAll(c.create(accountId, username, input.email)));
 
     try {
diff --git a/java/com/google/gerrit/server/restapi/account/CreateEmail.java b/java/com/google/gerrit/server/restapi/account/CreateEmail.java
index 6ee4539..70fbb26 100644
--- a/java/com/google/gerrit/server/restapi/account/CreateEmail.java
+++ b/java/com/google/gerrit/server/restapi/account/CreateEmail.java
@@ -83,6 +83,7 @@
   private final OutgoingEmailValidator validator;
   private final MessageIdGenerator messageIdGenerator;
   private final boolean isDevMode;
+  private final AuthRequest.Factory authRequestFactory;
 
   @Inject
   CreateEmail(
@@ -94,7 +95,8 @@
       RegisterNewEmailSender.Factory registerNewEmailFactory,
       PutPreferred putPreferred,
       OutgoingEmailValidator validator,
-      MessageIdGenerator messageIdGenerator) {
+      MessageIdGenerator messageIdGenerator,
+      AuthRequest.Factory authRequestFactory) {
     this.self = self;
     this.realm = realm;
     this.permissionBackend = permissionBackend;
@@ -104,6 +106,7 @@
     this.validator = validator;
     this.isDevMode = authConfig.getAuthType() == DEVELOPMENT_BECOME_ANY_ACCOUNT;
     this.messageIdGenerator = messageIdGenerator;
+    this.authRequestFactory = authRequestFactory;
   }
 
   @Override
@@ -151,7 +154,7 @@
         logger.atWarning().log("skipping email validation in developer mode");
       }
       try {
-        accountManager.link(user.getAccountId(), AuthRequest.forEmail(email));
+        accountManager.link(user.getAccountId(), authRequestFactory.createForEmail(email));
       } catch (AccountException e) {
         throw new ResourceConflictException(e.getMessage());
       }
diff --git a/java/com/google/gerrit/server/restapi/account/DeleteExternalIds.java b/java/com/google/gerrit/server/restapi/account/DeleteExternalIds.java
index 445a5d6..e099a70 100644
--- a/java/com/google/gerrit/server/restapi/account/DeleteExternalIds.java
+++ b/java/com/google/gerrit/server/restapi/account/DeleteExternalIds.java
@@ -18,6 +18,8 @@
 import static java.util.stream.Collectors.toMap;
 import static java.util.stream.Collectors.toSet;
 
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.Response;
@@ -29,6 +31,7 @@
 import com.google.gerrit.server.account.AccountManager;
 import com.google.gerrit.server.account.AccountResource;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.permissions.GlobalPermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
@@ -56,17 +59,20 @@
   private final AccountManager accountManager;
   private final ExternalIds externalIds;
   private final Provider<CurrentUser> self;
+  private final ExternalIdKeyFactory externalIdKeyFactory;
 
   @Inject
   DeleteExternalIds(
       PermissionBackend permissionBackend,
       AccountManager accountManager,
       ExternalIds externalIds,
-      Provider<CurrentUser> self) {
+      Provider<CurrentUser> self,
+      ExternalIdKeyFactory externalIdKeyFactory) {
     this.permissionBackend = permissionBackend;
     this.accountManager = accountManager;
     this.externalIds = externalIds;
     this.self = self;
+    this.externalIdKeyFactory = externalIdKeyFactory;
   }
 
   @Override
@@ -87,15 +93,24 @@
     List<ExternalId> toDelete = new ArrayList<>();
     Optional<ExternalId.Key> last = resource.getUser().getLastLoginExternalIdKey();
     for (String externalIdStr : extIds) {
-      ExternalId id = externalIdMap.get(ExternalId.Key.parse(externalIdStr));
+      ExternalId id = externalIdMap.get(externalIdKeyFactory.parse(externalIdStr));
 
       if (id == null) {
         throw new UnprocessableEntityException(
             String.format("External id %s does not exist", externalIdStr));
       }
 
-      if ((!id.isScheme(SCHEME_USERNAME))
-          && (!last.isPresent() || (!last.get().equals(id.key())))) {
+      if (!last.isPresent() || !last.get().equals(id.key())) {
+        if (id.isScheme(SCHEME_USERNAME)) {
+          if (self.get().hasSameAccountId(resource.getUser())) {
+            throw new AuthException("User cannot delete its own externalId in 'username:' scheme");
+          }
+          permissionBackend
+              .currentUser()
+              .checkAny(
+                  ImmutableSet.of(
+                      GlobalPermission.ADMINISTRATE_SERVER, GlobalPermission.MAINTAIN_SERVER));
+        }
         toDelete.add(id);
       } else {
         throw new ResourceConflictException(
diff --git a/java/com/google/gerrit/server/restapi/account/Module.java b/java/com/google/gerrit/server/restapi/account/Module.java
index 7570465..dd38ccf 100644
--- a/java/com/google/gerrit/server/restapi/account/Module.java
+++ b/java/com/google/gerrit/server/restapi/account/Module.java
@@ -99,10 +99,6 @@
     delete(STARRED_CHANGE_KIND).to(StarredChanges.Delete.class);
     bind(StarredChanges.Create.class);
 
-    child(ACCOUNT_KIND, "stars.changes").to(Stars.class);
-    get(STAR_KIND).to(Stars.Get.class);
-    post(STAR_KIND).to(Stars.Post.class);
-
     get(ACCOUNT_KIND, "external.ids").to(GetExternalIds.class);
     post(ACCOUNT_KIND, "external.ids:delete").to(DeleteExternalIds.class);
 
diff --git a/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java b/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java
index 2427def..9361e27 100644
--- a/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java
+++ b/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java
@@ -34,6 +34,8 @@
 import com.google.gerrit.server.account.AccountResource;
 import com.google.gerrit.server.account.AccountsUpdate;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.mail.send.HttpPasswordUpdateSender;
 import com.google.gerrit.server.permissions.GlobalPermission;
@@ -77,6 +79,8 @@
   private final ExternalIds externalIds;
   private final Provider<AccountsUpdate> accountsUpdateProvider;
   private final HttpPasswordUpdateSender.Factory httpPasswordUpdateSenderFactory;
+  private final ExternalIdFactory externalIdFactory;
+  private final ExternalIdKeyFactory externalIdKeyFactory;
 
   @Inject
   PutHttpPassword(
@@ -84,12 +88,16 @@
       PermissionBackend permissionBackend,
       ExternalIds externalIds,
       @UserInitiated Provider<AccountsUpdate> accountsUpdateProvider,
-      HttpPasswordUpdateSender.Factory httpPasswordUpdateSenderFactory) {
+      HttpPasswordUpdateSender.Factory httpPasswordUpdateSenderFactory,
+      ExternalIdFactory externalIdFactory,
+      ExternalIdKeyFactory externalIdKeyFactory) {
     this.self = self;
     this.permissionBackend = permissionBackend;
     this.externalIds = externalIds;
     this.accountsUpdateProvider = accountsUpdateProvider;
     this.httpPasswordUpdateSenderFactory = httpPasswordUpdateSenderFactory;
+    this.externalIdFactory = externalIdFactory;
+    this.externalIdKeyFactory = externalIdKeyFactory;
   }
 
   @Override
@@ -125,7 +133,7 @@
     String userName =
         user.getUserName().orElseThrow(() -> new ResourceConflictException("username must be set"));
     Optional<ExternalId> optionalExtId =
-        externalIds.get(ExternalId.Key.create(SCHEME_USERNAME, userName));
+        externalIds.get(externalIdKeyFactory.create(SCHEME_USERNAME, userName));
     ExternalId extId = optionalExtId.orElseThrow(ResourceNotFoundException::new);
     accountsUpdateProvider
         .get()
@@ -134,7 +142,7 @@
             extId.accountId(),
             u ->
                 u.updateExternalId(
-                    ExternalId.createWithPassword(
+                    externalIdFactory.createWithPassword(
                         extId.key(), extId.accountId(), extId.email(), newPassword)));
 
     try {
diff --git a/java/com/google/gerrit/server/restapi/account/PutPreferred.java b/java/com/google/gerrit/server/restapi/account/PutPreferred.java
index 32b5ff2..aee0b78 100644
--- a/java/com/google/gerrit/server/restapi/account/PutPreferred.java
+++ b/java/com/google/gerrit/server/restapi/account/PutPreferred.java
@@ -30,6 +30,7 @@
 import com.google.gerrit.server.account.AccountResource;
 import com.google.gerrit.server.account.AccountsUpdate;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
 import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.permissions.GlobalPermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
@@ -62,17 +63,20 @@
   private final PermissionBackend permissionBackend;
   private final Provider<AccountsUpdate> accountsUpdateProvider;
   private final ExternalIds externalIds;
+  private final ExternalIdFactory externalIdFactory;
 
   @Inject
   PutPreferred(
       Provider<CurrentUser> self,
       PermissionBackend permissionBackend,
       @ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider,
-      ExternalIds externalIds) {
+      ExternalIds externalIds,
+      ExternalIdFactory externalIdFactory) {
     this.self = self;
     this.permissionBackend = permissionBackend;
     this.accountsUpdateProvider = accountsUpdateProvider;
     this.externalIds = externalIds;
+    this.externalIdFactory = externalIdFactory;
   }
 
   @Override
@@ -137,7 +141,8 @@
                     }
 
                     // claim the email now
-                    u.addExternalId(ExternalId.createEmail(a.account().id(), preferredEmail));
+                    u.addExternalId(
+                        externalIdFactory.createEmail(a.account().id(), preferredEmail));
                     matchingEmail = preferredEmail;
                   } else {
                     // Realm says that the email doesn't belong to the user. This can only happen as
diff --git a/java/com/google/gerrit/server/restapi/account/PutUsername.java b/java/com/google/gerrit/server/restapi/account/PutUsername.java
index 05bf1fd..f295389 100644
--- a/java/com/google/gerrit/server/restapi/account/PutUsername.java
+++ b/java/com/google/gerrit/server/restapi/account/PutUsername.java
@@ -34,6 +34,8 @@
 import com.google.gerrit.server.account.AccountsUpdate;
 import com.google.gerrit.server.account.Realm;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.permissions.GlobalPermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
@@ -65,6 +67,8 @@
   private final Provider<AccountsUpdate> accountsUpdateProvider;
   private final SshKeyCache sshKeyCache;
   private final Realm realm;
+  private final ExternalIdFactory externalIdFactory;
+  private final ExternalIdKeyFactory externalIdKeyFactory;
 
   @Inject
   PutUsername(
@@ -73,13 +77,17 @@
       ExternalIds externalIds,
       @ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider,
       SshKeyCache sshKeyCache,
-      Realm realm) {
+      Realm realm,
+      ExternalIdFactory externalIdFactory,
+      ExternalIdKeyFactory externalIdKeyFactory) {
     this.self = self;
     this.permissionBackend = permissionBackend;
     this.externalIds = externalIds;
     this.accountsUpdateProvider = accountsUpdateProvider;
     this.sshKeyCache = sshKeyCache;
     this.realm = realm;
+    this.externalIdFactory = externalIdFactory;
+    this.externalIdKeyFactory = externalIdKeyFactory;
   }
 
   @Override
@@ -107,14 +115,14 @@
       throw new UnprocessableEntityException("invalid username");
     }
 
-    ExternalId.Key key = ExternalId.Key.create(SCHEME_USERNAME, input.username);
+    ExternalId.Key key = externalIdKeyFactory.create(SCHEME_USERNAME, input.username);
     try {
       accountsUpdateProvider
           .get()
           .update(
               "Set Username via API",
               accountId,
-              u -> u.addExternalId(ExternalId.create(key, accountId, null, null)));
+              u -> u.addExternalId(externalIdFactory.create(key, accountId, null, null)));
     } catch (DuplicateKeyException dupeErr) {
       // If we are using this identity, don't report the exception.
       Optional<ExternalId> other = externalIds.get(key);
diff --git a/java/com/google/gerrit/server/restapi/account/StarredChanges.java b/java/com/google/gerrit/server/restapi/account/StarredChanges.java
index e67fe9e..39c1fef 100644
--- a/java/com/google/gerrit/server/restapi/account/StarredChanges.java
+++ b/java/com/google/gerrit/server/restapi/account/StarredChanges.java
@@ -90,7 +90,7 @@
     return (RestReadView<AccountResource>)
         self -> {
           QueryChanges query = changes.list();
-          query.addQuery("starredby:" + self.getUser().getAccountId().get());
+          query.addQuery("has:star");
           return query.apply(TopLevelResource.INSTANCE);
         };
   }
diff --git a/java/com/google/gerrit/server/restapi/account/Stars.java b/java/com/google/gerrit/server/restapi/account/Stars.java
deleted file mode 100644
index cc362f2..0000000
--- a/java/com/google/gerrit/server/restapi/account/Stars.java
+++ /dev/null
@@ -1,183 +0,0 @@
-// Copyright (C) 2016 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.restapi.account;
-
-import com.google.gerrit.extensions.api.changes.StarsInput;
-import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.ChildCollection;
-import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.extensions.restapi.Response;
-import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.extensions.restapi.TopLevelResource;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.StarredChangesUtil;
-import com.google.gerrit.server.StarredChangesUtil.IllegalLabelException;
-import com.google.gerrit.server.account.AccountResource;
-import com.google.gerrit.server.account.AccountResource.Star;
-import com.google.gerrit.server.change.ChangeResource;
-import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.restapi.change.ChangesCollection;
-import com.google.gerrit.server.restapi.change.QueryChanges;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-import java.util.SortedSet;
-
-/**
- * Implements adding label stars to changes.
- *
- * <p>This handles {@code POST} and {@code GET} for {@code
- * /accounts/<account-identifier>/stars.changes/<change ID>}.
- */
-@Singleton
-public class Stars implements ChildCollection<AccountResource, AccountResource.Star> {
-
-  private final ChangesCollection changes;
-  private final ListStarredChanges listStarredChanges;
-  private final StarredChangesUtil starredChangesUtil;
-  private final DynamicMap<RestView<AccountResource.Star>> views;
-
-  @Inject
-  Stars(
-      ChangesCollection changes,
-      ListStarredChanges listStarredChanges,
-      StarredChangesUtil starredChangesUtil,
-      DynamicMap<RestView<AccountResource.Star>> views) {
-    this.changes = changes;
-    this.listStarredChanges = listStarredChanges;
-    this.starredChangesUtil = starredChangesUtil;
-    this.views = views;
-  }
-
-  @Override
-  public Star parse(AccountResource parent, IdString id)
-      throws RestApiException, PermissionBackendException, IOException {
-    IdentifiedUser user = parent.getUser();
-    // This enforces visibility of the change.
-    ChangeResource change = changes.parse(TopLevelResource.INSTANCE, id);
-    Set<String> labels = starredChangesUtil.getLabels(user.getAccountId(), change.getId());
-    return new AccountResource.Star(user, change, labels);
-  }
-
-  @Override
-  public DynamicMap<RestView<Star>> views() {
-    return views;
-  }
-
-  @Override
-  public ListStarredChanges list() {
-    return listStarredChanges;
-  }
-
-  @Singleton
-  public static class ListStarredChanges implements RestReadView<AccountResource> {
-
-    private final Provider<CurrentUser> self;
-    private final ChangesCollection changes;
-
-    @Inject
-    ListStarredChanges(Provider<CurrentUser> self, ChangesCollection changes) {
-      this.self = self;
-      this.changes = changes;
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public Response<List<ChangeInfo>> apply(AccountResource rsrc)
-        throws RestApiException, PermissionBackendException {
-      if (!self.get().hasSameAccountId(rsrc.getUser())) {
-        throw new AuthException("not allowed to list stars of another account");
-      }
-
-      // The type of the value in the response that is returned by QueryChanges depends on the
-      // number of queries that is provided as input. If a single query is provided as input the
-      // value type is {@code List<ChangeInfo>}, if multiple queries are provided as input the value
-      // type is {@code List<List<ChangeInfo>>) (one {@code List<ChangeInfo>} as result to each
-      // query). Since in this case we provide exactly one query ("has:stars") as input we know that
-      // the value always has the type {@code List<ChangeInfo>} and hence we can safely cast the
-      // value to this type.
-      QueryChanges query = changes.list();
-      query.addQuery("has:stars");
-      Response<?> response = query.apply(TopLevelResource.INSTANCE);
-      List<ChangeInfo> value = (List<ChangeInfo>) response.value();
-      return Response.ok(value);
-    }
-  }
-
-  @Singleton
-  public static class Get implements RestReadView<AccountResource.Star> {
-
-    private final Provider<CurrentUser> self;
-    private final StarredChangesUtil starredChangesUtil;
-
-    @Inject
-    Get(Provider<CurrentUser> self, StarredChangesUtil starredChangesUtil) {
-      this.self = self;
-      this.starredChangesUtil = starredChangesUtil;
-    }
-
-    @Override
-    public Response<SortedSet<String>> apply(AccountResource.Star rsrc) throws AuthException {
-      if (!self.get().hasSameAccountId(rsrc.getUser())) {
-        throw new AuthException("not allowed to get stars of another account");
-      }
-      return Response.ok(
-          starredChangesUtil.getLabels(self.get().getAccountId(), rsrc.getChange().getId()));
-    }
-  }
-
-  @Singleton
-  public static class Post implements RestModifyView<AccountResource.Star, StarsInput> {
-
-    private final Provider<CurrentUser> self;
-    private final StarredChangesUtil starredChangesUtil;
-
-    @Inject
-    Post(Provider<CurrentUser> self, StarredChangesUtil starredChangesUtil) {
-      this.self = self;
-      this.starredChangesUtil = starredChangesUtil;
-    }
-
-    @Override
-    public Response<Collection<String>> apply(AccountResource.Star rsrc, StarsInput in)
-        throws AuthException, BadRequestException {
-      if (!self.get().hasSameAccountId(rsrc.getUser())) {
-        throw new AuthException("not allowed to update stars of another account");
-      }
-      try {
-        return Response.ok(
-            starredChangesUtil.star(
-                self.get().getAccountId(),
-                rsrc.getChange().getProject(),
-                rsrc.getChange().getId(),
-                in.add,
-                in.remove));
-      } catch (IllegalLabelException e) {
-        throw new BadRequestException(e.getMessage());
-      }
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/restapi/change/MarkAsReviewed.java b/java/com/google/gerrit/server/restapi/change/MarkAsReviewed.java
deleted file mode 100644
index fa4555b..0000000
--- a/java/com/google/gerrit/server/restapi/change/MarkAsReviewed.java
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.restapi.change;
-
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.extensions.common.Input;
-import com.google.gerrit.extensions.restapi.Response;
-import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.server.StarredChangesUtil;
-import com.google.gerrit.server.StarredChangesUtil.IllegalLabelException;
-import com.google.gerrit.server.change.ChangeResource;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-@Singleton
-public class MarkAsReviewed
-    implements RestModifyView<ChangeResource, Input>, UiAction<ChangeResource> {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  private final ChangeData.Factory changeDataFactory;
-  private final StarredChangesUtil stars;
-
-  @Inject
-  MarkAsReviewed(ChangeData.Factory changeDataFactory, StarredChangesUtil stars) {
-    this.changeDataFactory = changeDataFactory;
-    this.stars = stars;
-  }
-
-  @Override
-  public Description getDescription(ChangeResource rsrc) {
-    return new UiAction.Description()
-        .setLabel("Mark Reviewed")
-        .setTitle("Mark the change as reviewed to unhighlight it in the dashboard")
-        .setVisible(!isReviewed(rsrc));
-  }
-
-  @Override
-  public Response<String> apply(ChangeResource rsrc, Input input)
-      throws RestApiException, IllegalLabelException {
-    stars.markAsReviewed(rsrc);
-    return Response.ok();
-  }
-
-  private boolean isReviewed(ChangeResource rsrc) {
-    try {
-      return changeDataFactory
-          .create(rsrc.getNotes())
-          .isReviewedBy(rsrc.getUser().asIdentifiedUser().getAccountId());
-    } catch (StorageException e) {
-      logger.atSevere().withCause(e).log("failed to check if change is reviewed");
-    }
-    return false;
-  }
-}
diff --git a/java/com/google/gerrit/server/restapi/change/MarkAsUnreviewed.java b/java/com/google/gerrit/server/restapi/change/MarkAsUnreviewed.java
deleted file mode 100644
index 601fc4a..0000000
--- a/java/com/google/gerrit/server/restapi/change/MarkAsUnreviewed.java
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.restapi.change;
-
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.extensions.common.Input;
-import com.google.gerrit.extensions.restapi.Response;
-import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.server.StarredChangesUtil;
-import com.google.gerrit.server.StarredChangesUtil.IllegalLabelException;
-import com.google.gerrit.server.change.ChangeResource;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-@Singleton
-public class MarkAsUnreviewed
-    implements RestModifyView<ChangeResource, Input>, UiAction<ChangeResource> {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  private final ChangeData.Factory changeDataFactory;
-  private final StarredChangesUtil stars;
-
-  @Inject
-  MarkAsUnreviewed(ChangeData.Factory changeDataFactory, StarredChangesUtil stars) {
-    this.changeDataFactory = changeDataFactory;
-    this.stars = stars;
-  }
-
-  @Override
-  public Description getDescription(ChangeResource rsrc) {
-    return new UiAction.Description()
-        .setLabel("Mark Unreviewed")
-        .setTitle("Mark the change as unreviewed to highlight it in the dashboard")
-        .setVisible(isReviewed(rsrc));
-  }
-
-  @Override
-  public Response<String> apply(ChangeResource rsrc, Input input) throws IllegalLabelException {
-    stars.markAsUnreviewed(rsrc);
-    return Response.ok();
-  }
-
-  private boolean isReviewed(ChangeResource rsrc) {
-    try {
-      return changeDataFactory
-          .create(rsrc.getNotes())
-          .isReviewedBy(rsrc.getUser().asIdentifiedUser().getAccountId());
-    } catch (StorageException e) {
-      logger.atSevere().withCause(e).log("failed to check if change is reviewed");
-    }
-    return false;
-  }
-}
diff --git a/java/com/google/gerrit/server/restapi/change/Module.java b/java/com/google/gerrit/server/restapi/change/Module.java
index f87c9a1..000a17e 100644
--- a/java/com/google/gerrit/server/restapi/change/Module.java
+++ b/java/com/google/gerrit/server/restapi/change/Module.java
@@ -120,8 +120,6 @@
     delete(CHANGE_KIND, "private").to(DeletePrivate.class);
     put(CHANGE_KIND, "ignore").to(Ignore.class);
     put(CHANGE_KIND, "unignore").to(Unignore.class);
-    put(CHANGE_KIND, "reviewed").to(MarkAsReviewed.class);
-    put(CHANGE_KIND, "unreviewed").to(MarkAsUnreviewed.class);
     post(CHANGE_KIND, "wip").to(SetWorkInProgress.class);
     post(CHANGE_KIND, "ready").to(SetReadyForReview.class);
     put(CHANGE_KIND, "message").to(PutMessage.class);
diff --git a/java/com/google/gerrit/server/restapi/group/AddMembers.java b/java/com/google/gerrit/server/restapi/group/AddMembers.java
index f44abec..4cb2f47 100644
--- a/java/com/google/gerrit/server/restapi/group/AddMembers.java
+++ b/java/com/google/gerrit/server/restapi/group/AddMembers.java
@@ -94,6 +94,7 @@
   private final AccountCache accountCache;
   private final AccountLoader.Factory infoFactory;
   private final Provider<GroupsUpdate> groupsUpdateProvider;
+  private final AuthRequest.Factory authRequestFactory;
 
   @Inject
   AddMembers(
@@ -102,13 +103,15 @@
       AccountResolver accountResolver,
       AccountCache accountCache,
       AccountLoader.Factory infoFactory,
-      @UserInitiated Provider<GroupsUpdate> groupsUpdateProvider) {
+      @UserInitiated Provider<GroupsUpdate> groupsUpdateProvider,
+      AuthRequest.Factory authRequestFactory) {
     this.accountManager = accountManager;
     this.authType = authConfig.getAuthType();
     this.accountResolver = accountResolver;
     this.accountCache = accountCache;
     this.infoFactory = infoFactory;
     this.groupsUpdateProvider = groupsUpdateProvider;
+    this.authRequestFactory = authRequestFactory;
   }
 
   @Override
@@ -190,7 +193,7 @@
     }
 
     try {
-      AuthRequest req = AuthRequest.forUser(user);
+      AuthRequest req = authRequestFactory.createForUser(user);
       req.setSkipAuthentication(true);
       return accountCache
           .get(accountManager.authenticate(req).getAccountId())
diff --git a/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java b/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java
index 868e7ea..3bf9d02 100644
--- a/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java
+++ b/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java
@@ -18,12 +18,11 @@
 import static java.util.stream.Collectors.toList;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.PermissionRule;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.server.config.AllProjectsConfigProvider;
 import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
 import com.google.gerrit.server.git.meta.VersionedMetaData;
 import com.google.gerrit.server.project.ProjectConfig;
@@ -31,6 +30,7 @@
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.CommitBuilder;
@@ -40,32 +40,30 @@
 
 public class ProjectConfigSchemaUpdate extends VersionedMetaData {
   public static class Factory {
-    private final SitePaths sitePaths;
     private final AllProjectsName allProjectsName;
+    private final AllProjectsConfigProvider allProjectsConfigProvider;
 
     @Inject
-    Factory(SitePaths sitePaths, AllProjectsName allProjectsName) {
-      this.sitePaths = sitePaths;
+    Factory(AllProjectsName allProjectsName, AllProjectsConfigProvider allProjectsConfigProvider) {
       this.allProjectsName = allProjectsName;
+      this.allProjectsConfigProvider = allProjectsConfigProvider;
     }
 
     ProjectConfigSchemaUpdate read(MetaDataUpdate update)
         throws IOException, ConfigInvalidException {
       ProjectConfigSchemaUpdate r =
-          new ProjectConfigSchemaUpdate(
-              update,
-              ProjectConfig.Factory.getBaseConfig(sitePaths, allProjectsName, allProjectsName));
+          new ProjectConfigSchemaUpdate(update, allProjectsConfigProvider.get(allProjectsName));
       r.load(update);
       return r;
     }
   }
 
   private final MetaDataUpdate update;
-  @Nullable private final StoredConfig baseConfig;
+  private final Optional<StoredConfig> baseConfig;
   private Config config;
   private boolean updated;
 
-  private ProjectConfigSchemaUpdate(MetaDataUpdate update, @Nullable StoredConfig baseConfig) {
+  private ProjectConfigSchemaUpdate(MetaDataUpdate update, Optional<StoredConfig> baseConfig) {
     this.update = update;
     this.baseConfig = baseConfig;
   }
@@ -77,8 +75,8 @@
 
   @Override
   protected void onLoad() throws IOException, ConfigInvalidException {
-    if (baseConfig != null) {
-      baseConfig.load();
+    if (baseConfig.isPresent()) {
+      baseConfig.get().load();
     }
     config = readConfig(ProjectConfig.PROJECT_CONFIG, baseConfig);
   }
diff --git a/java/com/google/gerrit/server/update/RetryHelper.java b/java/com/google/gerrit/server/update/RetryHelper.java
index b846662..2249b0e 100644
--- a/java/com/google/gerrit/server/update/RetryHelper.java
+++ b/java/com/google/gerrit/server/update/RetryHelper.java
@@ -548,8 +548,8 @@
    * @param retryer the retryer
    * @param listener metric listener
    * @return the result of executing the action
-   * @throws Throwable any error or exception that made the action fail, callers are expected to
-   *     catch and inspect this Throwable to decide carefully whether it should be re-thrown
+   * @throws Exception any exception that made the action fail, callers are expected to catch and
+   *     inspect this exception to decide carefully whether it should be re-thrown
    */
   private <T> T executeWithTimeoutCount(
       String actionType,
diff --git a/java/com/google/gerrit/server/update/RetryableAction.java b/java/com/google/gerrit/server/update/RetryableAction.java
index fbb643e..f79a849 100644
--- a/java/com/google/gerrit/server/update/RetryableAction.java
+++ b/java/com/google/gerrit/server/update/RetryableAction.java
@@ -57,6 +57,7 @@
     PLUGIN_UPDATE,
     REST_READ_REQUEST,
     REST_WRITE_REQUEST,
+    SEND_EMAIL,
   }
 
   @FunctionalInterface
diff --git a/java/com/google/gerrit/server/util/AttentionSetEmail.java b/java/com/google/gerrit/server/util/AttentionSetEmail.java
index 56b1dda..48ddd31 100644
--- a/java/com/google/gerrit/server/util/AttentionSetEmail.java
+++ b/java/com/google/gerrit/server/util/AttentionSetEmail.java
@@ -56,6 +56,7 @@
   }
 
   private ExecutorService sendEmailsExecutor;
+  private AccountTemplateUtil accountTemplateUtil;
   private AttentionSetSender sender;
   private Context ctx;
   private Change change;
@@ -67,6 +68,7 @@
   @Inject
   AttentionSetEmail(
       @SendEmailExecutor ExecutorService executor,
+      AccountTemplateUtil accountTemplateUtil,
       @Assisted AttentionSetSender sender,
       @Assisted Context ctx,
       @Assisted Change change,
@@ -74,6 +76,7 @@
       @Assisted MessageIdGenerator.MessageId messageId,
       @Assisted Account.Id attentionUserId) {
     this.sendEmailsExecutor = executor;
+    this.accountTemplateUtil = accountTemplateUtil;
     this.sender = sender;
     this.ctx = ctx;
     this.change = change;
@@ -97,7 +100,7 @@
       }
       sender.setNotify(ctx.getNotify(change.getId()));
       sender.setAttentionSetUser(attentionUserId);
-      sender.setReason(reason);
+      sender.setReason(accountTemplateUtil.replaceTemplates(reason));
       sender.setMessageId(messageId);
       sender.send();
     } catch (Exception e) {
diff --git a/java/com/google/gerrit/sshd/SshKeyCacheImpl.java b/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
index d5f0ee8..628a050 100644
--- a/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
+++ b/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.server.account.AccountSshKey;
 import com.google.gerrit.server.account.VersionedAuthorizedKeys;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gerrit.server.logging.Metadata;
@@ -97,11 +98,16 @@
   static class Loader extends CacheLoader<String, Iterable<SshKeyCacheEntry>> {
     private final ExternalIds externalIds;
     private final VersionedAuthorizedKeys.Accessor authorizedKeys;
+    private final ExternalIdKeyFactory externalIdKeyFactory;
 
     @Inject
-    Loader(ExternalIds externalIds, VersionedAuthorizedKeys.Accessor authorizedKeys) {
+    Loader(
+        ExternalIds externalIds,
+        VersionedAuthorizedKeys.Accessor authorizedKeys,
+        ExternalIdKeyFactory externalIdKeyFactory) {
       this.externalIds = externalIds;
       this.authorizedKeys = authorizedKeys;
+      this.externalIdKeyFactory = externalIdKeyFactory;
     }
 
     @Override
@@ -111,7 +117,7 @@
               "Loading SSH keys for account with username",
               Metadata.builder().username(username).build())) {
         Optional<ExternalId> user =
-            externalIds.get(ExternalId.Key.create(SCHEME_USERNAME, username));
+            externalIds.get(externalIdKeyFactory.create(SCHEME_USERNAME, username));
         if (!user.isPresent()) {
           return NO_SUCH_USER;
         }
diff --git a/java/com/google/gerrit/sshd/commands/SetAccountCommand.java b/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
index 43a1670..0c286ca 100644
--- a/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
+++ b/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.sshd.commands;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toList;
 
 import com.google.common.base.Strings;
 import com.google.gerrit.common.RawInputUtil;
@@ -36,6 +37,7 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountResource;
 import com.google.gerrit.server.account.AccountSshKey;
+import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.permissions.GlobalPermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -43,6 +45,7 @@
 import com.google.gerrit.server.restapi.account.CreateEmail;
 import com.google.gerrit.server.restapi.account.DeleteActive;
 import com.google.gerrit.server.restapi.account.DeleteEmail;
+import com.google.gerrit.server.restapi.account.DeleteExternalIds;
 import com.google.gerrit.server.restapi.account.DeleteSshKey;
 import com.google.gerrit.server.restapi.account.GetEmails;
 import com.google.gerrit.server.restapi.account.GetSshKeys;
@@ -122,10 +125,18 @@
   @Option(name = "--generate-http-password", usage = "generate a new HTTP password for the account")
   private boolean generateHttpPassword;
 
+  @Option(
+      name = "--delete-external-id",
+      metaVar = "EXTERNALID",
+      usage = "external id to delete from the account")
+  private List<String> externalIdsToDelete = new ArrayList<>();
+
   @Inject private IdentifiedUser.GenericFactory genericUserFactory;
 
   @Inject private CreateEmail createEmail;
 
+  @Inject private DeleteExternalIds deleteExternalIds;
+
   @Inject private GetEmails getEmails;
 
   @Inject private DeleteEmail deleteEmail;
@@ -150,6 +161,8 @@
 
   @Inject private Provider<CurrentUser> userProvider;
 
+  @Inject private ExternalIds externalIds;
+
   private AccountResource rsrc;
 
   @Override
@@ -210,6 +223,9 @@
           "--preferred-email and --delete-email options are mutually "
               + "exclusive for the same email address.");
     }
+    if (externalIdsToDelete.contains("ALL")) {
+      externalIdsToDelete = Collections.singletonList("ALL");
+    }
   }
 
   private void setAccount() throws Failure {
@@ -265,6 +281,10 @@
       if (!deleteSshKeys.isEmpty()) {
         deleteSshKeys(deleteSshKeys);
       }
+
+      for (String externalId : externalIdsToDelete) {
+        deleteExternalId(externalId);
+      }
     } catch (RestApiException e) {
       throw die(e.getMessage());
     } catch (Exception e) {
@@ -355,4 +375,21 @@
     }
     return sshKeys;
   }
+
+  private void deleteExternalId(String externalId)
+      throws IOException, RestApiException, ConfigInvalidException, PermissionBackendException {
+    List<String> ids;
+    if (externalId.equals("ALL")) {
+      ids =
+          externalIds.byAccount(rsrc.getUser().getAccountId()).stream()
+              .map(e -> e.key().get())
+              .collect(toList());
+      if (ids.isEmpty()) {
+        throw new ResourceNotFoundException("Account has no external Ids");
+      }
+    } else {
+      ids = Collections.singletonList(externalId);
+    }
+    deleteExternalIds.apply(rsrc, ids);
+  }
 }
diff --git a/java/com/google/gerrit/testing/InMemoryModule.java b/java/com/google/gerrit/testing/InMemoryModule.java
index 12da2c1..3949de0 100644
--- a/java/com/google/gerrit/testing/InMemoryModule.java
+++ b/java/com/google/gerrit/testing/InMemoryModule.java
@@ -44,6 +44,7 @@
 import com.google.gerrit.server.cache.h2.H2CacheModule;
 import com.google.gerrit.server.cache.mem.DefaultMemoryCacheModule;
 import com.google.gerrit.server.change.FileInfoJsonModule;
+import com.google.gerrit.server.config.AllProjectsConfigProvider;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.config.AllUsersName;
@@ -54,6 +55,7 @@
 import com.google.gerrit.server.config.CanonicalWebUrlModule;
 import com.google.gerrit.server.config.CanonicalWebUrlProvider;
 import com.google.gerrit.server.config.DefaultUrlFormatter;
+import com.google.gerrit.server.config.FileBasedAllProjectsConfigProvider;
 import com.google.gerrit.server.config.FileBasedGlobalPluginConfigProvider;
 import com.google.gerrit.server.config.GerritGlobalModule;
 import com.google.gerrit.server.config.GerritInstanceIdModule;
@@ -196,6 +198,7 @@
     bind(Path.class).annotatedWith(SitePath.class).toInstance(Paths.get("."));
     bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(cfg);
     bind(GerritOptions.class).toInstance(new GerritOptions(false, false));
+    bind(AllProjectsConfigProvider.class).to(FileBasedAllProjectsConfigProvider.class);
     bind(GlobalPluginConfigProvider.class).to(FileBasedGlobalPluginConfigProvider.class);
 
     bind(GitRepositoryManager.class).to(InMemoryRepositoryManager.class);
diff --git a/java/com/google/gerrit/testing/InMemoryRepositoryManager.java b/java/com/google/gerrit/testing/InMemoryRepositoryManager.java
index 09ae115..362e23c 100644
--- a/java/com/google/gerrit/testing/InMemoryRepositoryManager.java
+++ b/java/com/google/gerrit/testing/InMemoryRepositoryManager.java
@@ -17,6 +17,7 @@
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Sets;
 import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.Project.NameKey;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.RepositoryCaseMismatchException;
 import com.google.inject.Inject;
@@ -79,6 +80,16 @@
   }
 
   @Override
+  public synchronized Status getRepositoryStatus(NameKey name) {
+    try {
+      get(name);
+      return Status.ACTIVE;
+    } catch (RepositoryNotFoundException e) {
+      return Status.NON_EXISTENT;
+    }
+  }
+
+  @Override
   public synchronized Repo openRepository(Project.NameKey name) throws RepositoryNotFoundException {
     return get(name);
   }
diff --git a/java/com/google/gerrit/testing/InMemoryTestEnvironment.java b/java/com/google/gerrit/testing/InMemoryTestEnvironment.java
index 44d5cea..77df46c 100644
--- a/java/com/google/gerrit/testing/InMemoryTestEnvironment.java
+++ b/java/com/google/gerrit/testing/InMemoryTestEnvironment.java
@@ -46,6 +46,7 @@
   @Inject private IdentifiedUser.GenericFactory userFactory;
   @Inject private SchemaCreator schemaCreator;
   @Inject private ThreadLocalRequestContext requestContext;
+  @Inject private AuthRequest.Factory authRequestFactory;
 
   private LifecycleManager lifecycle;
 
@@ -99,7 +100,8 @@
     schemaCreator.create();
 
     // The first user is added to the "Administrators" group. See AccountManager#create().
-    setApiUser(accountManager.authenticate(AuthRequest.forUser("admin")).getAccountId());
+    setApiUser(
+        accountManager.authenticate(authRequestFactory.createForUser("admin")).getAccountId());
 
     // Inject target members after setting API user, so it can @Inject request-scoped objects if it
     // wants.
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 8c7d892..1da2176c 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -32,7 +32,6 @@
 import static com.google.gerrit.gpg.testing.TestKeys.validKeyWithSecondUserId;
 import static com.google.gerrit.gpg.testing.TestKeys.validKeyWithoutExpiration;
 import static com.google.gerrit.server.StarredChangesUtil.DEFAULT_LABEL;
-import static com.google.gerrit.server.StarredChangesUtil.IGNORE_LABEL;
 import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GPGKEY;
 import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
@@ -98,7 +97,6 @@
 import com.google.gerrit.extensions.api.changes.DraftInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.changes.ReviewerInput;
-import com.google.gerrit.extensions.api.changes.StarsInput;
 import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo;
 import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
 import com.google.gerrit.extensions.api.config.ConsistencyCheckInput;
@@ -133,6 +131,8 @@
 import com.google.gerrit.server.account.VersionedAuthorizedKeys;
 import com.google.gerrit.server.account.externalids.DuplicateExternalIdKeyException;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.account.externalids.ExternalIdNotes;
 import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
@@ -231,6 +231,8 @@
   @Inject private VersionedAuthorizedKeys.Accessor authorizedKeys;
   @Inject private ExtensionRegistry extensionRegistry;
   @Inject private PluginSetContext<ExceptionHook> exceptionHooks;
+  @Inject private ExternalIdKeyFactory externalIdKeyFactory;
+  @Inject private ExternalIdFactory externalIdFactory;
 
   @Inject protected Emails emails;
 
@@ -374,8 +376,8 @@
       accountIndexedCounter.assertReindexOf(accountId, 1);
       assertThat(externalIds.byAccount(accountId))
           .containsExactly(
-              ExternalId.createUsername(input.username, accountId, null),
-              ExternalId.createEmail(accountId, input.email));
+              externalIdFactory.createUsername(input.username, accountId, null),
+              externalIdFactory.createEmail(accountId, input.email));
     }
   }
 
@@ -428,7 +430,7 @@
   public void createAtomically() throws Exception {
     Account.Id accountId = Account.id(seq.nextAccountId());
     String fullName = "Foo";
-    ExternalId extId = ExternalId.createEmail(accountId, "foo@example.com");
+    ExternalId extId = externalIdFactory.createEmail(accountId, "foo@example.com");
     AccountState accountState =
         accountsUpdateProvider
             .get()
@@ -792,126 +794,7 @@
   }
 
   @Test
-  public void starUnstarChangeWithLabels() throws Exception {
-    AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
-    RefUpdateCounter refUpdateCounter = new RefUpdateCounter();
-    try (Registration registration =
-        extensionRegistry.newRegistration().add(accountIndexedCounter).add(refUpdateCounter)) {
-      PushOneCommit.Result r = createChange();
-      String triplet = project.get() + "~master~" + r.getChangeId();
-      refUpdateCounter.clear();
-
-      assertThat(gApi.accounts().self().getStars(triplet)).isEmpty();
-      assertThat(gApi.accounts().self().getStarredChanges()).isEmpty();
-
-      gApi.accounts()
-          .self()
-          .setStars(triplet, new StarsInput(ImmutableSet.of(DEFAULT_LABEL, "red", "blue")));
-      ChangeInfo change = info(triplet);
-      assertThat(change.starred).isTrue();
-      assertThat(change.stars).containsExactly("blue", "red", DEFAULT_LABEL).inOrder();
-      assertThat(gApi.accounts().self().getStars(triplet))
-          .containsExactly("blue", "red", DEFAULT_LABEL)
-          .inOrder();
-      List<ChangeInfo> starredChanges = gApi.accounts().self().getStarredChanges();
-      assertThat(starredChanges).hasSize(1);
-      ChangeInfo starredChange = starredChanges.get(0);
-      assertThat(starredChange._number).isEqualTo(r.getChange().getId().get());
-      assertThat(starredChange.starred).isTrue();
-      assertThat(starredChange.stars).containsExactly("blue", "red", DEFAULT_LABEL).inOrder();
-      refUpdateCounter.assertRefUpdateFor(
-          RefUpdateCounter.projectRef(
-              allUsers, RefNames.refsStarredChanges(Change.id(change._number), admin.id())));
-
-      gApi.accounts()
-          .self()
-          .setStars(
-              triplet,
-              new StarsInput(ImmutableSet.of("yellow"), ImmutableSet.of(DEFAULT_LABEL, "blue")));
-      change = info(triplet);
-      assertThat(change.starred).isNull();
-      assertThat(change.stars).containsExactly("red", "yellow").inOrder();
-      assertThat(gApi.accounts().self().getStars(triplet))
-          .containsExactly("red", "yellow")
-          .inOrder();
-      starredChanges = gApi.accounts().self().getStarredChanges();
-      assertThat(starredChanges).hasSize(1);
-      starredChange = starredChanges.get(0);
-      assertThat(starredChange._number).isEqualTo(r.getChange().getId().get());
-      assertThat(starredChange.starred).isNull();
-      assertThat(starredChange.stars).containsExactly("red", "yellow").inOrder();
-      refUpdateCounter.assertRefUpdateFor(
-          RefUpdateCounter.projectRef(
-              allUsers, RefNames.refsStarredChanges(Change.id(change._number), admin.id())));
-
-      accountIndexedCounter.assertNoReindex();
-
-      requestScopeOperations.setApiUser(user.id());
-      AuthException thrown =
-          assertThrows(
-              AuthException.class,
-              () -> gApi.accounts().id(Integer.toString((admin.id().get()))).getStars(triplet));
-      assertThat(thrown).hasMessageThat().contains("not allowed to get stars of another account");
-    }
-  }
-
-  @Test
-  public void starWithInvalidLabels() throws Exception {
-    PushOneCommit.Result r = createChange();
-    String triplet = project.get() + "~master~" + r.getChangeId();
-    BadRequestException thrown =
-        assertThrows(
-            BadRequestException.class,
-            () ->
-                gApi.accounts()
-                    .self()
-                    .setStars(
-                        triplet,
-                        new StarsInput(
-                            ImmutableSet.of(
-                                DEFAULT_LABEL, "invalid label", "blue", "another invalid label"))));
-    assertThat(thrown)
-        .hasMessageThat()
-        .contains("invalid labels: another invalid label, invalid label");
-  }
-
-  @Test
-  public void deleteStarLabelsFromChangeWithoutStarLabels() throws Exception {
-    PushOneCommit.Result r = createChange();
-    String triplet = project.get() + "~master~" + r.getChangeId();
-    assertThat(gApi.accounts().self().getStars(triplet)).isEmpty();
-
-    gApi.accounts().self().setStars(triplet, new StarsInput());
-
-    assertThat(gApi.accounts().self().getStars(triplet)).isEmpty();
-  }
-
-  @Test
-  public void starWithDefaultAndIgnoreLabel() throws Exception {
-    PushOneCommit.Result r = createChange();
-    String triplet = project.get() + "~master~" + r.getChangeId();
-    BadRequestException thrown =
-        assertThrows(
-            BadRequestException.class,
-            () ->
-                gApi.accounts()
-                    .self()
-                    .setStars(
-                        triplet,
-                        new StarsInput(ImmutableSet.of(DEFAULT_LABEL, "blue", IGNORE_LABEL))));
-    assertThat(thrown)
-        .hasMessageThat()
-        .contains(
-            "The labels "
-                + DEFAULT_LABEL
-                + " and "
-                + IGNORE_LABEL
-                + " are mutually exclusive."
-                + " Only one of them can be set.");
-  }
-
-  @Test
-  public void ignoreChangeBySetStars() throws Exception {
+  public void ignoreChange() throws Exception {
     AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
     try (Registration registration =
         extensionRegistry.newRegistration().add(accountIndexedCounter)) {
@@ -929,9 +812,7 @@
       gApi.changes().id(r.getChangeId()).addReviewer(in);
 
       requestScopeOperations.setApiUser(user.id());
-      gApi.accounts()
-          .self()
-          .setStars(r.getChangeId(), new StarsInput(ImmutableSet.of(IGNORE_LABEL)));
+      gApi.changes().id(r.getChangeId()).ignore(true);
 
       sender.clear();
       requestScopeOperations.setApiUser(admin.id());
@@ -951,9 +832,7 @@
       PushOneCommit.Result r = createChange();
 
       requestScopeOperations.setApiUser(user.id());
-      gApi.accounts()
-          .self()
-          .setStars(r.getChangeId(), new StarsInput(ImmutableSet.of(IGNORE_LABEL)));
+      gApi.changes().id(r.getChangeId()).ignore(true);
 
       sender.clear();
       requestScopeOperations.setApiUser(admin.id());
@@ -962,11 +841,7 @@
       in.reviewer = user.email();
       gApi.changes().id(r.getChangeId()).addReviewer(in);
       List<Message> messages = sender.getMessages();
-      assertThat(messages).hasSize(1);
-      Message message = messages.get(0);
-      assertThat(message.rcpt()).containsExactly(user.getNameEmail());
-      assertMailReplyTo(message, admin.email());
-      accountIndexedCounter.assertNoReindex();
+      assertThat(messages).hasSize(0);
     }
   }
 
@@ -1407,11 +1282,11 @@
               admin.id(),
               u ->
                   u.addExternalId(
-                          ExternalId.createWithEmail(
-                              ExternalId.Key.parse(extId1), admin.id(), email))
+                          externalIdFactory.createWithEmail(
+                              externalIdKeyFactory.parse(extId1), admin.id(), email))
                       .addExternalId(
-                          ExternalId.createWithEmail(
-                              ExternalId.Key.parse(extId2), admin.id(), email)));
+                          externalIdFactory.createWithEmail(
+                              externalIdKeyFactory.parse(extId2), admin.id(), email)));
       accountIndexedCounter.assertReindexOf(admin);
       assertThat(
               gApi.accounts().self().getExternalIds().stream()
@@ -1447,8 +1322,8 @@
             admin.id(),
             u ->
                 u.addExternalId(
-                    ExternalId.createWithEmail(
-                        ExternalId.Key.parse(ldapExternalId), admin.id(), ldapEmail)));
+                    externalIdFactory.createWithEmail(
+                        externalIdKeyFactory.parse(ldapExternalId), admin.id(), ldapEmail)));
     assertThat(
             gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet()))
         .contains(ldapExternalId);
@@ -1482,11 +1357,13 @@
             admin.id(),
             u ->
                 u.addExternalId(
-                        ExternalId.createWithEmail(
-                            ExternalId.Key.parse(nonLdapExternalId), admin.id(), nonLdapEMail))
+                        externalIdFactory.createWithEmail(
+                            externalIdKeyFactory.parse(nonLdapExternalId),
+                            admin.id(),
+                            nonLdapEMail))
                     .addExternalId(
-                        ExternalId.createWithEmail(
-                            ExternalId.Key.parse(ldapExternalId), admin.id(), ldapEmail)));
+                        externalIdFactory.createWithEmail(
+                            externalIdKeyFactory.parse(ldapExternalId), admin.id(), ldapEmail)));
     assertThat(
             gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet()))
         .containsAtLeast(ldapExternalId, nonLdapExternalId);
@@ -1549,8 +1426,8 @@
             admin.id(),
             u ->
                 u.addExternalId(
-                    ExternalId.createWithEmail(
-                        ExternalId.Key.parse("foo:bar"), admin.id(), email)));
+                    externalIdFactory.createWithEmail(
+                        externalIdKeyFactory.parse("foo:bar"), admin.id(), email)));
     assertEmail(emails.getAccountFor(email), admin);
 
     // wrong case doesn't match
@@ -1866,7 +1743,7 @@
           .update(
               "Add External ID",
               user.id(),
-              u -> u.addExternalId(ExternalId.create("foo", "myId", user.id())));
+              u -> u.addExternalId(externalIdFactory.create("foo", "myId", user.id())));
       accountIndexedCounter.assertReindexOf(user);
 
       TestKey key = validKeyWithSecondUserId();
@@ -2169,7 +2046,7 @@
         .update(
             "Delete External ID",
             account.id(),
-            u -> u.deleteExternalId(ExternalId.createEmail(account.id(), email)));
+            u -> u.deleteExternalId(externalIdFactory.createEmail(account.id(), email)));
     expectedProblems.add(
         new ConsistencyProblemInfo(
             ConsistencyProblemInfo.Status.ERROR,
@@ -2504,7 +2381,7 @@
         .update();
 
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId extIdA1 = ExternalId.create("foo", "A-1", accountId);
+    ExternalId extIdA1 = externalIdFactory.create("foo", "A-1", accountId);
     accountsUpdateProvider
         .get()
         .insert("Create Test Account", accountId, u -> u.addExternalId(extIdA1));
@@ -2512,7 +2389,7 @@
     AtomicInteger bgCounterA1 = new AtomicInteger(0);
     AtomicInteger bgCounterA2 = new AtomicInteger(0);
     PersonIdent ident = serverIdent.get();
-    ExternalId extIdA2 = ExternalId.create("foo", "A-2", accountId);
+    ExternalId extIdA2 = externalIdFactory.create("foo", "A-2", accountId);
     AccountsUpdate update =
         new AccountsUpdate(
             repoManager,
@@ -2553,8 +2430,8 @@
                 .collect(toSet()))
         .containsExactly(extIdA1.key().get());
 
-    ExternalId extIdB1 = ExternalId.create("foo", "B-1", accountId);
-    ExternalId extIdB2 = ExternalId.create("foo", "B-2", accountId);
+    ExternalId extIdB1 = externalIdFactory.create("foo", "B-1", accountId);
+    ExternalId extIdB2 = externalIdFactory.create("foo", "B-2", accountId);
     Optional<AccountState> updatedAccount =
         update.update(
             "Update External ID",
@@ -2617,23 +2494,24 @@
     // Manually inserting/updating/deleting an external ID of the user makes the index document
     // stale.
     try (Repository repo = repoManager.openRepository(allUsers)) {
-      ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(allUsers, repo);
+      ExternalIdNotes extIdNotes =
+          ExternalIdNotes.loadNoCacheUpdate(allUsers, repo, externalIdFactory);
 
-      ExternalId.Key key = ExternalId.Key.create("foo", "foo");
-      extIdNotes.insert(ExternalId.create(key, accountId));
+      ExternalId.Key key = externalIdKeyFactory.create("foo", "foo");
+      extIdNotes.insert(externalIdFactory.create(key, accountId));
       try (MetaDataUpdate update = metaDataUpdateFactory.create(allUsers)) {
         extIdNotes.commit(update);
       }
       assertStaleAccountAndReindex(accountId);
 
-      extIdNotes = ExternalIdNotes.loadNoCacheUpdate(allUsers, repo);
-      extIdNotes.upsert(ExternalId.createWithEmail(key, accountId, "foo@example.com"));
+      extIdNotes = ExternalIdNotes.loadNoCacheUpdate(allUsers, repo, externalIdFactory);
+      extIdNotes.upsert(externalIdFactory.createWithEmail(key, accountId, "foo@example.com"));
       try (MetaDataUpdate update = metaDataUpdateFactory.create(allUsers)) {
         extIdNotes.commit(update);
       }
       assertStaleAccountAndReindex(accountId);
 
-      extIdNotes = ExternalIdNotes.loadNoCacheUpdate(allUsers, repo);
+      extIdNotes = ExternalIdNotes.loadNoCacheUpdate(allUsers, repo, externalIdFactory);
       extIdNotes.delete(accountId, key);
       try (MetaDataUpdate update = metaDataUpdateFactory.create(allUsers)) {
         extIdNotes.commit(update);
@@ -2894,9 +2772,11 @@
     String extId1String = "foo:bar";
     String extId2String = "foo:baz";
     ExternalId extId1 =
-        ExternalId.createWithEmail(ExternalId.Key.parse(extId1String), admin.id(), "1@foo.com");
+        externalIdFactory.createWithEmail(
+            externalIdKeyFactory.parse(extId1String), admin.id(), "1@foo.com");
     ExternalId extId2 =
-        ExternalId.createWithEmail(ExternalId.Key.parse(extId2String), user.id(), "2@foo.com");
+        externalIdFactory.createWithEmail(
+            externalIdKeyFactory.parse(extId2String), user.id(), "2@foo.com");
 
     ObjectId revBefore;
     try (Repository repo = repoManager.openRepository(allUsers)) {
@@ -2936,9 +2816,11 @@
   @Test
   public void externalIdBatchUpdates_fail_sameAccount() {
     ExternalId extId1 =
-        ExternalId.createWithEmail(ExternalId.Key.parse("foo:bar"), admin.id(), "1@foo.com");
+        externalIdFactory.createWithEmail(
+            externalIdKeyFactory.parse("foo:bar"), admin.id(), "1@foo.com");
     ExternalId extId2 =
-        ExternalId.createWithEmail(ExternalId.Key.parse("foo:baz"), user.id(), "2@foo.com");
+        externalIdFactory.createWithEmail(
+            externalIdKeyFactory.parse("foo:baz"), user.id(), "2@foo.com");
 
     AccountsUpdate.UpdateArguments ua1 =
         new AccountsUpdate.UpdateArguments(
@@ -2957,9 +2839,11 @@
   @Test
   public void externalIdBatchUpdates_fail_duplicateKey() {
     ExternalId extIdAdmin =
-        ExternalId.createWithEmail(ExternalId.Key.parse("foo:bar"), admin.id(), "1@foo.com");
+        externalIdFactory.createWithEmail(
+            externalIdKeyFactory.parse("foo:bar"), admin.id(), "1@foo.com");
     ExternalId extIdUser =
-        ExternalId.createWithEmail(ExternalId.Key.parse("foo:bar"), user.id(), "2@foo.com");
+        externalIdFactory.createWithEmail(
+            externalIdKeyFactory.parse("foo:bar"), user.id(), "2@foo.com");
 
     AccountsUpdate.UpdateArguments ua1 =
         new AccountsUpdate.UpdateArguments(
@@ -2977,9 +2861,11 @@
   @Test
   public void externalIdBatchUpdates_commitMsg_multipleAccounts() throws Exception {
     ExternalId extId1 =
-        ExternalId.createWithEmail(ExternalId.Key.parse("foo:bar"), admin.id(), "1@foo.com");
+        externalIdFactory.createWithEmail(
+            externalIdKeyFactory.parse("foo:bar"), admin.id(), "1@foo.com");
     ExternalId extId2 =
-        ExternalId.createWithEmail(ExternalId.Key.parse("foo:baz"), user.id(), "2@foo.com");
+        externalIdFactory.createWithEmail(
+            externalIdKeyFactory.parse("foo:baz"), user.id(), "2@foo.com");
 
     AccountsUpdate.UpdateArguments ua1 =
         new AccountsUpdate.UpdateArguments(
@@ -3001,7 +2887,8 @@
   @Test
   public void externalIdBatchUpdates_commitMsg_singleAccount() throws Exception {
     ExternalId extId =
-        ExternalId.createWithEmail(ExternalId.Key.parse("foo:bar"), admin.id(), "1@foo.com");
+        externalIdFactory.createWithEmail(
+            externalIdKeyFactory.parse("foo:bar"), admin.id(), "1@foo.com");
 
     accountsUpdateProvider.get().update("foobar", admin.id(), (a, u) -> u.addExternalId(extId));
 
@@ -3158,7 +3045,7 @@
               account.id(),
               u ->
                   u.addExternalId(
-                      ExternalId.createWithEmail(name("test"), email, account.id(), email)));
+                      externalIdFactory.createWithEmail(name("test"), email, account.id(), email)));
       accountIndexedCounter.assertReindexOf(account);
       requestScopeOperations.setApiUser(account.id());
     }
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountManagerIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountManagerIT.java
index b41a2f3..7e23f0e 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountManagerIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountManagerIT.java
@@ -37,6 +37,8 @@
 import com.google.gerrit.server.account.AuthResult;
 import com.google.gerrit.server.account.SetInactiveFlag;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.account.externalids.ExternalIdNotes;
 import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
@@ -62,14 +64,17 @@
   @Inject private SshKeyCache sshKeyCache;
   @Inject private GroupsUpdate.Factory groupsUpdateFactory;
   @Inject private SetInactiveFlag setInactiveFlag;
+  @Inject private AuthRequest.Factory authRequestFactory;
+  @Inject private ExternalIdFactory externalIdFactory;
+  @Inject private ExternalIdKeyFactory externalIdKeyFactory;
 
   @Test
   public void authenticateNewAccountWithEmail() throws Exception {
     String email = "foo@example.com";
-    ExternalId.Key mailtoExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_MAILTO, email);
+    ExternalId.Key mailtoExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_MAILTO, email);
     assertNoSuchExternalIds(mailtoExtIdKey);
 
-    AuthRequest who = AuthRequest.forEmail(email);
+    AuthRequest who = authRequestFactory.createForEmail(email);
     AuthResult authResult = accountManager.authenticate(who);
     assertAuthResultForNewAccount(authResult, mailtoExtIdKey);
     assertExternalId(mailtoExtIdKey, email);
@@ -78,11 +83,12 @@
   @Test
   public void authenticateNewAccountWithUsername() throws Exception {
     String username = "foo";
-    ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
-    ExternalId.Key usernameExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_USERNAME, username);
+    ExternalId.Key gerritExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_GERRIT, username);
+    ExternalId.Key usernameExtIdKey =
+        externalIdKeyFactory.create(ExternalId.SCHEME_USERNAME, username);
     assertNoSuchExternalIds(gerritExtIdKey, usernameExtIdKey);
 
-    AuthRequest who = AuthRequest.forUser(username);
+    AuthRequest who = authRequestFactory.createForUser(username);
     AuthResult authResult = accountManager.authenticate(who);
     assertAuthResultForNewAccount(authResult, gerritExtIdKey);
     assertExternalIdsWithoutEmail(gerritExtIdKey, usernameExtIdKey);
@@ -91,11 +97,12 @@
   @Test
   public void authenticateNewAccountWithUsernameAndEmail() throws Exception {
     String username = "foo";
-    ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
-    ExternalId.Key usernameExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_USERNAME, username);
+    ExternalId.Key gerritExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_GERRIT, username);
+    ExternalId.Key usernameExtIdKey =
+        externalIdKeyFactory.create(ExternalId.SCHEME_USERNAME, username);
     assertNoSuchExternalIds(gerritExtIdKey, usernameExtIdKey);
 
-    AuthRequest who = AuthRequest.forUser(username);
+    AuthRequest who = authRequestFactory.createForUser(username);
     String email = "foo@example.com";
     who.setEmailAddress(email);
     AuthResult authResult = accountManager.authenticate(who);
@@ -107,12 +114,14 @@
   @Test
   public void authenticateNewAccountWithExternalUser() throws Exception {
     String username = "foo";
-    ExternalId.Key externalExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username);
-    ExternalId.Key usernameExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_USERNAME, username);
-    ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
+    ExternalId.Key externalExtIdKey =
+        externalIdKeyFactory.create(ExternalId.SCHEME_EXTERNAL, username);
+    ExternalId.Key usernameExtIdKey =
+        externalIdKeyFactory.create(ExternalId.SCHEME_USERNAME, username);
+    ExternalId.Key gerritExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_GERRIT, username);
     assertNoSuchExternalIds(externalExtIdKey, usernameExtIdKey, gerritExtIdKey);
 
-    AuthRequest who = AuthRequest.forExternalUser(username);
+    AuthRequest who = authRequestFactory.createForExternalUser(username);
     AuthResult authResult = accountManager.authenticate(who);
     assertAuthResultForNewAccount(authResult, externalExtIdKey);
     assertExternalIdsWithoutEmail(externalExtIdKey, usernameExtIdKey);
@@ -122,12 +131,14 @@
   @Test
   public void authenticateNewAccountWithExternalUserAndEmail() throws Exception {
     String username = "foo";
-    ExternalId.Key externalExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username);
-    ExternalId.Key usernameExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_USERNAME, username);
-    ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
+    ExternalId.Key externalExtIdKey =
+        externalIdKeyFactory.create(ExternalId.SCHEME_EXTERNAL, username);
+    ExternalId.Key usernameExtIdKey =
+        externalIdKeyFactory.create(ExternalId.SCHEME_USERNAME, username);
+    ExternalId.Key gerritExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_GERRIT, username);
     assertNoSuchExternalIds(externalExtIdKey, usernameExtIdKey, gerritExtIdKey);
 
-    AuthRequest who = AuthRequest.forExternalUser(username);
+    AuthRequest who = authRequestFactory.createForExternalUser(username);
     String email = "foo@example.com";
     who.setEmailAddress(email);
     AuthResult authResult = accountManager.authenticate(who);
@@ -141,13 +152,13 @@
   public void authenticateWithEmail() throws Exception {
     String email = "foo@example.com";
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId.Key mailtoExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_MAILTO, email);
+    ExternalId.Key mailtoExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_MAILTO, email);
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
-        u -> u.addExternalId(ExternalId.create(mailtoExtIdKey, accountId)));
+        u -> u.addExternalId(externalIdFactory.create(mailtoExtIdKey, accountId)));
 
-    AuthRequest who = AuthRequest.forEmail(email);
+    AuthRequest who = authRequestFactory.createForEmail(email);
     AuthResult authResult = accountManager.authenticate(who);
     assertAuthResultForExistingAccount(authResult, accountId, mailtoExtIdKey);
   }
@@ -156,13 +167,13 @@
   public void authenticateWithUsername() throws Exception {
     String username = "foo";
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
+    ExternalId.Key gerritExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_GERRIT, username);
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
-        u -> u.addExternalId(ExternalId.create(gerritExtIdKey, accountId)));
+        u -> u.addExternalId(externalIdFactory.create(gerritExtIdKey, accountId)));
 
-    AuthRequest who = AuthRequest.forUser(username);
+    AuthRequest who = authRequestFactory.createForUser(username);
     AuthResult authResult = accountManager.authenticate(who);
     assertAuthResultForExistingAccount(authResult, accountId, gerritExtIdKey);
   }
@@ -171,13 +182,14 @@
   public void authenticateWithExternalUser() throws Exception {
     String username = "foo";
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId.Key externalExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username);
+    ExternalId.Key externalExtIdKey =
+        externalIdKeyFactory.create(ExternalId.SCHEME_EXTERNAL, username);
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
-        u -> u.addExternalId(ExternalId.create(externalExtIdKey, accountId)));
+        u -> u.addExternalId(externalIdFactory.create(externalExtIdKey, accountId)));
 
-    AuthRequest who = AuthRequest.forExternalUser(username);
+    AuthRequest who = authRequestFactory.createForExternalUser(username);
     AuthResult authResult = accountManager.authenticate(who);
     assertAuthResultForExistingAccount(authResult, accountId, externalExtIdKey);
   }
@@ -187,15 +199,16 @@
     String username = "foo";
     String email = "foo@example.com";
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
+    ExternalId.Key gerritExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_GERRIT, username);
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
         u ->
             u.setPreferredEmail(email)
-                .addExternalId(ExternalId.createWithEmail(gerritExtIdKey, accountId, email)));
+                .addExternalId(
+                    externalIdFactory.createWithEmail(gerritExtIdKey, accountId, email)));
 
-    AuthRequest who = AuthRequest.forUser(username);
+    AuthRequest who = authRequestFactory.createForUser(username);
     String newEmail = "bar@example.com";
     who.setEmailAddress(newEmail);
     AuthResult authResult = accountManager.authenticate(who);
@@ -233,23 +246,26 @@
             projectCache,
             externalIds,
             groupsUpdateFactory,
-            setInactiveFlag));
+            setInactiveFlag,
+            externalIdFactory,
+            externalIdKeyFactory));
   }
 
   private void authenticateWithUsernameAndUpdateDisplayName(AccountManager am) throws Exception {
     String username = "foo";
     String email = "foo@example.com";
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
+    ExternalId.Key gerritExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_GERRIT, username);
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
         u ->
             u.setFullName("Initial Name")
                 .setPreferredEmail(email)
-                .addExternalId(ExternalId.createWithEmail(gerritExtIdKey, accountId, email)));
+                .addExternalId(
+                    externalIdFactory.createWithEmail(gerritExtIdKey, accountId, email)));
 
-    AuthRequest who = AuthRequest.forUser(username);
+    AuthRequest who = authRequestFactory.createForUser(username);
     String newName = "Updated Name";
     who.setDisplayName(newName);
     AuthResult authResult = am.authenticate(who);
@@ -263,12 +279,12 @@
   @Test
   public void cannotAuthenticateWithOrphanedExtId() throws Exception {
     String username = "foo";
-    ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
+    ExternalId.Key gerritExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_GERRIT, username);
     assertNoSuchExternalIds(gerritExtIdKey);
 
     // Create orphaned SCHEME_GERRIT external ID.
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId gerritExtId = ExternalId.create(gerritExtIdKey, accountId);
+    ExternalId gerritExtId = externalIdFactory.create(gerritExtIdKey, accountId);
     try (Repository allUsersRepo = repoManager.openRepository(allUsers);
         MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
       ExternalIdNotes extIdNotes = extIdNotesFactory.load(allUsersRepo);
@@ -276,7 +292,7 @@
       extIdNotes.commit(md);
     }
 
-    AuthRequest who = AuthRequest.forUser(username);
+    AuthRequest who = authRequestFactory.createForUser(username);
     AccountException thrown =
         assertThrows(AccountException.class, () -> accountManager.authenticate(who));
     assertThat(thrown).hasMessageThat().contains("Authentication error, account not found");
@@ -286,13 +302,13 @@
   public void cannotAuthenticateWithInactiveAccount() throws Exception {
     String username = "foo";
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
+    ExternalId.Key gerritExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_GERRIT, username);
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
-        u -> u.setActive(false).addExternalId(ExternalId.create(gerritExtIdKey, accountId)));
+        u -> u.setActive(false).addExternalId(externalIdFactory.create(gerritExtIdKey, accountId)));
 
-    AuthRequest who = AuthRequest.forUser(username);
+    AuthRequest who = authRequestFactory.createForUser(username);
     AccountException thrown =
         assertThrows(AccountException.class, () -> accountManager.authenticate(who));
     assertThat(thrown).hasMessageThat().contains("Authentication error, account inactive");
@@ -303,13 +319,13 @@
       throws Exception {
     String username = "foo";
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
+    ExternalId.Key gerritExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_GERRIT, username);
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
-        u -> u.setActive(false).addExternalId(ExternalId.create(gerritExtIdKey, accountId)));
+        u -> u.setActive(false).addExternalId(externalIdFactory.create(gerritExtIdKey, accountId)));
 
-    AuthRequest who = AuthRequest.forUser(username);
+    AuthRequest who = authRequestFactory.createForUser(username);
     who.setActive(true);
     who.setAuthProvidesAccountActiveStatus(true);
     AccountException thrown =
@@ -323,13 +339,13 @@
       throws Exception {
     String username = "foo";
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
+    ExternalId.Key gerritExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_GERRIT, username);
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
-        u -> u.setActive(false).addExternalId(ExternalId.create(gerritExtIdKey, accountId)));
+        u -> u.setActive(false).addExternalId(externalIdFactory.create(gerritExtIdKey, accountId)));
 
-    AuthRequest who = AuthRequest.forUser(username);
+    AuthRequest who = authRequestFactory.createForUser(username);
     who.setActive(true);
     who.setAuthProvidesAccountActiveStatus(true);
     AuthResult authResult = accountManager.authenticate(who);
@@ -344,13 +360,13 @@
       throws Exception {
     String username = "foo";
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
+    ExternalId.Key gerritExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_GERRIT, username);
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
-        u -> u.addExternalId(ExternalId.create(gerritExtIdKey, accountId)));
+        u -> u.addExternalId(externalIdFactory.create(gerritExtIdKey, accountId)));
 
-    AuthRequest who = AuthRequest.forUser(username);
+    AuthRequest who = authRequestFactory.createForUser(username);
     who.setActive(false);
     who.setAuthProvidesAccountActiveStatus(true);
     AuthResult authResult = accountManager.authenticate(who);
@@ -366,13 +382,13 @@
       throws Exception {
     String username = "foo";
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
+    ExternalId.Key gerritExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_GERRIT, username);
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
-        u -> u.addExternalId(ExternalId.create(gerritExtIdKey, accountId)));
+        u -> u.addExternalId(externalIdFactory.create(gerritExtIdKey, accountId)));
 
-    AuthRequest who = AuthRequest.forUser(username);
+    AuthRequest who = authRequestFactory.createForUser(username);
     who.setActive(false);
     who.setAuthProvidesAccountActiveStatus(true);
     AccountException thrown =
@@ -391,15 +407,17 @@
     // Create an account with an SCHEME_EXTERNAL external ID that occupies the email.
     String username = "foo";
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId.Key externalExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username);
+    ExternalId.Key externalExtIdKey =
+        externalIdKeyFactory.create(ExternalId.SCHEME_EXTERNAL, username);
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
-        u -> u.addExternalId(ExternalId.createWithEmail(externalExtIdKey, accountId, email)));
+        u ->
+            u.addExternalId(externalIdFactory.createWithEmail(externalExtIdKey, accountId, email)));
 
     // Try to authenticate with this email to create a new account with a SCHEME_MAILTO external ID.
     // Expect that this fails because the email is already assigned to the other account.
-    AuthRequest who = AuthRequest.forEmail(email);
+    AuthRequest who = authRequestFactory.createForEmail(email);
     AccountException thrown =
         assertThrows(AccountException.class, () -> accountManager.authenticate(who));
     assertThat(thrown)
@@ -414,15 +432,17 @@
     // Create an account with an SCHEME_EXTERNAL external ID that occupies the email.
     String username = "foo";
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId.Key externalExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username);
+    ExternalId.Key externalExtIdKey =
+        externalIdKeyFactory.create(ExternalId.SCHEME_EXTERNAL, username);
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
-        u -> u.addExternalId(ExternalId.createWithEmail(externalExtIdKey, accountId, email)));
+        u ->
+            u.addExternalId(externalIdFactory.createWithEmail(externalExtIdKey, accountId, email)));
 
     // Try to authenticate with a new username and claim the same email.
     // Expect that this fails because the email is already assigned to the other account.
-    AuthRequest who = AuthRequest.forUser("bar");
+    AuthRequest who = authRequestFactory.createForUser("bar");
     who.setEmailAddress(email);
     AccountException thrown =
         assertThrows(AccountException.class, () -> accountManager.authenticate(who));
@@ -439,25 +459,29 @@
     // Create an account with a SCHEME_GERRIT external ID and an email.
     String username = "foo";
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
+    ExternalId.Key gerritExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_GERRIT, username);
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
         u ->
             u.setPreferredEmail(email)
-                .addExternalId(ExternalId.createWithEmail(gerritExtIdKey, accountId, email)));
+                .addExternalId(
+                    externalIdFactory.createWithEmail(gerritExtIdKey, accountId, email)));
 
     // Create another account with an SCHEME_EXTERNAL external ID that occupies the new email.
     Account.Id accountId2 = Account.id(seq.nextAccountId());
-    ExternalId.Key externalExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, "bar");
+    ExternalId.Key externalExtIdKey =
+        externalIdKeyFactory.create(ExternalId.SCHEME_EXTERNAL, "bar");
     accountsUpdate.insert(
         "Create Test Account",
         accountId2,
-        u -> u.addExternalId(ExternalId.createWithEmail(externalExtIdKey, accountId2, newEmail)));
+        u ->
+            u.addExternalId(
+                externalIdFactory.createWithEmail(externalExtIdKey, accountId2, newEmail)));
 
     // Try to authenticate and update the email for the first account.
     // Expect that this fails because the new email is already assigned to the other account.
-    AuthRequest who = AuthRequest.forUser(username);
+    AuthRequest who = authRequestFactory.createForUser(username);
     who.setEmailAddress(newEmail);
     AccountException thrown =
         assertThrows(AccountException.class, () -> accountManager.authenticate(who));
@@ -482,21 +506,21 @@
 
     // Create an account with a SCHEME_GERRIT external ID
     String username = "foo";
-    ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
+    ExternalId.Key gerritExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_GERRIT, username);
     Account.Id accountId = Account.id(seq.nextAccountId());
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
-        u -> u.addExternalId(ExternalId.create(gerritExtIdKey, accountId)));
+        u -> u.addExternalId(externalIdFactory.create(gerritExtIdKey, accountId)));
 
     // Add the additional mail external ID with SCHEME_EMAIL
-    accountManager.link(accountId, AuthRequest.forEmail(email));
+    accountManager.link(accountId, authRequestFactory.createForEmail(email));
 
     // Try to authenticate and update the email for the account.
     // Expect that this to succeed because even if the email already exist
     // it is associated to the same account-id and thus is not really
     // a duplicate but simply a promotion of external id to preferred email.
-    AuthRequest who = AuthRequest.forUser(username);
+    AuthRequest who = authRequestFactory.createForUser(username);
     who.setEmailAddress(email);
     AuthResult authResult = accountManager.authenticate(who);
 
@@ -519,20 +543,20 @@
     // Create an account with a SCHEME_GERRIT external ID and no email
     String username = "foo";
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
+    ExternalId.Key gerritExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_GERRIT, username);
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
-        u -> u.addExternalId(ExternalId.create(gerritExtIdKey, accountId)));
+        u -> u.addExternalId(externalIdFactory.create(gerritExtIdKey, accountId)));
 
     // Check that email is not used yet.
     String email = "foo@example.com";
-    ExternalId.Key mailtoExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_MAILTO, email);
+    ExternalId.Key mailtoExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_MAILTO, email);
     assertNoSuchExternalIds(mailtoExtIdKey);
 
     // Link the email to the account.
     // Expect that a MAILTO external ID is created.
-    AuthRequest who = AuthRequest.forEmail(email);
+    AuthRequest who = authRequestFactory.createForEmail(email);
     AuthResult authResult = accountManager.link(accountId, who);
     assertAuthResultForExistingAccount(authResult, accountId, mailtoExtIdKey);
     assertExternalId(mailtoExtIdKey, accountId, email);
@@ -543,17 +567,18 @@
     // Create an account with a SCHEME_GERRIT external ID and no email
     String username = "foo";
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId.Key externalExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username);
+    ExternalId.Key externalExtIdKey =
+        externalIdKeyFactory.create(ExternalId.SCHEME_EXTERNAL, username);
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
         u ->
             u.addExternalId(
-                ExternalId.createWithEmail(externalExtIdKey, accountId, "old@example.com")));
+                externalIdFactory.createWithEmail(externalExtIdKey, accountId, "old@example.com")));
 
     // Link the email to the existing SCHEME_EXTERNAL external ID, but with a new email.
     // Expect that the email of the existing external ID is updated.
-    AuthRequest who = AuthRequest.forExternalUser(username);
+    AuthRequest who = authRequestFactory.createForExternalUser(username);
     String newEmail = "new@example.com";
     who.setEmailAddress(newEmail);
     AuthResult authResult = accountManager.link(accountId, who);
@@ -566,24 +591,26 @@
     // Create an account with a SCHEME_EXTERNAL external ID
     String username1 = "foo";
     Account.Id accountId1 = Account.id(seq.nextAccountId());
-    ExternalId.Key externalExtIdKey1 = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username1);
+    ExternalId.Key externalExtIdKey1 =
+        externalIdKeyFactory.create(ExternalId.SCHEME_EXTERNAL, username1);
     accountsUpdate.insert(
         "Create Test Account",
         accountId1,
-        u -> u.addExternalId(ExternalId.create(externalExtIdKey1, accountId1)));
+        u -> u.addExternalId(externalIdFactory.create(externalExtIdKey1, accountId1)));
 
     // Create another account with a SCHEME_EXTERNAL external ID
     String username2 = "bar";
     Account.Id accountId2 = Account.id(seq.nextAccountId());
-    ExternalId.Key externalExtIdKey2 = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username2);
+    ExternalId.Key externalExtIdKey2 =
+        externalIdKeyFactory.create(ExternalId.SCHEME_EXTERNAL, username2);
     accountsUpdate.insert(
         "Create Test Account",
         accountId2,
-        u -> u.addExternalId(ExternalId.create(externalExtIdKey2, accountId2)));
+        u -> u.addExternalId(externalIdFactory.create(externalExtIdKey2, accountId2)));
 
     // Try to link external ID of the first account to the second account.
     // Expect that this fails because the external ID is already assigned to the first account.
-    AuthRequest who = AuthRequest.forExternalUser(username1);
+    AuthRequest who = authRequestFactory.createForExternalUser(username1);
     AccountException thrown =
         assertThrows(AccountException.class, () -> accountManager.link(accountId2, who));
     assertThat(thrown)
@@ -598,24 +625,27 @@
     // Create an account with an SCHEME_EXTERNAL external ID that occupies the email.
     String username = "foo";
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId.Key externalExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username);
+    ExternalId.Key externalExtIdKey =
+        externalIdKeyFactory.create(ExternalId.SCHEME_EXTERNAL, username);
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
-        u -> u.addExternalId(ExternalId.createWithEmail(externalExtIdKey, accountId, email)));
+        u ->
+            u.addExternalId(externalIdFactory.createWithEmail(externalExtIdKey, accountId, email)));
 
     // Create another account with a SCHEME_GERRIT external ID and no email
     String username2 = "foo";
     Account.Id accountId2 = Account.id(seq.nextAccountId());
-    ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username2);
+    ExternalId.Key gerritExtIdKey =
+        externalIdKeyFactory.create(ExternalId.SCHEME_GERRIT, username2);
     accountsUpdate.insert(
         "Create Test Account",
         accountId2,
-        u -> u.addExternalId(ExternalId.create(gerritExtIdKey, accountId2)));
+        u -> u.addExternalId(externalIdFactory.create(gerritExtIdKey, accountId2)));
 
     // Try to link the email to the second account (via a new MAILTO external ID) and expect that
     // this fails because the email is already assigned to the first account.
-    AuthRequest who = AuthRequest.forEmail(email);
+    AuthRequest who = authRequestFactory.createForEmail(email);
     AccountException thrown =
         assertThrows(AccountException.class, () -> accountManager.link(accountId2, who));
     assertThat(thrown)
@@ -630,13 +660,15 @@
     // Create an account with an SCHEME_EXTERNAL external ID that occupies the email.
     String username = "foo";
     Account.Id accountId = Account.id(seq.nextAccountId());
-    ExternalId.Key externalExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username);
+    ExternalId.Key externalExtIdKey =
+        externalIdKeyFactory.create(ExternalId.SCHEME_EXTERNAL, username);
     accountsUpdate.insert(
         "Create Test Account",
         accountId,
-        u -> u.addExternalId(ExternalId.createWithEmail(externalExtIdKey, accountId, email)));
+        u ->
+            u.addExternalId(externalIdFactory.createWithEmail(externalExtIdKey, accountId, email)));
 
-    AuthRequest who = AuthRequest.forEmail(email);
+    AuthRequest who = authRequestFactory.createForEmail(email);
     AuthResult result = accountManager.link(accountId, who);
     assertThat(result.isNew()).isFalse();
     assertThat(result.getAccountId().get()).isEqualTo(accountId.get());
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 5c5d127..95a8950 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -127,7 +127,6 @@
 import com.google.gerrit.extensions.api.changes.ReviewerInput;
 import com.google.gerrit.extensions.api.changes.ReviewerResult;
 import com.google.gerrit.extensions.api.changes.RevisionApi;
-import com.google.gerrit.extensions.api.changes.StarsInput;
 import com.google.gerrit.extensions.api.groups.GroupApi;
 import com.google.gerrit.extensions.api.projects.BranchInput;
 import com.google.gerrit.extensions.api.projects.ConfigInput;
@@ -170,6 +169,7 @@
 import com.google.gerrit.server.change.ChangeMessages;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.testing.TestChangeETagComputation;
+import com.google.gerrit.server.experiments.ExperimentFeaturesConstants;
 import com.google.gerrit.server.git.ChangeMessageModifier;
 import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.index.change.ChangeIndex;
@@ -4048,12 +4048,14 @@
 
     ChangeInfo change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.UNSATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.UNSATISFIED, /* isLegacy= */ false);
 
     voteLabel(changeId, "code-review", 2);
     change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.SATISFIED, /* isLegacy= */ false);
   }
 
   @Test
@@ -4073,19 +4075,22 @@
     ChangeInfo change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
     // Requirement is satisfied because there are no votes
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.SATISFIED, /* isLegacy= */ false);
 
     voteLabel(changeId, "code-review", -1);
     change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
     // Requirement is still satisfied because -1 is not the max negative value
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.SATISFIED, /* isLegacy= */ false);
 
     voteLabel(changeId, "code-review", -2);
     change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
     // Requirement is now unsatisfied because -2 is the max negative value
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.UNSATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.UNSATISFIED, /* isLegacy= */ false);
   }
 
   @Test
@@ -4104,12 +4109,14 @@
 
     ChangeInfo change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.UNSATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.UNSATISFIED, /* isLegacy= */ false);
 
     voteLabel(changeId, "code-review", 1);
     change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.SATISFIED, /* isLegacy= */ false);
   }
 
   @Test
@@ -4135,15 +4142,19 @@
 
     ChangeInfo change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(2);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.UNSATISFIED);
-    assertSubmitRequirementStatus(change.submitRequirements, "verified", Status.UNSATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.UNSATISFIED, /* isLegacy= */ false);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "verified", Status.UNSATISFIED, /* isLegacy= */ false);
 
     voteLabel(changeId, "code-review", 2);
 
     change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(2);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
-    assertSubmitRequirementStatus(change.submitRequirements, "verified", Status.UNSATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.SATISFIED, /* isLegacy= */ false);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "verified", Status.UNSATISFIED, /* isLegacy= */ false);
   }
 
   @Test
@@ -4163,7 +4174,8 @@
 
     ChangeInfo change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.NOT_APPLICABLE);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.NOT_APPLICABLE, /* isLegacy= */ false);
   }
 
   @Test
@@ -4192,13 +4204,15 @@
     String changeId = r.getChangeId();
     ChangeInfo change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.UNSATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.UNSATISFIED, /* isLegacy= */ false);
 
     voteLabel(changeId, "build-cop-override", 1);
 
     change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.OVERRIDDEN);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.OVERRIDDEN, /* isLegacy= */ false);
   }
 
   @Test
@@ -4227,17 +4241,20 @@
     String changeId = r.getChangeId();
     ChangeInfo change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.UNSATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.UNSATISFIED, /* isLegacy= */ false);
 
     voteLabel(changeId, "code-review", 1);
     change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.UNSATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.UNSATISFIED, /* isLegacy= */ false);
 
     voteLabel(changeId, "code-review", 2);
     change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.SATISFIED, /* isLegacy= */ false);
   }
 
   @Test
@@ -4255,12 +4272,14 @@
     String changeId = r.getChangeId();
     ChangeInfo change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.UNSATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.UNSATISFIED, /* isLegacy= */ false);
 
     voteLabel(changeId, "code-review", 1);
     change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.SATISFIED, /* isLegacy= */ false);
   }
 
   @Test
@@ -4290,47 +4309,56 @@
     String changeId = r.getChangeId();
     ChangeInfo change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.UNSATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.UNSATISFIED, /* isLegacy= */ false);
 
     voteLabel(changeId, "code-review", 1);
     change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
     // +1 was enough to fulfill the requirement: override in child project was ignored
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.SATISFIED, /* isLegacy= */ false);
   }
 
   @Test
   public void submitRequirement_storedForClosedChanges() throws Exception {
-    configSubmitRequirement(
-        project,
-        SubmitRequirement.builder()
-            .setName("code-review")
-            .setSubmittabilityExpression(SubmitRequirementExpression.create("label:code-review=+2"))
-            .setAllowOverrideInChildProjects(false)
-            .build());
+    for (SubmitType submitType : SubmitType.values()) {
+      Project.NameKey project = createProjectForPush(submitType);
+      TestRepository<InMemoryRepository> repo = cloneProject(project);
+      configSubmitRequirement(
+          project,
+          SubmitRequirement.builder()
+              .setName("code-review")
+              .setSubmittabilityExpression(
+                  SubmitRequirementExpression.create("label:code-review=+2"))
+              .setAllowOverrideInChildProjects(false)
+              .build());
 
-    PushOneCommit.Result r = createChange("Add a file", "foo", "content");
-    String changeId = r.getChangeId();
+      PushOneCommit.Result r =
+          createChange(repo, "master", "Add a file", "foo", "content", "topic");
+      String changeId = r.getChangeId();
 
-    voteLabel(changeId, "code-review", 2);
+      voteLabel(changeId, "code-review", 2);
 
-    ChangeInfo change = gApi.changes().id(changeId).get();
-    assertThat(change.submitRequirements).hasSize(1);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+      ChangeInfo change = gApi.changes().id(changeId).get();
+      assertThat(change.submitRequirements).hasSize(1);
+      assertSubmitRequirementStatus(
+          change.submitRequirements, "code-review", Status.SATISFIED, /* isLegacy= */ false);
 
-    RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
-    revision.review(ReviewInput.approve());
-    revision.submit();
+      RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
+      revision.review(ReviewInput.approve());
+      revision.submit();
 
-    ChangeNotes notes = notesFactory.create(project, r.getChange().getId());
+      ChangeNotes notes = notesFactory.create(project, r.getChange().getId());
 
-    SubmitRequirementResult result =
-        notes.getSubmitRequirementsResult().stream().collect(MoreCollectors.onlyElement());
-    assertThat(result.status()).isEqualTo(SubmitRequirementResult.Status.SATISFIED);
-    assertThat(result.submittabilityExpressionResult().status())
-        .isEqualTo(SubmitRequirementExpressionResult.Status.PASS);
-    assertThat(result.submittabilityExpressionResult().expression().expressionString())
-        .isEqualTo("label:code-review=+2");
+      SubmitRequirementResult result =
+          notes.getSubmitRequirementsResult().stream().collect(MoreCollectors.onlyElement());
+      assertThat(result.status()).isEqualTo(SubmitRequirementResult.Status.SATISFIED);
+      assertThat(result.submittabilityExpressionResult().status())
+          .isEqualTo(SubmitRequirementExpressionResult.Status.PASS);
+      assertThat(result.submittabilityExpressionResult().expression().expressionString())
+          .isEqualTo("label:code-review=+2");
+    }
   }
 
   @Test
@@ -4348,13 +4376,15 @@
 
     ChangeInfo change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.UNSATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.UNSATISFIED, /* isLegacy= */ false);
 
     voteLabel(changeId, "code-review", 2);
 
     change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.SATISFIED, /* isLegacy= */ false);
 
     gApi.changes().id(changeId).current().submit();
 
@@ -4370,7 +4400,64 @@
     // The new "verified" submit requirement is not returned, since this change is closed
     change = gApi.changes().id(changeId).get();
     assertThat(change.submitRequirements).hasSize(1);
-    assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "code-review", Status.SATISFIED, /* isLegacy= */ false);
+  }
+
+  @Test
+  @GerritConfig(
+      name = "experiments.enabled",
+      value =
+          ExperimentFeaturesConstants
+              .GERRIT_BACKEND_REQUEST_FEATURE_ENABLE_LEGACY_SUBMIT_REQUIREMENTS)
+  public void submitRequirements_returnForLegacySubmitRecords_ifEnabled() throws Exception {
+    configLabel("build-cop-override", LabelFunction.MAX_WITH_BLOCK);
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(
+            allowLabel("build-cop-override")
+                .ref("refs/heads/master")
+                .group(REGISTERED_USERS)
+                .range(-1, 1))
+        .update();
+
+    // 1. Project has two legacy requirements: Code-Review and bco. Both unsatisfied.
+    PushOneCommit.Result r = createChange();
+    String changeId = r.getChangeId();
+    ChangeInfo change = gApi.changes().id(changeId).get();
+    assertThat(change.submitRequirements).hasSize(2);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "Code-Review", Status.UNSATISFIED, /* isLegacy= */ true);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "build-cop-override", Status.UNSATISFIED, /* isLegacy= */ true);
+
+    // 2. Vote +1 on bco. bco becomes satisfied
+    voteLabel(changeId, "build-cop-override", 1);
+    change = gApi.changes().id(changeId).get();
+    assertThat(change.submitRequirements).hasSize(2);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "Code-Review", Status.UNSATISFIED, /* isLegacy= */ true);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "build-cop-override", Status.SATISFIED, /* isLegacy= */ true);
+
+    // 3. Vote +1 on Code-Review. Code-Review becomes satisfied
+    voteLabel(changeId, "Code-Review", 2);
+    change = gApi.changes().id(changeId).get();
+    assertThat(change.submitRequirements).hasSize(2);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "Code-Review", Status.SATISFIED, /* isLegacy= */ true);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "build-cop-override", Status.SATISFIED, /* isLegacy= */ true);
+
+    // 4. Merge the change. Submit requirements status is presented from NoteDb.
+    gApi.changes().id(changeId).current().submit();
+    change = gApi.changes().id(changeId).get();
+    assertThat(change.submitRequirements).hasSize(2);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "Code-Review", Status.SATISFIED, /* isLegacy= */ true);
+    assertSubmitRequirementStatus(
+        change.submitRequirements, "build-cop-override", Status.SATISFIED, /* isLegacy= */ true);
   }
 
   @Test
@@ -4397,7 +4484,10 @@
             .get();
     assertThat(changeInfos).hasSize(1);
     assertSubmitRequirementStatus(
-        changeInfos.get(0).submitRequirements, "code-review", Status.SATISFIED);
+        changeInfos.get(0).submitRequirements,
+        "code-review",
+        Status.SATISFIED,
+        /* isLegacy= */ false);
   }
 
   @Test
@@ -4425,7 +4515,10 @@
             .get();
     assertThat(changeInfos).hasSize(1);
     assertSubmitRequirementStatus(
-        changeInfos.get(0).submitRequirements, "code-review", Status.SATISFIED);
+        changeInfos.get(0).submitRequirements,
+        "code-review",
+        Status.SATISFIED,
+        /* isLegacy= */ false);
   }
 
   @Test
@@ -4874,132 +4967,6 @@
   }
 
   @Test
-  public void markAsReviewed() throws Exception {
-    com.google.gerrit.acceptance.TestAccount user2 = accountCreator.user2();
-
-    PushOneCommit.Result r = createChange();
-
-    ReviewerInput in = new ReviewerInput();
-    in.reviewer = user.email();
-    gApi.changes().id(r.getChangeId()).addReviewer(in);
-
-    requestScopeOperations.setApiUser(user.id());
-    assertThat(gApi.changes().id(r.getChangeId()).get().reviewed).isNull();
-    gApi.changes().id(r.getChangeId()).markAsReviewed(true);
-    assertThat(gApi.changes().id(r.getChangeId()).get().reviewed).isTrue();
-
-    requestScopeOperations.setApiUser(user2.id());
-    sender.clear();
-    amendChange(r.getChangeId());
-
-    requestScopeOperations.setApiUser(user.id());
-    assertThat(gApi.changes().id(r.getChangeId()).get().reviewed).isNull();
-
-    List<Message> messages = sender.getMessages();
-    assertThat(messages).hasSize(1);
-    assertThat(messages.get(0).rcpt()).containsExactly(user.getNameEmail());
-  }
-
-  @Test
-  public void cannotSetUnreviewedLabelForPatchSetThatAlreadyHasReviewedLabel() throws Exception {
-    String changeId = createChange().getChangeId();
-
-    requestScopeOperations.setApiUser(user.id());
-    gApi.changes().id(changeId).markAsReviewed(true);
-    assertThat(gApi.changes().id(changeId).get().reviewed).isTrue();
-
-    BadRequestException thrown =
-        assertThrows(
-            BadRequestException.class,
-            () ->
-                gApi.accounts()
-                    .self()
-                    .setStars(
-                        changeId,
-                        new StarsInput(
-                            ImmutableSet.of(StarredChangesUtil.UNREVIEWED_LABEL + "/1"))));
-    assertThat(thrown)
-        .hasMessageThat()
-        .contains(
-            "The labels "
-                + StarredChangesUtil.REVIEWED_LABEL
-                + "/"
-                + 1
-                + " and "
-                + StarredChangesUtil.UNREVIEWED_LABEL
-                + "/"
-                + 1
-                + " are mutually exclusive. Only one of them can be set.");
-  }
-
-  @Test
-  public void cannotSetReviewedLabelForPatchSetThatAlreadyHasUnreviewedLabel() throws Exception {
-    String changeId = createChange().getChangeId();
-
-    requestScopeOperations.setApiUser(user.id());
-    gApi.changes().id(changeId).markAsReviewed(false);
-    assertThat(gApi.changes().id(changeId).get().reviewed).isNull();
-
-    BadRequestException thrown =
-        assertThrows(
-            BadRequestException.class,
-            () ->
-                gApi.accounts()
-                    .self()
-                    .setStars(
-                        changeId,
-                        new StarsInput(ImmutableSet.of(StarredChangesUtil.REVIEWED_LABEL + "/1"))));
-    assertThat(thrown)
-        .hasMessageThat()
-        .contains(
-            "The labels "
-                + StarredChangesUtil.REVIEWED_LABEL
-                + "/"
-                + 1
-                + " and "
-                + StarredChangesUtil.UNREVIEWED_LABEL
-                + "/"
-                + 1
-                + " are mutually exclusive. Only one of them can be set.");
-  }
-
-  @Test
-  public void setReviewedAndUnreviewedLabelsForDifferentPatchSets() throws Exception {
-    String changeId = createChange().getChangeId();
-
-    requestScopeOperations.setApiUser(user.id());
-    gApi.changes().id(changeId).markAsReviewed(true);
-    assertThat(gApi.changes().id(changeId).get().reviewed).isTrue();
-
-    amendChange(changeId);
-    assertThat(gApi.changes().id(changeId).get().reviewed).isNull();
-
-    gApi.changes().id(changeId).markAsReviewed(false);
-    assertThat(gApi.changes().id(changeId).get().reviewed).isNull();
-
-    assertThat(gApi.accounts().self().getStars(changeId))
-        .containsExactly(
-            StarredChangesUtil.REVIEWED_LABEL + "/" + 1,
-            StarredChangesUtil.UNREVIEWED_LABEL + "/" + 2);
-  }
-
-  @Test
-  public void cannotSetInvalidLabel() throws Exception {
-    String changeId = createChange().getChangeId();
-
-    // label cannot contain whitespace
-    String invalidLabel = "invalid label";
-    BadRequestException thrown =
-        assertThrows(
-            BadRequestException.class,
-            () ->
-                gApi.accounts()
-                    .self()
-                    .setStars(changeId, new StarsInput(ImmutableSet.of(invalidLabel))));
-    assertThat(thrown).hasMessageThat().contains("invalid labels: " + invalidLabel);
-  }
-
-  @Test
   public void changeDetailsDoesNotRequireIndex() throws Exception {
     // This set of options must be kept in sync with gr-rest-api-interface.js
     Set<ListChangesOption> options =
@@ -5077,9 +5044,12 @@
   private void assertSubmitRequirementStatus(
       Collection<SubmitRequirementResultInfo> results,
       String requirementName,
-      SubmitRequirementResultInfo.Status status) {
+      SubmitRequirementResultInfo.Status status,
+      boolean isLegacy) {
     for (SubmitRequirementResultInfo result : results) {
-      if (result.name.equals(requirementName) && result.status == status) {
+      if (result.name.equals(requirementName)
+          && result.status == status
+          && result.isLegacy == isLegacy) {
         return;
       }
     }
@@ -5092,4 +5062,15 @@
                 .map(r -> String.format("%s=%s", r.name, r.status))
                 .collect(toImmutableList())));
   }
+
+  private Project.NameKey createProjectForPush(SubmitType submitType) throws Exception {
+    Project.NameKey project = projectOperations.newProject().submitType(submitType).create();
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(allow(Permission.PUSH).ref("refs/heads/*").group(adminGroupUuid()))
+        .add(allow(Permission.SUBMIT).ref("refs/for/refs/heads/*").group(adminGroupUuid()))
+        .update();
+    return project;
+  }
 }
diff --git a/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java b/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
index 0b3b27a..f966fc8 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
@@ -834,6 +834,74 @@
   }
 
   @Test
+  public void copyWithListOfFilesUnchanged_withoutCopyCondition() throws Exception {
+    try (ProjectConfigUpdate u = updateProject(project)) {
+      u.getConfig()
+          .updateLabelType(
+              LabelId.CODE_REVIEW, b -> b.setCopyAllScoresIfListOfFilesDidNotChange(true));
+      u.save();
+    }
+    copyWithListOfFilesUnchanged();
+  }
+
+  @Test
+  public void copyWithListOfFilesUnchanged_withCopyCondition() throws Exception {
+    try (ProjectConfigUpdate u = updateProject(project)) {
+      u.getConfig()
+          .updateLabelType(LabelId.CODE_REVIEW, b -> b.setCopyCondition("has:unchanged-files"));
+      u.save();
+    }
+    copyWithListOfFilesUnchanged();
+  }
+
+  private void copyWithListOfFilesUnchanged() throws Exception {
+    Change.Id changeId =
+        changeOperations.newChange().project(project).file("file").content("content").create();
+    vote(admin, changeId.toString(), 2, 1);
+    vote(user, changeId.toString(), -2, -1);
+
+    changeOperations.change(changeId).newPatchset().file("file").content("new content").create();
+    ChangeInfo c = detailedChange(changeId.toString());
+
+    // Code-Review votes are copied over from ps1-> ps2 since the list of files were unchanged.
+    assertVotes(c, admin, 2, 0);
+    assertVotes(c, user, -2, 0);
+
+    changeOperations
+        .change(changeId)
+        .newPatchset()
+        .file("file")
+        .content("very new content")
+        .create();
+    c = detailedChange(changeId.toString());
+
+    // Code-Review votes are copied over from ps1-> ps3 since the list of files were unchanged.
+    assertVotes(c, admin, 2, 0);
+    assertVotes(c, user, -2, 0);
+
+    changeOperations
+        .change(changeId)
+        .newPatchset()
+        .file("new file")
+        .content("new content")
+        .create();
+
+    c = detailedChange(changeId.toString());
+    // Code-Review votes are not copied over from ps1-> ps4 since a file was added.
+    assertVotes(c, admin, 0, 0);
+    assertVotes(c, user, 0, 0);
+
+    changeOperations.change(changeId).newPatchset().file("file").content("content").create();
+
+    c = detailedChange(changeId.toString());
+    // Code-Review votes are not copied over from ps1 -> ps5 since a file was added on ps4.
+    // Although the list of files is the same between ps4->ps5, we don't copy votes from before
+    // ps4.
+    assertVotes(c, admin, 0, 0);
+    assertVotes(c, user, 0, 0);
+  }
+
+  @Test
   public void deleteStickyVote() throws Exception {
     String label = LabelId.CODE_REVIEW;
     try (ProjectConfigUpdate u = updateProject(project)) {
diff --git a/javatests/com/google/gerrit/acceptance/api/change/SubmitRuleIT.java b/javatests/com/google/gerrit/acceptance/api/change/SubmitRuleIT.java
index bc9f50a5..636b71d 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/SubmitRuleIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/SubmitRuleIT.java
@@ -27,6 +27,7 @@
 import com.google.gerrit.server.project.SubmitRuleOptions;
 import com.google.inject.Inject;
 import java.util.List;
+import java.util.stream.Collectors;
 import org.junit.Test;
 
 public class SubmitRuleIT extends AbstractDaemonTest {
@@ -39,6 +40,11 @@
     PushOneCommit.Result r = createChange();
     approve(r.getChangeId());
     List<SubmitRecord> recordsBeforeSubmission = submitRuleEvaluator.evaluate(r.getChange());
+    assertThat(
+            recordsBeforeSubmission.stream()
+                .map(record -> record.ruleName)
+                .collect(Collectors.toList()))
+        .containsExactly("gerrit~DefaultSubmitRule");
     gApi.changes().id(r.getChangeId()).current().submit();
     // Add a new label that blocks submission if not granted. In case we reevaluate the rules,
     // this would show up as blocking submission.
@@ -57,6 +63,11 @@
     PushOneCommit.Result r = createChange();
     approve(r.getChangeId());
     List<SubmitRecord> recordsBeforeSubmission = submitRuleEvaluator.evaluate(r.getChange());
+    assertThat(
+            recordsBeforeSubmission.stream()
+                .map(record -> record.ruleName)
+                .collect(Collectors.toList()))
+        .containsExactly("gerrit~DefaultSubmitRule");
     gApi.changes().id(r.getChangeId()).current().submit();
     // Add a new label that blocks submission if not granted. In case we reevaluate the rules,
     // this would show up as blocking submission.
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
index bcd98d4..eadd259 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
@@ -93,8 +93,7 @@
   @Inject private ProjectOperations projectOperations;
 
   private boolean intraline;
-  private boolean useNewDiffCacheListFiles;
-  private boolean useNewDiffCacheGetDiff;
+  private boolean useNewDiffCache;
 
   private ObjectId initialCommit;
   private ObjectId commit1;
@@ -109,11 +108,9 @@
     baseConfig.setString("cache", "diff_intraline", "timeout", "1 minute");
 
     intraline = baseConfig.getBoolean(TEST_PARAMETER_MARKER, "intraline", false);
-    useNewDiffCacheListFiles =
+    useNewDiffCache =
         Arrays.asList(baseConfig.getStringList("experiments", null, "enabled"))
             .contains(FileInfoJsonExperimentImpl.NEW_DIFF_CACHE_FEATURE);
-    useNewDiffCacheGetDiff =
-        baseConfig.getBoolean("cache", "diff_cache", "runNewDiffCache_GetDiff", false);
 
     ObjectId headCommit = testRepo.getRepository().resolve("HEAD");
     initialCommit = headCommit;
@@ -1325,7 +1322,7 @@
   }
 
   @Test
-  public void addedUnrelatedFileIsIgnored_ForPatchSetDiffWithRebase() throws Exception {
+  public void addedUnrelatedFileIsIgnored_forPatchSetDiffWithRebase() throws Exception {
     ObjectId commit2 = addCommit(commit1, "file_added_in_another_commit.txt", "Some file content");
 
     rebaseChangeOn(changeId, commit2);
@@ -1337,7 +1334,7 @@
   }
 
   @Test
-  public void removedUnrelatedFileIsIgnored_ForPatchSetDiffWithRebase() throws Exception {
+  public void removedUnrelatedFileIsIgnored_forPatchSetDiffWithRebase() throws Exception {
     ObjectId commit2 = addCommitRemovingFiles(commit1, FILE_NAME2);
 
     rebaseChangeOn(changeId, commit2);
@@ -1349,7 +1346,7 @@
   }
 
   @Test
-  public void renamedUnrelatedFileIsIgnored_ForPatchSetDiffWithRebase() throws Exception {
+  public void renamedUnrelatedFileIsIgnored_forPatchSetDiffWithRebase() throws Exception {
     ObjectId commit2 = addCommitRenamingFile(commit1, FILE_NAME2, "a_new_file_name.txt");
 
     rebaseChangeOn(changeId, commit2);
@@ -1361,10 +1358,10 @@
   }
 
   @Test
-  public void renamedUnrelatedFileIsIgnored_ForPatchSetDiffWithRebase_WhenEquallyModifiedInBoth()
+  public void renamedUnrelatedFileIsIgnored_forPatchSetDiffWithRebase_whenEquallyModifiedInBoth()
       throws Exception {
     // TODO(ghareeb): fix this test for the new diff cache implementation
-    assume().that(useNewDiffCacheListFiles).isFalse();
+    assume().that(useNewDiffCache).isFalse();
 
     Function<String, String> contentModification =
         fileContent -> fileContent.replace("1st line\n", "First line\n");
@@ -1387,7 +1384,7 @@
   }
 
   @Test
-  public void renamedUnrelatedFileIsIgnored_ForPatchSetDiffWithRebase_WhenModifiedDuringRebase()
+  public void renamedUnrelatedFileIsIgnored_forPatchSetDiffWithRebase_whenModifiedDuringRebase()
       throws Exception {
     String renamedFilePath = "renamed_some_file.txt";
     ObjectId commit2 =
@@ -1457,7 +1454,7 @@
   @Test
   public void filesTouchedByPatchSetsAndContainingOnlyRebaseHunksAreIgnored() throws Exception {
     // TODO(ghareeb): fix this test for the new diff cache implementation
-    assume().that(useNewDiffCacheListFiles).isFalse();
+    assume().that(useNewDiffCache).isFalse();
 
     addModifiedPatchSet(
         changeId, FILE_NAME, fileContent -> fileContent.replace("Line 50\n", "Line fifty\n"));
@@ -2179,7 +2176,7 @@
   }
 
   @Test
-  public void rebaseHunkInRenamedFileIsIdentified_WhenFileIsRenamedDuringRebase() throws Exception {
+  public void rebaseHunkInRenamedFileIsIdentified_whenFileIsRenamedDuringRebase() throws Exception {
     String renamedFilePath = "renamed_some_file.txt";
     ObjectId commit2 =
         addCommit(commit1, FILE_NAME, FILE_CONTENT.replace("Line 1\n", "Line one\n"));
@@ -2208,7 +2205,7 @@
   }
 
   @Test
-  public void rebaseHunkInRenamedFileIsIdentified_WhenFileIsRenamedInPatchSets() throws Exception {
+  public void rebaseHunkInRenamedFileIsIdentified_whenFileIsRenamedInPatchSets() throws Exception {
     String renamedFilePath = "renamed_some_file.txt";
     gApi.changes().id(changeId).edit().renameFile(FILE_NAME, renamedFilePath);
     gApi.changes().id(changeId).edit().publish();
@@ -2247,7 +2244,7 @@
   }
 
   @Test
-  public void renamedFileWithOnlyRebaseHunksIsIdentified_WhenRenamedBetweenPatchSets()
+  public void renamedFileWithOnlyRebaseHunksIsIdentified_whenRenamedBetweenPatchSets()
       throws Exception {
     String newFilePath1 = "renamed_some_file.txt";
     gApi.changes().id(changeId).edit().renameFile(FILE_NAME, newFilePath1);
@@ -2282,7 +2279,7 @@
   }
 
   @Test
-  public void renamedFileWithOnlyRebaseHunksIsIdentified_WhenRenamedForRebaseAndForPatchSets()
+  public void renamedFileWithOnlyRebaseHunksIsIdentified_whenRenamedForRebaseAndForPatchSets()
       throws Exception {
     String newFilePath1 = "renamed_some_file.txt";
     gApi.changes().id(changeId).edit().renameFile(FILE_NAME, newFilePath1);
@@ -2837,7 +2834,7 @@
   }
 
   @Test
-  public void symlinkConvertedToRegularFileIsIdentifiedAsRewritten() throws Exception {
+  public void addDeleteByJgit_isIdentifiedAsRewritten() throws Exception {
     String target = "file.txt";
     String symlink = "link.lnk";
 
@@ -2846,58 +2843,74 @@
         pushFactory
             .create(admin.newIdent(), testRepo, "Commit Subject", target, "content")
             .addSymlink(symlink, target);
-
     PushOneCommit.Result result = push.to("refs/for/master");
     String initialRev = gApi.changes().id(result.getChangeId()).get().currentRevision;
+    String cId = result.getChangeId();
 
-    // Delete the symlink with patchset 2
-    gApi.changes().id(result.getChangeId()).edit().deleteFile(symlink);
-    gApi.changes().id(result.getChangeId()).edit().publish();
+    // Delete the symlink with PS2
+    gApi.changes().id(cId).edit().deleteFile(symlink);
+    gApi.changes().id(cId).edit().publish();
 
-    // Re-add the symlink as a regular file with patchset 3
-    gApi.changes()
-        .id(result.getChangeId())
-        .edit()
-        .modifyFile(symlink, RawInputUtil.create("Content of the new file named 'symlink'"));
-    gApi.changes().id(result.getChangeId()).edit().publish();
+    // Re-add the symlink as a regular file with PS3
+    gApi.changes().id(cId).edit().modifyFile(symlink, RawInputUtil.create("new content"));
+    gApi.changes().id(cId).edit().publish();
 
-    Map<String, FileInfo> changedFiles =
-        gApi.changes().id(result.getChangeId()).current().files(initialRev);
-
+    // Changed files: JGit returns two {DELETED/ADDED} entries for the file.
+    // The diff logic combines both into a single REWRITTEN entry.
+    Map<String, FileInfo> changedFiles = gApi.changes().id(cId).current().files(initialRev);
     assertThat(changedFiles.keySet()).containsExactly("/COMMIT_MSG", symlink);
-
-    // Both old and new diff caches agree that the state is rewritten
     assertThat(changedFiles.get(symlink).status).isEqualTo('W'); // Rewritten
 
-    DiffInfo diffInfo =
-        gApi.changes().id(result.getChangeId()).current().file(symlink).diff(initialRev);
+    // Detailed diff: Old diff cache returns ADDED entry. New Diff Cache returns REWRITE.
+    DiffInfo diffInfo = gApi.changes().id(cId).current().file(symlink).diff(initialRev);
+    assertThat(diffInfo.content).hasSize(1);
+    assertThat(diffInfo).content().element(0).linesOfB().containsExactly("new content");
+    // TODO(ghareeb): remove conditional assertion when new diff cache is fully rolled out.
+    assertThat(diffInfo.changeType)
+        .isEqualTo(useNewDiffCache ? ChangeType.REWRITE : ChangeType.ADDED);
+  }
 
-    // TODO(ghareeb): Remove the else branch when the new diff cache is rolled out as default.
-    if (useNewDiffCacheGetDiff) {
-      // File diff in New diff cache: change type is correctly identified as REWRITTEN
-      assertThat(diffInfo.changeType).isEqualTo(ChangeType.REWRITE);
-      assertThat(diffInfo.content).hasSize(2);
-      assertThat(diffInfo)
-          .content()
-          .element(0)
-          .linesOfB()
-          .containsExactly("Content of the new file named 'symlink'");
-      assertThat(diffInfo).content().element(1).linesOfA().containsExactly("file.txt");
-    } else {
-      // File diff in old diff cache: The diff logic identifies two entries for the file:
-      // 1. One entry as 'DELETED' for the symlink.
-      // 2. Another entry as 'ADDED' for the new regular file.
-      // Since the diff logic returns a single entry, the implementation prioritizes  the 'ADDED'
-      // entry in this case so that the user is able to see the new content that was added to the
-      // file.
-      assertThat(diffInfo.changeType).isEqualTo(ChangeType.ADDED);
-      assertThat(diffInfo.content).hasSize(1);
-      assertThat(diffInfo)
-          .content()
-          .element(0)
-          .linesOfB()
-          .containsExactly("Content of the new file named 'symlink'");
-    }
+  @Test
+  public void renameDeleteByJgit_isIdentifiedAsRewritten6() throws Exception {
+    String target = "file.txt";
+    String symlink = "link.lnk";
+    PushOneCommit push =
+        pushFactory
+            .create(admin.newIdent(), testRepo, "Commit Subject", target, "content")
+            .addSymlink(symlink, target);
+    PushOneCommit.Result result = push.to("refs/for/master");
+    String cId = result.getChangeId();
+    String initialRev = gApi.changes().id(cId).get().currentRevision;
+
+    // Delete both symlink and target with PS2
+    gApi.changes().id(cId).edit().deleteFile(symlink);
+    gApi.changes().id(cId).edit().deleteFile(target);
+    gApi.changes().id(cId).edit().publish();
+
+    // Re-create the symlink as a regular file with PS3
+    gApi.changes().id(cId).edit().modifyFile(symlink, RawInputUtil.create("content"));
+    gApi.changes().id(cId).edit().publish();
+
+    // Changed files: JGit returns two {DELETED/RENAMED} entries for the file.
+    // The diff logic combines both into a single REWRITTEN entry.
+    Map<String, FileInfo> changedFiles = gApi.changes().id(cId).current().files(initialRev);
+    assertThat(changedFiles.keySet()).containsExactly("/COMMIT_MSG", symlink);
+    assertThat(changedFiles.get(symlink).status).isEqualTo('W'); // Rewritten
+
+    // Detailed diff: Old diff cache returns RENAMED entry. New Diff Cache returns REWRITE.
+    DiffInfo diffInfo = gApi.changes().id(cId).current().file(symlink).diff(initialRev);
+    assertThat(diffInfo)
+        .diffHeader()
+        .containsExactly(
+            "diff --git a/file.txt b/link.lnk",
+            "similarity index 100%",
+            "rename from file.txt",
+            "rename to link.lnk");
+    assertThat(diffInfo.content).hasSize(1);
+    assertThat(diffInfo).content().element(0).commonLines().containsExactly("content");
+    // TODO(ghareeb): remove conditional assertion when new diff cache is fully rolled out.
+    assertThat(diffInfo.changeType)
+        .isEqualTo(useNewDiffCache ? ChangeType.REWRITE : ChangeType.RENAMED);
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionNewDiffCacheForSingleFileIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionNewDiffCacheForSingleFileIT.java
deleted file mode 100644
index 46954e98..0000000
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionNewDiffCacheForSingleFileIT.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.acceptance.api.revision;
-
-import com.google.gerrit.testing.ConfigSuite;
-import org.eclipse.jgit.lib.Config;
-
-/**
- * Runs the {@link RevisionDiffIT} tests with the new diff cache, enabled for the single file "Get
- * Diff" endpoint. This is temporary until the new diff cache is fully deployed. The new diff cache
- * will become the default in the future.
- */
-public class RevisionNewDiffCacheForSingleFileIT extends RevisionDiffIT {
-  @ConfigSuite.Default
-  public static Config newDiffCacheConfig() {
-    Config config = new Config();
-    config.setBoolean("cache", "diff_cache", "runNewDiffCache_GetDiff", true);
-    return config;
-  }
-}
diff --git a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
index a90cd56..0e0168e 100644
--- a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
+++ b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -281,7 +281,7 @@
   }
 
   @Test
-  public void rebaseEditWithConflictsRest_Conflict() throws Exception {
+  public void rebaseEditWithConflictsRest_conflict() throws Exception {
     PatchSet currentPatchSet = getCurrentPatchSet(changeId2);
     createEmptyEditFor(changeId2);
     gApi.changes().id(changeId2).edit().modifyFile(FILE_NAME, RawInputUtil.create(CONTENT_NEW));
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 5cf0403..2253202 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -1266,13 +1266,13 @@
   }
 
   @Test
-  public void pushForMasterWithApprovals_MissingLabel() throws Exception {
+  public void pushForMasterWithApprovals_missingLabel() throws Exception {
     PushOneCommit.Result r = pushTo("refs/for/master%l=Verify");
     r.assertErrorStatus("label \"Verify\" is not a configured label");
   }
 
   @Test
-  public void pushForMasterWithApprovals_ValueOutOfRange() throws Exception {
+  public void pushForMasterWithApprovals_valueOutOfRange() throws Exception {
     PushOneCommit.Result r = pushTo("refs/for/master%l=Code-Review-3");
     r.assertErrorStatus("label \"Code-Review\": -3 is not a valid value");
   }
diff --git a/javatests/com/google/gerrit/acceptance/pgm/ChangeExternalIdCaseSensitivityIT.java b/javatests/com/google/gerrit/acceptance/pgm/ChangeExternalIdCaseSensitivityIT.java
new file mode 100644
index 0000000..83df896
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/pgm/ChangeExternalIdCaseSensitivityIT.java
@@ -0,0 +1,239 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.pgm;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_EXTERNAL;
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GERRIT;
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GPGKEY;
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_MAILTO;
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_UUID;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.StandaloneSiteTest;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.account.externalids.DuplicateExternalIdKeyException;
+import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
+import com.google.gerrit.server.account.externalids.ExternalIdNotes;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.meta.MetaDataUpdate;
+import java.io.IOException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.junit.After;
+import org.junit.Test;
+
+@NoHttpd
+public class ChangeExternalIdCaseSensitivityIT extends StandaloneSiteTest {
+
+  private static final boolean CASE_SENSITIVE = false;
+  private static final boolean CASE_INSENSITIVE = true;
+
+  private ServerContext ctx;
+  private ExternalIdNotes extIdNotes;
+  private ExternalIdFactory extIdFactory;
+  private MetaDataUpdate md;
+  private FileBasedConfig config;
+
+  @After
+  public void cleanup() throws Exception {
+    if (ctx != null) {
+      ctx.close();
+    }
+  }
+
+  @Test
+  public void externalIdNoteNameIsMigratedToCaseInsensitive() throws Exception {
+    prepareExternalIdNotes(CASE_SENSITIVE);
+
+    ctx.close();
+    runChangeExternalIdCaseSensitivity();
+    ctx = startServer();
+    extIdNotes = getExternalIdNotes(ctx);
+
+    assertExternalIdNotes(CASE_INSENSITIVE);
+  }
+
+  @Test
+  public void externalIdNoteNameIsMigratedToCaseSensitive() throws Exception {
+    prepareExternalIdNotes(CASE_INSENSITIVE);
+
+    ctx.close();
+    runChangeExternalIdCaseSensitivity();
+    ctx = startServer();
+    extIdNotes = getExternalIdNotes(ctx);
+
+    assertExternalIdNotes(CASE_SENSITIVE);
+  }
+
+  @Test
+  public void migrationFailsWithDuplicates() throws Exception {
+    prepareExternalIdNotes(CASE_SENSITIVE);
+    extIdNotes.insert(extIdFactory.create(SCHEME_USERNAME, "JohnDoe", Account.id(1)));
+    extIdNotes.commit(md);
+
+    assertThat(extIdNotes.get(ExternalId.Key.parse("username:johndoe", false)).isPresent())
+        .isTrue();
+    assertThat(extIdNotes.get(ExternalId.Key.parse("username:JohnDoe", false)).isPresent())
+        .isTrue();
+
+    ctx.close();
+    assertThrows(DuplicateExternalIdKeyException.class, () -> runChangeExternalIdCaseSensitivity());
+    ctx = startServer();
+    extIdNotes = getExternalIdNotes(ctx);
+
+    assertExternalIdNotes(CASE_SENSITIVE);
+    assertThat(extIdNotes.get(ExternalId.Key.parse("username:johndoe", false)).isPresent())
+        .isTrue();
+    assertThat(extIdNotes.get(ExternalId.Key.parse("username:JohnDoe", false)).isPresent())
+        .isTrue();
+  }
+
+  @Test
+  public void userNameCaseInsensitiveOptionIsSwitched() throws Exception {
+    configureUserNameCaseInsensitive(CASE_SENSITIVE);
+    assertThat(config.getBoolean("auth", "userNameCaseInsensitive", false)).isFalse();
+    runChangeExternalIdCaseSensitivity();
+    config.load();
+    assertThat(config.getBoolean("auth", "userNameCaseInsensitive", false)).isTrue();
+    runChangeExternalIdCaseSensitivity();
+    config.load();
+    assertThat(config.getBoolean("auth", "userNameCaseInsensitive", false)).isFalse();
+  }
+
+  @Test
+  public void dryrunDoesNotPersistChanges() throws Exception {
+    prepareExternalIdNotes(CASE_SENSITIVE);
+    ctx.close();
+    runGerrit("ChangeExternalIdCaseSensitivity", "-d", sitePaths.site_path.toString(), "--dryrun");
+
+    config.load();
+    assertThat(config.getBoolean("auth", "userNameCaseInsensitive", false)).isFalse();
+
+    ctx = startServer();
+    assertExternalIdNotes(CASE_SENSITIVE);
+  }
+
+  private void prepareExternalIdNotes(boolean userNameCaseInsensitive) throws Exception {
+    configureUserNameCaseInsensitive(userNameCaseInsensitive);
+    initSite();
+    ctx = startServer();
+    extIdFactory = ctx.getInjector().getInstance(ExternalIdFactory.class);
+    Project.NameKey allUsers = ctx.getInjector().getInstance(AllUsersName.class);
+    extIdNotes = getExternalIdNotes(ctx, allUsers);
+    md = getMetaDataUpdate(ctx, allUsers);
+
+    extIdNotes.insert(extIdFactory.create(SCHEME_USERNAME, "johndoe", Account.id(0)));
+    extIdNotes.insert(extIdFactory.create(SCHEME_GERRIT, "johndoe", Account.id(0)));
+
+    extIdNotes.insert(extIdFactory.create(SCHEME_USERNAME, "JaneDoe", Account.id(1)));
+    extIdNotes.insert(extIdFactory.create(SCHEME_GERRIT, "JaneDoe", Account.id(1)));
+
+    extIdNotes.insert(extIdFactory.create(SCHEME_MAILTO, "Jane@Doe.com", Account.id(1)));
+    extIdNotes.insert(extIdFactory.create(SCHEME_UUID, "Abc123", Account.id(1)));
+    extIdNotes.insert(extIdFactory.create(SCHEME_GPGKEY, "Abc123", Account.id(1)));
+    extIdNotes.insert(extIdFactory.create(SCHEME_EXTERNAL, "saml/JaneDoe", Account.id(1)));
+    extIdNotes.commit(md);
+
+    assertExternalIdNotes(userNameCaseInsensitive);
+  }
+
+  private void assertExternalIdNotes(boolean userNameCaseInsensitive) throws Exception {
+    assertThat(
+            extIdNotes
+                .get(ExternalId.Key.parse("username:johndoe", userNameCaseInsensitive))
+                .isPresent())
+        .isTrue();
+    assertThat(
+            extIdNotes
+                .get(ExternalId.Key.parse("username:JaneDoe", !userNameCaseInsensitive))
+                .isPresent())
+        .isFalse();
+    assertThat(
+            extIdNotes
+                .get(ExternalId.Key.parse("username:JaneDoe", userNameCaseInsensitive))
+                .isPresent())
+        .isTrue();
+
+    assertThat(
+            extIdNotes
+                .get(ExternalId.Key.parse("gerrit:johndoe", userNameCaseInsensitive))
+                .isPresent())
+        .isTrue();
+    assertThat(
+            extIdNotes
+                .get(ExternalId.Key.parse("gerrit:JaneDoe", !userNameCaseInsensitive))
+                .isPresent())
+        .isFalse();
+    assertThat(
+            extIdNotes
+                .get(ExternalId.Key.parse("gerrit:JaneDoe", userNameCaseInsensitive))
+                .isPresent())
+        .isTrue();
+
+    assertThat(extIdNotes.get(ExternalId.Key.parse("mailto:Jane@Doe.com", false)).isPresent())
+        .isTrue();
+    assertThat(extIdNotes.get(ExternalId.Key.parse("uuid:Abc123", false)).isPresent()).isTrue();
+    assertThat(extIdNotes.get(ExternalId.Key.parse("gpgkey:Abc123", false)).isPresent()).isTrue();
+    assertThat(extIdNotes.get(ExternalId.Key.parse("external:saml/JaneDoe", false)).isPresent())
+        .isTrue();
+  }
+
+  private void configureUserNameCaseInsensitive(boolean userNameCaseInsensitive)
+      throws IOException, ConfigInvalidException {
+    config = new FileBasedConfig(baseConfig, sitePaths.gerrit_config.toFile(), FS.DETECTED);
+    config.load();
+    config.setBoolean("auth", null, "userNameCaseInsensitive", userNameCaseInsensitive);
+    config.save();
+    if (userNameCaseInsensitive) {
+      assertThat(config.getBoolean("auth", "userNameCaseInsensitive", false)).isTrue();
+    } else {
+      assertThat(config.getBoolean("auth", "userNameCaseInsensitive", false)).isFalse();
+    }
+  }
+
+  private void initSite() throws Exception {
+    runGerrit("init", "-d", sitePaths.site_path.toString(), "--show-stack-trace");
+  }
+
+  private void runChangeExternalIdCaseSensitivity() throws Exception {
+    runGerrit("ChangeExternalIdCaseSensitivity", "-d", sitePaths.site_path.toString(), "--batch");
+  }
+
+  private static ExternalIdNotes getExternalIdNotes(ServerContext ctx) throws Exception {
+    return getExternalIdNotes(ctx, ctx.getInjector().getInstance(AllUsersName.class));
+  }
+
+  private static ExternalIdNotes getExternalIdNotes(ServerContext ctx, Project.NameKey allUsers)
+      throws Exception {
+    GitRepositoryManager repoManager = ctx.getInjector().getInstance(GitRepositoryManager.class);
+    ExternalIdNotes.FactoryNoReindex extIdNotesFactory =
+        ctx.getInjector().getInstance(ExternalIdNotes.FactoryNoReindex.class);
+    return extIdNotesFactory.load(repoManager.openRepository(allUsers));
+  }
+
+  private static MetaDataUpdate getMetaDataUpdate(ServerContext ctx, Project.NameKey allUsers)
+      throws Exception {
+    MetaDataUpdate.Server metaDataUpdateFactory =
+        ctx.getInjector().getInstance(MetaDataUpdate.Server.class);
+    return metaDataUpdateFactory.create(allUsers);
+  }
+}
diff --git a/javatests/com/google/gerrit/acceptance/rest/CancellationIT.java b/javatests/com/google/gerrit/acceptance/rest/CancellationIT.java
index 1a96d82..9f294b3 100644
--- a/javatests/com/google/gerrit/acceptance/rest/CancellationIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/CancellationIT.java
@@ -153,7 +153,7 @@
             "/projects/" + name("new"), new BasicHeader(RestApiServlet.X_GERRIT_DEADLINE, "1ms"));
     assertThat(response.getStatusCode()).isEqualTo(SC_REQUEST_TIMEOUT);
     assertThat(response.getEntityContent())
-        .isEqualTo("Client Provided Deadline Exceeded\n\ntimeout=1ms");
+        .isEqualTo("Client Provided Deadline Exceeded\n\nclient.timeout=1ms");
   }
 
   @Test
@@ -206,7 +206,8 @@
   public void stricterDeadlineTakesPrecedence() throws Exception {
     RestResponse response = adminRestSession.putWithHeaders("/projects/" + name("new"));
     assertThat(response.getStatusCode()).isEqualTo(SC_REQUEST_TIMEOUT);
-    assertThat(response.getEntityContent()).isEqualTo("Server Deadline Exceeded\n\ntimeout=1ms");
+    assertThat(response.getEntityContent())
+        .isEqualTo("Server Deadline Exceeded\n\nfoo.timeout=1ms");
   }
 
   @Test
@@ -215,7 +216,8 @@
   public void abortIfServerDeadlineExceeded_requestType() throws Exception {
     RestResponse response = adminRestSession.putWithHeaders("/projects/" + name("new"));
     assertThat(response.getStatusCode()).isEqualTo(SC_REQUEST_TIMEOUT);
-    assertThat(response.getEntityContent()).isEqualTo("Server Deadline Exceeded\n\ntimeout=1ms");
+    assertThat(response.getEntityContent())
+        .isEqualTo("Server Deadline Exceeded\n\ndefault.timeout=1ms");
   }
 
   @Test
@@ -224,7 +226,8 @@
   public void abortIfServerDeadlineExceeded_requestUriPattern() throws Exception {
     RestResponse response = adminRestSession.putWithHeaders("/projects/" + name("new"));
     assertThat(response.getStatusCode()).isEqualTo(SC_REQUEST_TIMEOUT);
-    assertThat(response.getEntityContent()).isEqualTo("Server Deadline Exceeded\n\ntimeout=1ms");
+    assertThat(response.getEntityContent())
+        .isEqualTo("Server Deadline Exceeded\n\ndefault.timeout=1ms");
   }
 
   @Test
@@ -233,7 +236,8 @@
   public void abortIfServerDeadlineExceeded_projectPattern() throws Exception {
     RestResponse response = adminRestSession.putWithHeaders("/projects/" + name("new"));
     assertThat(response.getStatusCode()).isEqualTo(SC_REQUEST_TIMEOUT);
-    assertThat(response.getEntityContent()).isEqualTo("Server Deadline Exceeded\n\ntimeout=1ms");
+    assertThat(response.getEntityContent())
+        .isEqualTo("Server Deadline Exceeded\n\ndefault.timeout=1ms");
   }
 
   @Test
@@ -242,7 +246,8 @@
   public void abortIfServerDeadlineExceeded_account() throws Exception {
     RestResponse response = adminRestSession.putWithHeaders("/projects/" + name("new"));
     assertThat(response.getStatusCode()).isEqualTo(SC_REQUEST_TIMEOUT);
-    assertThat(response.getEntityContent()).isEqualTo("Server Deadline Exceeded\n\ntimeout=1ms");
+    assertThat(response.getEntityContent())
+        .isEqualTo("Server Deadline Exceeded\n\ndefault.timeout=1ms");
   }
 
   @Test
@@ -292,7 +297,8 @@
   public void nonAdvisoryDeadlineIsAppliedIfStricterAdvisoryDeadlineExists() throws Exception {
     RestResponse response = adminRestSession.putWithHeaders("/projects/" + name("new"));
     assertThat(response.getStatusCode()).isEqualTo(SC_REQUEST_TIMEOUT);
-    assertThat(response.getEntityContent()).isEqualTo("Server Deadline Exceeded\n\ntimeout=2ms");
+    assertThat(response.getEntityContent())
+        .isEqualTo("Server Deadline Exceeded\n\ndefault.timeout=2ms");
   }
 
   @Test
@@ -373,7 +379,7 @@
             try {
               Thread.sleep(1000);
             } catch (InterruptedException e) {
-              throw new RuntimeException("interrupted during sleep");
+              throw new RuntimeException("interrupted during sleep", e);
             }
           }
         };
@@ -382,7 +388,7 @@
       RestResponse response = adminRestSession.putWithHeaders("/projects/" + name("new"));
       assertThat(response.getStatusCode()).isEqualTo(SC_REQUEST_TIMEOUT);
       assertThat(response.getEntityContent())
-          .isEqualTo("Server Deadline Exceeded\n\ntimeout=500ms");
+          .isEqualTo("Server Deadline Exceeded\n\ndefault.timeout=500ms");
     }
     // verify that the exceeded deadline for the previous request, isn't applied to a new request
     RestResponse response = adminRestSession.putWithHeaders("/projects/" + name("new2"));
@@ -397,7 +403,7 @@
             "/projects/" + name("new"), new BasicHeader(RestApiServlet.X_GERRIT_DEADLINE, "2ms"));
     assertThat(response.getStatusCode()).isEqualTo(SC_REQUEST_TIMEOUT);
     assertThat(response.getEntityContent())
-        .isEqualTo("Client Provided Deadline Exceeded\n\ntimeout=2ms");
+        .isEqualTo("Client Provided Deadline Exceeded\n\nclient.timeout=2ms");
   }
 
   @Test
@@ -519,7 +525,7 @@
   public void abortPushIfServerDeadlineExceeded() throws Exception {
     PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
     PushOneCommit.Result r = push.to("refs/for/master");
-    r.assertErrorStatus("Server Deadline Exceeded (timeout=1ms)");
+    r.assertErrorStatus("Server Deadline Exceeded (default.timeout=1ms)");
   }
 
   @Test
@@ -530,7 +536,7 @@
   public void abortPushIfTimeoutExceeded() throws Exception {
     PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
     PushOneCommit.Result r = push.to("refs/for/master");
-    r.assertErrorStatus("Server Deadline Exceeded (timeout=1ms)");
+    r.assertErrorStatus("Server Deadline Exceeded (receive.timeout=1ms)");
   }
 
   @Test
@@ -550,7 +556,7 @@
   public void receiveTimeoutTakesPrecedence() throws Exception {
     PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
     PushOneCommit.Result r = push.to("refs/for/master");
-    r.assertErrorStatus("Server Deadline Exceeded (timeout=1ms)");
+    r.assertErrorStatus("Server Deadline Exceeded (receive.timeout=1ms)");
   }
 
   @Test
@@ -560,7 +566,7 @@
     PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
     push.setPushOptions(pushOptions);
     PushOneCommit.Result r = push.to("refs/for/master");
-    r.assertErrorStatus("Client Provided Deadline Exceeded (timeout=1ms)");
+    r.assertErrorStatus("Client Provided Deadline Exceeded (client.timeout=1ms)");
   }
 
   @Test
@@ -611,7 +617,7 @@
     PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
     push.setPushOptions(pushOptions);
     PushOneCommit.Result r = push.to("refs/for/master");
-    r.assertErrorStatus("Client Provided Deadline Exceeded (timeout=2ms)");
+    r.assertErrorStatus("Client Provided Deadline Exceeded (client.timeout=2ms)");
   }
 
   @Test
@@ -625,7 +631,7 @@
     PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
     push.setPushOptions(pushOptions);
     PushOneCommit.Result r = push.to("refs/for/master");
-    r.assertErrorStatus("Server Deadline Exceeded (timeout=1ms)");
+    r.assertErrorStatus("Server Deadline Exceeded (receive.timeout=1ms)");
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/acceptance/rest/RestApiServletIT.java b/javatests/com/google/gerrit/acceptance/rest/RestApiServletIT.java
index 219ef12..4efdbba 100644
--- a/javatests/com/google/gerrit/acceptance/rest/RestApiServletIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/RestApiServletIT.java
@@ -30,6 +30,7 @@
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.httpd.restapi.ParameterParser;
 import com.google.gerrit.httpd.restapi.RestApiServlet;
 import com.google.inject.Inject;
 import java.io.IOException;
@@ -83,6 +84,11 @@
   }
 
   @Test
+  public void experimentRequestParamIsReserved() throws Exception {
+    assertRestResponseWithParameters(SC_OK, ParameterParser.EXPERIMENT_PARAMETER, "exp1");
+  }
+
+  @Test
   public void xGerritUpdatedRefNotSetByDefault() throws Exception {
     Result change = createChange();
     String origin = adminRestSession.url();
@@ -415,6 +421,15 @@
     return change.getChange().notes().getRevision();
   }
 
+  private RestResponse assertRestResponseWithParameters(int status, String k, String v)
+      throws Exception {
+    RestResponse response =
+        adminRestSession.getWithHeaders(ANY_REST_API + "?" + k + "=" + v, ACCEPT_STAR_HEADER);
+    assertThat(response.getStatusCode()).isEqualTo(status);
+
+    return response;
+  }
+
   private RestResponse prettyJsonRestResponse(String ppArgument, int ppValue) throws Exception {
     RestResponse response =
         adminRestSession.getWithHeaders(
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/AccountAssert.java b/javatests/com/google/gerrit/acceptance/rest/account/AccountAssert.java
index 0b0f2ec..79e8ab0 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/AccountAssert.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/AccountAssert.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.acceptance.rest.account;
 
-import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.common.collect.Iterables;
@@ -39,8 +38,7 @@
     if (testAccount.tags().isEmpty()) {
       assertThat(accountInfo.tags).isNull();
     } else {
-      assertThat(accountInfo.tags.stream().map(Enum::name).collect(toImmutableList()))
-          .containsExactlyElementsIn(testAccount.tags());
+      assertThat(accountInfo.tags).containsExactlyElementsIn(testAccount.tags());
     }
   }
 
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/EmailIT.java b/javatests/com/google/gerrit/acceptance/rest/account/EmailIT.java
index 8feac20..d055875 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/EmailIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/EmailIT.java
@@ -40,6 +40,8 @@
 import com.google.gerrit.server.account.Emails;
 import com.google.gerrit.server.account.Realm;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.config.AuthConfig;
@@ -63,6 +65,8 @@
   @Inject private ExternalIds externalIds;
   @Inject private Provider<Emails> emails;
   @Inject private RequestScopeOperations requestScopeOperations;
+  @Inject private ExternalIdFactory externalIdFactory;
+  @Inject private ExternalIdKeyFactory externalIdKeyFactory;
 
   @Test
   public void addEmail() throws Exception {
@@ -138,7 +142,7 @@
             admin.id(),
             u ->
                 u.addExternalId(
-                    ExternalId.createWithEmail(
+                    externalIdFactory.createWithEmail(
                         ExternalId.SCHEME_EXTERNAL, "foo", admin.id(), email)));
     assertThat(gApi.accounts().self().get().email).isNotEqualTo(email);
 
@@ -182,7 +186,7 @@
   public void setPreferredEmailToEmailFromCustomRealmThatDoesntExistAsExternalId()
       throws Exception {
     String email = "foo@example.com";
-    ExternalId.Key mailtoExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_MAILTO, email);
+    ExternalId.Key mailtoExtIdKey = externalIdKeyFactory.create(ExternalId.SCHEME_MAILTO, email);
     assertThat(externalIds.get(mailtoExtIdKey)).isEmpty();
     assertThat(gApi.accounts().self().get().email).isNotEqualTo(email);
 
@@ -200,7 +204,7 @@
 
   @Test
   public void setPreferredEmailToEmailFromCustomRealmThatBelongsToOtherAccount() throws Exception {
-    ExternalId mailToExtId = ExternalId.createEmail(user.id(), user.email());
+    ExternalId mailToExtId = externalIdFactory.createEmail(user.id(), user.email());
     assertThat(externalIds.get(mailToExtId.key())).isPresent();
 
     Context oldCtx =
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java b/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
index 20b378b..cd123aa 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
@@ -21,6 +21,8 @@
 import static com.google.gerrit.acceptance.GitUtil.pushHead;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GERRIT;
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GPGKEY;
 import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_MAILTO;
 import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
 import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_UUID;
@@ -37,6 +39,8 @@
 import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.config.GerritConfig;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
 import com.google.gerrit.common.data.GlobalCapability;
@@ -53,7 +57,10 @@
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.server.ServerInitiated;
 import com.google.gerrit.server.account.AccountsUpdate;
+import com.google.gerrit.server.account.externalids.DuplicateExternalIdKeyException;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.account.externalids.ExternalIdNotes;
 import com.google.gerrit.server.account.externalids.ExternalIdReader;
 import com.google.gerrit.server.account.externalids.ExternalIds;
@@ -92,6 +99,8 @@
   @Inject private ExternalIdNotes.Factory externalIdNotesFactory;
   @Inject private ProjectOperations projectOperations;
   @Inject private RequestScopeOperations requestScopeOperations;
+  @Inject private ExternalIdKeyFactory externalIdKeyFactory;
+  @Inject private ExternalIdFactory externalIdFactory;
 
   @ConfigSuite.Default
   public static Config partialCacheReloadingEnabled() {
@@ -194,7 +203,7 @@
   }
 
   @Test
-  public void deleteExternalIdOfOtherUserUnderOwnAccount_UnprocessableEntity() throws Exception {
+  public void deleteExternalIdOfOtherUserUnderOwnAccount_unprocessableEntity() throws Exception {
     List<AccountExternalIdInfo> extIds = gApi.accounts().self().getExternalIds();
     requestScopeOperations.setApiUser(user.id());
     UnprocessableEntityException thrown =
@@ -249,19 +258,60 @@
     gApi.accounts()
         .self()
         .deleteExternalIds(
-            ImmutableList.of(ExternalId.Key.create(SCHEME_MAILTO, preferredEmail).get()));
+            ImmutableList.of(externalIdKeyFactory.create(SCHEME_MAILTO, preferredEmail).get()));
     assertThat(gApi.accounts().self().get().email).isNull();
   }
 
   @Test
-  public void deleteExternalIds_Conflict() throws Exception {
+  public void deleteExternalIdOfUsernameByNonAdminForbidden() throws Exception {
     List<String> toDelete = new ArrayList<>();
     String externalIdStr = "username:" + user.username();
     toDelete.add(externalIdStr);
-    RestResponse response = userRestSession.post("/accounts/self/external.ids:delete", toDelete);
-    response.assertConflict();
-    assertThat(response.getEntityContent())
-        .isEqualTo(String.format("External id %s cannot be deleted", externalIdStr));
+    RestResponse response =
+        userRestSession.post("/accounts/" + admin.id() + "/external.ids:delete", toDelete);
+    response.assertForbidden();
+  }
+
+  @Test
+  public void deleteExternalIdOfUsernameSelfForbidden() throws Exception {
+    List<String> toDelete = new ArrayList<>();
+    String externalIdStr = "username:" + admin.username();
+    toDelete.add(externalIdStr);
+    RestResponse response = adminRestSession.post("/accounts/self/external.ids:delete", toDelete);
+    response.assertForbidden();
+  }
+
+  @Test
+  public void deleteExternalIdOfUsernameByAdmin() throws Exception {
+    List<String> toDelete = new ArrayList<>();
+    String externalIdStr = "username:" + user.username();
+    toDelete.add(externalIdStr);
+    RestResponse response =
+        adminRestSession.post("/accounts/" + user.id() + "/external.ids:delete", toDelete);
+    response.assertNoContent();
+    List<AccountExternalIdInfo> results = gApi.accounts().id(user.id().get()).getExternalIds();
+    assertThat(results).hasSize(1);
+    assertThat(results.get(0).identity).isEqualTo("mailto:user1@example.com");
+  }
+
+  @Test
+  public void deleteExternalIdOfUsernameMaintainServer() throws Exception {
+    projectOperations
+        .allProjectsForUpdate()
+        .add(allowCapability(GlobalCapability.MAINTAIN_SERVER).group(REGISTERED_USERS))
+        .add(allowCapability(GlobalCapability.MODIFY_ACCOUNT).group(REGISTERED_USERS))
+        .update();
+
+    List<String> toDelete = new ArrayList<>();
+    TestAccount user2 = accountCreator.user2();
+    String externalIdStr = "username:" + user2.username();
+    toDelete.add(externalIdStr);
+    RestResponse response =
+        userRestSession.post("/accounts/" + user2.id() + "/external.ids:delete", toDelete);
+    response.assertNoContent();
+    List<AccountExternalIdInfo> results = gApi.accounts().id(user2.id().get()).getExternalIds();
+    assertThat(results).hasSize(1);
+    assertThat(results.get(0).identity).isEqualTo("mailto:user2@example.com");
   }
 
   @Test
@@ -525,12 +575,12 @@
 
     // create valid external IDs
     insertExtId(
-        ExternalId.createWithPassword(
-            ExternalId.Key.parse(nextId(scheme, i)),
+        externalIdFactory.createWithPassword(
+            externalIdKeyFactory.parse(nextId(scheme, i)),
             admin.id(),
             "admin.other@example.com",
             "secret-password"));
-    insertExtId(ExternalId.createEmail(admin.id(), "admin.other@example.com"));
+    insertExtId(externalIdFactory.createEmail(admin.id(), "admin.other@example.com"));
     insertExtId(createExternalIdWithOtherCaseEmail(nextId(scheme, i)));
   }
 
@@ -630,29 +680,30 @@
   }
 
   private ExternalId createExternalIdWithOtherCaseEmail(String externalId) {
-    return ExternalId.createWithPassword(
-        ExternalId.Key.parse(externalId),
+    return externalIdFactory.createWithPassword(
+        externalIdKeyFactory.parse(externalId),
         admin.id(),
         admin.email().toUpperCase(Locale.US),
         "password");
   }
 
   private ExternalId createExternalIdForNonExistingAccount(String externalId) {
-    return ExternalId.create(ExternalId.Key.parse(externalId), Account.id(1));
+    return externalIdFactory.create(externalIdKeyFactory.parse(externalId), Account.id(1));
   }
 
   private ExternalId createExternalIdWithInvalidEmail(String externalId) {
-    return ExternalId.createWithEmail(
-        ExternalId.Key.parse(externalId), admin.id(), "invalid-email");
+    return externalIdFactory.createWithEmail(
+        externalIdKeyFactory.parse(externalId), admin.id(), "invalid-email");
   }
 
   private ExternalId createExternalIdWithDuplicateEmail(String externalId) {
-    return ExternalId.createWithEmail(ExternalId.Key.parse(externalId), user.id(), admin.email());
+    return externalIdFactory.createWithEmail(
+        externalIdKeyFactory.parse(externalId), user.id(), admin.email());
   }
 
   private ExternalId createExternalIdWithBadPassword(String username) {
-    return ExternalId.create(
-        ExternalId.Key.create(SCHEME_USERNAME, username),
+    return externalIdFactory.create(
+        externalIdKeyFactory.create(SCHEME_USERNAME, username),
         admin.id(),
         null,
         "non-hashed-password-is-not-allowed");
@@ -664,14 +715,14 @@
 
   @Test
   public void readExternalIdWithAccountIdThatCanBeExpressedInKiB() throws Exception {
-    ExternalId.Key extIdKey = ExternalId.Key.parse("foo:bar");
+    ExternalId.Key extIdKey = externalIdKeyFactory.parse("foo:bar");
     Account.Id accountId = Account.id(1024 * 100);
     accountsUpdateProvider
         .get()
         .insert(
             "Create Account with Bad External ID",
             accountId,
-            u -> u.addExternalId(ExternalId.create(extIdKey, accountId)));
+            u -> u.addExternalId(externalIdFactory.create(extIdKey, accountId)));
     Optional<ExternalId> extId = externalIds.get(extIdKey);
     assertThat(extId.map(ExternalId::accountId)).hasValue(accountId);
   }
@@ -681,7 +732,7 @@
     Set<ExternalId> expectedExtIds = new HashSet<>(externalIds.byAccount(admin.id()));
     try (AutoCloseable ctx = createFailOnLoadContext()) {
       // insert external ID
-      ExternalId extId = ExternalId.create("foo", "bar", admin.id());
+      ExternalId extId = externalIdFactory.create("foo", "bar", admin.id());
       insertExtId(extId);
       expectedExtIds.add(extId);
       assertThat(externalIds.byAccount(admin.id())).containsExactlyElementsIn(expectedExtIds);
@@ -689,7 +740,7 @@
       // update external ID
       expectedExtIds.remove(extId);
       ExternalId extId2 =
-          ExternalId.createWithEmail("foo", "bar", admin.id(), "foo.bar@example.com");
+          externalIdFactory.createWithEmail("foo", "bar", admin.id(), "foo.bar@example.com");
       accountsUpdateProvider
           .get()
           .update("Update External ID", admin.id(), u -> u.updateExternalId(extId2));
@@ -711,7 +762,7 @@
 
     try (AutoCloseable ctx = createFailOnLoadContext()) {
       // update external ID branch so that external IDs need to be reloaded
-      insertExtIdBehindGerritsBack(ExternalId.create("foo", "bar", admin.id()));
+      insertExtIdBehindGerritsBack(externalIdFactory.create("foo", "bar", admin.id()));
 
       assertThrows(IOException.class, () -> externalIds.byAccount(admin.id()));
     }
@@ -723,7 +774,7 @@
 
     try (AutoCloseable ctx = createFailOnLoadContext()) {
       // update external ID branch so that external IDs need to be reloaded
-      insertExtIdBehindGerritsBack(ExternalId.create("foo", "bar", admin.id()));
+      insertExtIdBehindGerritsBack(externalIdFactory.create("foo", "bar", admin.id()));
 
       assertThrows(IOException.class, () -> externalIds.byEmail(admin.email()));
     }
@@ -732,7 +783,7 @@
   @Test
   public void byAccountUpdateExternalIdsBehindGerritsBack() throws Exception {
     Set<ExternalId> expectedExternalIds = new HashSet<>(externalIds.byAccount(admin.id()));
-    ExternalId newExtId = ExternalId.create("foo", "bar", admin.id());
+    ExternalId newExtId = externalIdFactory.create("foo", "bar", admin.id());
     insertExtIdBehindGerritsBack(newExtId);
     expectedExternalIds.add(newExtId);
     assertThat(externalIds.byAccount(admin.id())).containsExactlyElementsIn(expectedExternalIds);
@@ -740,10 +791,10 @@
 
   @Test
   public void unsetEmail() throws Exception {
-    ExternalId extId = ExternalId.createWithEmail("x", "1", user.id(), "x@example.com");
+    ExternalId extId = externalIdFactory.createWithEmail("x", "1", user.id(), "x@example.com");
     insertExtId(extId);
 
-    ExternalId extIdWithoutEmail = ExternalId.create("x", "1", user.id());
+    ExternalId extIdWithoutEmail = externalIdFactory.create("x", "1", user.id());
     try (Repository allUsersRepo = repoManager.openRepository(allUsers);
         MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
       ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
@@ -757,10 +808,11 @@
   @Test
   public void unsetHttpPassword() throws Exception {
     ExternalId extId =
-        ExternalId.createWithPassword(ExternalId.Key.create("y", "1"), user.id(), null, "secret");
+        externalIdFactory.createWithPassword(
+            externalIdKeyFactory.create("y", "1"), user.id(), null, "secret");
     insertExtId(extId);
 
-    ExternalId extIdWithoutPassword = ExternalId.create("y", "1", user.id());
+    ExternalId extIdWithoutPassword = externalIdFactory.create("y", "1", user.id());
     try (Repository allUsersRepo = repoManager.openRepository(allUsers);
         MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
       ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
@@ -771,6 +823,75 @@
     }
   }
 
+  @Test
+  @GerritConfig(name = "auth.userNameCaseInsensitive", value = "true")
+  public void createCaseInsensitiveExternalId_DuplicateKey() throws Exception {
+    try (Repository allUsersRepo = repoManager.openRepository(allUsers);
+        MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
+      ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
+      testCaseInsensitiveExternalIdKey(md, extIdNotes, SCHEME_USERNAME, "JohnDoe", Account.id(42));
+      assertThrows(
+          DuplicateExternalIdKeyException.class,
+          () ->
+              extIdNotes.insert(
+                  externalIdFactory.create(SCHEME_USERNAME, "johndoe", Account.id(23))));
+    }
+  }
+
+  @Test
+  @GerritConfig(name = "auth.userNameCaseInsensitive", value = "true")
+  public void createCaseInsensitiveExternalId_SchemeWithUsername() throws Exception {
+    try (Repository allUsersRepo = repoManager.openRepository(allUsers);
+        MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
+      ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
+
+      testCaseInsensitiveExternalIdKey(md, extIdNotes, SCHEME_USERNAME, "janedoe", Account.id(66));
+      testCaseInsensitiveExternalIdKey(md, extIdNotes, SCHEME_GERRIT, "JaneDoe", Account.id(66));
+    }
+  }
+
+  @Test
+  @GerritConfig(name = "auth.userNameCaseInsensitive", value = "true")
+  public void createCaseSensitiveExternalId_SchemeWithoutUsername() throws Exception {
+    try (Repository allUsersRepo = repoManager.openRepository(allUsers);
+        MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
+      ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
+
+      testCaseSensitiveExternalIdKey(md, extIdNotes, SCHEME_MAILTO, "Jane@doe.com", Account.id(66));
+      testCaseSensitiveExternalIdKey(md, extIdNotes, SCHEME_UUID, "1234ABCD", Account.id(66));
+      testCaseSensitiveExternalIdKey(md, extIdNotes, SCHEME_GPGKEY, "1234ABCD", Account.id(66));
+    }
+  }
+
+  private void testCaseSensitiveExternalIdKey(
+      MetaDataUpdate md, ExternalIdNotes extIdNotes, String scheme, String id, Account.Id accountId)
+      throws DuplicateExternalIdKeyException, IOException, ConfigInvalidException {
+    ExternalId extId = externalIdFactory.create(scheme, id, accountId);
+    extIdNotes.insert(extId);
+    extIdNotes.commit(md);
+    assertThat(extIdNotes.get(externalIdKeyFactory.create(scheme, id)).get().accountId().get())
+        .isEqualTo(accountId.get());
+    assertThat(extIdNotes.get(externalIdKeyFactory.create(scheme, id.toLowerCase())).isPresent())
+        .isFalse();
+  }
+
+  private void testCaseInsensitiveExternalIdKey(
+      MetaDataUpdate md, ExternalIdNotes extIdNotes, String scheme, String id, Account.Id accountId)
+      throws DuplicateExternalIdKeyException, IOException, ConfigInvalidException {
+    ExternalId extId = externalIdFactory.create(scheme, id, accountId);
+    extIdNotes.insert(extId);
+    extIdNotes.commit(md);
+    assertThat(extIdNotes.get(externalIdKeyFactory.create(scheme, id)).get().accountId().get())
+        .isEqualTo(accountId.get());
+    assertThat(
+            extIdNotes
+                .get(externalIdKeyFactory.create(scheme, id.toLowerCase()))
+                .get()
+                .accountId()
+                .get())
+        .isEqualTo(accountId.get());
+  }
+
   private boolean isPartialCacheReloadingEnabled() {
     return cfg.getBoolean("cache", "external_ids_map", "enablePartialReloads", true);
   }
@@ -796,7 +917,8 @@
   private void insertExtIdBehindGerritsBack(ExternalId extId) throws Exception {
     try (Repository repo = repoManager.openRepository(allUsers)) {
       // Inserting an external ID "behind Gerrit's back" means that the caches are not updated.
-      ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(allUsers, repo);
+      ExternalIdNotes extIdNotes =
+          ExternalIdNotes.loadNoCacheUpdate(allUsers, repo, externalIdFactory);
       extIdNotes.insert(extId);
       try (MetaDataUpdate metaDataUpdate =
           new MetaDataUpdate(GitReferenceUpdated.DISABLED, null, repo)) {
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/GetAccountDetailIT.java b/javatests/com/google/gerrit/acceptance/rest/account/GetAccountDetailIT.java
index 84f95f7..a1e9bf1 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/GetAccountDetailIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/GetAccountDetailIT.java
@@ -17,22 +17,42 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.acceptance.rest.account.AccountAssert.assertAccountInfo;
 
+import com.google.common.collect.ImmutableList;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
 import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.extensions.annotations.Exports;
 import com.google.gerrit.extensions.common.AccountDetailInfo;
 import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.server.account.AccountTagProvider;
 import com.google.gerrit.server.account.ServiceUserClassifier;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.inject.Inject;
+import com.google.inject.Module;
+import java.util.List;
 import org.junit.Test;
 
 public class GetAccountDetailIT extends AbstractDaemonTest {
   @Inject private GroupOperations groupOperations;
   @Inject private AccountOperations accountOperations;
 
+  @Override
+  public Module createModule() {
+    return new FactoryModule() {
+      @Override
+      public void configure() {
+        bind(AccountTagProvider.class)
+            .annotatedWith(Exports.named("CustomAccountTagProvider"))
+            .to(CustomAccountTagProvider.class);
+      }
+    };
+  }
+
   @Test
   public void getDetail() throws Exception {
     RestResponse r = adminRestSession.get("/accounts/" + admin.username() + "/detail/");
@@ -56,7 +76,14 @@
         .update();
     RestResponse r = adminRestSession.get("/accounts/" + serviceUser.get() + "/detail/");
     AccountDetailInfo info = newGson().fromJson(r.getReader(), AccountDetailInfo.class);
-    assertThat(info.tags).containsExactly(AccountInfo.Tag.SERVICE_USER);
+    assertThat(info.tags).containsExactly(AccountInfo.Tags.SERVICE_USER);
+  }
+
+  @Test
+  public void getDetailForExtensionPointAccountTag() throws Exception {
+    RestResponse r = userRestSession.get("/accounts/" + user.username() + "/detail/");
+    AccountDetailInfo info = newGson().fromJson(r.getReader(), AccountDetailInfo.class);
+    assertThat(info.tags).containsExactly("BASIC_USER");
   }
 
   @Test
@@ -75,4 +102,25 @@
     AccountDetailInfo info = newGson().fromJson(r.getReader(), AccountDetailInfo.class);
     assertThat(info._accountId).isEqualTo(id.get());
   }
+
+  private static class CustomAccountTagProvider implements AccountTagProvider {
+    private PermissionBackend permissions;
+
+    @Inject
+    public CustomAccountTagProvider(PermissionBackend permissions) {
+      this.permissions = permissions;
+    }
+
+    @Override
+    public List<String> getTags(Account.Id id) {
+      try {
+        if (!permissions.currentUser().test(GlobalPermission.ADMINISTRATE_SERVER)) {
+          return ImmutableList.of("BASIC_USER");
+        }
+      } catch (Exception e) {
+        throw new IllegalStateException("can't check admin permissions", e);
+      }
+      return ImmutableList.of();
+    }
+  }
 }
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/PutUsernameIT.java b/javatests/com/google/gerrit/acceptance/rest/account/PutUsernameIT.java
index e05d0db..f46cf0c 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/PutUsernameIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/PutUsernameIT.java
@@ -18,6 +18,7 @@
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.config.GerritConfig;
 import com.google.gerrit.extensions.api.accounts.UsernameInput;
 import org.junit.Test;
 
@@ -42,6 +43,16 @@
   }
 
   @Test
+  @GerritConfig(name = "auth.userNameCaseInsensitive", value = "true")
+  public void setExistingCaseInsensitive_Conflict() throws Exception {
+    UsernameInput in = new UsernameInput();
+    in.username = admin.username().toUpperCase();
+    adminRestSession
+        .put("/accounts/" + accountCreator.create().id().get() + "/username", in)
+        .assertConflict();
+  }
+
+  @Test
   public void setNew_MethodNotAllowed() throws Exception {
     UsernameInput in = new UsernameInput();
     in.username = "newUsername";
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/AccountsRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/AccountsRestApiBindingsIT.java
index eb125a0..13353bd 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/AccountsRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/AccountsRestApiBindingsIT.java
@@ -29,7 +29,7 @@
 import com.google.gerrit.gpg.testing.TestKey;
 import com.google.gerrit.server.ServerInitiated;
 import com.google.gerrit.server.account.AccountsUpdate;
-import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import org.junit.Test;
@@ -43,6 +43,7 @@
 public class AccountsRestApiBindingsIT extends AbstractDaemonTest {
   @Inject private @ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider;
   @Inject private RequestScopeOperations requestScopeOperations;
+  @Inject private ExternalIdFactory externalIdFactory;
 
   /**
    * Account REST endpoints to be tested, each URL contains a placeholder for the account
@@ -86,7 +87,6 @@
           RestCall.get("/accounts/%s/preferences.edit"),
           RestCall.put("/accounts/%s/preferences.edit"),
           RestCall.get("/accounts/%s/starred.changes"),
-          RestCall.get("/accounts/%s/stars.changes"),
           RestCall.post("/accounts/%s/index"),
           RestCall.get("/accounts/%s/agreements"),
           RestCall.put("/accounts/%s/agreements"),
@@ -141,9 +141,7 @@
   private static final ImmutableList<RestCall> STAR_ENDPOINTS =
       ImmutableList.of(
           RestCall.put("/accounts/%s/starred.changes/%s"),
-          RestCall.delete("/accounts/%s/starred.changes/%s"),
-          RestCall.get("/accounts/%s/stars.changes/%s"),
-          RestCall.post("/accounts/%s/stars.changes/%s"));
+          RestCall.delete("/accounts/%s/starred.changes/%s"));
 
   @Test
   public void accountEndpoints() throws Exception {
@@ -169,7 +167,7 @@
             admin.id(),
             u ->
                 u.addExternalId(
-                    ExternalId.createWithEmail(name("test"), email, admin.id(), email)));
+                    externalIdFactory.createWithEmail(name("test"), email, admin.id(), email)));
 
     requestScopeOperations.setApiUser(admin.id());
     gApi.accounts()
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
index 68b24ce..79484ca 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
@@ -76,8 +76,6 @@
           RestCall.post("/changes/%s/ready"),
           RestCall.put("/changes/%s/ignore"),
           RestCall.put("/changes/%s/unignore"),
-          RestCall.put("/changes/%s/reviewed"),
-          RestCall.put("/changes/%s/unreviewed"),
           RestCall.get("/changes/%s/messages"),
           RestCall.put("/changes/%s/message"),
           RestCall.post("/changes/%s/merge"),
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
index bcca8bb..0a11b15 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
@@ -64,7 +64,6 @@
 import com.google.gerrit.server.restapi.change.GetAttentionSet;
 import com.google.gerrit.server.util.AccountTemplateUtil;
 import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.FakeEmailSender;
 import com.google.gerrit.testing.TestCommentHelper;
 import com.google.gerrit.truth.NullAwareCorrespondence;
 import com.google.inject.Inject;
@@ -89,7 +88,6 @@
   @Inject private AccountOperations accountOperations;
   @Inject private RequestScopeOperations requestScopeOperations;
 
-  @Inject private FakeEmailSender email;
   @Inject private TestCommentHelper testCommentHelper;
   @Inject private Provider<InternalChangeQuery> changeQueryProvider;
   @Inject private ProjectOperations projectOperations;
@@ -145,7 +143,7 @@
     assertThat(r.getChange().attentionSet()).containsExactly(expectedAttentionSetUpdate);
 
     // Only one email since the second add was ignored.
-    String emailBody = Iterables.getOnlyElement(email.getMessages()).body();
+    String emailBody = Iterables.getOnlyElement(sender.getMessages()).body();
     assertThat(emailBody)
         .contains(
             String.format(
@@ -218,7 +216,6 @@
         ReviewInput.create().addUserToAttentionSet(user.email(), manualReason);
 
     change(r).current().review(reviewInput);
-
     AttentionSetInfo attentionSetInfo = change(r).get().attentionSet.get(user.id().get());
     assertThat(attentionSetInfo.reason).isEqualTo(manualReason);
     assertThat(attentionSetInfo.reasonAccount).isEqualTo(getAccountInfo(user.id()));
@@ -270,7 +267,7 @@
     assertThat(r.getChange().attentionSet()).containsExactly(expectedAttentionSetUpdate);
 
     // Only one email since the second remove was ignored.
-    String emailBody = Iterables.getOnlyElement(email.getMessages()).body();
+    String emailBody = Iterables.getOnlyElement(sender.getMessages()).body();
     assertThat(emailBody)
         .contains(
             user.fullName()
@@ -706,7 +703,7 @@
     assertThat(attentionSet).hasReasonThat().isEqualTo("reason");
 
     // No emails for adding to attention set were sent.
-    assertThat(email.getMessages()).isEmpty();
+    assertThat(sender.getMessages()).isEmpty();
   }
 
   @Test
@@ -715,7 +712,7 @@
     // implictly adds the user to the attention set when adding as reviewer
     change(r).addReviewer(user.email());
     requestScopeOperations.setApiUser(user.id());
-    email.clear();
+    sender.clear();
 
     ReviewInput reviewInput =
         ReviewInput.create().removeUserFromAttentionSet(user.email(), "reason");
@@ -728,7 +725,7 @@
     assertThat(attentionSet).hasReasonThat().isEqualTo("reason");
 
     // No emails for removing from attention set were sent.
-    assertThat(email.getMessages()).isEmpty();
+    assertThat(sender.getMessages()).isEmpty();
   }
 
   @Test
@@ -1537,6 +1534,45 @@
   }
 
   @Test
+  public void addToAttentionSetEmail_withTemplateReason() throws Exception {
+    PushOneCommit.Result r = createChange();
+    requestScopeOperations.setApiUser(user.id());
+    String templateReason = "Added by " + AccountTemplateUtil.getAccountTemplate(user.id());
+    int accountId =
+        change(r)
+            .addToAttentionSet(new AttentionSetInput(admin.email(), templateReason))
+            ._accountId;
+
+    assertThat(accountId).isEqualTo(admin.id().get());
+    String emailBody = Iterables.getOnlyElement(sender.getMessages()).body();
+    assertThat(emailBody)
+        .contains(
+            String.format(
+                "%s requires the attention of %s to this change.\n The reason is: Added by %s.",
+                user.fullName(), admin.fullName(), user.getNameEmail()));
+  }
+
+  @Test
+  public void removeFromAttentionSetEmail_withTemplateReason() throws Exception {
+    PushOneCommit.Result r = createChange();
+    // implicitly adds the user to the attention set when adding as reviewer
+    change(r).addReviewer(user.email());
+    sender.clear();
+    requestScopeOperations.setApiUser(user.id());
+
+    String templateReason = "Removed by " + AccountTemplateUtil.getAccountTemplate(user.id());
+    change(r).attention(user.id().toString()).remove(new AttentionSetInput(templateReason));
+
+    String emailBody = Iterables.getOnlyElement(sender.getMessages()).body();
+    assertThat(emailBody)
+        .contains(
+            String.format(
+                "%s removed themselves from the attention set of this change.\n"
+                    + " The reason is: Removed by %s.",
+                user.fullName(), user.getNameEmail()));
+  }
+
+  @Test
   public void attentionSetEmailFooter() throws Exception {
     PushOneCommit.Result r = createChange();
 
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/CorsIT.java b/javatests/com/google/gerrit/acceptance/rest/change/CorsIT.java
index 61e5a2e..ae872b2b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/CorsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/CorsIT.java
@@ -27,6 +27,7 @@
 import static com.google.common.net.HttpHeaders.VARY;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.TruthJUnit.assume;
 
 import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableList;
@@ -41,6 +42,7 @@
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.server.UrlEncoded;
 import com.google.gerrit.testing.ConfigSuite;
+import java.lang.reflect.Method;
 import java.nio.charset.StandardCharsets;
 import java.util.Locale;
 import java.util.stream.Stream;
@@ -206,6 +208,10 @@
 
   @Test
   public void crossDomainPutTopic() throws Exception {
+    // Setting cookies with HttpOnly requires Servlet API 3+ which not all deployments might have
+    // available.
+    assume().that(cookieHasSetHttpOnlyMethod()).isTrue();
+
     Result change = createChange();
     BasicCookieStore cookies = new BasicCookieStore();
     Executor http = Executor.newInstance().use(cookies);
@@ -327,4 +333,14 @@
       assertWithMessage(ACCESS_CONTROL_ALLOW_HEADERS).that(allowHeaders).isNull();
     }
   }
+
+  private static boolean cookieHasSetHttpOnlyMethod() {
+    Method setHttpOnly = null;
+    try {
+      setHttpOnly = Cookie.class.getMethod("setHttpOnly", boolean.class);
+    } catch (NoSuchMethodException | SecurityException e) {
+      return false;
+    }
+    return setHttpOnly != null;
+  }
 }
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
index ad06226..92a4028 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
@@ -54,6 +54,7 @@
 import com.google.gerrit.extensions.api.projects.BranchInput;
 import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
+import com.google.gerrit.extensions.client.ReviewerState;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.ChangeInput;
 import com.google.gerrit.extensions.common.ChangeMessageInfo;
@@ -420,6 +421,69 @@
   }
 
   @Test
+  public void createAuthorAddedAsCcAndNotified() throws Exception {
+    ChangeInput input = newChangeInput(ChangeStatus.NEW);
+    input.author = new AccountInput();
+    input.author.email = user.email();
+    input.author.name = user.fullName();
+
+    ChangeInfo info = assertCreateSucceeds(input);
+    assertThat(info.reviewers.get(ReviewerState.CC)).hasSize(1);
+    assertThat(Iterables.getOnlyElement(info.reviewers.get(ReviewerState.CC)).email)
+        .isEqualTo(user.email());
+    assertThat(
+            Iterables.getOnlyElement(Iterables.getOnlyElement(sender.getMessages()).rcpt()).email())
+        .isEqualTo(user.email());
+  }
+
+  @Test
+  public void createAuthorAddedAsCcNotNotifiedWithNotifyNone() throws Exception {
+    ChangeInput input = newChangeInput(ChangeStatus.NEW);
+    input.author = new AccountInput();
+    input.author.email = user.email();
+    input.author.name = user.fullName();
+    input.notify = NotifyHandling.NONE;
+
+    ChangeInfo info = assertCreateSucceeds(input);
+    assertThat(info.reviewers.get(ReviewerState.CC)).hasSize(1);
+    assertThat(Iterables.getOnlyElement(info.reviewers.get(ReviewerState.CC)).email)
+        .isEqualTo(user.email());
+    assertThat(sender.getMessages()).isEmpty();
+  }
+
+  @Test
+  public void createWithMergeConflictAuthorAddedAsCcNotNotifiedWithNotifyNone() throws Exception {
+    String fileName = "shared.txt";
+    String sourceBranch = "sourceBranch";
+    String sourceSubject = "source change";
+    String sourceContent = "source content";
+    String targetBranch = "targetBranch";
+    String targetSubject = "target change";
+    String targetContent = "target content";
+    changeInTwoBranches(
+        sourceBranch,
+        sourceSubject,
+        fileName,
+        sourceContent,
+        targetBranch,
+        targetSubject,
+        fileName,
+        targetContent);
+    ChangeInput input = newMergeChangeInput(targetBranch, sourceBranch, "", true);
+    input.workInProgress = true;
+    input.author = new AccountInput();
+    input.author.email = user.email();
+    input.author.name = user.fullName();
+    input.notify = NotifyHandling.NONE;
+    ChangeInfo info = assertCreateSucceeds(input);
+
+    assertThat(info.reviewers.get(ReviewerState.CC)).hasSize(1);
+    assertThat(Iterables.getOnlyElement(info.reviewers.get(ReviewerState.CC)).email)
+        .isEqualTo(user.email());
+    assertThat(sender.getMessages()).isEmpty();
+  }
+
+  @Test
   public void createNewWorkInProgressChange() throws Exception {
     ChangeInput input = newChangeInput(ChangeStatus.NEW);
     input.workInProgress = true;
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java b/javatests/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java
index daeb032..ef5e7dc 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java
@@ -52,14 +52,14 @@
   }
 
   @Test
-  public void flushAll_Forbidden() throws Exception {
+  public void flushAll_forbidden() throws Exception {
     userRestSession
         .post("/config/server/caches/", new PostCaches.Input(FLUSH_ALL))
         .assertForbidden();
   }
 
   @Test
-  public void flushAll_BadRequest() throws Exception {
+  public void flushAll_badRequest() throws Exception {
     adminRestSession
         .post("/config/server/caches/", new PostCaches.Input(FLUSH_ALL, Arrays.asList("projects")))
         .assertBadRequest();
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java b/javatests/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
index 02db412..5f60250 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
@@ -102,12 +102,12 @@
   }
 
   @Test
-  public void createProjectHttpWhenProjectAlreadyExists_Conflict() throws Exception {
+  public void createProjectHttpWhenProjectAlreadyExists_conflict() throws Exception {
     adminRestSession.put("/projects/" + allProjects.get()).assertConflict();
   }
 
   @Test
-  public void createProjectHttpWhenProjectAlreadyExists_PreconditionFailed() throws Exception {
+  public void createProjectHttpWhenProjectAlreadyExists_preconditionFailed() throws Exception {
     adminRestSession
         .putWithHeaders(
             "/projects/" + allProjects.get(), new BasicHeader(HttpHeaders.IF_NONE_MATCH, "*"))
@@ -140,7 +140,7 @@
 
   @Test
   @UseLocalDisk
-  public void createProjectHttpWithUnreasonableName_BadRequest() throws Exception {
+  public void createProjectHttpWithUnreasonableName_badRequest() throws Exception {
     ImmutableList<String> forbiddenStrings =
         ImmutableList.of(
             "/../", "/./", "//", ".git/", "?", "%", "*", ":", "<", ">", "|", "$", "/+", "~");
@@ -153,14 +153,14 @@
   }
 
   @Test
-  public void createProjectHttpWithNameMismatch_BadRequest() throws Exception {
+  public void createProjectHttpWithNameMismatch_badRequest() throws Exception {
     ProjectInput in = new ProjectInput();
     in.name = name("otherName");
     adminRestSession.put("/projects/" + name("someName"), in).assertBadRequest();
   }
 
   @Test
-  public void createProjectHttpWithInvalidRefName_BadRequest() throws Exception {
+  public void createProjectHttpWithInvalidRefName_badRequest() throws Exception {
     ProjectInput in = new ProjectInput();
     in.branches = Collections.singletonList(name("invalid ref name"));
     adminRestSession.put("/projects/" + name("newProject"), in).assertBadRequest();
diff --git a/javatests/com/google/gerrit/acceptance/server/account/AccountResolverIT.java b/javatests/com/google/gerrit/acceptance/server/account/AccountResolverIT.java
index c7beb2d..0cfa0f8 100644
--- a/javatests/com/google/gerrit/acceptance/server/account/AccountResolverIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/account/AccountResolverIT.java
@@ -23,6 +23,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.config.GerritConfig;
 import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
 import com.google.gerrit.acceptance.testsuite.account.TestAccount;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
@@ -160,6 +161,22 @@
   }
 
   @Test
+  @GerritConfig(name = "auth.userNameCaseInsensitive", value = "true")
+  public void byUsernameCaseInsensitive() throws Exception {
+    String existingUsername = "myusername";
+    Account.Id idWithUsername = accountOperations.newAccount().username(existingUsername).create();
+
+    String existingMixedCaseUsername = "MyMixedCaseUsername";
+    Account.Id idWithMixedCaseUsername =
+        accountOperations.newAccount().username(existingMixedCaseUsername).create();
+
+    assertThat(resolve(existingUsername)).containsExactly(idWithUsername);
+    assertThat(resolve(existingMixedCaseUsername)).containsExactly(idWithMixedCaseUsername);
+    assertThat(resolve(existingMixedCaseUsername.toLowerCase()))
+        .containsExactly(idWithMixedCaseUsername);
+  }
+
+  @Test
   public void byNameAndEmail() throws Exception {
     String email = name("user@example.com");
     Account.Id idWithEmail = accountOperations.newAccount().preferredEmail(email).create();
diff --git a/javatests/com/google/gerrit/acceptance/server/change/CommentContextIT.java b/javatests/com/google/gerrit/acceptance/server/change/CommentContextIT.java
index 81cb7159..a2765d9 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/CommentContextIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/CommentContextIT.java
@@ -425,7 +425,7 @@
   }
 
   @Test
-  public void commentContextReturnsCorrectContentType_Java() throws Exception {
+  public void commentContextReturnsCorrectContentType_java() throws Exception {
     String javaContent =
         "public class Main {\n"
             + " public static void main(String[]args){\n"
@@ -448,7 +448,7 @@
   }
 
   @Test
-  public void commentContextReturnsCorrectContentType_Cpp() throws Exception {
+  public void commentContextReturnsCorrectContentType_cpp() throws Exception {
     String cppContent =
         "#include <iostream>\n"
             + "\n"
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java b/javatests/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java
index d767f48..cb1a679 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java
@@ -93,8 +93,9 @@
    * @param f1 Comment on file one.
    * @return A string with all inline comments and the original quoted email.
    */
-  static String newPlaintextBody(String changeURL, String changeMessage, String c1, String f1) {
-    return (changeMessage == null ? "" : changeMessage + "\n")
+  static String newPlaintextBody(
+      String changeURL, String patchsetLevelComment, String c1, String f1) {
+    return (patchsetLevelComment == null ? "" : patchsetLevelComment + "\n")
         + "> Foo Bar has posted comments on this change. (  \n"
         + "> "
         + changeURL
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java b/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
index 65cb97a..85238f8 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
@@ -301,6 +301,32 @@
     addReviewerToReviewableChange(batch());
   }
 
+  private void addReviewerToIgnoredChange(Adder adder) throws Exception {
+    StagedChange sc = stageReviewableChange();
+    requestScopeOperations.setApiUser(sc.reviewer.id());
+    gApi.changes().id(sc.changeId).ignore(true);
+    TestAccount addedReviewer = accountCreator.create("added", "added@example.com", "added", null);
+    addReviewer(adder, sc.changeId, sc.owner, addedReviewer.email(), CC_ON_OWN_COMMENTS, null);
+
+    assertThat(sender)
+        .sent("newchange", sc)
+        .to(addedReviewer)
+        .cc(sc.owner)
+        .cc(StagedUsers.REVIEWER_BY_EMAIL, StagedUsers.CC_BY_EMAIL)
+        .noOneElse();
+    assertThat(sender).didNotSend();
+  }
+
+  @Test
+  public void addReviewerToIgnoredChangeSingly() throws Exception {
+    addReviewerToIgnoredChange(singly());
+  }
+
+  @Test
+  public void addReviewerToIgnoredChangeBatch() throws Exception {
+    addReviewerToIgnoredChange(batch());
+  }
+
   private void addReviewerToReviewableChangeByOwnerCcingSelf(Adder adder) throws Exception {
     StagedChange sc = stageReviewableChange();
     TestAccount reviewer = accountCreator.create("added", "added@example.com", "added", null);
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java b/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
index 85c0212..06d9349 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
@@ -17,6 +17,7 @@
 import static com.google.common.collect.ImmutableSet.toImmutableSet;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.entities.Patch.PATCHSET_LEVEL;
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.clearInvocations;
@@ -97,7 +98,7 @@
   }
 
   @Test
-  public void parseAndPersistChangeMessage() throws Exception {
+  public void parseAndPersistPatchsetLevelComment() throws Exception {
     String changeId = createChangeWithReview();
     ChangeInfo changeInfo = gApi.changes().id(changeId).get();
     String ts =
@@ -114,8 +115,19 @@
 
     Collection<ChangeMessageInfo> messages = gApi.changes().id(changeId).get().messages;
     assertThat(messages).hasSize(3);
-    assertThat(Iterables.getLast(messages).message).isEqualTo("Patch Set 1:\n\nTest Message");
+    assertThat(Iterables.getLast(messages).message).isEqualTo("Patch Set 1:\n\n(1 comment)");
     assertThat(Iterables.getLast(messages).tag).isEqualTo("mailMessageId=some id");
+    // Assert comment
+    List<CommentInfo> comments = gApi.changes().id(changeId).current().commentsAsList();
+    assertThat(comments).hasSize(3);
+    assertThat(comments.get(0).path).isEqualTo(PATCHSET_LEVEL);
+    assertThat(comments.get(0).message).isEqualTo("Test Message");
+    assertThat(comments.get(0).tag).isEqualTo("mailMessageId=some id");
+    assertThat(comments.get(0).parent).isNull();
+    assertThat(comments.get(0).side).isNull();
+    assertThat(comments.get(0).line).isNull();
+    assertThat(comments.get(0).parent).isNull();
+    assertThat(comments.get(0).inReplyTo).isNull();
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/ExternalIdNotesUpsertPreprocessorIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/ExternalIdNotesUpsertPreprocessorIT.java
index 64ad900..277c0e6 100644
--- a/javatests/com/google/gerrit/acceptance/server/notedb/ExternalIdNotesUpsertPreprocessorIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/notedb/ExternalIdNotesUpsertPreprocessorIT.java
@@ -24,9 +24,9 @@
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.extensions.annotations.Exports;
 import com.google.gerrit.server.ServerInitiated;
-import com.google.gerrit.server.account.Accounts;
 import com.google.gerrit.server.account.AccountsUpdate;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
 import com.google.gerrit.server.account.externalids.ExternalIdNotes;
 import com.google.gerrit.server.account.externalids.ExternalIdUpsertPreprocessor;
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
@@ -50,7 +50,7 @@
   @Inject private Sequences sequences;
   @Inject private @ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider;
   @Inject private ExternalIdNotes.Factory extIdNotesFactory;
-  @Inject private Accounts accounts;
+  @Inject private ExternalIdFactory extIdFactory;
 
   public static class Module extends AbstractModule {
     @Override
@@ -72,7 +72,7 @@
   @Test
   public void insertAccount() throws Exception {
     Account.Id id = Account.id(sequences.nextAccountId());
-    ExternalId extId = ExternalId.create("foo", "bar", id);
+    ExternalId extId = extIdFactory.create("foo", "bar", id);
     accountsUpdateProvider.get().insert("test", id, u -> u.addExternalId(extId));
     assertThat(testPreprocessor.upserted).containsExactly(extId);
   }
@@ -80,8 +80,8 @@
   @Test
   public void replaceByKeys() throws Exception {
     Account.Id id = Account.id(sequences.nextAccountId());
-    ExternalId extId1 = ExternalId.create("foo", "bar1", id);
-    ExternalId extId2 = ExternalId.create("foo", "bar2", id);
+    ExternalId extId1 = extIdFactory.create("foo", "bar1", id);
+    ExternalId extId2 = extIdFactory.create("foo", "bar2", id);
     accountsUpdateProvider.get().insert("test", id, u -> u.addExternalId(extId1));
 
     testPreprocessor.reset();
@@ -97,7 +97,7 @@
   @Test
   public void insert() throws Exception {
     Account.Id id = Account.id(sequences.nextAccountId());
-    ExternalId extId = ExternalId.create("foo", "bar", id);
+    ExternalId extId = extIdFactory.create("foo", "bar", id);
 
     try (Repository allUsersRepo = repoManager.openRepository(allUsers);
         MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
@@ -111,7 +111,7 @@
   @Test
   public void upsert() throws Exception {
     Account.Id id = Account.id(sequences.nextAccountId());
-    ExternalId extId = ExternalId.create("foo", "bar", id);
+    ExternalId extId = extIdFactory.create("foo", "bar", id);
 
     try (Repository allUsersRepo = repoManager.openRepository(allUsers);
         MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
@@ -125,8 +125,8 @@
   @Test
   public void replace() throws Exception {
     Account.Id id = Account.id(sequences.nextAccountId());
-    ExternalId extId1 = ExternalId.create("foo", "bar1", id);
-    ExternalId extId2 = ExternalId.create("foo", "bar2", id);
+    ExternalId extId1 = extIdFactory.create("foo", "bar1", id);
+    ExternalId extId2 = extIdFactory.create("foo", "bar2", id);
     accountsUpdateProvider.get().insert("test", id, u -> u.addExternalId(extId1));
 
     testPreprocessor.reset();
@@ -142,8 +142,8 @@
   @Test
   public void replace_viaAccountsUpdate() throws Exception {
     Account.Id id = Account.id(sequences.nextAccountId());
-    ExternalId extId1 = ExternalId.create("foo", "bar", id, "email1@foo", "hash");
-    ExternalId extId2 = ExternalId.create("foo", "bar", id, "email2@foo", "hash");
+    ExternalId extId1 = extIdFactory.create("foo", "bar", id, "email1@foo", "hash");
+    ExternalId extId2 = extIdFactory.create("foo", "bar", id, "email2@foo", "hash");
     accountsUpdateProvider.get().insert("test", id, u -> u.addExternalId(extId1));
 
     testPreprocessor.reset();
@@ -154,7 +154,7 @@
   @Test
   public void blockUpsert() throws Exception {
     Account.Id id = Account.id(sequences.nextAccountId());
-    ExternalId extId = ExternalId.create("foo", "bar", id);
+    ExternalId extId = extIdFactory.create("foo", "bar", id);
     testPreprocessor.throwException = true;
     StorageException e =
         assertThrows(
@@ -167,8 +167,8 @@
   @Test
   public void blockUpsert_replace() throws Exception {
     Account.Id id = Account.id(sequences.nextAccountId());
-    ExternalId extId1 = ExternalId.create("foo", "bar", id, "email1@foo", "hash");
-    ExternalId extId2 = ExternalId.create("foo", "bar", id, "email2@foo", "hash");
+    ExternalId extId1 = extIdFactory.create("foo", "bar", id, "email1@foo", "hash");
+    ExternalId extId2 = extIdFactory.create("foo", "bar", id, "email2@foo", "hash");
     accountsUpdateProvider.get().insert("test", id, u -> u.addExternalId(extId1));
 
     assertThat(accounts.get(id).get().externalIds()).containsExactly(extId1);
diff --git a/javatests/com/google/gerrit/acceptance/server/permissions/ExternalUserPermissionIT.java b/javatests/com/google/gerrit/acceptance/server/permissions/ExternalUserPermissionIT.java
index 9e4907c..46687e3 100644
--- a/javatests/com/google/gerrit/acceptance/server/permissions/ExternalUserPermissionIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/permissions/ExternalUserPermissionIT.java
@@ -44,7 +44,7 @@
 import com.google.gerrit.server.PropertyMap;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupMembership;
-import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.permissions.ChangePermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
@@ -70,6 +70,7 @@
   @Inject private ChangeNotes.Factory changeNotesFactory;
   @Inject private ExternalUser.Factory externalUserFactory;
   @Inject private GroupOperations groupOperations;
+  @Inject private ExternalIdKeyFactory externalIdKeyFactory;
 
   @Before
   public void setUp() {
@@ -295,7 +296,7 @@
   ExternalUser createUserInGroup(String userId, String groupId) {
     return externalUserFactory.create(
         ImmutableSet.of(),
-        ImmutableSet.of(ExternalId.Key.parse("company-auth:" + groupId + "-" + userId)),
+        ImmutableSet.of(externalIdKeyFactory.parse("company-auth:" + groupId + "-" + userId)),
         PropertyMap.EMPTY);
   }
 }
diff --git a/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java b/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
index 33276e7..ba86976 100644
--- a/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
@@ -16,9 +16,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
-import static com.google.gerrit.server.StarredChangesUtil.IGNORE_LABEL;
 
-import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
@@ -32,7 +30,6 @@
 import com.google.gerrit.entities.Permission;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
-import com.google.gerrit.extensions.api.changes.StarsInput;
 import com.google.gerrit.extensions.common.GroupInfo;
 import com.google.gerrit.testing.FakeEmailSender.Message;
 import com.google.inject.Inject;
@@ -470,7 +467,7 @@
 
     // ignore the change
     requestScopeOperations.setApiUser(user.id());
-    gApi.accounts().self().setStars(r.getChangeId(), new StarsInput(ImmutableSet.of(IGNORE_LABEL)));
+    gApi.changes().id(r.getChangeId()).ignore(true);
 
     sender.clear();
 
diff --git a/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java b/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java
index 9e85558..6e19c39 100644
--- a/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java
@@ -116,7 +116,7 @@
             /* submittabilityExpr= */ "message:\"Fix bug\"",
             /* overrideExpr= */ "");
 
-    SubmitRequirementResult result = evaluator.evaluate(sr, changeData);
+    SubmitRequirementResult result = evaluator.evaluateRequirement(sr, changeData);
     assertThat(result.status()).isEqualTo(SubmitRequirementResult.Status.NOT_APPLICABLE);
   }
 
@@ -128,7 +128,7 @@
             /* submittabilityExpr= */ "message:\"Fix a bug\"",
             /* overrideExpr= */ "");
 
-    SubmitRequirementResult result = evaluator.evaluate(sr, changeData);
+    SubmitRequirementResult result = evaluator.evaluateRequirement(sr, changeData);
     assertThat(result.status()).isEqualTo(SubmitRequirementResult.Status.SATISFIED);
   }
 
@@ -141,7 +141,7 @@
             /* submittabilityExpr= */ "label:\"code-review=+2\"",
             /* overrideExpr= */ "");
 
-    SubmitRequirementResult result = evaluator.evaluate(sr, changeData);
+    SubmitRequirementResult result = evaluator.evaluateRequirement(sr, changeData);
     assertThat(result.status()).isEqualTo(SubmitRequirementResult.Status.UNSATISFIED);
     assertThat(result.submittabilityExpressionResult().failingAtoms())
         .containsExactly("label:\"code-review=+2\"");
@@ -163,7 +163,7 @@
             /* submittabilityExpr= */ "label:\"code-review=+2\"",
             /* overrideExpr= */ "label:\"build-cop-override=+1\"");
 
-    SubmitRequirementResult result = evaluator.evaluate(sr, changeData);
+    SubmitRequirementResult result = evaluator.evaluateRequirement(sr, changeData);
     assertThat(result.status()).isEqualTo(SubmitRequirementResult.Status.OVERRIDDEN);
   }
 
@@ -178,7 +178,7 @@
             /* submittabilityExpr= */ "label:\"code-review=+2\"",
             /* overrideExpr= */ "label:\"build-cop-override=+1\"");
 
-    SubmitRequirementResult result = evaluator.evaluate(sr, changeData);
+    SubmitRequirementResult result = evaluator.evaluateRequirement(sr, changeData);
     assertThat(result.status()).isEqualTo(SubmitRequirementResult.Status.ERROR);
     assertThat(result.applicabilityExpressionResult().get().errorMessage().get())
         .isEqualTo("Unsupported operator invalid_field:invalid_value");
@@ -195,7 +195,7 @@
             /* submittabilityExpr= */ "invalid_field:invalid_value",
             /* overrideExpr= */ "label:\"build-cop-override=+1\"");
 
-    SubmitRequirementResult result = evaluator.evaluate(sr, changeData);
+    SubmitRequirementResult result = evaluator.evaluateRequirement(sr, changeData);
     assertThat(result.status()).isEqualTo(SubmitRequirementResult.Status.ERROR);
     assertThat(result.submittabilityExpressionResult().errorMessage().get())
         .isEqualTo("Unsupported operator invalid_field:invalid_value");
@@ -209,7 +209,7 @@
             /* submittabilityExpr= */ "label:\"code-review=+2\"",
             /* overrideExpr= */ "invalid_field:invalid_value");
 
-    SubmitRequirementResult result = evaluator.evaluate(sr, changeData);
+    SubmitRequirementResult result = evaluator.evaluateRequirement(sr, changeData);
     assertThat(result.status()).isEqualTo(SubmitRequirementResult.Status.ERROR);
     assertThat(result.overrideExpressionResult().get().errorMessage().get())
         .isEqualTo("Unsupported operator invalid_field:invalid_value");
diff --git a/javatests/com/google/gerrit/acceptance/ssh/SetAccountIT.java b/javatests/com/google/gerrit/acceptance/ssh/SetAccountIT.java
new file mode 100644
index 0000000..a82876e
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/ssh/SetAccountIT.java
@@ -0,0 +1,93 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.ssh;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.UseSsh;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.server.account.externalids.ExternalIds;
+import com.google.inject.Inject;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+
+@UseSsh
+@NoHttpd
+public class SetAccountIT extends AbstractDaemonTest {
+  @Inject private ExternalIds externalIds;
+  @Inject private ProjectOperations projectOperations;
+
+  @Test
+  public void setAccount_deleteExternalId_all() throws Exception {
+    TestAccount testAccount = accountCreator.create("user1", "user1@example.com", null, null);
+    adminSshSession.exec("gerrit set-account --delete-external-id ALL user1");
+    adminSshSession.assertSuccess();
+    assertThat(externalIds.byAccount(testAccount.id()).isEmpty()).isTrue();
+  }
+
+  @Test
+  public void setAccount_deleteExternalId_single() throws Exception {
+    TestAccount testAccount = accountCreator.create("user2", "user2@example.com", null, null);
+    List<String> extIdKeys = getExternalIdKeys(testAccount);
+    assertThat(extIdKeys.contains("username:user2")).isTrue();
+    assertThat(extIdKeys.contains("mailto:user2@example.com")).isTrue();
+    adminSshSession.exec("gerrit set-account --delete-external-id username:user2 user2");
+    adminSshSession.assertSuccess();
+    extIdKeys = getExternalIdKeys(testAccount);
+    assertThat(extIdKeys.contains("username:user3")).isFalse();
+    assertThat(extIdKeys.contains("mailto:user3@example.com")).isFalse();
+  }
+
+  @Test
+  public void setAccount_deleteExternalId_multiple() throws Exception {
+    TestAccount testAccount = accountCreator.create("user3", "user3@example.com", null, null);
+    List<String> extIdKeys = getExternalIdKeys(testAccount);
+    assertThat(extIdKeys.contains("username:user3")).isTrue();
+    assertThat(extIdKeys.contains("mailto:user3@example.com")).isTrue();
+    adminSshSession.exec(
+        "gerrit set-account --delete-external-id username:user3 --delete-external-id mailto:user3@example.com user3");
+    adminSshSession.assertSuccess();
+    extIdKeys = getExternalIdKeys(testAccount);
+    assertThat(extIdKeys.contains("username:user3")).isFalse();
+    assertThat(extIdKeys.contains("mailto:user3@example.com")).isFalse();
+  }
+
+  @Test
+  public void setAccount_deleteExternalId_byUser() throws Exception {
+    userSshSession.exec("gerrit set-account --delete-external-id mailto:admin@example.com admin");
+    userSshSession.assertFailure();
+    projectOperations
+        .allProjectsForUpdate()
+        .add(allowCapability(GlobalCapability.MODIFY_ACCOUNT).group(REGISTERED_USERS))
+        .update();
+    userSshSession.exec("gerrit set-account --delete-external-id mailto:admin@example.com admin");
+    userSshSession.assertSuccess();
+    userSshSession.exec("gerrit set-account --delete-external-id username:admin admin");
+    userSshSession.assertFailure();
+  }
+
+  private List<String> getExternalIdKeys(TestAccount account) throws Exception {
+    return externalIds.byAccount(account.id()).stream()
+        .map(e -> e.key().get())
+        .collect(Collectors.toList());
+  }
+}
diff --git a/javatests/com/google/gerrit/acceptance/ssh/SshCancellationIT.java b/javatests/com/google/gerrit/acceptance/ssh/SshCancellationIT.java
index d08e219..e21cb26 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/SshCancellationIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/SshCancellationIT.java
@@ -122,7 +122,7 @@
   @Test
   public void abortIfClientProvidedDeadlineExceeded() throws Exception {
     adminSshSession.exec("gerrit create-project --deadline 1ms " + name("new"));
-    adminSshSession.assertFailure("Client Provided Deadline Exceeded (timeout=1ms)");
+    adminSshSession.assertFailure("Client Provided Deadline Exceeded (client.timeout=1ms)");
   }
 
   @Test
@@ -149,17 +149,18 @@
     adminSshSession.assertSuccess();
   }
 
+  @Test
   @GerritConfig(name = "deadline.default.timeout", value = "1ms")
   public void abortIfServerDeadlineExceeded() throws Exception {
     adminSshSession.exec("gerrit create-project " + name("new"));
-    adminSshSession.assertFailure("Server Deadline Exceeded (timeout=1ms)");
+    adminSshSession.assertFailure("Server Deadline Exceeded (default.timeout=1ms)");
   }
 
   @Test
   @GerritConfig(name = "deadline.default.timeout", value = "1ms")
   public void clientProvidedDeadlineOverridesServerDeadline() throws Exception {
     adminSshSession.exec("gerrit create-project --deadline 2ms " + name("new"));
-    adminSshSession.assertFailure("Client Provided Deadline Exceeded (timeout=2ms)");
+    adminSshSession.assertFailure("Client Provided Deadline Exceeded (client.timeout=2ms)");
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/auth/ldap/LdapRealmTest.java b/javatests/com/google/gerrit/auth/ldap/LdapRealmTest.java
index 7a61626..0883033 100644
--- a/javatests/com/google/gerrit/auth/ldap/LdapRealmTest.java
+++ b/javatests/com/google/gerrit/auth/ldap/LdapRealmTest.java
@@ -30,6 +30,7 @@
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.AccountGroup;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gerrit.testing.InMemoryModule;
 import com.google.inject.Guice;
@@ -44,6 +45,7 @@
 
 public final class LdapRealmTest {
   @Inject private LdapRealm ldapRealm = null;
+  @Inject private ExternalIdFactory externalIdFactory;
 
   @Before
   public void setUpInjector() throws Exception {
@@ -67,7 +69,7 @@
   }
 
   private ExternalId id(String scheme, String id) {
-    return ExternalId.create(scheme, id, Account.id(1000));
+    return externalIdFactory.create(scheme, id, Account.id(1000));
   }
 
   private boolean accountBelongsToRealm(ExternalId... ids) {
diff --git a/javatests/com/google/gerrit/auth/oauth/OAuthRealmTest.java b/javatests/com/google/gerrit/auth/oauth/OAuthRealmTest.java
index 1af78e3..3ec6f28 100644
--- a/javatests/com/google/gerrit/auth/oauth/OAuthRealmTest.java
+++ b/javatests/com/google/gerrit/auth/oauth/OAuthRealmTest.java
@@ -24,6 +24,7 @@
 
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
 import com.google.gerrit.testing.InMemoryModule;
 import com.google.inject.Guice;
 import com.google.inject.Inject;
@@ -34,6 +35,7 @@
 
 public final class OAuthRealmTest {
   @Inject private OAuthRealm oauthRealm = null;
+  @Inject private ExternalIdFactory externalIdFactory;
 
   @Before
   public void setUpInjector() throws Exception {
@@ -42,7 +44,7 @@
   }
 
   private ExternalId id(String scheme, String id) {
-    return ExternalId.create(scheme, id, Account.id(1000));
+    return externalIdFactory.create(scheme, id, Account.id(1000));
   }
 
   private boolean accountBelongsToRealm(ExternalId... ids) {
diff --git a/javatests/com/google/gerrit/auth/openid/OpenIdRealmTest.java b/javatests/com/google/gerrit/auth/openid/OpenIdRealmTest.java
index 05b0ec0..f83409b 100644
--- a/javatests/com/google/gerrit/auth/openid/OpenIdRealmTest.java
+++ b/javatests/com/google/gerrit/auth/openid/OpenIdRealmTest.java
@@ -25,6 +25,7 @@
 
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
 import com.google.gerrit.testing.InMemoryModule;
 import com.google.inject.Guice;
 import com.google.inject.Inject;
@@ -35,6 +36,7 @@
 
 public final class OpenIdRealmTest {
   @Inject private OpenIdRealm openidRealm = null;
+  @Inject private ExternalIdFactory extIdFactory;
 
   @Before
   public void setUpInjector() throws Exception {
@@ -43,7 +45,7 @@
   }
 
   private ExternalId id(String scheme, String id) {
-    return ExternalId.create(scheme, id, Account.id(1000));
+    return extIdFactory.create(scheme, id, Account.id(1000));
   }
 
   private boolean accountBelongsToRealm(ExternalId... ids) {
diff --git a/javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java b/javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
index 45b3419..05e9808 100644
--- a/javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
+++ b/javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.gpg;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.gpg.GerritPublicKeyChecker.toExtIdKey;
 import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
 import static com.google.gerrit.gpg.testing.TestKeys.validKeyWithSecondUserId;
 import static com.google.gerrit.gpg.testing.TestTrustKeys.keyA;
@@ -39,6 +38,7 @@
 import com.google.gerrit.server.account.AccountsUpdate;
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
 import com.google.gerrit.server.schema.SchemaCreator;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.gerrit.testing.InMemoryModule;
@@ -76,6 +76,10 @@
 
   @Inject private ThreadLocalRequestContext requestContext;
 
+  @Inject private AuthRequest.Factory authRequestFactory;
+
+  @Inject private ExternalIdFactory externalIdFactory;
+
   private LifecycleManager lifecycle;
   private Account.Id userId;
   private IdentifiedUser user;
@@ -101,7 +105,7 @@
     lifecycle.start();
 
     schemaCreator.create();
-    userId = accountManager.authenticate(AuthRequest.forUser("user")).getAccountId();
+    userId = accountManager.authenticate(authRequestFactory.createForUser("user")).getAccountId();
     // Note: does not match any key in TestKeys.
     accountsUpdateProvider
         .get()
@@ -121,7 +125,7 @@
   }
 
   private IdentifiedUser addUser(String name) throws Exception {
-    AuthRequest req = AuthRequest.forUser(name);
+    AuthRequest req = authRequestFactory.createForUser(name);
     Account.Id id = accountManager.authenticate(req).getAccountId();
     return userFactory.create(id);
   }
@@ -202,16 +206,18 @@
     reloadUser();
 
     TestKey key = validKeyWithSecondUserId();
-    PublicKeyChecker checker = checkerFactory.create(user, store).disableTrust();
+    GerritPublicKeyChecker checker =
+        (GerritPublicKeyChecker) checkerFactory.create(user, store).disableTrust();
     assertProblems(
         checker.check(key.getPublicKey()),
         Status.BAD,
         "No identities found for user; check http://test/settings#Identities");
 
-    checker = checkerFactory.create().setStore(store).disableTrust();
+    checker = (GerritPublicKeyChecker) checkerFactory.create().setStore(store).disableTrust();
     assertProblems(
         checker.check(key.getPublicKey()), Status.BAD, "Key is not associated with any users");
-    insertExtId(ExternalId.create(toExtIdKey(key.getPublicKey()), user.getAccountId()));
+    insertExtId(
+        externalIdFactory.create(checker.toExtIdKey(key.getPublicKey()), user.getAccountId()));
     assertProblems(checker.check(key.getPublicKey()), Status.BAD, "No identities found for user");
   }
 
@@ -362,13 +368,15 @@
   private void add(PGPPublicKeyRing kr, IdentifiedUser user) throws Exception {
     Account.Id id = user.getAccountId();
     List<ExternalId> newExtIds = new ArrayList<>(2);
-    newExtIds.add(ExternalId.create(toExtIdKey(kr.getPublicKey()), id));
+    GerritPublicKeyChecker checker =
+        (GerritPublicKeyChecker) checkerFactory.create(user, store).disableTrust();
+    newExtIds.add(externalIdFactory.create(checker.toExtIdKey(kr.getPublicKey()), id));
 
     String userId = Iterators.getOnlyElement(kr.getPublicKey().getUserIDs(), null);
     if (userId != null) {
       String email = PushCertificateIdent.parse(userId).getEmailAddress();
       assertThat(email).contains("@");
-      newExtIds.add(ExternalId.createEmail(id, email));
+      newExtIds.add(externalIdFactory.createEmail(id, email));
     }
 
     store.add(kr);
@@ -401,7 +409,7 @@
   }
 
   private void addExternalId(String scheme, String id, String email) throws Exception {
-    insertExtId(ExternalId.createWithEmail(scheme, id, user.getAccountId(), email));
+    insertExtId(externalIdFactory.createWithEmail(scheme, id, user.getAccountId(), email));
   }
 
   private void insertExtId(ExternalId extId) throws Exception {
diff --git a/javatests/com/google/gerrit/httpd/ProjectBasicAuthFilterTest.java b/javatests/com/google/gerrit/httpd/ProjectBasicAuthFilterTest.java
index 2f0fafa..162a171 100644
--- a/javatests/com/google/gerrit/httpd/ProjectBasicAuthFilterTest.java
+++ b/javatests/com/google/gerrit/httpd/ProjectBasicAuthFilterTest.java
@@ -32,8 +32,12 @@
 import com.google.gerrit.server.account.AccountException;
 import com.google.gerrit.server.account.AccountManager;
 import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.AuthResult;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
+import com.google.gerrit.server.account.externalids.PasswordVerifier;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.util.http.testutil.FakeHttpServletRequest;
 import com.google.gerrit.util.http.testutil.FakeHttpServletResponse;
@@ -62,12 +66,6 @@
   private static final String AUTH_PASSWORD = "jd123";
   private static final String GERRIT_COOKIE_KEY = "GerritAccount";
   private static final String AUTH_COOKIE_VALUE = "gerritcookie";
-  private static final ExternalId AUTH_USER_PASSWORD_EXTERNAL_ID =
-      ExternalId.createWithPassword(
-          ExternalId.Key.create(ExternalId.SCHEME_USERNAME, AUTH_USER),
-          AUTH_ACCOUNT_ID,
-          null,
-          AUTH_PASSWORD);
 
   @Mock private DynamicItem<WebSession> webSessionItem;
 
@@ -93,14 +91,23 @@
   private FakeHttpServletRequest req;
   private HttpServletResponse res;
   private AuthResult authSuccessful;
+  private ExternalIdFactory extIdFactory;
+  private ExternalIdKeyFactory extIdKeyFactory;
+  private PasswordVerifier pwdVerifier;
+  private AuthRequest.Factory authRequestFactory;
 
   @Before
   public void setUp() throws Exception {
     req = new FakeHttpServletRequest("gerrit.example.com", 80, "", "");
     res = new FakeHttpServletResponse();
 
+    extIdKeyFactory = new ExternalIdKeyFactory(authConfig);
+    extIdFactory = new ExternalIdFactory(extIdKeyFactory);
+    authRequestFactory = new AuthRequest.Factory(extIdKeyFactory);
+    pwdVerifier = new PasswordVerifier(extIdKeyFactory);
+
     authSuccessful =
-        new AuthResult(AUTH_ACCOUNT_ID, ExternalId.Key.create("username", AUTH_USER), false);
+        new AuthResult(AUTH_ACCOUNT_ID, extIdKeyFactory.create("username", AUTH_USER), false);
     doReturn(Optional.of(accountState)).when(accountCache).getByUsername(AUTH_USER);
     doReturn(Optional.of(accountState)).when(accountCache).get(AUTH_ACCOUNT_ID);
     doReturn(account).when(accountState).account();
@@ -121,7 +128,13 @@
     res.setStatus(HttpServletResponse.SC_OK);
 
     ProjectBasicAuthFilter basicAuthFilter =
-        new ProjectBasicAuthFilter(webSessionItem, accountCache, accountManager, authConfig);
+        new ProjectBasicAuthFilter(
+            webSessionItem,
+            accountCache,
+            accountManager,
+            authConfig,
+            authRequestFactory,
+            pwdVerifier);
 
     basicAuthFilter.doFilter(req, res, chain);
 
@@ -136,7 +149,13 @@
     res.setStatus(HttpServletResponse.SC_OK);
 
     ProjectBasicAuthFilter basicAuthFilter =
-        new ProjectBasicAuthFilter(webSessionItem, accountCache, accountManager, authConfig);
+        new ProjectBasicAuthFilter(
+            webSessionItem,
+            accountCache,
+            accountManager,
+            authConfig,
+            authRequestFactory,
+            pwdVerifier);
 
     basicAuthFilter.doFilter(req, res, chain);
 
@@ -155,7 +174,13 @@
     doReturn(GitBasicAuthPolicy.LDAP).when(authConfig).getGitBasicAuthPolicy();
 
     ProjectBasicAuthFilter basicAuthFilter =
-        new ProjectBasicAuthFilter(webSessionItem, accountCache, accountManager, authConfig);
+        new ProjectBasicAuthFilter(
+            webSessionItem,
+            accountCache,
+            accountManager,
+            authConfig,
+            authRequestFactory,
+            pwdVerifier);
 
     basicAuthFilter.doFilter(req, res, chain);
 
@@ -175,7 +200,13 @@
     res.setStatus(HttpServletResponse.SC_OK);
 
     ProjectBasicAuthFilter basicAuthFilter =
-        new ProjectBasicAuthFilter(webSessionItem, accountCache, accountManager, authConfig);
+        new ProjectBasicAuthFilter(
+            webSessionItem,
+            accountCache,
+            accountManager,
+            authConfig,
+            authRequestFactory,
+            pwdVerifier);
 
     basicAuthFilter.doFilter(req, res, chain);
 
@@ -216,7 +247,13 @@
     doReturn(GitBasicAuthPolicy.LDAP).when(authConfig).getGitBasicAuthPolicy();
 
     ProjectBasicAuthFilter basicAuthFilter =
-        new ProjectBasicAuthFilter(webSessionItem, accountCache, accountManager, authConfig);
+        new ProjectBasicAuthFilter(
+            webSessionItem,
+            accountCache,
+            accountManager,
+            authConfig,
+            authRequestFactory,
+            pwdVerifier);
 
     basicAuthFilter.doFilter(req, res, chain);
 
@@ -235,7 +272,13 @@
     doReturn(GitBasicAuthPolicy.LDAP).when(authConfig).getGitBasicAuthPolicy();
 
     ProjectBasicAuthFilter basicAuthFilter =
-        new ProjectBasicAuthFilter(webSessionItem, accountCache, accountManager, authConfig);
+        new ProjectBasicAuthFilter(
+            webSessionItem,
+            accountCache,
+            accountManager,
+            authConfig,
+            authRequestFactory,
+            pwdVerifier);
 
     basicAuthFilter.doFilter(req, res, chain);
 
@@ -255,7 +298,13 @@
     res.setStatus(HttpServletResponse.SC_OK);
 
     ProjectBasicAuthFilter basicAuthFilter =
-        new ProjectBasicAuthFilter(webSessionItem, accountCache, accountManager, authConfig);
+        new ProjectBasicAuthFilter(
+            webSessionItem,
+            accountCache,
+            accountManager,
+            authConfig,
+            authRequestFactory,
+            pwdVerifier);
 
     basicAuthFilter.doFilter(req, res, chain);
   }
@@ -278,9 +327,13 @@
   }
 
   private void initMockedUsernamePasswordExternalId() {
-    doReturn(ImmutableSet.builder().add(AUTH_USER_PASSWORD_EXTERNAL_ID).build())
-        .when(accountState)
-        .externalIds();
+    ExternalId extId =
+        extIdFactory.createWithPassword(
+            extIdKeyFactory.create(ExternalId.SCHEME_USERNAME, AUTH_USER),
+            AUTH_ACCOUNT_ID,
+            null,
+            AUTH_PASSWORD);
+    doReturn(ImmutableSet.builder().add(extId).build()).when(accountState).externalIds();
   }
 
   private void requestBasicAuth(FakeHttpServletRequest fakeReq) {
diff --git a/javatests/com/google/gerrit/mail/AbstractParserTest.java b/javatests/com/google/gerrit/mail/AbstractParserTest.java
index b22b8ad..a2432a2 100644
--- a/javatests/com/google/gerrit/mail/AbstractParserTest.java
+++ b/javatests/com/google/gerrit/mail/AbstractParserTest.java
@@ -31,11 +31,11 @@
   protected static final String CHANGE_URL =
       "https://gerrit-review.googlesource.com/c/project/+/123";
 
-  protected static void assertChangeMessage(String message, MailComment comment) {
+  protected static void assertPatchsetComment(String message, MailComment comment) {
     assertThat(comment.fileName).isNull();
     assertThat(comment.message).isEqualTo(message);
     assertThat(comment.inReplyTo).isNull();
-    assertThat(comment.type).isEqualTo(MailComment.CommentType.CHANGE_MESSAGE);
+    assertThat(comment.type).isEqualTo(MailComment.CommentType.PATCHSET_LEVEL);
   }
 
   protected static void assertInlineComment(
diff --git a/javatests/com/google/gerrit/mail/HtmlParserTest.java b/javatests/com/google/gerrit/mail/HtmlParserTest.java
index d661278..a7ff172 100644
--- a/javatests/com/google/gerrit/mail/HtmlParserTest.java
+++ b/javatests/com/google/gerrit/mail/HtmlParserTest.java
@@ -35,7 +35,7 @@
     List<MailComment> parsedComments = HtmlParser.parse(b.build(), comments, "");
 
     assertThat(parsedComments).hasSize(1);
-    assertChangeMessage("Looks good to me", parsedComments.get(0));
+    assertPatchsetComment("Looks good to me", parsedComments.get(0));
   }
 
   @Test
@@ -56,7 +56,7 @@
     List<MailComment> parsedComments = HtmlParser.parse(b.build(), comments, "");
 
     assertThat(parsedComments).hasSize(1);
-    assertChangeMessage(
+    assertPatchsetComment(
         "Did you consider this: http://gerritcodereview.com", parsedComments.get(0));
   }
 
@@ -77,7 +77,7 @@
     List<MailComment> parsedComments = HtmlParser.parse(b.build(), comments, CHANGE_URL);
 
     assertThat(parsedComments).hasSize(3);
-    assertChangeMessage("Looks good to me", parsedComments.get(0));
+    assertPatchsetComment("Looks good to me", parsedComments.get(0));
     assertInlineComment("I have a comment on this.", parsedComments.get(1), comments.get(1));
     assertInlineComment("Also have a comment here.", parsedComments.get(2), comments.get(4));
   }
@@ -100,7 +100,7 @@
     List<MailComment> parsedComments = HtmlParser.parse(b.build(), comments, CHANGE_URL);
 
     assertThat(parsedComments).hasSize(3);
-    assertChangeMessage("Looks good to me", parsedComments.get(0));
+    assertPatchsetComment("Looks good to me", parsedComments.get(0));
     assertInlineComment(
         "How about [1]? This would help IMHO.\n\n[1] http://gerritcodereview.com",
         parsedComments.get(1),
@@ -125,7 +125,7 @@
     List<MailComment> parsedComments = HtmlParser.parse(b.build(), comments, CHANGE_URL);
 
     assertThat(parsedComments).hasSize(3);
-    assertChangeMessage("Looks good to me", parsedComments.get(0));
+    assertPatchsetComment("Looks good to me", parsedComments.get(0));
     assertFileComment("This is a nice file", parsedComments.get(1), comments.get(1).key.filename);
     assertInlineComment("Also have a comment here.", parsedComments.get(2), comments.get(4));
   }
@@ -168,7 +168,7 @@
     List<MailComment> parsedComments = HtmlParser.parse(b.build(), comments, CHANGE_URL);
 
     assertThat(parsedComments).hasSize(3);
-    assertChangeMessage(txtMessage, parsedComments.get(0));
+    assertPatchsetComment(txtMessage, parsedComments.get(0));
     assertFileComment(txtMessage, parsedComments.get(1), comments.get(1).key.filename);
     assertInlineComment(txtMessage, parsedComments.get(2), comments.get(4));
   }
diff --git a/javatests/com/google/gerrit/mail/TextParserTest.java b/javatests/com/google/gerrit/mail/TextParserTest.java
index f1d6179..caed5f8 100644
--- a/javatests/com/google/gerrit/mail/TextParserTest.java
+++ b/javatests/com/google/gerrit/mail/TextParserTest.java
@@ -43,7 +43,7 @@
     List<MailComment> parsedComments = TextParser.parse(b.build(), comments, CHANGE_URL);
 
     assertThat(parsedComments).hasSize(1);
-    assertChangeMessage("Looks good to me", parsedComments.get(0));
+    assertPatchsetComment("Looks good to me", parsedComments.get(0));
   }
 
   @Test
@@ -64,7 +64,7 @@
     List<MailComment> parsedComments = TextParser.parse(b.build(), comments, CHANGE_URL);
 
     assertThat(parsedComments).hasSize(3);
-    assertChangeMessage("Looks good to me", parsedComments.get(0));
+    assertPatchsetComment("Looks good to me", parsedComments.get(0));
     assertInlineComment("I have a comment on this.", parsedComments.get(1), comments.get(1));
     assertInlineComment("Also have a comment here.", parsedComments.get(2), comments.get(3));
   }
@@ -87,7 +87,7 @@
     List<MailComment> parsedComments = TextParser.parse(b.build(), comments, CHANGE_URL);
 
     assertThat(parsedComments).hasSize(3);
-    assertChangeMessage("Looks good to me", parsedComments.get(0));
+    assertPatchsetComment("Looks good to me", parsedComments.get(0));
     assertFileComment("This is a nice file", parsedComments.get(1), comments.get(1).key.filename);
     assertInlineComment("Also have a comment here.", parsedComments.get(2), comments.get(3));
   }
@@ -138,7 +138,7 @@
     List<MailComment> parsedComments = TextParser.parse(b.build(), comments, CHANGE_URL);
 
     assertThat(parsedComments).hasSize(3);
-    assertChangeMessage("Looks good to me", parsedComments.get(0));
+    assertPatchsetComment("Looks good to me", parsedComments.get(0));
     assertFileComment("This is a nice file", parsedComments.get(1), comments.get(1).key.filename);
     assertInlineComment("Also have a comment here.", parsedComments.get(2), comments.get(3));
   }
@@ -161,7 +161,7 @@
     List<MailComment> parsedComments = TextParser.parse(b.build(), comments, CHANGE_URL);
 
     assertThat(parsedComments).hasSize(2);
-    assertChangeMessage("Looks good to me", parsedComments.get(0));
+    assertPatchsetComment("Looks good to me", parsedComments.get(0));
     assertInlineComment("Comment in reply to file comment", parsedComments.get(1), comments.get(0));
   }
 
@@ -174,7 +174,7 @@
     List<MailComment> parsedComments = TextParser.parse(b.build(), defaultComments(), CHANGE_URL);
 
     assertThat(parsedComments).hasSize(1);
-    assertChangeMessage(
+    assertPatchsetComment(
         "Nice change\n\nMy other comment on the same entity", parsedComments.get(0));
   }
 
diff --git a/javatests/com/google/gerrit/pgm/init/api/AllProjectsConfigTest.java b/javatests/com/google/gerrit/pgm/init/api/AllProjectsConfigTest.java
index e34b578..fd47567 100644
--- a/javatests/com/google/gerrit/pgm/init/api/AllProjectsConfigTest.java
+++ b/javatests/com/google/gerrit/pgm/init/api/AllProjectsConfigTest.java
@@ -17,6 +17,8 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.common.collect.ImmutableList;
+import com.google.gerrit.server.config.AllProjectsConfigProvider;
+import com.google.gerrit.server.config.FileBasedAllProjectsConfigProvider;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.securestore.testing.InMemorySecureStore;
 import java.io.File;
@@ -76,8 +78,11 @@
     InitFlags flags = new InitFlags(sitePaths, secureStore, ImmutableList.of(), false);
     Section.Factory sections =
         (name, subsection) -> new Section(flags, sitePaths, secureStore, ui, name, subsection);
+    AllProjectsConfigProvider configProvider = new FileBasedAllProjectsConfigProvider(sitePaths);
+
     allProjectsConfig =
-        new AllProjectsConfig(new AllProjectsNameOnInitProvider(sections), sitePaths, flags);
+        new AllProjectsConfig(
+            new AllProjectsNameOnInitProvider(sections), configProvider, sitePaths, flags);
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/server/CancellationMetricsTest.java b/javatests/com/google/gerrit/server/CancellationMetricsTest.java
index b27d9d2..2343c71 100644
--- a/javatests/com/google/gerrit/server/CancellationMetricsTest.java
+++ b/javatests/com/google/gerrit/server/CancellationMetricsTest.java
@@ -28,6 +28,7 @@
     assertThat(redact("/changes/123")).isEqualTo("/changes/*");
     assertThat(redact("/changes/123/detail")).isEqualTo("/changes/*/detail");
     assertThat(redact("/changes/123/detail/")).isEqualTo("/changes/*/detail/");
+    assertThat(redact("/accounts/self/capabilities")).isEqualTo("/accounts/*/capabilities");
     assertThat(redact("/foo/123/bar/567")).isEqualTo("/foo/*/bar/*");
     assertThat(redact("/foo/123/bar/567/baz")).isEqualTo("/foo/*/bar/*/baz");
     assertThat(redact("/foo/123/bar/567/baz/")).isEqualTo("/foo/*/bar/*/baz/");
diff --git a/javatests/com/google/gerrit/server/account/externalids/AllExternalIdsTest.java b/javatests/com/google/gerrit/server/account/externalids/AllExternalIdsTest.java
index 814df03..058384e 100644
--- a/javatests/com/google/gerrit/server/account/externalids/AllExternalIdsTest.java
+++ b/javatests/com/google/gerrit/server/account/externalids/AllExternalIdsTest.java
@@ -25,12 +25,26 @@
 import com.google.gerrit.server.account.externalids.AllExternalIds.Serializer;
 import com.google.gerrit.server.cache.proto.Cache.AllExternalIdsProto;
 import com.google.gerrit.server.cache.proto.Cache.AllExternalIdsProto.ExternalIdProto;
+import com.google.gerrit.server.config.AuthConfig;
 import com.google.inject.TypeLiteral;
+import java.lang.reflect.Type;
 import java.util.Arrays;
 import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mockito;
 
 public class AllExternalIdsTest {
+  private AuthConfig authConfig;
+  private ExternalIdFactory externalIdFactory;
+
+  @Before
+  public void setUp() throws Exception {
+    authConfig = Mockito.mock(AuthConfig.class);
+    Mockito.when(authConfig.isUserNameCaseInsensitive()).thenReturn(false);
+    externalIdFactory = new ExternalIdFactory(new ExternalIdKeyFactory(authConfig));
+  }
+
   @Test
   public void serializeEmptyExternalIds() throws Exception {
     assertRoundTrip(allExternalIds(), AllExternalIdsProto.getDefaultInstance());
@@ -42,10 +56,10 @@
     Account.Id accountId2 = Account.id(1002);
     assertRoundTrip(
         allExternalIds(
-            ExternalId.create("scheme1", "id1", accountId1),
-            ExternalId.create("scheme2", "id2", accountId1),
-            ExternalId.create("scheme2", "id3", accountId2),
-            ExternalId.create("scheme3", "id4", accountId2)),
+            externalIdFactory.create("scheme1", "id1", accountId1),
+            externalIdFactory.create("scheme2", "id2", accountId1),
+            externalIdFactory.create("scheme2", "id3", accountId2),
+            externalIdFactory.create("scheme3", "id4", accountId2)),
         AllExternalIdsProto.newBuilder()
             .addExternalId(
                 ExternalIdProto.newBuilder().setKey("scheme1:id1").setAccountId(1001).build())
@@ -61,7 +75,7 @@
   @Test
   public void serializeExternalIdWithEmail() throws Exception {
     assertRoundTrip(
-        allExternalIds(ExternalId.createEmail(Account.id(1001), "foo@example.com")),
+        allExternalIds(externalIdFactory.createEmail(Account.id(1001), "foo@example.com")),
         AllExternalIdsProto.newBuilder()
             .addExternalId(
                 ExternalIdProto.newBuilder()
@@ -75,7 +89,7 @@
   public void serializeExternalIdWithPassword() throws Exception {
     assertRoundTrip(
         allExternalIds(
-            ExternalId.create("scheme", "id", Account.id(1001), null, "hashed password")),
+            externalIdFactory.create("scheme", "id", Account.id(1001), null, "hashed password")),
         AllExternalIdsProto.newBuilder()
             .addExternalId(
                 ExternalIdProto.newBuilder()
@@ -89,8 +103,8 @@
   public void serializeExternalIdWithBlobId() throws Exception {
     assertRoundTrip(
         allExternalIds(
-            ExternalId.create(
-                ExternalId.create("scheme", "id", Account.id(1001)),
+            externalIdFactory.create(
+                externalIdFactory.create("scheme", "id", Account.id(1001)),
                 ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"))),
         AllExternalIdsProto.newBuilder()
             .addExternalId(
@@ -121,12 +135,14 @@
   public void externalIdMethods() {
     assertThatSerializedClass(ExternalId.class)
         .hasAutoValueMethods(
-            ImmutableMap.of(
-                "key", ExternalId.Key.class,
-                "accountId", Account.Id.class,
-                "email", String.class,
-                "password", String.class,
-                "blobId", ObjectId.class));
+            ImmutableMap.<String, Type>builder()
+                .put("key", ExternalId.Key.class)
+                .put("accountId", Account.Id.class)
+                .put("isCaseInsensitive", boolean.class)
+                .put("email", String.class)
+                .put("password", String.class)
+                .put("blobId", ObjectId.class)
+                .build());
   }
 
   private static AllExternalIds allExternalIds(ExternalId... externalIds) {
diff --git a/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java b/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java
index 5717e78..fab90d4 100644
--- a/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java
+++ b/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java
@@ -28,6 +28,7 @@
 import com.google.gerrit.server.account.externalids.testing.ExternalIdTestUtil;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.AllUsersNameProvider;
+import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
@@ -56,12 +57,18 @@
   private GitRepositoryManager repoManager = new InMemoryRepositoryManager();
   private ExternalIdReader externalIdReader;
   private ExternalIdReader externalIdReaderSpy;
+  private AuthConfig authConfig;
+  private ExternalIdFactory externalIdFactory;
 
   @Before
   public void setUp() throws Exception {
+    authConfig = Mockito.mock(AuthConfig.class);
+    Mockito.when(authConfig.isUserNameCaseInsensitive()).thenReturn(false);
+    externalIdFactory = new ExternalIdFactory(new ExternalIdKeyFactory(authConfig));
     externalIdCache = CacheBuilder.newBuilder().build();
     repoManager.createRepository(ALL_USERS).close();
-    externalIdReader = new ExternalIdReader(repoManager, ALL_USERS, new DisabledMetricMaker());
+    externalIdReader =
+        new ExternalIdReader(repoManager, ALL_USERS, new DisabledMetricMaker(), externalIdFactory);
     externalIdReaderSpy = Mockito.spy(externalIdReader);
     loader = createLoader(true);
   }
@@ -151,7 +158,8 @@
     ObjectId head =
         modifyExternalId(
             externalId(1, 1),
-            ExternalId.create("fooschema", "bar1", Account.id(1), "foo@bar.com", "password"));
+            externalIdFactory.create(
+                "fooschema", "bar1", Account.id(1), "foo@bar.com", "password"));
     assertThat(allFromGit(head).byAccount().size()).isEqualTo(1);
     externalIdCache.put(firstState, allFromGit(firstState));
 
@@ -212,7 +220,8 @@
         externalIdReaderSpy,
         Providers.of(externalIdCache),
         new DisabledMetricMaker(),
-        cfg);
+        cfg,
+        externalIdFactory);
   }
 
   private AllExternalIds allFromGit(ObjectId revision) throws Exception {
@@ -256,13 +265,14 @@
   }
 
   private ExternalId externalId(int key, int accountId) {
-    return ExternalId.create("fooschema", "bar" + key, Account.id(accountId));
+    return externalIdFactory.create("fooschema", "bar" + key, Account.id(accountId));
   }
 
   private ObjectId performExternalIdUpdate(Consumer<ExternalIdNotes> update) throws Exception {
     try (Repository repo = repoManager.openRepository(ALL_USERS)) {
       PersonIdent updater = new PersonIdent("Foo bar", "foo@bar.com");
-      ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(ALL_USERS, repo);
+      ExternalIdNotes extIdNotes =
+          ExternalIdNotes.loadNoCacheUpdate(ALL_USERS, repo, externalIdFactory);
       update.accept(extIdNotes);
       try (MetaDataUpdate metaDataUpdate =
           new MetaDataUpdate(GitReferenceUpdated.DISABLED, null, repo)) {
diff --git a/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java b/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java
index cd28ac4..5e3be9a 100644
--- a/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java
+++ b/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java
@@ -71,6 +71,7 @@
   @Inject private ProjectConfig.Factory projectConfigFactory;
   @Inject private GerritApi gApi;
   @Inject private ProjectOperations projectOperations;
+  @Inject private AuthRequest.Factory authRequestFactory;
 
   private LifecycleManager lifecycle;
   private Account.Id userId;
@@ -87,7 +88,7 @@
     lifecycle.start();
 
     schemaCreator.create();
-    userId = accountManager.authenticate(AuthRequest.forUser("user")).getAccountId();
+    userId = accountManager.authenticate(authRequestFactory.createForUser("user")).getAccountId();
     user = userFactory.create(userId);
 
     requestContext.setContext(() -> user);
diff --git a/javatests/com/google/gerrit/server/git/GitRepositoryManagerTest.java b/javatests/com/google/gerrit/server/git/GitRepositoryManagerTest.java
index 7832bec..6b8177e 100644
--- a/javatests/com/google/gerrit/server/git/GitRepositoryManagerTest.java
+++ b/javatests/com/google/gerrit/server/git/GitRepositoryManagerTest.java
@@ -36,6 +36,12 @@
   }
 
   private static class TestGitRepositoryManager implements GitRepositoryManager {
+
+    @Override
+    public Status getRepositoryStatus(NameKey name) {
+      throw new UnsupportedOperationException("Not implemented");
+    }
+
     @Override
     public Repository openRepository(NameKey name) {
       throw new UnsupportedOperationException("Not implemented");
diff --git a/javatests/com/google/gerrit/server/git/LocalDiskRepositoryManagerTest.java b/javatests/com/google/gerrit/server/git/LocalDiskRepositoryManagerTest.java
index febb142..12130ea 100644
--- a/javatests/com/google/gerrit/server/git/LocalDiskRepositoryManagerTest.java
+++ b/javatests/com/google/gerrit/server/git/LocalDiskRepositoryManagerTest.java
@@ -20,6 +20,7 @@
 
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.GitRepositoryManager.Status;
 import com.google.gerrit.server.ioutil.HostPlatform;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -67,6 +68,7 @@
     try (Repository repo = repoManager.openRepository(projectA)) {
       assertThat(repo).isNotNull();
     }
+    assertThat(repoManager.getRepositoryStatus(projectA)).isEqualTo(Status.ACTIVE);
     assertThat(repoManager.list()).containsExactly(projectA);
   }
 
@@ -199,7 +201,7 @@
   public void testProjectRecreation() throws Exception {
     repoManager.createRepository(Project.nameKey("a"));
     assertThrows(
-        IllegalStateException.class, () -> repoManager.createRepository(Project.nameKey("a")));
+        RepositoryExistsException.class, () -> repoManager.createRepository(Project.nameKey("a")));
   }
 
   @Test
@@ -207,7 +209,8 @@
     repoManager.createRepository(Project.nameKey("a"));
     LocalDiskRepositoryManager newRepoManager = new LocalDiskRepositoryManager(site, cfg);
     assertThrows(
-        IllegalStateException.class, () -> newRepoManager.createRepository(Project.nameKey("a")));
+        RepositoryExistsException.class,
+        () -> newRepoManager.createRepository(Project.nameKey("a")));
   }
 
   @Test
@@ -221,6 +224,19 @@
   }
 
   @Test
+  public void testGetRepositoryStatusNameCaseMismatch() throws Exception {
+    assume().that(HostPlatform.isWin32() || HostPlatform.isMac()).isTrue();
+    repoManager.createRepository(Project.nameKey("a"));
+    assertThat(repoManager.getRepositoryStatus(Project.nameKey("A"))).isEqualTo(Status.ACTIVE);
+  }
+
+  @Test
+  public void testGetRepositoryStatusNonExistent() throws Exception {
+    assertThat(repoManager.getRepositoryStatus(Project.nameKey("non-existent")))
+        .isEqualTo(Status.NON_EXISTENT);
+  }
+
+  @Test
   public void testNameCaseMismatch() throws Exception {
     assume().that(HostPlatform.isWin32() || HostPlatform.isMac()).isTrue();
     repoManager.createRepository(Project.nameKey("a"));
@@ -272,6 +288,12 @@
   }
 
   @Test
+  public void testGetRepoStatusInvalidName() throws Exception {
+    assertThat(repoManager.getRepositoryStatus(Project.nameKey("project%?|<>A")))
+        .isEqualTo(Status.NON_EXISTENT);
+  }
+
+  @Test
   public void list() throws Exception {
     Project.NameKey projectA = Project.nameKey("projectA");
     createRepository(repoManager.getBasePath(projectA), projectA.get());
diff --git a/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java b/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
index 92a5fbe..d16efc3 100644
--- a/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
+++ b/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
@@ -56,25 +56,33 @@
             .build();
     ExternalId extId1 =
         ExternalId.create(
-            ExternalId.Key.create(ExternalId.SCHEME_MAILTO, "foo.bar@example.com"),
+            ExternalId.Key.create(ExternalId.SCHEME_MAILTO, "foo.bar@example.com", false),
             id,
             "foo.bar@example.com",
             null,
             ObjectId.fromString("1b9a0cf038ea38a0ab08617c39aa8e28413a27ca"));
     ExternalId extId2 =
         ExternalId.create(
-            ExternalId.Key.create(ExternalId.SCHEME_USERNAME, "foo"),
+            ExternalId.Key.create(ExternalId.SCHEME_USERNAME, "foo", false),
             id,
             null,
             "secret",
             ObjectId.fromString("5b3a73dc9a668a5b89b5f049225261e3e3291d1a"));
+    ExternalId extId3 =
+        ExternalId.create(
+            ExternalId.Key.create(ExternalId.SCHEME_USERNAME, "Bar", true),
+            id,
+            null,
+            "secret",
+            ObjectId.fromString("483ea804e84282e15ddcdd1d15a797eb4796a760"));
     List<String> values =
         toStrings(
             AccountField.EXTERNAL_ID_STATE.get(
-                AccountState.forAccount(account, ImmutableSet.of(extId1, extId2))));
+                AccountState.forAccount(account, ImmutableSet.of(extId1, extId2, extId3))));
     String expectedValue1 = extId1.key().sha1().name() + ":" + extId1.blobId().name();
     String expectedValue2 = extId2.key().sha1().name() + ":" + extId2.blobId().name();
-    assertThat(values).containsExactly(expectedValue1, expectedValue2);
+    String expectedValue3 = extId3.key().sha1().name() + ":" + extId3.blobId().name();
+    assertThat(values).containsExactly(expectedValue1, expectedValue2, expectedValue3);
   }
 
   private List<String> toStrings(Iterable<byte[]> values) {
diff --git a/javatests/com/google/gerrit/server/index/change/FakeQueryBuilder.java b/javatests/com/google/gerrit/server/index/change/FakeQueryBuilder.java
index 521af2f..66a98e8 100644
--- a/javatests/com/google/gerrit/server/index/change/FakeQueryBuilder.java
+++ b/javatests/com/google/gerrit/server/index/change/FakeQueryBuilder.java
@@ -55,6 +55,7 @@
             null,
             new Config(),
             null,
+            null,
             null));
   }
 
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
index 209d880..048e3fd 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
@@ -684,6 +684,7 @@
             .submitRequirementsResult(
                 ImmutableList.of(
                     SubmitRequirementResult.builder()
+                        .legacy(true)
                         .patchSetCommitId(
                             ObjectId.fromString("26e50c7d315a33a13e5cc00902781fa876bc36cd"))
                         .submitRequirement(
@@ -713,6 +714,7 @@
         newProtoBuilder()
             .addSubmitRequirementResult(
                 SubmitRequirementResultProto.newBuilder()
+                    .setLegacy(true)
                     .setCommit(
                         ObjectIdConverter.create()
                             .toByteString(
@@ -1035,6 +1037,8 @@
     assertThatSerializedClass(SubmitRecord.class)
         .hasFields(
             ImmutableMap.of(
+                "ruleName",
+                new TypeLiteral<String>() {}.getType(),
                 "status",
                 SubmitRecord.Status.class,
                 "labels",
@@ -1065,7 +1069,7 @@
             .setChangeId(ID.get())
             .setMergedOnMillis(234567L)
             .setHasMergedOn(true)
-            .setColumns(colsProto.toBuilder())
+            .setColumns(colsProto)
             .build());
   }
 
@@ -1128,7 +1132,7 @@
             .setChangeId(ID.get())
             .setServerId(DEFAULT_SERVER_ID)
             .setHasServerId(true)
-            .setColumns(colsProto.toBuilder())
+            .setColumns(colsProto)
             .build());
   }
 
diff --git a/javatests/com/google/gerrit/server/notedb/CommitRewriterTest.java b/javatests/com/google/gerrit/server/notedb/CommitRewriterTest.java
index 77db31f..26e1881 100644
--- a/javatests/com/google/gerrit/server/notedb/CommitRewriterTest.java
+++ b/javatests/com/google/gerrit/server/notedb/CommitRewriterTest.java
@@ -34,11 +34,11 @@
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.entities.SubmitRecord;
 import com.google.gerrit.json.OutputFormat;
-import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.ReviewerStatusUpdate;
 import com.google.gerrit.server.notedb.ChangeNoteUtil.AttentionStatusInNoteDb;
 import com.google.gerrit.server.notedb.CommitRewriter.BackfillResult;
+import com.google.gerrit.server.notedb.CommitRewriter.CommitDiff;
 import com.google.gerrit.server.notedb.CommitRewriter.RunOptions;
 import com.google.gerrit.server.util.AccountTemplateUtil;
 import com.google.gerrit.server.util.time.TimeUtil;
@@ -124,6 +124,38 @@
   }
 
   @Test
+  public void outputDiffOff_refsReported() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.setChangeMessage("Change has been successfully merged by " + changeOwner.getName());
+    ObjectId commitToFix = update.commit();
+
+    ChangeUpdate updateWithSubject = newUpdate(c, changeOwner);
+    updateWithSubject.setSubjectForCommit("Update with subject");
+    updateWithSubject.commit();
+
+    Ref metaRefBefore = repo.exactRef(RefNames.changeMetaRef(c.getId()));
+    RunOptions options = new RunOptions();
+    options.dryRun = false;
+    options.outputDiff = false;
+    options.verifyCommits = false;
+    BackfillResult backfillResult = rewriter.backfillProject(project, repo, options);
+    assertThat(backfillResult.fixedRefDiff.keySet())
+        .containsExactly(RefNames.changeMetaRef(c.getId()));
+    Ref metaRefAfter = repo.exactRef(RefNames.changeMetaRef(c.getId()));
+
+    assertThat(metaRefBefore.getObjectId()).isNotEqualTo(metaRefAfter.getObjectId());
+
+    assertFixedCommits(ImmutableList.of(commitToFix), backfillResult, c.getId());
+
+    List<String> commitHistoryDiff = commitHistoryDiff(backfillResult, c.getId());
+    assertThat(commitHistoryDiff).containsExactly("");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
+  }
+
+  @Test
   public void fixAuthorIdent() throws Exception {
     Change c = newChange();
     Timestamp when = TimeUtil.nowTs();
@@ -164,6 +196,8 @@
 
     assertValidCommits(
         commitsBeforeRewrite, commitsAfterRewrite, ImmutableList.of(invalidCommitIndex));
+    assertFixedCommits(ImmutableList.of(invalidUpdateCommit.getId()), result, c.getId());
+
     RevCommit fixedUpdateCommit = commitsAfterRewrite.get(invalidCommitIndex);
     PersonIdent originalAuthorIdent = invalidUpdateCommit.getAuthorIdent();
     PersonIdent fixedAuthorIdent = fixedUpdateCommit.getAuthorIdent();
@@ -177,10 +211,13 @@
     assertThat(invalidUpdateCommit.getCommitterIdent())
         .isEqualTo(fixedUpdateCommit.getCommitterIdent());
     assertThat(fixedUpdateCommit.getFullMessage()).doesNotContain(changeOwner.getName());
-    List<String> commitHistoryDiff = result.fixedRefDiff.get(RefNames.changeMetaRef(c.getId()));
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
     assertThat(commitHistoryDiff).hasSize(1);
     assertThat(commitHistoryDiff.get(0)).contains("-author Change Owner <1@gerrit>");
     assertThat(commitHistoryDiff.get(0)).contains("+author Gerrit User 1 <1@gerrit>");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   @Test
@@ -188,7 +225,7 @@
     Change c = newChange();
 
     String realUserIdentToFix = getAccountIdentToFix(otherUser.getAccount());
-    RevCommit invalidUpdateCommit =
+    ObjectId invalidUpdateCommit =
         writeUpdate(
             RefNames.changeMetaRef(c.getId()),
             getChangeUpdateBody(c, "Comment on behalf of user", "Real-user: " + realUserIdentToFix),
@@ -233,31 +270,42 @@
     ImmutableList<RevCommit> commitsAfterRewrite = logMetaRef(repo, metaRefAfterRewrite);
     assertValidCommits(
         commitsBeforeRewrite, commitsAfterRewrite, ImmutableList.of(invalidCommitIndex));
+    assertFixedCommits(ImmutableList.of(invalidUpdateCommit), result, c.getId());
 
-    List<String> commitHistoryDiff = result.fixedRefDiff.get(RefNames.changeMetaRef(c.getId()));
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
     assertThat(commitHistoryDiff)
         .containsExactly(
             "@@ -9 +9 @@\n"
                 + "-Real-user: Other Account <2@gerrit>\n"
                 + "+Real-user: Gerrit User 2 <2@gerrit>\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   @Test
   public void fixReviewerFooterIdent() throws Exception {
     Change c = newChange();
     String reviewerIdentToFix = getAccountIdentToFix(otherUser.getAccount());
-    ImmutableList<RevCommit> commitsToFix =
-        new ImmutableList.Builder<RevCommit>()
+    ImmutableList<ObjectId> commitsToFix =
+        new ImmutableList.Builder<ObjectId>()
             .add(
                 writeUpdate(
                     RefNames.changeMetaRef(c.getId()),
+                    // valid change message that should not be overwritten
                     getChangeUpdateBody(
-                        c, /*changeMessage=*/ null, "Reviewer: " + reviewerIdentToFix),
+                        c,
+                        "Removed reviewer <GERRIT_ACCOUNT_1>.",
+                        "Reviewer: " + reviewerIdentToFix),
                     getAuthorIdent(changeOwner.getAccount())))
             .add(
                 writeUpdate(
                     RefNames.changeMetaRef(c.getId()),
-                    getChangeUpdateBody(c, /*changeMessage=*/ null, "CC: " + reviewerIdentToFix),
+                    // valid change message that should not be overwritten
+                    getChangeUpdateBody(
+                        c,
+                        "Removed cc <GERRIT_ACCOUNT_2> with the following votes:\n\n * Code-Review+2 by <GERRIT_ACCOUNT_2>",
+                        "CC: " + reviewerIdentToFix),
                     getAuthorIdent(otherUser.getAccount())))
             .add(
                 writeUpdate(
@@ -299,23 +347,29 @@
 
     ImmutableList<RevCommit> commitsAfterRewrite = logMetaRef(repo, metaRefAfterRewrite);
     assertValidCommits(commitsBeforeRewrite, commitsAfterRewrite, invalidCommits);
+    assertFixedCommits(commitsToFix, result, c.getId());
 
-    List<String> commitHistoryDiff = result.fixedRefDiff.get(RefNames.changeMetaRef(c.getId()));
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
     assertThat(commitHistoryDiff)
         .containsExactly(
-            "@@ -7 +7 @@\n"
+            "@@ -9 +9 @@\n"
                 + "-Reviewer: Other Account <2@gerrit>\n"
                 + "+Reviewer: Gerrit User 2 <2@gerrit>\n",
-            "@@ -7 +7 @@\n" + "-CC: Other Account <2@gerrit>\n" + "+CC: Gerrit User 2 <2@gerrit>\n",
+            "@@ -11 +11 @@\n"
+                + "-CC: Other Account <2@gerrit>\n"
+                + "+CC: Gerrit User 2 <2@gerrit>\n",
             "@@ -9 +9 @@\n"
                 + "-Removed: Other Account <2@gerrit>\n"
                 + "+Removed: Gerrit User 2 <2@gerrit>\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   @Test
   public void fixReviewerMessage() throws Exception {
     Change c = newChange();
-    ImmutableList.Builder<RevCommit> commitsToFix = new ImmutableList.Builder<>();
+    ImmutableList.Builder<ObjectId> commitsToFix = new ImmutableList.Builder<>();
     ChangeUpdate addReviewerUpdate = newUpdate(c, changeOwner);
     addReviewerUpdate.putReviewer(otherUserId, REVIEWER);
     addReviewerUpdate.commit();
@@ -325,7 +379,7 @@
             RefNames.changeMetaRef(c.getId()),
             getChangeUpdateBody(
                 c,
-                "Removed reviewer " + otherUser.getAccount().fullName(),
+                String.format("Removed reviewer %s.", otherUser.getAccount().fullName()),
                 "Removed: " + getValidIdentAsString(otherUser.getAccount())),
             getAuthorIdent(changeOwner.getAccount())));
 
@@ -338,7 +392,9 @@
             RefNames.changeMetaRef(c.getId()),
             getChangeUpdateBody(
                 c,
-                "Removed cc " + otherUser.getAccount().fullName(),
+                String.format(
+                    "Removed cc %s with the following votes:\n\n * Code-Review+2",
+                    otherUser.getAccount().fullName()),
                 "Removed: " + getValidIdentAsString(otherUser.getAccount())),
             getAuthorIdent(changeOwner.getAccount())));
 
@@ -378,7 +434,9 @@
 
     assertThat(notesBeforeRewrite.getReviewerUpdates()).isEqualTo(expectedReviewerUpdates);
     assertThat(changeMessages(notesBeforeRewrite))
-        .containsExactly("Removed reviewer Other Account", "Removed cc Other Account");
+        .containsExactly(
+            "Removed reviewer Other Account.",
+            "Removed cc Other Account with the following votes:\n\n * Code-Review+2");
     assertThat(notesAfterRewrite.getReviewerUpdates()).isEqualTo(expectedReviewerUpdates);
     assertThat(changeMessages(notesAfterRewrite)).containsExactly("Removed reviewer", "Removed cc");
 
@@ -387,12 +445,58 @@
 
     ImmutableList<RevCommit> commitsAfterRewrite = logMetaRef(repo, metaRefAfterRewrite);
     assertValidCommits(commitsBeforeRewrite, commitsAfterRewrite, invalidCommits);
+    assertFixedCommits(commitsToFix.build(), result, c.getId());
 
-    List<String> commitHistoryDiff = result.fixedRefDiff.get(RefNames.changeMetaRef(c.getId()));
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
     assertThat(commitHistoryDiff)
         .containsExactly(
-            "@@ -6 +6 @@\n" + "-Removed reviewer Other Account\n" + "+Removed reviewer\n",
-            "@@ -6 +6 @@\n" + "-Removed cc Other Account\n" + "+Removed cc\n");
+            "@@ -6 +6 @@\n" + "-Removed reviewer Other Account.\n" + "+Removed reviewer\n",
+            "@@ -6,3 +6 @@\n"
+                + "-Removed cc Other Account with the following votes:\n"
+                + "-\n"
+                + "- * Code-Review+2\n"
+                + "+Removed cc\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
+  }
+
+  @Test
+  public void fixReviewerMessageNoReviewerFooter() throws Exception {
+    Change c = newChange();
+
+    writeUpdate(
+        RefNames.changeMetaRef(c.getId()),
+        getChangeUpdateBody(
+            c, String.format("Removed reviewer %s.", otherUser.getAccount().fullName())),
+        getAuthorIdent(changeOwner.getAccount()));
+
+    writeUpdate(
+        RefNames.changeMetaRef(c.getId()),
+        getChangeUpdateBody(
+            c,
+            String.format(
+                "Removed cc %s with the following votes:\n\n * Code-Review+2",
+                otherUser.getAccount().fullName())),
+        getAuthorIdent(changeOwner.getAccount()));
+
+    RunOptions options = new RunOptions();
+    options.dryRun = false;
+    BackfillResult result = rewriter.backfillProject(project, repo, options);
+    assertThat(result.fixedRefDiff.keySet()).containsExactly(RefNames.changeMetaRef(c.getId()));
+
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
+    assertThat(commitHistoryDiff)
+        .containsExactly(
+            "@@ -6 +6 @@\n" + "-Removed reviewer Other Account.\n" + "+Removed reviewer\n",
+            "@@ -6,3 +6 @@\n"
+                + "-Removed cc Other Account with the following votes:\n"
+                + "-\n"
+                + "- * Code-Review+2\n"
+                + "+Removed cc\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   @Test
@@ -404,8 +508,8 @@
     approvalUpdateByOtherUser.putApproval(VERIFIED, (short) -1);
     approvalUpdateByOtherUser.commit();
 
-    ImmutableList<RevCommit> commitsToFix =
-        new ImmutableList.Builder<RevCommit>()
+    ImmutableList<ObjectId> commitsToFix =
+        new ImmutableList.Builder<ObjectId>()
             .add(
                 writeUpdate(
                     RefNames.changeMetaRef(c.getId()),
@@ -506,8 +610,9 @@
 
     ImmutableList<RevCommit> commitsAfterRewrite = logMetaRef(repo, metaRefAfterRewrite);
     assertValidCommits(commitsBeforeRewrite, commitsAfterRewrite, invalidCommits);
+    assertFixedCommits(commitsToFix, result, c.getId());
 
-    List<String> commitHistoryDiff = result.fixedRefDiff.get(RefNames.changeMetaRef(c.getId()));
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
     assertThat(commitHistoryDiff)
         .containsExactly(
             "@@ -7,2 +7,2 @@\n"
@@ -523,6 +628,9 @@
             "@@ -7 +7 @@\n"
                 + "-Label: -Verified Change Owner <1@gerrit>\n"
                 + "+Label: -Verified Gerrit User 1 <1@gerrit>\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   @Test
@@ -535,8 +643,8 @@
     approvalUpdateByOtherUser.putApprovalFor(changeOwner.getAccountId(), VERIFIED, (short) -1);
     approvalUpdateByOtherUser.commit();
 
-    ImmutableList<RevCommit> commitsToFix =
-        new ImmutableList.Builder<RevCommit>()
+    ImmutableList<ObjectId> commitsToFix =
+        new ImmutableList.Builder<ObjectId>()
             .add(
                 writeUpdate(
                     RefNames.changeMetaRef(c.getId()),
@@ -626,8 +734,9 @@
 
     ImmutableList<RevCommit> commitsAfterRewrite = logMetaRef(repo, metaRefAfterRewrite);
     assertValidCommits(commitsBeforeRewrite, commitsAfterRewrite, invalidCommits);
+    assertFixedCommits(commitsToFix, result, c.getId());
 
-    List<String> commitHistoryDiff = result.fixedRefDiff.get(RefNames.changeMetaRef(c.getId()));
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
     assertThat(commitHistoryDiff)
         .containsExactly(
             "@@ -6 +6 @@\n"
@@ -642,6 +751,9 @@
             "@@ -6 +6 @@\n"
                 + "-Removed Verified+2 by Change Owner <change@owner.com>\n"
                 + "+Removed Verified+2 by <GERRIT_ACCOUNT_1>\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   @Test
@@ -657,7 +769,7 @@
         RefNames.changeMetaRef(c.getId()),
         getChangeUpdateBody(
             c,
-            /*changeMessage=*/ "Removed Verified+2 by " + changeOwner.getNameEmail(),
+            /*changeMessage=*/ "Removed Verified+2 by " + otherUser.getNameEmail(),
             "Label: -Verified"),
         invalidAuthorIdent);
 
@@ -666,12 +778,123 @@
     BackfillResult result = rewriter.backfillProject(project, repo, options);
     assertThat(result.fixedRefDiff.keySet()).containsExactly(RefNames.changeMetaRef(c.getId()));
 
-    List<String> commitHistoryDiff = result.fixedRefDiff.get(RefNames.changeMetaRef(c.getId()));
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
+    // Other Account does not applier in any change updates, replaced with default
     assertThat(commitHistoryDiff)
         .containsExactly(
             "@@ -6 +6 @@\n"
-                + "-Removed Verified+2 by Change Owner <change@owner.com>\n"
+                + "-Removed Verified+2 by Other Account <other@account.com>\n"
                 + "+Removed Verified+2 by Gerrit Account\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
+  }
+
+  @Test
+  public void fixRemoveVoteChangeMessageWithNoFooterLabel() throws Exception {
+    Change c = newChange();
+    ChangeUpdate approvalUpdate = newUpdate(c, changeOwner);
+    approvalUpdate.putApproval(VERIFIED, (short) +2);
+
+    approvalUpdate.putApprovalFor(otherUserId, VERIFIED, (short) -1);
+    approvalUpdate.commit();
+    writeUpdate(
+        RefNames.changeMetaRef(c.getId()),
+
+        // Even though footer is missing, accounts are matched among the account in change updates.
+        getChangeUpdateBody(c, /*changeMessage=*/ "Removed Verified-1 by Other Account (0002)"),
+        getAuthorIdent(changeOwner.getAccount()));
+
+    writeUpdate(
+        RefNames.changeMetaRef(c.getId()),
+        getChangeUpdateBody(
+            c, /*changeMessage=*/ "Removed Verified+2 by " + changeOwner.getNameEmail()),
+        getAuthorIdent(changeOwner.getAccount()));
+
+    // No rewrite for default
+    writeUpdate(
+        RefNames.changeMetaRef(c.getId()),
+        getChangeUpdateBody(c, /*changeMessage=*/ "Removed Verified+2 by Gerrit Account"),
+        getAuthorIdent(changeOwner.getAccount()));
+
+    RunOptions options = new RunOptions();
+    options.dryRun = false;
+    BackfillResult result = rewriter.backfillProject(project, repo, options);
+    assertThat(result.fixedRefDiff.keySet()).containsExactly(RefNames.changeMetaRef(c.getId()));
+
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
+    assertThat(commitHistoryDiff)
+        .containsExactly(
+            "@@ -6 +6 @@\n"
+                + "-Removed Verified-1 by Other Account (0002)\n"
+                + "+Removed Verified-1 by <GERRIT_ACCOUNT_2>\n",
+            "@@ -6 +6 @@\n"
+                + "-Removed Verified+2 by Change Owner <change@owner.com>\n"
+                + "+Removed Verified+2 by <GERRIT_ACCOUNT_1>\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
+  }
+
+  @Test
+  public void fixRemoveVotesChangeMessage() throws Exception {
+    Change c = newChange();
+    ChangeUpdate approvalUpdate = newUpdate(c, changeOwner);
+    approvalUpdate.putApproval(VERIFIED, (short) +2);
+
+    approvalUpdate.putApprovalFor(otherUserId, VERIFIED, (short) -1);
+    approvalUpdate.commit();
+    writeUpdate(
+        RefNames.changeMetaRef(c.getId()),
+
+        // Even though footer is missing, accounts are matched among the account in change updates.
+        getChangeUpdateBody(
+            c,
+            /*changeMessage=*/ "Removed the following votes:\n"
+                + String.format("* Verified-1 by %s\n", otherUser.getNameEmail())),
+        getAuthorIdent(changeOwner.getAccount()));
+
+    writeUpdate(
+        RefNames.changeMetaRef(c.getId()),
+        getChangeUpdateBody(
+            c,
+            /*changeMessage=*/ "Removed the following votes:\n"
+                + String.format("* Verified+2 by %s\n", changeOwner.getNameEmail())
+                + String.format("* Verified-1 by %s\n", changeOwner.getNameEmail())
+                + String.format("* Code-Review by %s\n", otherUser.getNameEmail())),
+        getAuthorIdent(changeOwner.getAccount()));
+
+    // No rewrite for default
+    writeUpdate(
+        RefNames.changeMetaRef(c.getId()),
+        getChangeUpdateBody(
+            c,
+            /*changeMessage=*/ "Removed the following votes:\n"
+                + "* Verified+2 by Gerrit Account\n"
+                + "* Verified-1 by <GERRIT_ACCOUNT_2>\n"),
+        getAuthorIdent(changeOwner.getAccount()));
+
+    RunOptions options = new RunOptions();
+    options.dryRun = false;
+    BackfillResult result = rewriter.backfillProject(project, repo, options);
+    assertThat(result.fixedRefDiff.keySet()).containsExactly(RefNames.changeMetaRef(c.getId()));
+
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
+    assertThat(commitHistoryDiff)
+        .containsExactly(
+            "@@ -7 +7 @@\n"
+                + "-* Verified-1 by Other Account <other@account.com>\n"
+                + "+* Verified-1 by <GERRIT_ACCOUNT_2>\n",
+            "@@ -7,3 +7,3 @@\n"
+                + "-* Verified+2 by Change Owner <change@owner.com>\n"
+                + "-* Verified-1 by Change Owner <change@owner.com>\n"
+                + "-* Code-Review by Other Account <other@account.com>\n"
+                + "+* Verified+2 by <GERRIT_ACCOUNT_1>\n"
+                + "+* Verified-1 by <GERRIT_ACCOUNT_1>\n"
+                + "+* Code-Review by <GERRIT_ACCOUNT_2>\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   @Test
@@ -726,8 +949,19 @@
     ChangeUpdate validAttentionSetUpdate = newUpdate(c, changeOwner);
     validAttentionSetUpdate.addToPlannedAttentionSetUpdates(
         AttentionSetUpdate.createForWrite(otherUserId, Operation.REMOVE, "Removed by someone"));
+    validAttentionSetUpdate.addToPlannedAttentionSetUpdates(
+        AttentionSetUpdate.createForWrite(
+            changeOwner.getAccountId(), Operation.ADD, "Added by someone"));
     validAttentionSetUpdate.commit();
 
+    ChangeUpdate invalidRemovedByClickUpdate = newUpdate(c, changeOwner);
+    invalidRemovedByClickUpdate.addToPlannedAttentionSetUpdates(
+        AttentionSetUpdate.createForWrite(
+            changeOwner.getAccountId(),
+            Operation.REMOVE,
+            String.format("Removed by %s by clicking the attention icon", otherUser.getName())));
+    commitsToFix.add(invalidRemovedByClickUpdate.commit());
+
     Ref metaRefBeforeRewrite = repo.exactRef(RefNames.changeMetaRef(c.getId()));
 
     ImmutableList<RevCommit> commitsBeforeRewrite = logMetaRef(repo, metaRefBeforeRewrite);
@@ -747,6 +981,16 @@
     ImmutableList<AttentionSetUpdate> attentionSetUpdatesBeforeRewrite =
         ImmutableList.of(
             AttentionSetUpdate.createFromRead(
+                invalidRemovedByClickUpdate.getWhen().toInstant(),
+                changeOwner.getAccountId(),
+                Operation.REMOVE,
+                String.format("Removed by %s by clicking the attention icon", otherUser.getName())),
+            AttentionSetUpdate.createFromRead(
+                validAttentionSetUpdate.getWhen().toInstant(),
+                changeOwner.getAccountId(),
+                Operation.ADD,
+                "Added by someone"),
+            AttentionSetUpdate.createFromRead(
                 validAttentionSetUpdate.getWhen().toInstant(),
                 otherUserId,
                 Operation.REMOVE,
@@ -780,6 +1024,16 @@
     ImmutableList<AttentionSetUpdate> attentionSetUpdatesAfterRewrite =
         ImmutableList.of(
             AttentionSetUpdate.createFromRead(
+                invalidRemovedByClickUpdate.getWhen().toInstant(),
+                changeOwner.getAccountId(),
+                Operation.REMOVE,
+                "Removed by someone by clicking the attention icon"),
+            AttentionSetUpdate.createFromRead(
+                validAttentionSetUpdate.getWhen().toInstant(),
+                changeOwner.getAccountId(),
+                Operation.ADD,
+                "Added by someone"),
+            AttentionSetUpdate.createFromRead(
                 validAttentionSetUpdate.getWhen().toInstant(),
                 otherUserId,
                 Operation.REMOVE,
@@ -822,9 +1076,10 @@
 
     ImmutableList<RevCommit> commitsAfterRewrite = logMetaRef(repo, metaRefAfterRewrite);
     assertValidCommits(commitsBeforeRewrite, commitsAfterRewrite, invalidCommits);
+    assertFixedCommits(commitsToFix.build(), result, c.getId());
 
-    List<String> commitHistoryDiff = result.fixedRefDiff.get(RefNames.changeMetaRef(c.getId()));
-    assertThat(commitHistoryDiff).hasSize(3);
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
+    assertThat(commitHistoryDiff).hasSize(4);
     assertThat(commitHistoryDiff.get(0))
         .isEqualTo(
             "@@ -8 +8 @@\n"
@@ -844,6 +1099,14 @@
             "-Attention: {\"person_ident\":\"Change Owner \\u003c1@gerrit\\u003e\",\"operation\":\"REMOVE\",\"reason\":\"Other Account replied on the change\"}",
             "+Attention: {\"person_ident\":\"Gerrit User 2 \\u003c2@gerrit\\u003e\",\"operation\":\"ADD\",\"reason\":\"Added by someone using the hovercard menu\"}",
             "+Attention: {\"person_ident\":\"Gerrit User 1 \\u003c1@gerrit\\u003e\",\"operation\":\"REMOVE\",\"reason\":\"Someone replied on the change\"}");
+    assertThat(commitHistoryDiff.get(3))
+        .isEqualTo(
+            "@@ -7 +7 @@\n"
+                + "-Attention: {\"person_ident\":\"Gerrit User 1 \\u003c1@gerrit\\u003e\",\"operation\":\"REMOVE\",\"reason\":\"Removed by Other Account by clicking the attention icon\"}\n"
+                + "+Attention: {\"person_ident\":\"Gerrit User 1 \\u003c1@gerrit\\u003e\",\"operation\":\"REMOVE\",\"reason\":\"Removed by someone by clicking the attention icon\"}\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   @Test
@@ -882,16 +1145,19 @@
               Operation.REMOVE,
               String.format("Removed by %s using the hovercard menu", okAccountName)));
       secondAttentionSetUpdate.commit();
-      ChangeUpdate clearAttentionSetUpdate = newUpdate(c, changeOwner);
-      clearAttentionSetUpdate.addToPlannedAttentionSetUpdates(
-          AttentionSetUpdate.createForWrite(changeOwner.getAccountId(), Operation.REMOVE, "Clear"));
-      clearAttentionSetUpdate.commit();
-      attentionSetUpdatesBeforeRewrite.add(
-          AttentionSetUpdate.createFromRead(
-              clearAttentionSetUpdate.getWhen().toInstant(),
+      ChangeUpdate thirdAttentionSetUpdate = newUpdate(c, changeOwner);
+      thirdAttentionSetUpdate.addToPlannedAttentionSetUpdates(
+          AttentionSetUpdate.createForWrite(
               changeOwner.getAccountId(),
               Operation.REMOVE,
-              "Clear"),
+              String.format("Removed by %s by clicking the attention icon", okAccountName)));
+      thirdAttentionSetUpdate.commit();
+      attentionSetUpdatesBeforeRewrite.add(
+          AttentionSetUpdate.createFromRead(
+              thirdAttentionSetUpdate.getWhen().toInstant(),
+              changeOwner.getAccountId(),
+              Operation.REMOVE,
+              String.format("Removed by %s by clicking the attention icon", okAccountName)),
           AttentionSetUpdate.createFromRead(
               secondAttentionSetUpdate.getWhen().toInstant(),
               otherUserId,
@@ -907,7 +1173,6 @@
               otherUserId,
               Operation.ADD,
               String.format("Added by %s using the hovercard menu", okAccountName)));
-      clearAttentionSetUpdate.getNotes();
     }
 
     ChangeNotes notesBeforeRewrite = newNotes(c);
@@ -924,6 +1189,9 @@
     assertThat(notesBeforeRewrite.getMetaId()).isEqualTo(notesAfterRewrite.getMetaId());
     assertThat(metaRefBefore.getObjectId()).isEqualTo(metaRefAfter.getObjectId());
     assertThat(backfillResult.fixedRefDiff).isEmpty();
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   @Test
@@ -934,19 +1202,19 @@
     invalidMergedMessageUpdate.setChangeMessage(
         "Change has been successfully merged by " + changeOwner.getName());
     invalidMergedMessageUpdate.setTopic("");
-    invalidMergedMessageUpdate.setTag(ChangeMessagesUtil.TAG_MERGED);
+
     commitsToFix.add(invalidMergedMessageUpdate.commit());
     ChangeUpdate invalidCherryPickedMessageUpdate = newUpdate(c, changeOwner);
     invalidCherryPickedMessageUpdate.setChangeMessage(
         "Change has been successfully cherry-picked as e40dc1a50dc7f457a37579e2755374f3e1a5413b by "
             + changeOwner.getName());
-    invalidCherryPickedMessageUpdate.setTag(ChangeMessagesUtil.TAG_MERGED);
+
     commitsToFix.add(invalidCherryPickedMessageUpdate.commit());
     ChangeUpdate invalidRebasedMessageUpdate = newUpdate(c, changeOwner);
     invalidRebasedMessageUpdate.setChangeMessage(
         "Change has been successfully rebased and submitted as e40dc1a50dc7f457a37579e2755374f3e1a5413b by "
             + changeOwner.getName());
-    invalidRebasedMessageUpdate.setTag(ChangeMessagesUtil.TAG_MERGED);
+
     commitsToFix.add(invalidRebasedMessageUpdate.commit());
     ChangeUpdate validSubmitMessageUpdate = newUpdate(c, changeOwner);
     validSubmitMessageUpdate.setChangeMessage(
@@ -988,8 +1256,9 @@
 
     ImmutableList<RevCommit> commitsAfterRewrite = logMetaRef(repo, metaRefAfterRewrite);
     assertValidCommits(commitsBeforeRewrite, commitsAfterRewrite, invalidCommits);
+    assertFixedCommits(commitsToFix.build(), result, c.getId());
 
-    List<String> commitHistoryDiff = result.fixedRefDiff.get(RefNames.changeMetaRef(c.getId()));
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
     assertThat(commitHistoryDiff)
         .containsExactly(
             "@@ -6 +6 @@\n"
@@ -1001,6 +1270,9 @@
             "@@ -6 +6 @@\n"
                 + "-Change has been successfully rebased and submitted as e40dc1a50dc7f457a37579e2755374f3e1a5413b by Change Owner\n"
                 + "+Change has been successfully rebased and submitted as e40dc1a50dc7f457a37579e2755374f3e1a5413b\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   @Test
@@ -1032,7 +1304,7 @@
     BackfillResult result = rewriter.backfillProject(project, repo, options);
     assertThat(result.fixedRefDiff.keySet()).containsExactly(RefNames.changeMetaRef(c.getId()));
 
-    List<String> commitHistoryDiff = result.fixedRefDiff.get(RefNames.changeMetaRef(c.getId()));
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
     assertThat(commitHistoryDiff)
         .containsExactly(
             "@@ -1 +1 @@\n"
@@ -1047,6 +1319,9 @@
                 + "@@ -15 +15 @@\n"
                 + "-Submitted-with: OK: Code-Review: Change Owner <1@gerrit>\n"
                 + "+Submitted-with: OK: Code-Review: Gerrit User 1 <1@gerrit>\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   @Test
@@ -1108,8 +1383,9 @@
     ImmutableList<RevCommit> commitsAfterRewrite = logMetaRef(repo, metaRefAfterRewrite);
     assertValidCommits(
         commitsBeforeRewrite, commitsAfterRewrite, ImmutableList.of(invalidCommitIndex));
+    assertFixedCommits(ImmutableList.of(invalidUpdateCommit.getId()), result, c.getId());
 
-    List<String> commitHistoryDiff = result.fixedRefDiff.get(RefNames.changeMetaRef(c.getId()));
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
     assertThat(commitHistoryDiff)
         .containsExactly(
             "@@ -10 +10 @@\n"
@@ -1118,6 +1394,9 @@
                 + "@@ -12 +12 @@\n"
                 + "-Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
                 + "+Submitted-with: OK: Verified: Gerrit User 1 <1@gerrit>\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   @Test
@@ -1179,8 +1458,9 @@
 
     ImmutableList<RevCommit> commitsAfterRewrite = logMetaRef(repo, metaRefAfterRewrite);
     assertValidCommits(commitsBeforeRewrite, commitsAfterRewrite, invalidCommits);
+    assertFixedCommits(commitsToFix.build(), result, c.getId());
 
-    List<String> commitHistoryDiff = result.fixedRefDiff.get(RefNames.changeMetaRef(c.getId()));
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
     assertThat(commitHistoryDiff)
         .containsExactly(
             "@@ -6 +6 @@\n"
@@ -1189,6 +1469,9 @@
             "@@ -6 +6 @@\n"
                 + "-Change message removed by: Change Owner\n"
                 + "+Change message removed\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   @Test
@@ -1288,8 +1571,9 @@
 
     ImmutableList<RevCommit> commitsAfterRewrite = logMetaRef(repo, metaRefAfterRewrite);
     assertValidCommits(commitsBeforeRewrite, commitsAfterRewrite, invalidCommits);
+    assertFixedCommits(commitsToFix.build(), result, c.getId());
 
-    List<String> commitHistoryDiff = result.fixedRefDiff.get(RefNames.changeMetaRef(c.getId()));
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
     assertThat(commitHistoryDiff)
         .containsExactly(
             "@@ -6 +6 @@\n"
@@ -1307,6 +1591,9 @@
             "@@ -6 +6 @@\n"
                 + "-Reviewer User who was added as reviewer owns the following files:\n"
                 + "+Gerrit Account, who was added as reviewer owns the following files:\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   @Test
@@ -1315,30 +1602,51 @@
     Change c = newChange();
     ImmutableList.Builder<ObjectId> commitsToFix = new ImmutableList.Builder<>();
 
-    ChangeUpdate invalidOnApprovalUpdate = newUpdate(c, changeOwner);
-    invalidOnApprovalUpdate.setChangeMessage(
-        "Patch Set 1: Code-Review+2\n\n"
+    ChangeUpdate invalidOnReviewUpdate = newUpdate(c, changeOwner);
+    invalidOnReviewUpdate.setChangeMessage(
+        "Patch Set 1: Any-Label+2 Other-Label+2 Code-Review+2\n\n"
+            + "By voting Code-Review+2 the following files are now code-owner approved by Change Owner:\n"
+            + "   * file1.java\n"
+            + "   * file2.ts\n"
+            + "By voting Any-Label+2 the code-owners submit requirement is overridden by Change Owner\n"
+            + "By voting Other-Label+2 the code-owners submit requirement is still overridden by Change Owner\n");
+    commitsToFix.add(invalidOnReviewUpdate.commit());
+
+    ChangeUpdate invalidOnReviewUpdateAnyOrder = newUpdate(c, changeOwner);
+    invalidOnReviewUpdateAnyOrder.setChangeMessage(
+        "Patch Set 1: Any-Label+2 Other-Label+2 Code-Review+2\n\n"
+            + "By voting Any-Label+2 the code-owners submit requirement is overridden by Change Owner\n"
+            + "By voting Other-Label+2 the code-owners submit requirement is still overridden by Change Owner\n"
             + "By voting Code-Review+2 the following files are now code-owner approved by Change Owner:\n"
             + "   * file1.java\n"
             + "   * file2.ts\n");
-    commitsToFix.add(invalidOnApprovalUpdate.commit());
-    ChangeUpdate invalidOnApprovalMultipleByUpdate = newUpdate(c, otherUser);
-    invalidOnApprovalMultipleByUpdate.setChangeMessage(
+    commitsToFix.add(invalidOnReviewUpdateAnyOrder.commit());
+    ChangeUpdate invalidOnApprovalUpdate = newUpdate(c, otherUser);
+    invalidOnApprovalUpdate.setChangeMessage(
         "Patch Set 1: -Code-Review\n\n"
             + "By removing the Code-Review+2 vote the following files are no longer explicitly code-owner approved by Other Account:\n"
             + "   * file1.java\n"
             + "   * file2.ts\n"
             + "\nThe listed files are still implicitly approved by Other Account.\n");
-    commitsToFix.add(invalidOnApprovalMultipleByUpdate.commit());
+    commitsToFix.add(invalidOnApprovalUpdate.commit());
 
     ChangeUpdate invalidOnOverrideUpdate = newUpdate(c, changeOwner);
     invalidOnOverrideUpdate.setChangeMessage(
         "Patch Set 1: -Owners-Override\n\n"
             + "(1 comment)\n\n"
-            + "By removing the Owners-Override+1 vote the code-owners submit requirement is no longer overridden by Change Owner:\n"
-            + "   * file3.java\n");
+            + "By removing the Owners-Override+1 vote the code-owners submit requirement is no longer overridden by Change Owner\n");
+
     commitsToFix.add(invalidOnOverrideUpdate.commit());
 
+    ChangeUpdate partiallyValidOnReviewUpdate = newUpdate(c, changeOwner);
+    partiallyValidOnReviewUpdate.setChangeMessage(
+        "Patch Set 1: Any-Label+2 Code-Review+2\n\n"
+            + "By voting Code-Review+2 the following files are now code-owner approved by <GERRIT_ACCOUNT_1>:\n"
+            + "   * file1.java\n"
+            + "   * file2.ts\n"
+            + "By voting Any-Label+2 the code-owners submit requirement is overridden by Change Owner\n");
+    commitsToFix.add(partiallyValidOnReviewUpdate.commit());
+
     ChangeUpdate validOnApprovalUpdate = newUpdate(c, changeOwner);
     validOnApprovalUpdate.setChangeMessage(
         "Patch Set 1: Code-Review-2\n\n"
@@ -1349,8 +1657,7 @@
     ChangeUpdate validOnOverrideUpdate = newUpdate(c, changeOwner);
     validOnOverrideUpdate.setChangeMessage(
         "Patch Set 1: Owners-Override+1\n\n"
-            + "By voting Owners-Override+1 the code-owners submit requirement is still overridden by <GERRIT_ACCOUNT_1>:\n"
-            + "   * file1.java\n");
+            + "By voting Owners-Override+1 the code-owners submit requirement is still overridden by <GERRIT_ACCOUNT_1>\n");
     validOnOverrideUpdate.commit();
 
     Ref metaRefBeforeRewrite = repo.exactRef(RefNames.changeMetaRef(c.getId()));
@@ -1370,11 +1677,18 @@
 
     ChangeNotes notesAfterRewrite = newNotes(c);
 
-    assertThat(changeMessages(notesBeforeRewrite)).hasSize(5);
+    assertThat(changeMessages(notesBeforeRewrite)).hasSize(7);
     assertThat(changeMessages(notesAfterRewrite))
         .containsExactly(
-            "Patch Set 1: Code-Review+2\n"
-                + "\n"
+            "Patch Set 1: Any-Label+2 Other-Label+2 Code-Review+2\n\n"
+                + "By voting Code-Review+2 the following files are now code-owner approved by <GERRIT_ACCOUNT_1>:\n"
+                + "   * file1.java\n"
+                + "   * file2.ts\n"
+                + "By voting Any-Label+2 the code-owners submit requirement is overridden by <GERRIT_ACCOUNT_1>\n"
+                + "By voting Other-Label+2 the code-owners submit requirement is still overridden by <GERRIT_ACCOUNT_1>\n",
+            "Patch Set 1: Any-Label+2 Other-Label+2 Code-Review+2\n\n"
+                + "By voting Any-Label+2 the code-owners submit requirement is overridden by <GERRIT_ACCOUNT_1>\n"
+                + "By voting Other-Label+2 the code-owners submit requirement is still overridden by <GERRIT_ACCOUNT_1>\n"
                 + "By voting Code-Review+2 the following files are now code-owner approved by <GERRIT_ACCOUNT_1>:\n"
                 + "   * file1.java\n"
                 + "   * file2.ts\n",
@@ -1388,27 +1702,43 @@
                 + "\n"
                 + "(1 comment)\n"
                 + "\n"
-                + "By removing the Owners-Override+1 vote the code-owners submit requirement is no longer overridden by <GERRIT_ACCOUNT_1>:\n"
-                + "   * file3.java\n",
+                + "By removing the Owners-Override+1 vote the code-owners submit requirement is no longer overridden by <GERRIT_ACCOUNT_1>\n",
+            "Patch Set 1: Any-Label+2 Code-Review+2\n\n"
+                + "By voting Code-Review+2 the following files are now code-owner approved by <GERRIT_ACCOUNT_1>:\n"
+                + "   * file1.java\n"
+                + "   * file2.ts\n"
+                + "By voting Any-Label+2 the code-owners submit requirement is overridden by <GERRIT_ACCOUNT_1>\n",
             "Patch Set 1: Code-Review-2\n\n"
                 + "By voting Code-Review-2 the following files are no longer explicitly code-owner approved by <GERRIT_ACCOUNT_1>:\n"
                 + "   * file4.java\n",
             "Patch Set 1: Owners-Override+1\n"
                 + "\n"
-                + "By voting Owners-Override+1 the code-owners submit requirement is still overridden by <GERRIT_ACCOUNT_1>:\n"
-                + "   * file1.java\n");
+                + "By voting Owners-Override+1 the code-owners submit requirement is still overridden by <GERRIT_ACCOUNT_1>\n");
 
     Ref metaRefAfterRewrite = repo.exactRef(RefNames.changeMetaRef(c.getId()));
     assertThat(metaRefAfterRewrite.getObjectId()).isNotEqualTo(metaRefBeforeRewrite.getObjectId());
 
     ImmutableList<RevCommit> commitsAfterRewrite = logMetaRef(repo, metaRefAfterRewrite);
     assertValidCommits(commitsBeforeRewrite, commitsAfterRewrite, invalidCommits);
+    assertFixedCommits(commitsToFix.build(), result, c.getId());
 
-    List<String> commitHistoryDiff = result.fixedRefDiff.get(RefNames.changeMetaRef(c.getId()));
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
     assertThat(commitHistoryDiff)
         .containsExactly(
             "@@ -8 +8 @@\n"
                 + "-By voting Code-Review+2 the following files are now code-owner approved by Change Owner:\n"
+                + "+By voting Code-Review+2 the following files are now code-owner approved by <GERRIT_ACCOUNT_1>:\n"
+                + "@@ -11,2 +11,2 @@\n"
+                + "-By voting Any-Label+2 the code-owners submit requirement is overridden by Change Owner\n"
+                + "-By voting Other-Label+2 the code-owners submit requirement is still overridden by Change Owner\n"
+                + "+By voting Any-Label+2 the code-owners submit requirement is overridden by <GERRIT_ACCOUNT_1>\n"
+                + "+By voting Other-Label+2 the code-owners submit requirement is still overridden by <GERRIT_ACCOUNT_1>\n",
+            "@@ -8,3 +8,3 @@\n"
+                + "-By voting Any-Label+2 the code-owners submit requirement is overridden by Change Owner\n"
+                + "-By voting Other-Label+2 the code-owners submit requirement is still overridden by Change Owner\n"
+                + "-By voting Code-Review+2 the following files are now code-owner approved by Change Owner:\n"
+                + "+By voting Any-Label+2 the code-owners submit requirement is overridden by <GERRIT_ACCOUNT_1>\n"
+                + "+By voting Other-Label+2 the code-owners submit requirement is still overridden by <GERRIT_ACCOUNT_1>\n"
                 + "+By voting Code-Review+2 the following files are now code-owner approved by <GERRIT_ACCOUNT_1>:\n",
             "@@ -8 +8 @@\n"
                 + "-By removing the Code-Review+2 vote the following files are no longer explicitly code-owner approved by Other Account:\n"
@@ -1417,8 +1747,14 @@
                 + "-The listed files are still implicitly approved by Other Account.\n"
                 + "+The listed files are still implicitly approved by <GERRIT_ACCOUNT_2>.\n",
             "@@ -10 +10 @@\n"
-                + "-By removing the Owners-Override+1 vote the code-owners submit requirement is no longer overridden by Change Owner:\n"
-                + "+By removing the Owners-Override+1 vote the code-owners submit requirement is no longer overridden by <GERRIT_ACCOUNT_1>:\n");
+                + "-By removing the Owners-Override+1 vote the code-owners submit requirement is no longer overridden by Change Owner\n"
+                + "+By removing the Owners-Override+1 vote the code-owners submit requirement is no longer overridden by <GERRIT_ACCOUNT_1>\n",
+            "@@ -11 +11 @@\n"
+                + "-By voting Any-Label+2 the code-owners submit requirement is overridden by Change Owner\n"
+                + "+By voting Any-Label+2 the code-owners submit requirement is overridden by <GERRIT_ACCOUNT_1>\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   @Test
@@ -1466,6 +1802,7 @@
     ImmutableList<RevCommit> commitsAfterRewrite = logMetaRef(repo, metaRefAfterRewrite);
     assertValidCommits(
         commitsBeforeRewrite, commitsAfterRewrite, ImmutableList.of(invalidCommitIndex));
+    assertFixedCommits(ImmutableList.of(invalidUpdateCommit.getId()), result, c.getId());
 
     RevCommit fixedUpdateCommit = commitsAfterRewrite.get(invalidCommitIndex);
     assertThat(invalidUpdateCommit.getAuthorIdent()).isEqualTo(fixedUpdateCommit.getAuthorIdent());
@@ -1478,20 +1815,23 @@
     String expectedFixedIdent = getValidIdentAsString(changeOwner.getAccount());
     assertThat(fixedUpdateCommit.getFullMessage()).contains(expectedFixedIdent);
 
-    List<String> commitHistoryDiff = result.fixedRefDiff.get(RefNames.changeMetaRef(c.getId()));
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
     assertThat(commitHistoryDiff)
         .containsExactly(
             "@@ -9 +9 @@\n"
                 + "-Assignee: Change Owner <1@gerrit>\n"
                 + "+Assignee: Gerrit User 1 <1@gerrit>\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   @Test
   public void fixAssigneeChangeMessage() throws Exception {
     Change c = newChange();
 
-    ImmutableList<RevCommit> commitsToFix =
-        new ImmutableList.Builder<RevCommit>()
+    ImmutableList<ObjectId> commitsToFix =
+        new ImmutableList.Builder<ObjectId>()
             .add(
                 writeUpdate(
                     RefNames.changeMetaRef(c.getId()),
@@ -1561,7 +1901,9 @@
 
     ImmutableList<RevCommit> commitsAfterRewrite = logMetaRef(repo, metaRefAfterRewrite);
     assertValidCommits(commitsBeforeRewrite, commitsAfterRewrite, invalidCommits);
-    List<String> commitHistoryDiff = result.fixedRefDiff.get(RefNames.changeMetaRef(c.getId()));
+    assertFixedCommits(commitsToFix, result, c.getId());
+
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
     assertThat(commitHistoryDiff)
         .containsExactly(
             "@@ -6 +6 @@\n"
@@ -1577,6 +1919,70 @@
                 + "@@ -9 +9 @@\n"
                 + "-Assignee:\n"
                 + "+Assignee: \n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
+  }
+
+  @Test
+  public void fixAssigneeChangeMessageNoAssigneeFooter() throws Exception {
+    Change c = newChange();
+    writeUpdate(
+        RefNames.changeMetaRef(c.getId()),
+        getChangeUpdateBody(c, "Assignee added: " + changeOwner.getName()),
+        getAuthorIdent(changeOwner.getAccount()));
+
+    writeUpdate(
+        RefNames.changeMetaRef(c.getId()),
+        getChangeUpdateBody(
+            c,
+            String.format(
+                "Assignee changed from: %s to: %s", changeOwner.getName(), otherUser.getName())),
+        getAuthorIdent(otherUser.getAccount()));
+    writeUpdate(
+        RefNames.changeMetaRef(c.getId()),
+        getChangeUpdateBody(c, "Assignee deleted: " + otherUser.getName()),
+        getAuthorIdent(changeOwner.getAccount()));
+    Account reviewer =
+        Account.builder(Account.id(3), TimeUtil.nowTs())
+            .setFullName("Reviewer User")
+            .setPreferredEmail("reviewer@account.com")
+            .build();
+    accountCache.put(reviewer);
+    // Even though account is present in the cache, it won't be used because it does not appear in
+    // the history of this change.
+    writeUpdate(
+        RefNames.changeMetaRef(c.getId()),
+        getChangeUpdateBody(c, "Assignee added: " + reviewer.getName()),
+        getAuthorIdent(changeOwner.getAccount()));
+
+    writeUpdate(
+        RefNames.changeMetaRef(c.getId()),
+        getChangeUpdateBody(c, "Assignee deleted: Gerrit Account"),
+        getAuthorIdent(changeOwner.getAccount()));
+
+    RunOptions options = new RunOptions();
+    options.dryRun = false;
+    BackfillResult result = rewriter.backfillProject(project, repo, options);
+    assertThat(result.fixedRefDiff.keySet()).containsExactly(RefNames.changeMetaRef(c.getId()));
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
+    assertThat(commitHistoryDiff)
+        .containsExactly(
+            "@@ -6 +6 @@\n"
+                + "-Assignee added: Change Owner\n"
+                + "+Assignee added: <GERRIT_ACCOUNT_1>\n",
+            "@@ -6 +6 @@\n"
+                + "-Assignee changed from: Change Owner to: Other Account\n"
+                + "+Assignee changed from: <GERRIT_ACCOUNT_1> to: <GERRIT_ACCOUNT_2>\n",
+            "@@ -6 +6 @@\n"
+                + "-Assignee deleted: Other Account\n"
+                + "+Assignee deleted: <GERRIT_ACCOUNT_2>\n",
+            "@@ -6 +6 @@\n"
+                + "-Assignee added: Reviewer User\n"
+                + "+Assignee added: Gerrit Account\n");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   @Test
@@ -1621,6 +2027,7 @@
     ImmutableList<RevCommit> commitsAfterRewrite = logMetaRef(repo, metaRefAfterRewrite);
     assertValidCommits(
         commitsBeforeRewrite, commitsAfterRewrite, ImmutableList.of(invalidCommitIndex));
+    assertFixedCommits(ImmutableList.of(invalidUpdateCommit.getId()), result, c.getId());
 
     RevCommit fixedUpdateCommit = commitsAfterRewrite.get(invalidCommitIndex);
     assertThat(invalidUpdateCommit.getAuthorIdent())
@@ -1629,7 +2036,7 @@
     assertThat(fixedUpdateCommit.getFullMessage()).doesNotContain(changeOwner.getName());
     assertThat(fixedUpdateCommit.getFullMessage()).doesNotContain(otherUser.getName());
 
-    List<String> commitHistoryDiff = result.fixedRefDiff.get(RefNames.changeMetaRef(c.getId()));
+    List<String> commitHistoryDiff = commitHistoryDiff(result, c.getId());
     assertThat(commitHistoryDiff).hasSize(1);
     assertThat(commitHistoryDiff.get(0)).contains("-author Change Owner <1@gerrit>");
     assertThat(commitHistoryDiff.get(0)).contains("+author Gerrit User 1 <1@gerrit>");
@@ -1641,6 +2048,9 @@
                 + "@@ -9 +9 @@\n"
                 + "-Assignee: Other Account <2@gerrit>\n"
                 + "+Assignee: Gerrit User 2 <2@gerrit>");
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    assertThat(secondRunResult.fixedRefDiff.keySet()).isEmpty();
+    assertThat(secondRunResult.refsFailedToFix).isEmpty();
   }
 
   private RevCommit writeUpdate(String metaRef, String body, PersonIdent author) throws Exception {
@@ -1702,6 +2112,15 @@
     }
   }
 
+  private void assertFixedCommits(
+      ImmutableList<ObjectId> expectedFixedCommits, BackfillResult result, Change.Id changeId) {
+    assertThat(expectedFixedCommits)
+        .containsExactlyElementsIn(
+            result.fixedRefDiff.get(RefNames.changeMetaRef(changeId)).stream()
+                .map(CommitDiff::oldSha1)
+                .collect(toImmutableList()));
+  }
+
   private String getAccountIdentToFix(Account account) {
     return String.format("%s <%s>", account.getName(), account.id().get() + "@" + serverId);
   }
@@ -1718,6 +2137,12 @@
         .collect(toImmutableList());
   }
 
+  private ImmutableList<String> commitHistoryDiff(BackfillResult result, Change.Id changeId) {
+    return result.fixedRefDiff.get(RefNames.changeMetaRef(changeId)).stream()
+        .map(CommitDiff::diff)
+        .collect(toImmutableList());
+  }
+
   private PersonIdent getAuthorIdent(Account account) {
     Timestamp when = TimeUtil.nowTs();
     return changeNoteUtil.newAccountIdIdent(account.id(), when, serverIdent);
diff --git a/javatests/com/google/gerrit/server/project/CommitsCollectionTest.java b/javatests/com/google/gerrit/server/project/CommitsCollectionTest.java
index 1035fe7..5daf68e 100644
--- a/javatests/com/google/gerrit/server/project/CommitsCollectionTest.java
+++ b/javatests/com/google/gerrit/server/project/CommitsCollectionTest.java
@@ -64,6 +64,7 @@
   @Inject protected AllProjectsName allProjects;
   @Inject private CommitsCollection commits;
   @Inject private ProjectOperations projectOperations;
+  @Inject private AuthRequest.Factory authRequestFactory;
 
   private TestRepository<InMemoryRepository> repo;
   private Project.NameKey project;
@@ -72,7 +73,8 @@
   public void setUp() throws Exception {
     setUpPermissions();
 
-    Account.Id user = accountManager.authenticate(AuthRequest.forUser("user")).getAccountId();
+    Account.Id user =
+        accountManager.authenticate(authRequestFactory.createForUser("user")).getAccountId();
     testEnvironment.setApiUser(user);
     project = projectOperations.newProject().create();
     repo = new TestRepository<>(repoManager.openRepository(project));
diff --git a/javatests/com/google/gerrit/server/project/ProjectConfigTest.java b/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
index 9130d3e..7f0b685 100644
--- a/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
+++ b/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
@@ -36,6 +36,7 @@
 import com.google.gerrit.entities.SubmitRequirementExpression;
 import com.google.gerrit.extensions.client.InheritableBoolean;
 import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.FileBasedAllProjectsConfigProvider;
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
@@ -68,7 +69,10 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
+@RunWith(JUnit4.class)
 public class ProjectConfigTest {
   private static final String LABEL_SCORES_CONFIG =
       "  copyAnyScore = "
@@ -113,7 +117,8 @@
   public void setUp() throws Exception {
     sitePaths = new SitePaths(temporaryFolder.newFolder().toPath());
     Files.createDirectories(sitePaths.etc_dir);
-    factory = new ProjectConfig.Factory(sitePaths, ALL_PROJECTS);
+    factory =
+        new ProjectConfig.Factory(ALL_PROJECTS, new FileBasedAllProjectsConfigProvider(sitePaths));
     db = new InMemoryRepository(new DfsRepositoryDescription("repo"));
     tr = new TestRepository<>(db);
   }
@@ -327,8 +332,8 @@
     assertThat(cfg.getValidationErrors()).hasSize(1);
     assertThat(Iterables.getOnlyElement(cfg.getValidationErrors()).getMessage())
         .isEqualTo(
-            "project.config: Submit requirement \"code-review\" does not define a submittability expression."
-                + " Skipping this requirement.");
+            "project.config: Submit requirement \"code-review\" does not define a submittability"
+                + " expression. Skipping this requirement.");
   }
 
   @Test
@@ -866,7 +871,9 @@
     rev = commit(cfg);
     assertThat(text(rev, "project.config"))
         .isEqualTo(
-            "[commentlink \"bugzilla\"]\n\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n");
+            "[commentlink \"bugzilla\"]\n"
+                + "\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n"
+                + "\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n");
   }
 
   @Test
@@ -891,7 +898,9 @@
     rev = commit(cfg);
     assertThat(text(rev, "project.config"))
         .isEqualTo(
-            "[commentlink \"bugzilla\"]\n\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n");
+            "[commentlink \"bugzilla\"]\n"
+                + "\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n"
+                + "\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n");
   }
 
   @Test
@@ -913,7 +922,9 @@
     rev = commit(cfg);
     assertThat(text(rev, "project.config"))
         .isEqualTo(
-            "[commentlink \"bugzilla\"]\n\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n");
+            "[commentlink \"bugzilla\"]\n"
+                + "\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n"
+                + "\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n");
   }
 
   @Test
@@ -978,7 +989,9 @@
     rev = commit(cfg);
     assertThat(text(rev, "project.config"))
         .isEqualTo(
-            "[commentlink \"bugzilla\"]\n\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n");
+            "[commentlink \"bugzilla\"]\n"
+                + "\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n"
+                + "\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n");
   }
 
   @Test
@@ -987,8 +1000,11 @@
     Path tmp = Files.createTempFile("gerrit_test_", "_site");
     Files.deleteIfExists(tmp);
     SitePaths sitePaths = new SitePaths(tmp);
+    FileBasedAllProjectsConfigProvider allProjectsConfigProvider =
+        new FileBasedAllProjectsConfigProvider(sitePaths);
     byte[] hashedContents =
-        ProjectCacheImpl.allProjectsFileProjectConfigHash(ALL_PROJECTS, sitePaths);
+        ProjectCacheImpl.allProjectsFileProjectConfigHash(
+            allProjectsConfigProvider.get(ALL_PROJECTS));
     assertThat(hashedContents).isEqualTo(new byte[16]); // Empty/absent config
     FileBasedConfig fileBasedConfig =
         new FileBasedConfig(
@@ -1000,7 +1016,9 @@
             FS.DETECTED);
     fileBasedConfig.setString("plugin", "my-plugin", "key", "value");
     fileBasedConfig.save();
-    hashedContents = ProjectCacheImpl.allProjectsFileProjectConfigHash(ALL_PROJECTS, sitePaths);
+    hashedContents =
+        ProjectCacheImpl.allProjectsFileProjectConfigHash(
+            allProjectsConfigProvider.get(ALL_PROJECTS));
     assertThat(hashedContents)
         .isEqualTo(
             new byte[] {
diff --git a/javatests/com/google/gerrit/server/project/SubmitRequirementsAdapterTest.java b/javatests/com/google/gerrit/server/project/SubmitRequirementsAdapterTest.java
new file mode 100644
index 0000000..2212fe7
--- /dev/null
+++ b/javatests/com/google/gerrit/server/project/SubmitRequirementsAdapterTest.java
@@ -0,0 +1,238 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.project;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.LabelFunction;
+import com.google.gerrit.entities.LabelType;
+import com.google.gerrit.entities.LabelValue;
+import com.google.gerrit.entities.SubmitRecord;
+import com.google.gerrit.entities.SubmitRecord.Label;
+import com.google.gerrit.entities.SubmitRecord.Status;
+import com.google.gerrit.entities.SubmitRequirementExpressionResult;
+import com.google.gerrit.entities.SubmitRequirementResult;
+import java.util.Arrays;
+import java.util.List;
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SubmitRequirementsAdapterTest {
+  private List<LabelType> labelTypes;
+
+  private static final ObjectId psCommitId = ObjectId.zeroId();
+
+  @Before
+  public void setup() {
+    LabelType codeReview =
+        LabelType.builder(
+                "Code-Review",
+                ImmutableList.of(
+                    LabelValue.create((short) 1, "Looks good to me"),
+                    LabelValue.create((short) 0, "No score"),
+                    LabelValue.create((short) -1, "I would prefer this is not merged as is")))
+            .setFunction(LabelFunction.MAX_WITH_BLOCK)
+            .build();
+
+    LabelType verified =
+        LabelType.builder(
+                "Verified",
+                ImmutableList.of(
+                    LabelValue.create((short) 1, "Looks good to me"),
+                    LabelValue.create((short) 0, "No score"),
+                    LabelValue.create((short) -1, "I would prefer this is not merged as is")))
+            .setFunction(LabelFunction.MAX_NO_BLOCK)
+            .build();
+
+    LabelType codeStyle =
+        LabelType.builder(
+                "Code-Style",
+                ImmutableList.of(
+                    LabelValue.create((short) 1, "Looks good to me"),
+                    LabelValue.create((short) 0, "No score"),
+                    LabelValue.create((short) -1, "I would prefer this is not merged as is")))
+            .setFunction(LabelFunction.ANY_WITH_BLOCK)
+            .build();
+
+    labelTypes = Arrays.asList(codeReview, verified, codeStyle);
+  }
+
+  @Test
+  public void defaultSubmitRule_withLabelsAllPass() {
+    SubmitRecord submitRecord =
+        createSubmitRecord(
+            "gerrit~DefaultSubmitRule",
+            Status.OK,
+            Arrays.asList(
+                createLabel("Code-Review", Label.Status.OK),
+                createLabel("Verified", Label.Status.OK)));
+
+    List<SubmitRequirementResult> requirements =
+        SubmitRequirementsAdapter.createResult(submitRecord, labelTypes, psCommitId);
+
+    assertThat(requirements).hasSize(2);
+    assertResult(
+        requirements.get(0),
+        /* reqName= */ "Code-Review",
+        /* submitExpression= */ "label:Code-Review=MAX -label:Code-Review=MIN",
+        SubmitRequirementResult.Status.SATISFIED,
+        SubmitRequirementExpressionResult.Status.PASS);
+    assertResult(
+        requirements.get(1),
+        /* reqName= */ "Verified",
+        /* submitExpression= */ "label:Verified=MAX",
+        SubmitRequirementResult.Status.SATISFIED,
+        SubmitRequirementExpressionResult.Status.PASS);
+  }
+
+  @Test
+  public void defaultSubmitRule_withLabelsAllNeed() {
+    SubmitRecord submitRecord =
+        createSubmitRecord(
+            "gerrit~DefaultSubmitRule",
+            Status.OK,
+            Arrays.asList(
+                createLabel("Code-Review", Label.Status.NEED),
+                createLabel("Verified", Label.Status.NEED)));
+
+    List<SubmitRequirementResult> requirements =
+        SubmitRequirementsAdapter.createResult(submitRecord, labelTypes, psCommitId);
+
+    assertThat(requirements).hasSize(2);
+    assertResult(
+        requirements.get(0),
+        /* reqName= */ "Code-Review",
+        /* submitExpression= */ "label:Code-Review=MAX -label:Code-Review=MIN",
+        SubmitRequirementResult.Status.UNSATISFIED,
+        SubmitRequirementExpressionResult.Status.FAIL);
+    assertResult(
+        requirements.get(1),
+        /* reqName= */ "Verified",
+        /* submitExpression= */ "label:Verified=MAX",
+        SubmitRequirementResult.Status.UNSATISFIED,
+        SubmitRequirementExpressionResult.Status.FAIL);
+  }
+
+  @Test
+  public void customSubmitRule_noLabels_withStatusOk() {
+    SubmitRecord submitRecord =
+        createSubmitRecord("gerrit~IgnoreSelfApprovalRule", Status.OK, Arrays.asList());
+
+    List<SubmitRequirementResult> requirements =
+        SubmitRequirementsAdapter.createResult(submitRecord, labelTypes, psCommitId);
+
+    assertThat(requirements).hasSize(1);
+    assertResult(
+        requirements.get(0),
+        /* reqName= */ "gerrit~IgnoreSelfApprovalRule",
+        /* submitExpression= */ "rule:gerrit~IgnoreSelfApprovalRule",
+        SubmitRequirementResult.Status.SATISFIED,
+        SubmitRequirementExpressionResult.Status.PASS);
+  }
+
+  @Test
+  public void customSubmitRule_nullLabels_withStatusOk() {
+    SubmitRecord submitRecord =
+        createSubmitRecord("gerrit~IgnoreSelfApprovalRule", Status.OK, /* labels= */ null);
+
+    List<SubmitRequirementResult> requirements =
+        SubmitRequirementsAdapter.createResult(submitRecord, labelTypes, psCommitId);
+
+    assertThat(requirements).hasSize(1);
+    assertResult(
+        requirements.get(0),
+        /* reqName= */ "gerrit~IgnoreSelfApprovalRule",
+        /* submitExpression= */ "rule:gerrit~IgnoreSelfApprovalRule",
+        SubmitRequirementResult.Status.SATISFIED,
+        SubmitRequirementExpressionResult.Status.PASS);
+  }
+
+  @Test
+  public void customSubmitRule_noLabels_withStatusNotReady() {
+    SubmitRecord submitRecord =
+        createSubmitRecord("gerrit~IgnoreSelfApprovalRule", Status.NOT_READY, Arrays.asList());
+
+    List<SubmitRequirementResult> requirements =
+        SubmitRequirementsAdapter.createResult(submitRecord, labelTypes, psCommitId);
+
+    assertThat(requirements).hasSize(1);
+    assertResult(
+        requirements.get(0),
+        /* reqName= */ "gerrit~IgnoreSelfApprovalRule",
+        /* submitExpression= */ "rule:gerrit~IgnoreSelfApprovalRule",
+        SubmitRequirementResult.Status.UNSATISFIED,
+        SubmitRequirementExpressionResult.Status.FAIL);
+  }
+
+  @Test
+  public void customSubmitRule_withLabels() {
+    SubmitRecord submitRecord =
+        createSubmitRecord(
+            "gerrit~PrologRule",
+            Status.NOT_READY,
+            Arrays.asList(
+                createLabel("custom-label-1", Label.Status.NEED),
+                createLabel("custom-label-2", Label.Status.REJECT)));
+
+    List<SubmitRequirementResult> requirements =
+        SubmitRequirementsAdapter.createResult(submitRecord, labelTypes, psCommitId);
+
+    assertThat(requirements).hasSize(2);
+    assertResult(
+        requirements.get(0),
+        /* reqName= */ "custom-label-1",
+        /* submitExpression= */ "label:custom-label-1=gerrit~PrologRule",
+        SubmitRequirementResult.Status.UNSATISFIED,
+        SubmitRequirementExpressionResult.Status.FAIL);
+    assertResult(
+        requirements.get(1),
+        /* reqName= */ "custom-label-2",
+        /* submitExpression= */ "label:custom-label-2=gerrit~PrologRule",
+        SubmitRequirementResult.Status.UNSATISFIED,
+        SubmitRequirementExpressionResult.Status.FAIL);
+  }
+
+  private void assertResult(
+      SubmitRequirementResult r,
+      String reqName,
+      String submitExpression,
+      SubmitRequirementResult.Status status,
+      SubmitRequirementExpressionResult.Status expressionStatus) {
+    assertThat(r.submitRequirement().name()).isEqualTo(reqName);
+    assertThat(r.submitRequirement().submittabilityExpression().expressionString())
+        .isEqualTo(submitExpression);
+    assertThat(r.status()).isEqualTo(status);
+    assertThat(r.submittabilityExpressionResult().status()).isEqualTo(expressionStatus);
+  }
+
+  private SubmitRecord createSubmitRecord(
+      String ruleName, SubmitRecord.Status status, @Nullable List<Label> labels) {
+    SubmitRecord record = new SubmitRecord();
+    record.ruleName = ruleName;
+    record.status = status;
+    record.labels = labels;
+    return record;
+  }
+
+  private Label createLabel(String name, Label.Status status) {
+    Label label = new Label();
+    label.label = name;
+    label.status = status;
+    return label;
+  }
+}
diff --git a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
index b7be40b..16f7199 100644
--- a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
+++ b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
@@ -65,6 +65,7 @@
 import com.google.gerrit.server.account.AccountsUpdate;
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AllUsersName;
@@ -139,6 +140,10 @@
 
   @Inject protected ExternalIds externalIds;
 
+  @Inject private ExternalIdKeyFactory externalIdKeyFactory;
+
+  @Inject protected AuthRequest.Factory authRequestFactory;
+
   protected LifecycleManager lifecycle;
   protected Injector injector;
   protected AccountInfo currentUserInfo;
@@ -659,7 +664,7 @@
     List<AccountExternalIdInfo> externalIdInfos = gApi.accounts().self().getExternalIds();
     List<ByteArrayWrapper> blobs = new ArrayList<>();
     for (AccountExternalIdInfo info : externalIdInfos) {
-      Optional<ExternalId> extId = externalIds.get(ExternalId.Key.parse(info.identity));
+      Optional<ExternalId> extId = externalIds.get(externalIdKeyFactory.parse(info.identity));
       assertThat(extId).isPresent();
       blobs.add(new ByteArrayWrapper(extId.get().toByteArray()));
     }
@@ -772,9 +777,10 @@
   private Account.Id createAccount(String username, String fullName, String email, boolean active)
       throws Exception {
     try (ManualRequestContext ctx = oneOffRequestContext.open()) {
-      Account.Id id = accountManager.authenticate(AuthRequest.forUser(username)).getAccountId();
+      Account.Id id =
+          accountManager.authenticate(authRequestFactory.createForUser(username)).getAccountId();
       if (email != null) {
-        accountManager.link(id, AuthRequest.forEmail(email));
+        accountManager.link(id, authRequestFactory.createForEmail(email));
       }
       accountsUpdate
           .get()
@@ -791,7 +797,7 @@
   private void addEmails(AccountInfo account, String... emails) throws Exception {
     Account.Id id = Account.id(account._accountId);
     for (String email : emails) {
-      accountManager.link(id, AuthRequest.forEmail(email));
+      accountManager.link(id, authRequestFactory.createForEmail(email));
     }
     accountIndexer.index(id);
   }
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 2d6f9b6..92cbc41 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -42,6 +42,9 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Streams;
 import com.google.common.truth.ThrowableSubject;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
+import com.google.gerrit.acceptance.FakeSubmitRule;
 import com.google.gerrit.acceptance.config.GerritConfig;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.Nullable;
@@ -68,7 +71,6 @@
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
 import com.google.gerrit.extensions.api.changes.ReviewerInput;
-import com.google.gerrit.extensions.api.changes.StarsInput;
 import com.google.gerrit.extensions.api.groups.GroupInput;
 import com.google.gerrit.extensions.api.projects.ConfigInput;
 import com.google.gerrit.extensions.api.projects.ProjectInput;
@@ -103,7 +105,7 @@
 import com.google.gerrit.server.account.AccountsUpdate;
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.VersionedAccountQueries;
-import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
 import com.google.gerrit.server.change.ChangeInserter;
 import com.google.gerrit.server.change.ChangeTriplet;
 import com.google.gerrit.server.change.NotifyResolver;
@@ -137,7 +139,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -174,6 +175,7 @@
   @Inject protected IdentifiedUser.GenericFactory userFactory;
   @Inject protected ChangeIndexCollection indexes;
   @Inject protected ChangeIndexer indexer;
+  @Inject protected ExtensionRegistry extensionRegistry;
   @Inject protected IndexConfig indexConfig;
   @Inject protected InMemoryRepositoryManager repoManager;
   @Inject protected Provider<AnonymousUser> anonymousUserProvider;
@@ -190,6 +192,8 @@
   @Inject protected ProjectCache projectCache;
   @Inject protected MetaDataUpdate.Server metaDataUpdateFactory;
   @Inject protected IdentifiedUser.GenericFactory identifiedUserFactory;
+  @Inject protected AuthRequest.Factory authRequestFactory;
+  @Inject protected ExternalIdFactory externalIdFactory;
 
   @Inject private ProjectConfig.Factory projectConfigFactory;
   @Inject private ProjectOperations projectOperations;
@@ -224,14 +228,16 @@
   protected void setUpDatabase() throws Exception {
     schemaCreator.create();
 
-    userId = accountManager.authenticate(AuthRequest.forUser("user")).getAccountId();
+    userId = accountManager.authenticate(authRequestFactory.createForUser("user")).getAccountId();
     String email = "user@example.com";
     accountsUpdate
         .get()
         .update(
             "Add Email",
             userId,
-            u -> u.addExternalId(ExternalId.createEmail(userId, email)).setPreferredEmail(email));
+            u ->
+                u.addExternalId(externalIdFactory.createEmail(userId, email))
+                    .setPreferredEmail(email));
     resetUser();
   }
 
@@ -415,7 +421,7 @@
     TestRepository<Repo> repo = createProject("repo");
     Change change1 = insert(repo, newChange(repo), userId);
     Account.Id user2 =
-        accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
     Change change2 = insert(repo, newChange(repo), user2);
 
     // No private changes.
@@ -583,7 +589,7 @@
     TestRepository<Repo> repo = createProject("repo");
     Change change1 = insert(repo, newChange(repo), userId);
     Account.Id user2 =
-        accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
     Change change2 = insert(repo, newChange(repo), user2);
 
     assertQuery("is:owner", change1);
@@ -628,7 +634,7 @@
     Change change1 = createChange(repo, johnDoe);
     Change change2 = createChange(repo, john);
     Change change3 = createChange(repo, doeSmith);
-    Change change4 = createChange(repo, selfName);
+    createChange(repo, selfName);
 
     // Only email address.
     assertQuery(searchOperator + "john.doe@example.com", change1);
@@ -697,7 +703,7 @@
     TestRepository<Repo> repo = createProject("repo");
     Change change1 = insert(repo, newChange(repo), userId);
     Account.Id user2 =
-        accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
     Change change2 = insert(repo, newChange(repo), user2);
     Change change3 = insert(repo, newChange(repo), user2);
     gApi.changes().id(change3.getId().get()).current().review(ReviewInput.approve());
@@ -987,7 +993,7 @@
 
   @Test
   public void byLabel() throws Exception {
-    accountManager.authenticate(AuthRequest.forUser("anotheruser"));
+    accountManager.authenticate(authRequestFactory.createForUser("anotheruser"));
     TestRepository<Repo> repo = createProject("repo");
     ChangeInserter ins = newChange(repo);
     ChangeInserter ins2 = newChange(repo);
@@ -1160,6 +1166,26 @@
     assertQuery("label:Code-Review=+1,owner");
   }
 
+  @Test
+  public void byLabelNonUploader() throws Exception {
+    TestRepository<Repo> repo = createProject("repo");
+    ChangeInserter ins = newChange(repo);
+    Account.Id user1 = createAccount("user1");
+
+    // create a change with "user"
+    Change reviewPlus1Change = insert(repo, ins);
+
+    // add a +1 vote with "user". Query doesn't match since voter is the uploader.
+    gApi.changes().id(reviewPlus1Change.getId().get()).current().review(ReviewInput.recommend());
+    assertQuery("label:Code-Review=+1,user=non_uploader");
+
+    // add a +1 vote with "user1". Query will match since voter is a non-uploader.
+    requestContext.setContext(newRequestContext(user1));
+    gApi.changes().id(reviewPlus1Change.getId().get()).current().review(ReviewInput.recommend());
+    assertQuery("label:Code-Review=+1,user=non_uploader", reviewPlus1Change);
+    assertQuery("label:Code-Review=+1,non_uploader", reviewPlus1Change);
+  }
+
   private Change[] codeReviewInRange(Map<Integer, Change> changes, int start, int end) {
     int size = 0;
     Change[] range = new Change[end - start + 1];
@@ -1181,7 +1207,7 @@
   }
 
   private Account.Id createAccount(String name) throws Exception {
-    return accountManager.authenticate(AuthRequest.forUser(name)).getAccountId();
+    return accountManager.authenticate(authRequestFactory.createForUser(name)).getAccountId();
   }
 
   @Test
@@ -1341,7 +1367,7 @@
     TestRepository<Repo> repo = createProject("repo");
     Change change = insert(repo, newChange(repo), userId);
     Account.Id user2 =
-        accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
     for (int i = 0; i < 5; i++) {
       insert(repo, newChange(repo), user2);
     }
@@ -1354,7 +1380,7 @@
   public void filterOutAllResults() throws Exception {
     TestRepository<Repo> repo = createProject("repo");
     Account.Id user2 =
-        accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
     for (int i = 0; i < 5; i++) {
       insert(repo, newChange(repo), user2);
     }
@@ -2140,11 +2166,11 @@
     Account.Id user3 = createAccount("user3");
 
     // Explicitly authenticate user2 and user3 so that display name gets set
-    AuthRequest authRequest = AuthRequest.forUser("user2");
+    AuthRequest authRequest = authRequestFactory.createForUser("user2");
     authRequest.setDisplayName("Another User");
     authRequest.setEmailAddress("user2@example.com");
     accountManager.authenticate(authRequest);
-    authRequest = AuthRequest.forUser("user3");
+    authRequest = authRequestFactory.createForUser("user3");
     authRequest.setDisplayName("Another User");
     authRequest.setEmailAddress("user3@example.com");
     accountManager.authenticate(authRequest);
@@ -2215,7 +2241,10 @@
     Change change2 = insert(repo, newChange(repo));
 
     int user2 =
-        accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId().get();
+        accountManager
+            .authenticate(authRequestFactory.createForUser("anotheruser"))
+            .getAccountId()
+            .get();
 
     ReviewInput input = new ReviewInput();
     input.message = "toplevel";
@@ -2234,7 +2263,49 @@
   }
 
   @Test
-  public void byDraftBy() throws Exception {
+  public void bySubmitRuleResult() throws Exception {
+    if (getSchemaVersion() < 68) {
+      assertMissingField(ChangeField.SUBMIT_RULE_RESULT);
+      return;
+    }
+    try (Registration registration =
+        extensionRegistry.newRegistration().add(new FakeSubmitRule())) {
+      TestRepository<Repo> repo = createProject("repo");
+      Change change = insert(repo, newChange(repo));
+      assertQuery("rule:gerrit~FakeSubmitRule");
+
+      // FakeSubmitRule returns true if change has one or more hashtags.
+      HashtagsInput hashtag = new HashtagsInput();
+      hashtag.add = ImmutableSet.of("Tag1");
+      gApi.changes().id(change.getId().get()).setHashtags(hashtag);
+      assertQuery("rule:gerrit~FakeSubmitRule", change);
+      assertQuery("rule:gerrit~FakeSubmitRule=OK", change);
+      assertQuery("rule:gerrit~FakeSubmitRule=NOT_READY");
+
+      // The 'gerrit~' prefix can be omitted for core submit rules
+      assertQuery("rule:FakeSubmitRule", change);
+    }
+  }
+
+  @Test
+  public void byNonExistingSubmitRule_returnsEmpty() throws Exception {
+    // Some submit rules could be removed from the gerrit.config but there can be records for
+    // merged changes in NoteDb for these rules. We allow querying for non-existent rules to handle
+    // this case.
+    if (getSchemaVersion() < 68) {
+      assertMissingField(ChangeField.SUBMIT_RULE_RESULT);
+      return;
+    }
+    try (Registration registration =
+        extensionRegistry.newRegistration().add(new FakeSubmitRule())) {
+      TestRepository<Repo> repo = createProject("repo");
+      insert(repo, newChange(repo));
+      assertQuery("rule:non-existent-rule");
+    }
+  }
+
+  @Test
+  public void byHasDraft() throws Exception {
     TestRepository<Repo> repo = createProject("repo");
     Change change1 = insert(repo, newChange(repo));
     Change change2 = insert(repo, newChange(repo));
@@ -2253,16 +2324,17 @@
     in.path = Patch.COMMIT_MSG;
     gApi.changes().id(change2.getId().get()).current().createDraft(in);
 
-    int user2 =
-        accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId().get();
+    Account.Id user2 =
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
 
     assertQuery("has:draft", change2, change1);
-    assertQuery("draftby:" + userId.get(), change2, change1);
-    assertQuery("draftby:" + user2);
+
+    requestContext.setContext(newRequestContext(user2));
+    assertQuery("has:draft");
   }
 
   @Test
-  public void byDraftByExcludesZombieDrafts() throws Exception {
+  public void byHasDraftExcludesZombieDrafts() throws Exception {
     Project.NameKey project = Project.nameKey("repo");
     TestRepository<Repo> repo = createProject(project.get());
     Change change = insert(repo, newChange(repo));
@@ -2274,7 +2346,7 @@
     in.path = Patch.COMMIT_MSG;
     gApi.changes().id(id.get()).current().createDraft(in);
 
-    assertQuery("draftby:" + userId, change);
+    assertQuery("has:draft", change);
     assertQuery("commentby:" + userId);
 
     try (TestRepository<Repo> allUsers =
@@ -2286,7 +2358,7 @@
       rin.drafts = DraftHandling.PUBLISH_ALL_REVISIONS;
       gApi.changes().id(id.get()).current().review(rin);
 
-      assertQuery("draftby:" + userId);
+      assertQuery("has:draft");
       assertQuery("commentby:" + userId, change);
       assertThat(allUsers.getRepository().exactRef(draftsRef.getName())).isNull();
 
@@ -2296,7 +2368,7 @@
     }
 
     indexer.index(project, id);
-    assertQuery("draftby:" + userId);
+    assertQuery("has:draft");
   }
 
   @Test
@@ -2309,69 +2381,62 @@
     gApi.accounts().self().starChange(change1.getId().toString());
     gApi.accounts().self().starChange(change2.getId().toString());
 
-    int user2 =
-        accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId().get();
+    Account.Id user2 =
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
 
-    assertQuery("starredby:self", change2, change1);
-    assertQuery("starredby:" + user2);
+    assertQuery("has:star", change2, change1);
+    assertQuery("star:star", change2, change1);
+
+    requestContext.setContext(newRequestContext(user2));
+    assertQuery("has:star");
+    assertQuery("star:star");
   }
 
   @Test
   public void byStar() throws Exception {
     TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
+    Change change1 = insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
     Change change2 = insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
-    Change change3 = insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
-    Change change4 = insert(repo, newChange(repo));
+    Change change3 = insert(repo, newChange(repo));
 
-    gApi.accounts()
-        .self()
-        .setStars(
-            change1.getId().toString(),
-            new StarsInput(new HashSet<>(Arrays.asList("red", "blue"))));
-    gApi.accounts()
-        .self()
-        .setStars(
-            change2.getId().toString(),
-            new StarsInput(
-                new HashSet<>(Arrays.asList(StarredChangesUtil.DEFAULT_LABEL, "green", "blue"))));
+    Account.Id user2 =
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
+    requestContext.setContext(newRequestContext(user2));
 
-    gApi.accounts()
-        .self()
-        .setStars(
-            change4.getId().toString(), new StarsInput(new HashSet<>(Arrays.asList("ignore"))));
-
-    // check labeled stars
-    assertQuery("star:red", change1);
-    assertQuery("star:blue", change2, change1);
-    assertQuery("has:stars", change4, change2, change1);
+    gApi.accounts().self().starChange(change1.getId().toString());
+    gApi.changes().id(change3.getChangeId()).ignore(true);
 
     // check default star
-    assertQuery("has:star", change2);
-    assertQuery("is:starred", change2);
-    assertQuery("starredby:self", change2);
-    assertQuery("star:" + StarredChangesUtil.DEFAULT_LABEL, change2);
+    assertQuery("has:star", change1);
+    assertQuery("is:starred", change1);
+    assertQuery("star:" + StarredChangesUtil.DEFAULT_LABEL, change1);
 
     // check ignored
-    assertQuery("is:ignored", change4);
-    assertQuery("-is:ignored", change3, change2, change1);
+    assertQuery("is:ignored", change3);
+    assertQuery("-is:ignored", change2, change1);
+    assertQuery("star:ignore", change3);
+    assertQuery("-star:ignore", change2, change1);
   }
 
   @Test
   public void byIgnore() throws Exception {
     TestRepository<Repo> repo = createProject("repo");
     Account.Id user2 =
-        accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
     Change change1 = insert(repo, newChange(repo), user2);
     Change change2 = insert(repo, newChange(repo), user2);
 
     gApi.changes().id(change1.getId().toString()).ignore(true);
     assertQuery("is:ignored", change1);
     assertQuery("-is:ignored", change2);
+    assertQuery("star:ignore", change1);
+    assertQuery("-star:ignore", change2);
 
     gApi.changes().id(change1.getId().toString()).ignore(false);
     assertQuery("is:ignored");
     assertQuery("-is:ignored", change2, change1);
+    assertQuery("star:ignore");
+    assertQuery("-star:ignore", change2, change1);
   }
 
   @Test
@@ -2380,7 +2445,7 @@
     Change change1 = insert(repo, newChange(repo));
 
     Account.Id user2 =
-        accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
     Change change2 = insert(repo, newChange(repo), user2);
 
     ReviewInput input = new ReviewInput();
@@ -2451,6 +2516,17 @@
   }
 
   @Test
+  public void cherrypick() throws Exception {
+    assume().that(getSchema().hasField(ChangeField.CHERRY_PICK)).isTrue();
+    TestRepository<Repo> repo = createProject("repo");
+    Change change1 = insert(repo, newChange(repo));
+    Change change2 = insert(repo, newCherryPickChange(repo, "foo", change1.currentPatchSetId()));
+
+    assertQuery("is:cherrypick", change2);
+    assertQuery("-is:cherrypick", change1);
+  }
+
+  @Test
   public void merge() throws Exception {
     assume().that(getSchema().hasField(ChangeField.MERGE)).isTrue();
     TestRepository<Repo> repo = createProject("repo");
@@ -2487,7 +2563,7 @@
     gApi.changes().id(change1.getId().get()).current().review(new ReviewInput().message("comment"));
 
     Account.Id user2 =
-        accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
     requestContext.setContext(newRequestContext(user2));
 
     gApi.changes().id(change2.getId().get()).current().review(new ReviewInput().message("comment"));
@@ -2553,7 +2629,7 @@
   public void byReviewed() throws Exception {
     TestRepository<Repo> repo = createProject("repo");
     Account.Id otherUser =
-        accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
     Change change1 = insert(repo, newChange(repo));
     Change change2 = insert(repo, newChange(repo));
 
@@ -2573,9 +2649,12 @@
 
   @Test
   public void reviewerin() throws Exception {
-    Account.Id user1 = accountManager.authenticate(AuthRequest.forUser("user1")).getAccountId();
-    Account.Id user2 = accountManager.authenticate(AuthRequest.forUser("user2")).getAccountId();
-    Account.Id user3 = accountManager.authenticate(AuthRequest.forUser("user3")).getAccountId();
+    Account.Id user1 =
+        accountManager.authenticate(authRequestFactory.createForUser("user1")).getAccountId();
+    Account.Id user2 =
+        accountManager.authenticate(authRequestFactory.createForUser("user2")).getAccountId();
+    Account.Id user3 =
+        accountManager.authenticate(authRequestFactory.createForUser("user3")).getAccountId();
     TestRepository<Repo> repo = createProject("repo");
 
     Change change1 = insert(repo, newChange(repo));
@@ -2852,7 +2931,6 @@
     insert(repo2, ins2);
 
     assertQuery("is:watched");
-    assertQuery("watchedby:self");
 
     List<ProjectWatchInfo> projectsToWatch = new ArrayList<>();
     ProjectWatchInfo pwi = new ProjectWatchInfo();
@@ -2866,7 +2944,6 @@
     resetUser();
 
     assertQuery("is:watched", change1);
-    assertQuery("watchedby:self", change1);
   }
 
   @Test
@@ -2892,19 +2969,6 @@
   }
 
   @Test
-  public void selfAndMe() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo), userId);
-    insert(repo, newChange(repo));
-    gApi.accounts().self().starChange(change1.getId().toString());
-    gApi.accounts().self().starChange(change2.getId().toString());
-
-    assertQuery("starredby:self", change2, change1);
-    assertQuery("starredby:me", change2, change1);
-  }
-
-  @Test
   public void defaultFieldWithManyUsers() throws Exception {
     for (int i = 0; i < ChangeQueryBuilder.MAX_ACCOUNTS_PER_DEFAULT_FIELD * 2; i++) {
       createAccount("user" + i, "User " + i, "user" + i + "@example.com", true);
@@ -3038,8 +3102,7 @@
       }
       for (Account.Id ignorerId : ignoredBy) {
         requestContext.setContext(newRequestContext(ignorerId));
-        StarsInput in = new StarsInput(new HashSet<>(Arrays.asList("ignore")));
-        gApi.accounts().self().setStars("" + id, in);
+        gApi.changes().id(change.getChangeId()).ignore(true);
       }
       DraftInput in = new DraftInput();
       in.path = Patch.COMMIT_MSG;
@@ -3389,7 +3452,7 @@
     AttentionSetInput input = new AttentionSetInput(userId.toString(), "reason 1");
     gApi.changes().id(change.getChangeId()).addToAttentionSet(input);
     Account.Id user2Id =
-        accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
 
     // Add the second user as cc to ensure that user took part of the change and can be added to the
     // attention set.
@@ -3441,7 +3504,7 @@
         .isEqualTo("Unknown named destination: foo");
 
     Account.Id anotherUserId =
-        accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
     String destination1 = "refs/heads/master\trepo1";
     String destination2 = "refs/heads/master\trepo2";
     String destination3 = "refs/heads/master\trepo1\nrefs/heads/master\trepo2";
@@ -3518,7 +3581,7 @@
     Change change2 = insert(repo, newChangeForBranch(repo, "stable"));
 
     Account.Id anotherUserId =
-        accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
     String queryListText =
         "query1\tproject:repo\n"
             + "query2\tproject:repo status:open\n"
@@ -3602,7 +3665,7 @@
 
   @Test
   public void selfFailsForAnonymousUser() throws Exception {
-    for (String query : ImmutableList.of("assignee:self", "starredby:self", "is:starred")) {
+    for (String query : ImmutableList.of("assignee:self", "has:star", "is:starred", "star:star")) {
       assertQuery(query);
       RequestContext oldContext = requestContext.setContext(anonymousUserProvider::get);
 
@@ -3620,7 +3683,7 @@
   @Test
   public void selfSucceedsForInactiveAccount() throws Exception {
     Account.Id user2 =
-        accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
 
     TestRepository<Repo> repo = createProject("repo");
     Change change = insert(repo, newChange(repo));
@@ -3674,12 +3737,12 @@
   }
 
   protected ChangeInserter newChange(TestRepository<Repo> repo) throws Exception {
-    return newChange(repo, null, null, null, null, false, false);
+    return newChange(repo, null, null, null, null, null, false, false);
   }
 
   protected ChangeInserter newChangeForCommit(TestRepository<Repo> repo, RevCommit commit)
       throws Exception {
-    return newChange(repo, commit, null, null, null, false, false);
+    return newChange(repo, commit, null, null, null, null, false, false);
   }
 
   protected ChangeInserter newChangeWithFiles(TestRepository<Repo> repo, String... paths)
@@ -3693,25 +3756,30 @@
 
   protected ChangeInserter newChangeForBranch(TestRepository<Repo> repo, String branch)
       throws Exception {
-    return newChange(repo, null, branch, null, null, false, false);
+    return newChange(repo, null, branch, null, null, null, false, false);
   }
 
   protected ChangeInserter newChangeWithStatus(TestRepository<Repo> repo, Change.Status status)
       throws Exception {
-    return newChange(repo, null, null, status, null, false, false);
+    return newChange(repo, null, null, status, null, null, false, false);
   }
 
   protected ChangeInserter newChangeWithTopic(TestRepository<Repo> repo, String topic)
       throws Exception {
-    return newChange(repo, null, null, null, topic, false, false);
+    return newChange(repo, null, null, null, topic, null, false, false);
   }
 
   protected ChangeInserter newChangeWorkInProgress(TestRepository<Repo> repo) throws Exception {
-    return newChange(repo, null, null, null, null, true, false);
+    return newChange(repo, null, null, null, null, null, true, false);
   }
 
   protected ChangeInserter newChangePrivate(TestRepository<Repo> repo) throws Exception {
-    return newChange(repo, null, null, null, null, false, true);
+    return newChange(repo, null, null, null, null, null, false, true);
+  }
+
+  protected ChangeInserter newCherryPickChange(
+      TestRepository<Repo> repo, String branch, PatchSet.Id cherryPickOf) throws Exception {
+    return newChange(repo, null, branch, null, null, cherryPickOf, false, true);
   }
 
   protected ChangeInserter newChange(
@@ -3720,6 +3788,7 @@
       @Nullable String branch,
       @Nullable Change.Status status,
       @Nullable String topic,
+      @Nullable PatchSet.Id cherryPickOf,
       boolean workInProgress,
       boolean isPrivate)
       throws Exception {
@@ -3740,7 +3809,8 @@
             .setStatus(status)
             .setTopic(topic)
             .setWorkInProgress(workInProgress)
-            .setPrivate(isPrivate);
+            .setPrivate(isPrivate)
+            .setCherryPickOf(cherryPickOf);
     return ins;
   }
 
@@ -3993,9 +4063,10 @@
   private Account.Id createAccount(String username, String fullName, String email, boolean active)
       throws Exception {
     try (ManualRequestContext ctx = oneOffRequestContext.open()) {
-      Account.Id id = accountManager.authenticate(AuthRequest.forUser(username)).getAccountId();
+      Account.Id id =
+          accountManager.authenticate(authRequestFactory.createForUser(username)).getAccountId();
       if (email != null) {
-        accountManager.link(id, AuthRequest.forEmail(email));
+        accountManager.link(id, authRequestFactory.createForEmail(email));
       }
       accountsUpdate
           .get()
diff --git a/javatests/com/google/gerrit/server/query/change/BUILD b/javatests/com/google/gerrit/server/query/change/BUILD
index 0f102a8..08456d1 100644
--- a/javatests/com/google/gerrit/server/query/change/BUILD
+++ b/javatests/com/google/gerrit/server/query/change/BUILD
@@ -17,6 +17,7 @@
         "//prolog:gerrit-prolog-common",
     ],
     deps = [
+        "//java/com/google/gerrit/acceptance:lib",
         "//java/com/google/gerrit/acceptance/config",
         "//java/com/google/gerrit/acceptance/testsuite/project",
         "//java/com/google/gerrit/common:annotations",
diff --git a/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java b/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
index f392747..568b5a0 100644
--- a/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
+++ b/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
@@ -106,6 +106,8 @@
 
   @Inject protected GroupIndexCollection indexes;
 
+  @Inject protected AuthRequest.Factory authRequestFactory;
+
   @Inject private GroupIndexCollection groupIndexes;
 
   protected LifecycleManager lifecycle;
@@ -397,9 +399,10 @@
   private Account.Id createAccountOutsideRequestContext(
       String username, String fullName, String email, boolean active) throws Exception {
     try (ManualRequestContext ctx = oneOffRequestContext.open()) {
-      Account.Id id = accountManager.authenticate(AuthRequest.forUser(username)).getAccountId();
+      Account.Id id =
+          accountManager.authenticate(authRequestFactory.createForUser(username)).getAccountId();
       if (email != null) {
-        accountManager.link(id, AuthRequest.forEmail(email));
+        accountManager.link(id, authRequestFactory.createForEmail(email));
       }
       accountsUpdate
           .get()
diff --git a/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java b/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
index 2317c7e..60d1655 100644
--- a/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
+++ b/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
@@ -104,6 +104,8 @@
 
   @Inject protected AllUsersName allUsers;
 
+  @Inject protected AuthRequest.Factory authRequestFactory;
+
   protected LifecycleManager lifecycle;
   protected Injector injector;
   protected AccountInfo currentUserInfo;
@@ -309,9 +311,10 @@
   private Account.Id createAccount(String username, String fullName, String email, boolean active)
       throws Exception {
     try (ManualRequestContext ctx = oneOffRequestContext.open()) {
-      Account.Id id = accountManager.authenticate(AuthRequest.forUser(username)).getAccountId();
+      Account.Id id =
+          accountManager.authenticate(authRequestFactory.createForUser(username)).getAccountId();
       if (email != null) {
-        accountManager.link(id, AuthRequest.forEmail(email));
+        accountManager.link(id, authRequestFactory.createForEmail(email));
       }
       accountsUpdate
           .get()
diff --git a/javatests/com/google/gerrit/server/schema/ProjectConfigSchemaUpdateTest.java b/javatests/com/google/gerrit/server/schema/ProjectConfigSchemaUpdateTest.java
index da22f76..e0ba666 100644
--- a/javatests/com/google/gerrit/server/schema/ProjectConfigSchemaUpdateTest.java
+++ b/javatests/com/google/gerrit/server/schema/ProjectConfigSchemaUpdateTest.java
@@ -19,6 +19,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.FileBasedAllProjectsConfigProvider;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
@@ -36,7 +37,10 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
+@RunWith(JUnit4.class)
 public class ProjectConfigSchemaUpdateTest {
   private static final String ALL_PROJECTS = "All-The-Projects";
 
@@ -64,8 +68,11 @@
     try (Repository repo = new FileRepository(allProjectsRepoFile)) {
       repo.create(true);
     }
+    FileBasedAllProjectsConfigProvider configProvider =
+        new FileBasedAllProjectsConfigProvider(sitePaths);
 
-    factory = new ProjectConfigSchemaUpdate.Factory(sitePaths, new AllProjectsName(ALL_PROJECTS));
+    factory =
+        new ProjectConfigSchemaUpdate.Factory(new AllProjectsName(ALL_PROJECTS), configProvider);
   }
 
   @Test
diff --git a/package.json b/package.json
index 9df72da..6ad3ab4 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
     "twinkie": "^1.1.2"
   },
   "devDependencies": {
-    "@typescript-eslint/eslint-plugin": "^4.25.0",
+    "@typescript-eslint/eslint-plugin": "^4.29.0",
     "eslint": "^7.24.0",
     "eslint-config-google": "^0.14.0",
     "eslint-plugin-html": "^6.1.2",
@@ -18,7 +18,6 @@
     "eslint-plugin-node": "^11.1.0",
     "eslint-plugin-prettier": "^3.4.0",
     "gts": "^3.1.0",
-    "polymer-cli": "^1.9.11",
     "prettier": "2.3.1",
     "rollup": "^2.45.2",
     "terser": "^5.6.1",
@@ -44,6 +43,9 @@
     "type": "git",
     "url": "https://gerrit.googlesource.com/gerrit"
   },
+  "resolutions": {
+    "lodash": "4.17.21"
+  },
   "author": "",
   "license": "Apache-2.0"
 }
diff --git a/plugins/package.json b/plugins/package.json
index f761be9..4e3c376 100644
--- a/plugins/package.json
+++ b/plugins/package.json
@@ -5,7 +5,7 @@
     "dependencies": {
       "@polymer/decorators": "^3.0.0",
       "@polymer/polymer": "^3.4.1",
-      "@gerritcodereview/typescript-api": "3.4.2",
+      "@gerritcodereview/typescript-api": "3.4.4",
       "lit": "2.0.0-rc.3"
     },
     "license": "Apache-2.0",
diff --git a/plugins/tsconfig-plugins-base.json b/plugins/tsconfig-plugins-base.json
index b9d14e1..b580549 100644
--- a/plugins/tsconfig-plugins-base.json
+++ b/plugins/tsconfig-plugins-base.json
@@ -1,7 +1,7 @@
 {
   "compilerOptions": {
     /* Basic Options */
-    "target": "es2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
+    "target": "es2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
     "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
     "inlineSourceMap": true, /* Generates corresponding '.map' file. */
     "rootDir": ".", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
diff --git a/plugins/yarn.lock b/plugins/yarn.lock
index 1faa71a..3ff1cc4 100644
--- a/plugins/yarn.lock
+++ b/plugins/yarn.lock
@@ -2,10 +2,10 @@
 # yarn lockfile v1
 
 
-"@gerritcodereview/typescript-api@3.4.2":
-  version "3.4.2"
-  resolved "https://registry.yarnpkg.com/@gerritcodereview/typescript-api/-/typescript-api-3.4.2.tgz#79e8ff336608cbf18e651bfa9541d7bdead5e1f9"
-  integrity sha512-iqHd6G43pV4Wk5iNw95AQmWUBuIrY+dvQ1Ne8ZYkOwRhdruh4BAPhMtsmqWDlcVQbfcwZD5F2zFkGB4J4htggw==
+"@gerritcodereview/typescript-api@3.4.4":
+  version "3.4.4"
+  resolved "https://registry.yarnpkg.com/@gerritcodereview/typescript-api/-/typescript-api-3.4.4.tgz#9f09687038088dd7edd3b4e30d249502eb21bfbc"
+  integrity sha512-MAiQwntcQ59b92yYDsVIXj3oBbAB4C7HELkLFFbYs4ZjzC43XqqtR9VF0dh5OUC8wzFZttgUiOmGehk9edpPuw==
 
 "@lit/reactive-element@^1.0.0-rc.2":
   version "1.0.0-rc.2"
diff --git a/polygerrit-ui/app/api/BUILD_for_publishing_api_only b/polygerrit-ui/app/api/BUILD_for_publishing_api_only
new file mode 100644
index 0000000..9d3029b
--- /dev/null
+++ b/polygerrit-ui/app/api/BUILD_for_publishing_api_only
@@ -0,0 +1,50 @@
+# This BUILD file is only for publishing the
+# "Gerrit Frontend Plugin TypeScript API" as an npm package.
+#
+# Publishing procedure:
+# - Execute the `publish.sh` script from the Gerrit root dir.
+# - Verify that the contents look good.
+# - Increment the version in package.json.
+# - Execute `publish.sh --upload`.
+#
+# NB: Renaming to 'BUILD' breaks the app/BUILD, because then the api/ sources
+# are not visible anymore to the parent BUILD. And if ts_projects depend on each
+# other, then the api/ files would have to be imported with their full package
+# names.
+load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm")
+load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project")
+
+filegroup(
+    name = "js_plugin_api_srcs",
+    srcs = glob(["**/*.ts"]),
+)
+
+ts_config(
+    name = "ts_config",
+    src = "tsconfig.json",
+    deps = [
+        "//plugins:tsconfig-plugins-base.json",
+    ],
+)
+
+ts_project(
+    name = "js_plugin_api_compiled",
+    srcs = glob(["**/*.ts"]),
+    incremental = True,
+    tsc = "//tools/node_tools:tsc-bin",
+    tsconfig = ":ts_config",
+)
+
+# Use this rule for publishing the js plugin api as a package to the npm repo.
+pkg_npm(
+    name = "js_plugin_api_npm_package",
+    srcs = glob(
+        ["**/*"],
+        exclude = [
+            "BUILD",
+            "tsconfig.json",
+            "publish.sh",
+        ],
+    ),
+    deps = [":js_plugin_api_compiled"],
+)
diff --git a/polygerrit-ui/app/api/README.md b/polygerrit-ui/app/api/README.md
index 550063f..b5710bf 100644
--- a/polygerrit-ui/app/api/README.md
+++ b/polygerrit-ui/app/api/README.md
@@ -1,23 +1,25 @@
-# API
+# Gerrit TypeScript Plugin API
 
-In this folder, we declare the API of various parts of the Gerrit webclient.
-There are two primary use cases for this:
+This package contains the types for developing browser plugins for the
+Gerrit Code Review web application. General documentation for plugin
+developers can be found at
+[gerrit-review.googlesource.com](https://gerrit-review.googlesource.com/Documentation/pg-plugin-dev.html).
 
-* apps that embed our diff viewer, gr-diff
-* Gerrit plugins that need to access some part of Gerrit to extend it
+The `.ts` files only contain types, interfaces and enums, and thus the compiled
+`.js` files only contain the enums. For JavaScript plugins this package is not
+really useful or necessary, but it also serves as the source of truth for
+what plugin APIs are actually supported.
 
-Both may be built as a separate bundle, but would like to type check against
-the same types the Gerrit/gr-diff bundle uses. For this reason, this folder
-should contain only types, with the exception of enums, where having the
-value side is deemed an acceptable duplication.
+Versioning of this API matches the MAJOR and MINOR versions of the general
+Gerrit releases, but the PATCH version is independent. When you are building
+a plugin for Gerrit x.y.z, then you should use the API package x.y.n, where
+n is the highest available patch version of the API. Patch versions will only
+contain additions and fixes, minor versions may include API removals.
 
 All types in here should use the `declare` keyword to prevent bundlers from
 renaming fields, which would break communication across separately built
-bundles. Again enums are the exception, because their keys are not referenced
+bundles. enums are the exception, because their keys are not referenced
 across bundles, and values will not be renamed by bundlers as they are strings.
 
-This API is used by other apps embedding gr-diff and any breaking changes
+This API is also used by other apps embedding gr-diff and any breaking changes
 should be discussed with the Gerrit core team and properly versioned.
-
-Gerrit types should either directly use or extend these types, so that
-breaking changes to the implementation require changes to these files.
diff --git a/polygerrit-ui/app/api/change-actions.ts b/polygerrit-ui/app/api/change-actions.ts
index 2ce697a..4380195 100644
--- a/polygerrit-ui/app/api/change-actions.ts
+++ b/polygerrit-ui/app/api/change-actions.ts
@@ -72,6 +72,9 @@
 export type PrimaryActionKey = ChangeActions | RevisionActions;
 
 export declare interface ChangeActionsPluginApi {
+  // Deprecated. This API method will be removed.
+  ensureEl(): Element;
+
   addPrimaryActionKey(key: PrimaryActionKey): void;
 
   removePrimaryActionKey(key: string): void;
diff --git a/polygerrit-ui/app/api/checks.ts b/polygerrit-ui/app/api/checks.ts
index b64cd91..6c11e57 100644
--- a/polygerrit-ui/app/api/checks.ts
+++ b/polygerrit-ui/app/api/checks.ts
@@ -249,6 +249,12 @@
    */
   primary?: boolean;
   /**
+   * Summary actions will get an even more prominent treatment in the UI. They
+   * will show up in the checks summary right below the commit message. This
+   * only affects top-level actions (i.e. actions in FetchResponse).
+   */
+  summary?: boolean;
+  /**
    * Renders the action button in a disabled state. That can be useful for
    * actions that are present most of the time, but sometimes don't apply. Then
    * a grayed out button with a tooltip makes it easier for the user to
diff --git a/polygerrit-ui/app/api/package.json b/polygerrit-ui/app/api/package.json
new file mode 100644
index 0000000..8af6832
--- /dev/null
+++ b/polygerrit-ui/app/api/package.json
@@ -0,0 +1,9 @@
+{
+  "name": "@gerritcodereview/typescript-api",
+  "version": "3.4.4",
+  "description": "Gerrit Code Review - TypeScript API",
+  "homepage": "https://www.gerritcodereview.com/",
+  "browser": true,
+  "dependencies": {},
+  "license": "Apache-2.0"
+}
diff --git a/polygerrit-ui/app/api/publish.sh b/polygerrit-ui/app/api/publish.sh
new file mode 100755
index 0000000..16de4c9
--- /dev/null
+++ b/polygerrit-ui/app/api/publish.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+# Should be executed from the root of the Gerrit repo:
+# polygerrit-ui/app/api/publish.sh
+#
+# Builds the npm package @gerritcodereview/typescript-api
+#
+# Adding the `--upload` argument will also publish the package.
+
+bazel_bin=$(which bazelisk 2>/dev/null)
+if [[ -z "$bazel_bin" ]]; then
+    echo "Warning: bazelisk is not installed; falling back to bazel."
+    bazel_bin=bazel
+fi
+api_path=polygerrit-ui/app/api
+
+function cleanup() {
+  echo "Cleaning up ..."
+  rm -f ${api_path}/BUILD
+}
+trap cleanup EXIT
+cp ${api_path}/BUILD_for_publishing_api_only ${api_path}/BUILD
+
+${bazel_bin} build //${api_path}:js_plugin_api_npm_package
+
+if [ "$1" == "--upload" ]; then
+  echo 'Uploading npm package @gerritcodereview/typescript-api'
+  ${bazel_bin} run //${api_path}:js_plugin_api_npm_package.publish
+fi
diff --git a/polygerrit-ui/app/api/rest-api.ts b/polygerrit-ui/app/api/rest-api.ts
index f3bb39b..4a4b79d 100644
--- a/polygerrit-ui/app/api/rest-api.ts
+++ b/polygerrit-ui/app/api/rest-api.ts
@@ -30,10 +30,6 @@
  * enums =======================================================================
  */
 
-export enum AccountTag {
-  SERVICE_USER = 'SERVICE_USER',
-}
-
 /**
  * The authentication type that is configured on the server.
  * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#auth-info
@@ -237,7 +233,15 @@
   _more_accounts?: boolean; // not set if false
   status?: string; // status message of the account
   inactive?: boolean; // not set if false
-  tags?: AccountTag[];
+  tags?: string[];
+}
+
+/**
+ * The AccountDetailInfo entity contains detailed information about an account.
+ * https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#account-detail-info
+ */
+export interface AccountDetailInfo extends AccountInfo {
+  registered_on: Timestamp;
 }
 
 /**
diff --git a/polygerrit-ui/app/api/rest.ts b/polygerrit-ui/app/api/rest.ts
index 46dad3d..86f33a9 100644
--- a/polygerrit-ui/app/api/rest.ts
+++ b/polygerrit-ui/app/api/rest.ts
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {ProjectInfoWithName, ServerInfo} from './rest-api';
+import {AccountDetailInfo, ProjectInfoWithName, ServerInfo} from './rest-api';
 
 export type RequestPayload = string | object;
 
@@ -37,6 +37,8 @@
 
   invalidateReposCache(): void;
 
+  getAccount(): Promise<AccountDetailInfo | undefined>;
+
   getRepos(
     filter: string,
     reposPerPage: number,
diff --git a/polygerrit-ui/app/api/styles.ts b/polygerrit-ui/app/api/styles.ts
index e393667..ded3beb 100644
--- a/polygerrit-ui/app/api/styles.ts
+++ b/polygerrit-ui/app/api/styles.ts
@@ -34,6 +34,7 @@
 }
 
 export interface Styles {
+  font: Style;
   form: Style;
   menuPage: Style;
   subPage: Style;
diff --git a/polygerrit-ui/app/api/tsconfig.json b/polygerrit-ui/app/api/tsconfig.json
new file mode 100644
index 0000000..4d8ecac
--- /dev/null
+++ b/polygerrit-ui/app/api/tsconfig.json
@@ -0,0 +1,6 @@
+{
+  "extends": "../../../plugins/tsconfig-plugins-base.json",
+  "include": [
+    "**/*",
+  ],
+}
diff --git a/polygerrit-ui/app/constants/constants.ts b/polygerrit-ui/app/constants/constants.ts
index 84f1c15..2acad49 100644
--- a/polygerrit-ui/app/constants/constants.ts
+++ b/polygerrit-ui/app/constants/constants.ts
@@ -22,7 +22,6 @@
 import {DiffPreferencesInfo} from '../types/diff';
 import {EditPreferencesInfo, PreferencesInfo} from '../types/common';
 import {
-  AccountTag,
   AuthType,
   ChangeStatus,
   ConfigParameterInfoType,
@@ -42,7 +41,6 @@
 } from '../api/rest-api';
 
 export {
-  AccountTag,
   AuthType,
   ChangeStatus,
   ConfigParameterInfoType,
@@ -61,6 +59,10 @@
   SubmitType,
 };
 
+export enum AccountTag {
+  SERVICE_USER = 'SERVICE_USER',
+}
+
 export enum PrimaryTab {
   FILES = 'files',
   /**
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.ts b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.ts
index c41fe57..6efaf0c 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.ts
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.ts
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 import '@polymer/iron-input/iron-input';
+import '../../../styles/gr-font-styles';
 import '../../../styles/gr-form-styles';
 import '../../../styles/shared-styles';
 import '../../shared/gr-button/gr-button';
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_html.ts b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_html.ts
index 46968eb..1438825 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_html.ts
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_html.ts
@@ -17,6 +17,9 @@
 import {html} from '@polymer/polymer/lib/utils/html-tag';
 
 export const htmlTemplate = html`
+  <style include="gr-font-styles">
+    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+  </style>
   <style include="shared-styles">
     :host {
       display: block;
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
index 3e52165..cdd2913 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
@@ -46,8 +46,11 @@
   };
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = ListViewMixin(PolymerElement);
+
 @customElement('gr-admin-group-list')
-export class GrAdminGroupList extends ListViewMixin(PolymerElement) {
+export class GrAdminGroupList extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.ts b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.ts
index a2d62341..5a48399 100644
--- a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.ts
@@ -16,8 +16,8 @@
  */
 import '../../shared/gr-dialog/gr-dialog';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property} from 'lit-element';
+import {css, html, LitElement} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -26,7 +26,7 @@
 }
 
 @customElement('gr-confirm-delete-item-dialog')
-export class GrConfirmDeleteItemDialog extends GrLitElement {
+export class GrConfirmDeleteItemDialog extends LitElement {
   /**
    * Fired when the confirm button is pressed.
    *
diff --git a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.js b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.js
index af33691..321f069 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.js
@@ -31,41 +31,33 @@
     element = basicFixture.instantiate();
   });
 
-  test('name is updated correctly', done => {
+  test('name is updated correctly', async () => {
     assert.isFalse(element.hasNewGroupName);
 
     const inputEl = element.root.querySelector('iron-input');
     inputEl.bindValue = GROUP_NAME;
 
-    setTimeout(() => {
-      assert.isTrue(element.hasNewGroupName);
-      assert.deepEqual(element._name, GROUP_NAME);
-      done();
-    });
+    await new Promise(resolve => setTimeout(resolve));
+    assert.isTrue(element.hasNewGroupName);
+    assert.deepEqual(element._name, GROUP_NAME);
   });
 
-  test('test for redirecting to group on successful creation', done => {
+  test('test for redirecting to group on successful creation', async () => {
     stubRestApi('createGroup').returns(Promise.resolve({status: 201}));
     stubRestApi('getGroupConfig').returns(Promise.resolve({group_id: 551}));
 
     const showStub = sinon.stub(page, 'show');
-    element.handleCreateGroup()
-        .then(() => {
-          assert.isTrue(showStub.calledWith('/admin/groups/551'));
-          done();
-        });
+    await element.handleCreateGroup();
+    assert.isTrue(showStub.calledWith('/admin/groups/551'));
   });
 
-  test('test for unsuccessful group creation', done => {
+  test('test for unsuccessful group creation', async () => {
     stubRestApi('createGroup').returns(Promise.resolve({status: 409}));
     stubRestApi('getGroupConfig').returns(Promise.resolve({group_id: 551}));
 
     const showStub = sinon.stub(page, 'show');
-    element.handleCreateGroup()
-        .then(() => {
-          assert.isFalse(showStub.called);
-          done();
-        });
+    await element.handleCreateGroup();
+    assert.isFalse(showStub.called);
   });
 });
 
diff --git a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.ts b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.ts
index b6a08b87..ea0919c 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.ts
@@ -35,7 +35,7 @@
     element = basicFixture.instantiate();
   });
 
-  test('branch created', done => {
+  test('branch created', async () => {
     stubRestApi('createRepoBranch').returns(Promise.resolve(new Response()));
 
     assert.isFalse(element.hasNewItemName);
@@ -46,15 +46,14 @@
     ironInput(element.$.itemNameSection).bindValue = 'test-branch2';
     ironInput(element.$.itemRevisionSection).bindValue = 'HEAD';
 
-    setTimeout(() => {
-      assert.isTrue(element.hasNewItemName);
-      assert.equal(element._itemName, 'test-branch2' as BranchName);
-      assert.equal(element._itemRevision, 'HEAD');
-      done();
-    });
+    await flush();
+
+    assert.isTrue(element.hasNewItemName);
+    assert.equal(element._itemName, 'test-branch2' as BranchName);
+    assert.equal(element._itemRevision, 'HEAD');
   });
 
-  test('tag created', done => {
+  test('tag created', async () => {
     stubRestApi('createRepoTag').returns(Promise.resolve(new Response()));
 
     assert.isFalse(element.hasNewItemName);
@@ -65,15 +64,13 @@
     ironInput(element.$.itemNameSection).bindValue = 'test-tag2';
     ironInput(element.$.itemRevisionSection).bindValue = 'HEAD';
 
-    setTimeout(() => {
-      assert.isTrue(element.hasNewItemName);
-      assert.equal(element._itemName, 'test-tag2' as BranchName);
-      assert.equal(element._itemRevision, 'HEAD');
-      done();
-    });
+    await flush();
+    assert.isTrue(element.hasNewItemName);
+    assert.equal(element._itemName, 'test-tag2' as BranchName);
+    assert.equal(element._itemRevision, 'HEAD');
   });
 
-  test('tag created with annotations', done => {
+  test('tag created with annotations', async () => {
     stubRestApi('createRepoTag').returns(Promise.resolve(new Response()));
 
     assert.isFalse(element.hasNewItemName);
@@ -86,13 +83,11 @@
     ironInput(element.$.itemAnnotationSection).bindValue = 'test-message2';
     ironInput(element.$.itemRevisionSection).bindValue = 'HEAD';
 
-    setTimeout(() => {
-      assert.isTrue(element.hasNewItemName);
-      assert.equal(element._itemName, 'test-tag2' as BranchName);
-      assert.equal(element._itemAnnotation, 'test-message2');
-      assert.equal(element._itemRevision, 'HEAD');
-      done();
-    });
+    await flush();
+    assert.isTrue(element.hasNewItemName);
+    assert.equal(element._itemName, 'test-tag2' as BranchName);
+    assert.equal(element._itemAnnotation, 'test-message2');
+    assert.equal(element._itemRevision, 'HEAD');
   });
 
   test('_computeHideItemClass returns hideItem if type is branches', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.ts b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.ts
index cdc2fb3..d40b810 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.ts
@@ -36,8 +36,11 @@
 import {appContext} from '../../../services/app-context';
 import {ErrorCallback} from '../../../api/rest';
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = ListViewMixin(PolymerElement);
+
 @customElement('gr-group-audit-log')
-export class GrGroupAuditLog extends ListViewMixin(PolymerElement) {
+export class GrGroupAuditLog extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
index 70242d8..c38f8be 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
+import '../../../styles/gr-font-styles';
 import '../../../styles/gr-form-styles';
 import '../../../styles/gr-subpage-styles';
 import '../../../styles/gr-table-styles';
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_html.ts b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_html.ts
index 0c856bc..518abac 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_html.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_html.ts
@@ -17,6 +17,9 @@
 import {html} from '@polymer/polymer/lib/utils/html-tag';
 
 export const htmlTemplate = html`
+  <style include="gr-font-styles">
+    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+  </style>
   <style include="gr-form-styles">
     /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
   </style>
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.js b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.js
index 54c5099..d26d14c 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.js
@@ -18,7 +18,7 @@
 import '../../../test/common-test-setup-karma.js';
 import './gr-group-members.js';
 import {dom, flush} from '@polymer/polymer/lib/legacy/polymer.dom.js';
-import {addListenerForTest, stubBaseUrl, stubRestApi} from '../../../test/test-utils.js';
+import {addListenerForTest, mockPromise, stubBaseUrl, stubRestApi} from '../../../test/test-utils.js';
 import {ItemType} from './gr-group-members.js';
 
 const basicFixture = fixtureFromElement('gr-group-members');
@@ -235,42 +235,32 @@
     assert.isTrue(exceptionThrown);
   });
 
-  test('_getAccountSuggestions empty', done => {
-    element
-        ._getAccountSuggestions('nonexistent').then(accounts => {
-          assert.equal(accounts.length, 0);
-          done();
-        });
+  test('_getAccountSuggestions empty', async () => {
+    const accounts = await element._getAccountSuggestions('nonexistent');
+    assert.equal(accounts.length, 0);
   });
 
-  test('_getAccountSuggestions non-empty', done => {
-    element
-        ._getAccountSuggestions('test-').then(accounts => {
-          assert.equal(accounts.length, 3);
-          assert.equal(accounts[0].name,
-              'test-account <test.account@example.com>');
-          assert.equal(accounts[1].name, 'test-admin <test.admin@example.com>');
-          assert.equal(accounts[2].name, 'test-git');
-          done();
-        });
+  test('_getAccountSuggestions non-empty', async () => {
+    const accounts = await element._getAccountSuggestions('test-');
+    assert.equal(accounts.length, 3);
+    assert.equal(accounts[0].name,
+        'test-account <test.account@example.com>');
+    assert.equal(accounts[1].name, 'test-admin <test.admin@example.com>');
+    assert.equal(accounts[2].name, 'test-git');
   });
 
-  test('_getGroupSuggestions empty', done => {
-    element
-        ._getGroupSuggestions('nonexistent').then(groups => {
-          assert.equal(groups.length, 0);
-          done();
-        });
+  test('_getGroupSuggestions empty', async () => {
+    const groups = await element._getGroupSuggestions('nonexistent');
+
+    assert.equal(groups.length, 0);
   });
 
-  test('_getGroupSuggestions non-empty', done => {
-    element
-        ._getGroupSuggestions('test').then(groups => {
-          assert.equal(groups.length, 2);
-          assert.equal(groups[0].name, 'test-admin');
-          assert.equal(groups[1].name, 'test/Administrator (admin)');
-          done();
-        });
+  test('_getGroupSuggestions non-empty', async () => {
+    const groups = await element._getGroupSuggestions('test');
+
+    assert.equal(groups.length, 2);
+    assert.equal(groups[0].name, 'test-admin');
+    assert.equal(groups[1].name, 'test/Administrator (admin)');
   });
 
   test('_computeHideItemClass returns string for admin', () => {
@@ -343,22 +333,24 @@
     assert.equal(element._computeGroupUrl(url), url);
   });
 
-  test('fires page-error', done => {
+  test('fires page-error', async () => {
     groupStub.restore();
 
     element.groupId = 1;
 
     const response = {status: 404};
-    stubRestApi('getGroupConfig')
-        .callsFake((group, errFn) => {
-          errFn(response);
-        });
+    stubRestApi('getGroupConfig').callsFake((group, errFn) => {
+      errFn(response);
+      return Promise.resolve();
+    });
+    const promise = mockPromise();
     addListenerForTest(document, 'page-error', e => {
       assert.deepEqual(e.detail.response, response);
-      done();
+      promise.resolve();
     });
 
     element._loadGroupDetails();
+    await promise;
   });
 
   test('_computeItemName', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts b/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
index 18862b5..596fe5b 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
@@ -16,6 +16,7 @@
  */
 
 import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
+import '../../../styles/gr-font-styles';
 import '../../../styles/gr-form-styles';
 import '../../../styles/gr-subpage-styles';
 import '../../../styles/shared-styles';
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group_html.ts b/polygerrit-ui/app/elements/admin/gr-group/gr-group_html.ts
index 98d21f9..6bc5d2a 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group_html.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group_html.ts
@@ -17,6 +17,9 @@
 import {html} from '@polymer/polymer/lib/utils/html-tag';
 
 export const htmlTemplate = html`
+  <style include="gr-font-styles">
+    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+  </style>
   <style include="shared-styles">
     /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
   </style>
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.js b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.js
index 0668330..e492a15 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.js
@@ -17,7 +17,11 @@
 
 import '../../../test/common-test-setup-karma.js';
 import './gr-group.js';
-import {stubRestApi, addListenerForTest} from '../../../test/test-utils.js';
+import {
+  addListenerForTest,
+  mockPromise,
+  stubRestApi,
+} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-group');
 
@@ -49,17 +53,15 @@
         .display === 'none');
   });
 
-  test('default values are populated with internal group', done => {
+  test('default values are populated with internal group', async () => {
     stubRestApi('getIsGroupOwner').returns(Promise.resolve(true));
     element.groupId = 1;
-    element._loadGroup().then(() => {
-      assert.isTrue(element._groupIsInternal);
-      assert.isFalse(element.$.visibleToAll.bindValue);
-      done();
-    });
+    await element._loadGroup();
+    assert.isTrue(element._groupIsInternal);
+    assert.isFalse(element.$.visibleToAll.bindValue);
   });
 
-  test('default values with external group', done => {
+  test('default values with external group', async () => {
     const groupExternal = {...group};
     groupExternal.id = 'external-group-id';
     groupStub.restore();
@@ -67,14 +69,12 @@
         Promise.resolve(groupExternal));
     stubRestApi('getIsGroupOwner').returns(Promise.resolve(true));
     element.groupId = 1;
-    element._loadGroup().then(() => {
-      assert.isFalse(element._groupIsInternal);
-      assert.isFalse(element.$.visibleToAll.bindValue);
-      done();
-    });
+    await element._loadGroup();
+    assert.isFalse(element._groupIsInternal);
+    assert.isFalse(element.$.visibleToAll.bindValue);
   });
 
-  test('rename group', done => {
+  test('rename group', async () => {
     const groupName = 'test-group';
     const groupName2 = 'test-group2';
     element.groupId = 1;
@@ -88,25 +88,22 @@
 
     const button = element.$.inputUpdateNameBtn;
 
-    element._loadGroup().then(() => {
-      assert.isTrue(button.hasAttribute('disabled'));
-      assert.isFalse(element.$.Title.classList.contains('edited'));
+    await element._loadGroup();
+    assert.isTrue(button.hasAttribute('disabled'));
+    assert.isFalse(element.$.Title.classList.contains('edited'));
 
-      element.$.groupNameInput.text = groupName2;
+    element.$.groupNameInput.text = groupName2;
 
-      assert.isFalse(button.hasAttribute('disabled'));
-      assert.isTrue(element.$.groupName.classList.contains('edited'));
+    assert.isFalse(button.hasAttribute('disabled'));
+    assert.isTrue(element.$.groupName.classList.contains('edited'));
 
-      element._handleSaveName().then(() => {
-        assert.isTrue(button.hasAttribute('disabled'));
-        assert.isFalse(element.$.Title.classList.contains('edited'));
-        assert.equal(element._groupName, groupName2);
-        done();
-      });
-    });
+    await element._handleSaveName();
+    assert.isTrue(button.hasAttribute('disabled'));
+    assert.isFalse(element.$.Title.classList.contains('edited'));
+    assert.equal(element._groupName, groupName2);
   });
 
-  test('rename group owner', done => {
+  test('rename group owner', async () => {
     const groupName = 'test-group';
     element.groupId = 1;
     element._groupConfig = {
@@ -119,24 +116,21 @@
 
     const button = element.$.inputUpdateOwnerBtn;
 
-    element._loadGroup().then(() => {
-      assert.isTrue(button.hasAttribute('disabled'));
-      assert.isFalse(element.$.Title.classList.contains('edited'));
+    await element._loadGroup();
+    assert.isTrue(button.hasAttribute('disabled'));
+    assert.isFalse(element.$.Title.classList.contains('edited'));
 
-      element.$.groupOwnerInput.text = 'testId2';
+    element.$.groupOwnerInput.text = 'testId2';
 
-      assert.isFalse(button.hasAttribute('disabled'));
-      assert.isTrue(element.$.groupOwner.classList.contains('edited'));
+    assert.isFalse(button.hasAttribute('disabled'));
+    assert.isTrue(element.$.groupOwner.classList.contains('edited'));
 
-      element._handleSaveOwner().then(() => {
-        assert.isTrue(button.hasAttribute('disabled'));
-        assert.isFalse(element.$.Title.classList.contains('edited'));
-        done();
-      });
-    });
+    await element._handleSaveOwner();
+    assert.isTrue(button.hasAttribute('disabled'));
+    assert.isFalse(element.$.Title.classList.contains('edited'));
   });
 
-  test('test for undefined group name', done => {
+  test('test for undefined group name', async () => {
     groupStub.restore();
 
     stubRestApi('getGroupConfig').returns(Promise.resolve({}));
@@ -149,16 +143,13 @@
 
     // Test that loading shows instead of filling
     // in group details
-    element._loadGroup().then(() => {
-      assert.isTrue(element.$.loading.classList.contains('loading'));
+    await element._loadGroup();
+    assert.isTrue(element.$.loading.classList.contains('loading'));
 
-      assert.isTrue(element._loading);
-
-      done();
-    });
+    assert.isTrue(element._loading);
   });
 
-  test('test fire event', done => {
+  test('test fire event', async () => {
     element._groupConfig = {
       name: 'test-group',
     };
@@ -166,11 +157,8 @@
     stubRestApi('saveGroupName').returns(Promise.resolve({status: 200}));
 
     const showStub = sinon.stub(element, 'dispatchEvent');
-    element._handleSaveName()
-        .then(() => {
-          assert.isTrue(showStub.called);
-          done();
-        });
+    await element._handleSaveName();
+    assert.isTrue(showStub.called);
   });
 
   test('_computeGroupDisabled', () => {
@@ -206,7 +194,7 @@
     assert.equal(element._computeLoadingClass(false), '');
   });
 
-  test('fires page-error', done => {
+  test('fires page-error', async () => {
     groupStub.restore();
 
     element.groupId = 1;
@@ -214,14 +202,17 @@
     const response = {status: 404};
     stubRestApi('getGroupConfig').callsFake((group, errFn) => {
       errFn(response);
+      return Promise.resolve(undefined);
     });
 
+    const promise = mockPromise();
     addListenerForTest(document, 'page-error', e => {
       assert.deepEqual(e.detail.response, response);
-      done();
+      promise.resolve();
     });
 
     element._loadGroup();
+    await promise;
   });
 
   test('uuid', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.ts b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.ts
index f275dfd..13bd79d 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.ts
@@ -32,8 +32,12 @@
 interface PluginInfoWithName extends PluginInfo {
   name: string;
 }
+
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = ListViewMixin(PolymerElement);
+
 @customElement('gr-plugin-list')
-export class GrPluginList extends ListViewMixin(PolymerElement) {
+export class GrPluginList extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.js b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.js
index a9281e4..62c89b2 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.js
@@ -18,7 +18,10 @@
 import '../../../test/common-test-setup-karma.js';
 import './gr-plugin-list.js';
 import 'lodash/lodash.js';
-import {addListenerForTest, stubRestApi} from '../../../test/test-utils.js';
+import {
+  addListenerForTest,
+  mockPromise,
+  stubRestApi} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-plugin-list');
 
@@ -52,52 +55,45 @@
     counter = 0;
   });
 
-  suite('list with plugins', () => {
-    setup(done => {
+  suite('list with plugins', async () => {
+    setup(async () => {
       plugins = _.times(26, pluginGenerator);
       stubRestApi('getPlugins').returns(Promise.resolve(plugins));
-      element._paramsChanged(value).then(() => { flush(done); });
+      await element._paramsChanged(value);
+      await flush();
     });
 
-    test('plugin in the list is formatted correctly', done => {
-      flush(() => {
-        assert.equal(element._plugins[4].id, 'test5');
-        assert.equal(element._plugins[4].index_url, 'plugins/test5/');
-        assert.equal(element._plugins[4].version, 'version-5');
-        assert.equal(element._plugins[4].api_version, 'api-version-5');
-        assert.equal(element._plugins[4].disabled, false);
-        done();
-      });
+    test('plugin in the list is formatted correctly', async () => {
+      await flush();
+      assert.equal(element._plugins[4].id, 'test5');
+      assert.equal(element._plugins[4].index_url, 'plugins/test5/');
+      assert.equal(element._plugins[4].version, 'version-5');
+      assert.equal(element._plugins[4].api_version, 'api-version-5');
+      assert.equal(element._plugins[4].disabled, false);
     });
 
-    test('with and without urls', done => {
-      flush(() => {
-        const names = element.root.querySelectorAll('.name');
-        assert.isOk(names[1].querySelector('a'));
-        assert.equal(names[1].querySelector('a').innerText, 'test1');
-        assert.isNotOk(names[2].querySelector('a'));
-        assert.equal(names[2].innerText, 'test2');
-        done();
-      });
+    test('with and without urls', async () => {
+      await flush();
+      const names = element.root.querySelectorAll('.name');
+      assert.isOk(names[1].querySelector('a'));
+      assert.equal(names[1].querySelector('a').innerText, 'test1');
+      assert.isNotOk(names[2].querySelector('a'));
+      assert.equal(names[2].innerText, 'test2');
     });
 
-    test('versions', done => {
-      flush(() => {
-        const versions = element.root.querySelectorAll('.version');
-        assert.equal(versions[2].innerText, 'version-2');
-        assert.equal(versions[3].innerText, '--');
-        done();
-      });
+    test('versions', async () => {
+      await flush();
+      const versions = element.root.querySelectorAll('.version');
+      assert.equal(versions[2].innerText, 'version-2');
+      assert.equal(versions[3].innerText, '--');
     });
 
-    test('api versions', done => {
-      flush(() => {
-        const apiVersions = element.root.querySelectorAll(
-            '.apiVersion');
-        assert.equal(apiVersions[3].innerText, 'api-version-3');
-        assert.equal(apiVersions[4].innerText, '--');
-        done();
-      });
+    test('api versions', async () => {
+      await flush();
+      const apiVersions = element.root.querySelectorAll(
+          '.apiVersion');
+      assert.equal(apiVersions[3].innerText, 'api-version-3');
+      assert.equal(apiVersions[4].innerText, '--');
     });
 
     test('_shownPlugins', () => {
@@ -106,10 +102,11 @@
   });
 
   suite('list with less then 26 plugins', () => {
-    setup(done => {
+    setup(async () => {
       plugins = _.times(25, pluginGenerator);
       stubRestApi('getPlugins').returns(Promise.resolve(plugins));
-      element._paramsChanged(value).then(() => { flush(done); });
+      await element._paramsChanged(value);
+      await flush();
     });
 
     test('_shownPlugins', () => {
@@ -133,7 +130,7 @@
   });
 
   suite('loading', () => {
-    test('correct contents are displayed', () => {
+    test('correct contents are displayed', async () => {
       assert.isTrue(element._loading);
       assert.equal(element.computeLoadingClass(element._loading), 'loading');
       assert.equal(getComputedStyle(element.$.loading).display, 'block');
@@ -141,30 +138,33 @@
       element._loading = false;
       element._plugins = _.times(25, pluginGenerator);
 
-      flush();
+      await flush();
       assert.equal(element.computeLoadingClass(element._loading), '');
       assert.equal(getComputedStyle(element.$.loading).display, 'none');
     });
   });
 
   suite('404', () => {
-    test('fires page-error', done => {
+    test('fires page-error', async () => {
       const response = {status: 404};
       stubRestApi('getPlugins').callsFake(
           (filter, pluginsPerPage, opt_offset, errFn) => {
             errFn(response);
+            return Promise.resolve(undefined);
           });
 
+      const promise = mockPromise();
       addListenerForTest(document, 'page-error', e => {
         assert.deepEqual(e.detail.response, response);
-        done();
+        promise.resolve();
       });
 
       const value = {
         filter: 'test',
         offset: 25,
       };
-      element._paramsChanged(value);
+      await element._paramsChanged(value);
+      await promise;
     });
   });
 });
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access-interfaces.ts b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access-interfaces.ts
index 08493c5..6136f1e1 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access-interfaces.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access-interfaces.ts
@@ -14,9 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 /**
- * @fileOverview This file contains interfaces shared between gr-repo-access
+ * @fileoverview This file contains interfaces shared between gr-repo-access
  * and nested elements (gr-access-section, gr-permission)
  */
 
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
index 9c98545..56e981a 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
@@ -14,6 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import '../../../styles/gr-font-styles';
 import '../../../styles/gr-menu-page-styles';
 import '../../../styles/gr-subpage-styles';
 import '../../../styles/shared-styles';
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_html.ts b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_html.ts
index f5ef86b..65f0564 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_html.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_html.ts
@@ -17,6 +17,9 @@
 import {html} from '@polymer/polymer/lib/utils/html-tag';
 
 export const htmlTemplate = html`
+  <style include="gr-font-styles">
+    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+  </style>
   <style include="shared-styles">
     /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
   </style>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.js b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.js
index 2b7fdf5..1ccfd5e 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.js
@@ -20,7 +20,11 @@
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
 import {toSortedPermissionsArray} from '../../../utils/access-util.js';
-import {addListenerForTest, stubRestApi} from '../../../test/test-utils.js';
+import {
+  addListenerForTest,
+  mockPromise,
+  stubRestApi,
+} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-repo-access');
 
@@ -238,19 +242,22 @@
     assert.equal(element._computeLoadingClass(false), '');
   });
 
-  test('fires page-error', done => {
+  test('fires page-error', async () => {
     const response = {status: 404};
 
     stubRestApi('getRepoAccessRights').callsFake((repoName, errFn) => {
       errFn(response);
+      return Promise.resolve(undefined);
     });
 
+    const promise = mockPromise();
     addListenerForTest(document, 'page-error', e => {
       assert.deepEqual(e.detail.response, response);
-      done();
+      promise.resolve();
     });
 
     element.repo = 'test';
+    await promise;
   });
 
   suite('with defined sections', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
index 3cef13f..de43dc6 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
+import '../../../styles/gr-font-styles';
 import '../../../styles/gr-form-styles';
 import '../../../styles/gr-subpage-styles';
 import '../../../styles/shared-styles';
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_html.ts b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_html.ts
index f572ac3..9948f8f 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_html.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_html.ts
@@ -17,6 +17,9 @@
 import {html} from '@polymer/polymer/lib/utils/html-tag';
 
 export const htmlTemplate = html`
+  <style include="gr-font-styles">
+    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+  </style>
   <style include="shared-styles">
     /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
   </style>
@@ -36,11 +39,11 @@
     <div id="loadedContent" class$="[[_computeLoadingClass(_loading)]]">
       <h2 id="options" class="heading-2">Command</h2>
       <div id="form">
-        <h3>Create change</h3>
+        <h3 class="heading-3">Create change</h3>
         <gr-button loading="[[_creatingChange]]" on-click="_createNewChange">
           Create change
         </gr-button>
-        <h3>Edit repo config</h3>
+        <h3 class="heading-3">Edit repo config</h3>
         <gr-button
           id="editRepoConfig"
           loading="[[_editingConfig]]"
@@ -48,7 +51,7 @@
         >
           Edit repo config
         </gr-button>
-        <h3 hidden="[[!_repoConfig.actions.gc.enabled]]">
+        <h3 class="heading-3" hidden="[[!_repoConfig.actions.gc.enabled]]">
           [[_repoConfig.actions.gc.label]]
         </h3>
         <gr-button
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.js b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.js
index 893efe55..ac48484 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.js
@@ -18,7 +18,11 @@
 import '../../../test/common-test-setup-karma.js';
 import './gr-repo-commands.js';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
-import {addListenerForTest, stubRestApi} from '../../../test/test-utils.js';
+import {
+  addListenerForTest,
+  mockPromise,
+  stubRestApi,
+} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-repo-commands');
 
@@ -112,7 +116,7 @@
   });
 
   suite('404', () => {
-    test('fires page-error', done => {
+    test('fires page-error', async () => {
       repoStub.restore();
 
       element.repo = 'test';
@@ -120,13 +124,18 @@
       const response = {status: 404};
       stubRestApi('getProjectConfig').callsFake((repo, errFn) => {
         errFn(response);
+        return Promise.resolve(undefined);
       });
+
+      await flush();
+      const promise = mockPromise();
       addListenerForTest(document, 'page-error', e => {
         assert.deepEqual(e.detail.response, response);
-        done();
+        promise.resolve();
       });
 
       element._loadRepo();
+      await promise;
     });
   });
 });
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.ts b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.ts
index 4cc0a80..fc7ef4b 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.ts
@@ -22,8 +22,8 @@
 import {ErrorCallback} from '../../../api/rest';
 import {sharedStyles} from '../../../styles/shared-styles';
 import {tableStyles} from '../../../styles/gr-table-styles';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property, PropertyValues} from 'lit-element';
+import {LitElement, css, html, PropertyValues} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 interface DashboardRef {
   section: string;
@@ -31,7 +31,7 @@
 }
 
 @customElement('gr-repo-dashboards')
-export class GrRepoDashboards extends GrLitElement {
+export class GrRepoDashboards extends LitElement {
   @property({type: String})
   repo?: RepoName;
 
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.ts b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.ts
index ede2bb9..a54eafb 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.ts
@@ -21,6 +21,7 @@
 import {GerritNav} from '../../core/gr-navigation/gr-navigation';
 import {
   addListenerForTest,
+  mockPromise,
   queryAndAssert,
   stubRestApi,
 } from '../../../test/test-utils';
@@ -92,7 +93,7 @@
       );
     });
 
-    test('loading, sections, and ordering', done => {
+    test('loading, sections, and ordering', async () => {
       assert.isTrue(element._loading);
       assert.notEqual(
         getComputedStyle(queryAndAssert(element, '#loadingContainer')).display,
@@ -103,29 +104,25 @@
         'none'
       );
       element.repo = 'test' as RepoName;
-      flush(() => {
-        assert.equal(
-          getComputedStyle(queryAndAssert(element, '#loadingContainer'))
-            .display,
-          'none'
-        );
-        assert.notEqual(
-          getComputedStyle(queryAndAssert(element, '#dashboards')).display,
-          'none'
-        );
+      await flush();
+      assert.equal(
+        getComputedStyle(queryAndAssert(element, '#loadingContainer')).display,
+        'none'
+      );
+      assert.notEqual(
+        getComputedStyle(queryAndAssert(element, '#dashboards')).display,
+        'none'
+      );
 
-        const dashboard = element._dashboards!;
-        assert.equal(dashboard.length!, 2);
-        assert.equal(dashboard[0].section!, 'custom');
-        assert.equal(dashboard[1].section!, 'default');
+      const dashboard = element._dashboards!;
+      assert.equal(dashboard.length!, 2);
+      assert.equal(dashboard[0].section!, 'custom');
+      assert.equal(dashboard[1].section!, 'default');
 
-        const dashboards = dashboard[0].dashboards;
-        assert.equal(dashboards.length, 2);
-        assert.equal(dashboards[0].id, 'custom:custom1');
-        assert.equal(dashboards[1].id, 'custom:custom2');
-
-        done();
-      });
+      const dashboards = dashboard[0].dashboards;
+      assert.equal(dashboards.length, 2);
+      assert.equal(dashboards[0].id, 'custom:custom1');
+      assert.equal(dashboards[1].id, 'custom:custom2');
     });
   });
 
@@ -148,19 +145,21 @@
   });
 
   suite('404', () => {
-    test('fires page-error', done => {
+    test('fires page-error', async () => {
       const response = {status: 404} as Response;
       stubRestApi('getRepoDashboards').callsFake((_repo, errFn) => {
         errFn!(response);
         return Promise.resolve([]);
       });
 
+      const promise = mockPromise();
       addListenerForTest(document, 'page-error', e => {
         assert.deepEqual((e as PageErrorEvent).detail.response, response);
-        done();
+        promise.resolve();
       });
 
       element.repo = 'test' as RepoName;
+      await promise;
     });
   });
 });
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
index e243974..3693845 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
@@ -59,8 +59,12 @@
     createNewModal: GrCreatePointerDialog;
   };
 }
+
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = ListViewMixin(PolymerElement);
+
 @customElement('gr-repo-detail-list')
-export class GrRepoDetailList extends ListViewMixin(PolymerElement) {
+export class GrRepoDetailList extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.js b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.js
index 1af9c02..d5eb5d5 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.js
@@ -20,7 +20,11 @@
 import 'lodash/lodash.js';
 import {page} from '../../../utils/page-wrapper-utils.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
-import {addListenerForTest, stubRestApi} from '../../../test/test-utils.js';
+import {
+  addListenerForTest,
+  mockPromise,
+  stubRestApi,
+} from '../../../test/test-utils.js';
 import {RepoDetailView} from '../../core/gr-navigation/gr-navigation.js';
 
 const basicFixture = fixtureFromElement('gr-repo-detail-list');
@@ -71,7 +75,7 @@
     });
 
     suite('list of repo branches', () => {
-      setup(done => {
+      setup(async () => {
         branches = [{
           ref: 'HEAD',
           revision: 'master',
@@ -82,53 +86,43 @@
           repo: 'test',
           detail: 'branches',
         };
-        element._paramsChanged(params).then(() => { flush(done); });
+        await element._paramsChanged(params);
+        await flush();
       });
 
-      test('test for branch in the list', done => {
-        flush(() => {
-          assert.equal(element._items[2].ref, 'refs/heads/test2');
-          done();
-        });
+      test('test for branch in the list', () => {
+        assert.equal(element._items[2].ref, 'refs/heads/test2');
       });
 
-      test('test for web links in the branches list', done => {
-        flush(() => {
-          assert.equal(element._items[2].web_links[0].url,
-              'https://git.example.org/branch/test;refs/heads/test2');
-          done();
-        });
+      test('test for web links in the branches list', () => {
+        assert.equal(element._items[2].web_links[0].url,
+            'https://git.example.org/branch/test;refs/heads/test2');
       });
 
-      test('test for refs/heads/ being striped from ref', done => {
-        flush(() => {
-          assert.equal(element._stripRefs(element._items[2].ref,
-              element.detailType), 'test2');
-          done();
-        });
+      test('test for refs/heads/ being striped from ref', () => {
+        assert.equal(element._stripRefs(element._items[2].ref,
+            element.detailType), 'test2');
       });
 
       test('_shownItems', () => {
         assert.equal(element._shownItems.length, 25);
       });
 
-      test('Edit HEAD button not admin', done => {
+      test('Edit HEAD button not admin', async () => {
         sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
         stubRestApi('getRepoAccess').returns(
             Promise.resolve({
               test: {is_owner: false},
             }));
-        element._determineIfOwner('test').then(() => {
-          assert.equal(element._isOwner, false);
-          assert.equal(getComputedStyle(dom(element.root)
-              .querySelector('.revisionNoEditing')).display, 'inline');
-          assert.equal(getComputedStyle(dom(element.root)
-              .querySelector('.revisionEdit')).display, 'none');
-          done();
-        });
+        await element._determineIfOwner('test');
+        assert.equal(element._isOwner, false);
+        assert.equal(getComputedStyle(dom(element.root)
+            .querySelector('.revisionNoEditing')).display, 'inline');
+        assert.equal(getComputedStyle(dom(element.root)
+            .querySelector('.revisionEdit')).display, 'none');
       });
 
-      test('Edit HEAD button admin', done => {
+      test('Edit HEAD button admin', async () => {
         const saveBtn = element.root.querySelector('.saveBtn');
         const cancelBtn = element.root.querySelector('.cancelBtn');
         const editBtn = element.root.querySelector('.editBtn');
@@ -143,76 +137,74 @@
               test: {is_owner: true},
             }));
         sinon.stub(element, '_handleSaveRevision');
-        element._determineIfOwner('test').then(() => {
-          assert.equal(element._isOwner, true);
-          // The revision container for non-editing enabled row is not visible.
-          assert.equal(getComputedStyle(revisionNoEditing).display, 'none');
+        await element._determineIfOwner('test');
+        assert.equal(element._isOwner, true);
+        // The revision container for non-editing enabled row is not visible.
+        assert.equal(getComputedStyle(revisionNoEditing).display, 'none');
 
-          // The revision container for editing enabled row is visible.
-          assert.notEqual(getComputedStyle(dom(element.root)
-              .querySelector('.revisionEdit')).display, 'none');
+        // The revision container for editing enabled row is visible.
+        assert.notEqual(getComputedStyle(dom(element.root)
+            .querySelector('.revisionEdit')).display, 'none');
 
-          // The revision and edit button are visible.
-          assert.notEqual(getComputedStyle(revisionWithEditing).display,
-              'none');
-          assert.notEqual(getComputedStyle(editBtn).display, 'none');
+        // The revision and edit button are visible.
+        assert.notEqual(getComputedStyle(revisionWithEditing).display,
+            'none');
+        assert.notEqual(getComputedStyle(editBtn).display, 'none');
 
-          // The input, cancel, and save buttons are not visible.
-          const hiddenElements = dom(element.root)
-              .querySelectorAll('.canEdit .editItem');
+        // The input, cancel, and save buttons are not visible.
+        const hiddenElements = dom(element.root)
+            .querySelectorAll('.canEdit .editItem');
 
-          for (const item of hiddenElements) {
-            assert.equal(getComputedStyle(item).display, 'none');
-          }
+        for (const item of hiddenElements) {
+          assert.equal(getComputedStyle(item).display, 'none');
+        }
 
-          MockInteractions.tap(editBtn);
-          flush();
-          // The revision and edit button are not visible.
-          assert.equal(getComputedStyle(revisionWithEditing).display, 'none');
-          assert.equal(getComputedStyle(editBtn).display, 'none');
+        MockInteractions.tap(editBtn);
+        await flush();
+        // The revision and edit button are not visible.
+        assert.equal(getComputedStyle(revisionWithEditing).display, 'none');
+        assert.equal(getComputedStyle(editBtn).display, 'none');
 
-          // The input, cancel, and save buttons are not visible.
-          for (const item of hiddenElements) {
-            assert.notEqual(getComputedStyle(item).display, 'none');
-          }
+        // The input, cancel, and save buttons are not visible.
+        for (const item of hiddenElements) {
+          assert.notEqual(getComputedStyle(item).display, 'none');
+        }
 
-          // The revised ref was set correctly
-          assert.equal(element._revisedRef, 'master');
+        // The revised ref was set correctly
+        assert.equal(element._revisedRef, 'master');
 
-          assert.isFalse(saveBtn.disabled);
+        assert.isFalse(saveBtn.disabled);
 
-          // Delete the ref.
-          element._revisedRef = '';
-          assert.isTrue(saveBtn.disabled);
+        // Delete the ref.
+        element._revisedRef = '';
+        assert.isTrue(saveBtn.disabled);
 
-          // Change the ref to something else
-          element._revisedRef = 'newRef';
-          element._repo = 'test';
-          assert.isFalse(saveBtn.disabled);
+        // Change the ref to something else
+        element._revisedRef = 'newRef';
+        element._repo = 'test';
+        assert.isFalse(saveBtn.disabled);
 
-          // Save button calls handleSave. since this is stubbed, the edit
-          // section remains open.
-          MockInteractions.tap(saveBtn);
-          assert.isTrue(element._handleSaveRevision.called);
+        // Save button calls handleSave. since this is stubbed, the edit
+        // section remains open.
+        MockInteractions.tap(saveBtn);
+        assert.isTrue(element._handleSaveRevision.called);
 
-          // When cancel is tapped, the edit secion closes.
-          MockInteractions.tap(cancelBtn);
-          flush();
+        // When cancel is tapped, the edit secion closes.
+        MockInteractions.tap(cancelBtn);
+        await flush();
 
-          // The revision and edit button are visible.
-          assert.notEqual(getComputedStyle(revisionWithEditing).display,
-              'none');
-          assert.notEqual(getComputedStyle(editBtn).display, 'none');
+        // The revision and edit button are visible.
+        assert.notEqual(getComputedStyle(revisionWithEditing).display,
+            'none');
+        assert.notEqual(getComputedStyle(editBtn).display, 'none');
 
-          // The input, cancel, and save buttons are not visible.
-          for (const item of hiddenElements) {
-            assert.equal(getComputedStyle(item).display, 'none');
-          }
-          done();
-        });
+        // The input, cancel, and save buttons are not visible.
+        for (const item of hiddenElements) {
+          assert.equal(getComputedStyle(item).display, 'none');
+        }
       });
 
-      test('_handleSaveRevision with invalid rev', done => {
+      test('_handleSaveRevision with invalid rev', async () => {
         const event = {model: {set: sinon.stub()}};
         element._isEditing = true;
         stubRestApi('setRepoHead').returns(
@@ -221,14 +213,12 @@
             })
         );
 
-        element._setRepoHead('test', 'newRef', event).then(() => {
-          assert.isTrue(element._isEditing);
-          assert.isFalse(event.model.set.called);
-          done();
-        });
+        await element._setRepoHead('test', 'newRef', event);
+        assert.isTrue(element._isEditing);
+        assert.isFalse(event.model.set.called);
       });
 
-      test('_handleSaveRevision with valid rev', done => {
+      test('_handleSaveRevision with valid rev', async () => {
         const event = {model: {set: sinon.stub()}};
         element._isEditing = true;
         stubRestApi('setRepoHead').returns(
@@ -237,11 +227,9 @@
             })
         );
 
-        element._setRepoHead('test', 'newRef', event).then(() => {
-          assert.isFalse(element._isEditing);
-          assert.isTrue(event.model.set.called);
-          done();
-        });
+        await element._setRepoHead('test', 'newRef', event);
+        assert.isFalse(element._isEditing);
+        assert.isTrue(event.model.set.called);
       });
 
       test('test _computeItemName', () => {
@@ -251,7 +239,7 @@
     });
 
     suite('list with less then 25 branches', () => {
-      setup(done => {
+      setup(async () => {
         branches = _.times(25, branchGenerator);
         stubRestApi('getRepoBranches').returns(Promise.resolve(branches));
 
@@ -260,7 +248,8 @@
           detail: 'branches',
         };
 
-        element._paramsChanged(params).then(() => { flush(done); });
+        await element._paramsChanged(params);
+        await flush();
       });
 
       test('_shownItems', () => {
@@ -287,16 +276,18 @@
     });
 
     suite('404', () => {
-      test('fires page-error', done => {
+      test('fires page-error', async () => {
         const response = {status: 404};
         stubRestApi('getRepoBranches').callsFake(
             (filter, repo, reposBranchesPerPage, opt_offset, errFn) => {
               errFn(response);
+              return Promise.resolve();
             });
 
+        const promise = mockPromise();
         addListenerForTest(document, 'page-error', e => {
           assert.deepEqual(e.detail.response, response);
-          done();
+          promise.resolve();
         });
 
         const params = {
@@ -306,6 +297,7 @@
           offset: 25,
         };
         element._paramsChanged(params);
+        await promise;
       });
     });
   });
@@ -341,7 +333,7 @@
     });
 
     suite('list of repo tags', () => {
-      setup(done => {
+      setup(async () => {
         tags = _.times(26, tagGenerator);
         stubRestApi('getRepoTags').returns(Promise.resolve(tags));
 
@@ -350,50 +342,37 @@
           detail: 'tags',
         };
 
-        element._paramsChanged(params).then(() => { flush(done); });
+        await element._paramsChanged(params);
+        await flush();
       });
 
-      test('test for tag in the list', done => {
-        flush(() => {
-          assert.equal(element._items[1].ref, 'refs/tags/test2');
-          done();
-        });
+      test('test for tag in the list', async () => {
+        assert.equal(element._items[1].ref, 'refs/tags/test2');
       });
 
-      test('test for tag message in the list', done => {
-        flush(() => {
-          assert.equal(element._items[1].message, 'Annotated tag');
-          done();
-        });
+      test('test for tag message in the list', async () => {
+        assert.equal(element._items[1].message, 'Annotated tag');
       });
 
-      test('test for tagger in the tag list', done => {
+      test('test for tagger in the tag list', async () => {
         const tagger = {
           name: 'Test User',
           email: 'test.user@gmail.com',
           date: '2017-09-19 14:54:00.000000000',
           tz: 540,
         };
-        flush(() => {
-          assert.deepEqual(element._items[1].tagger, tagger);
-          done();
-        });
+
+        assert.deepEqual(element._items[1].tagger, tagger);
       });
 
-      test('test for web links in the tags list', done => {
-        flush(() => {
-          assert.equal(element._items[1].web_links[0].url,
-              'https://git.example.org/tag/test;refs/tags/test2');
-          done();
-        });
+      test('test for web links in the tags list', async () => {
+        assert.equal(element._items[1].web_links[0].url,
+            'https://git.example.org/tag/test;refs/tags/test2');
       });
 
-      test('test for refs/tags/ being striped from ref', done => {
-        flush(() => {
-          assert.equal(element._stripRefs(element._items[1].ref,
-              element.detailType), 'test2');
-          done();
-        });
+      test('test for refs/tags/ being striped from ref', async () => {
+        assert.equal(element._stripRefs(element._items[1].ref,
+            element.detailType), 'test2');
       });
 
       test('_shownItems', () => {
@@ -411,7 +390,7 @@
     });
 
     suite('list with less then 25 tags', () => {
-      setup(done => {
+      setup(async () => {
         tags = _.times(25, tagGenerator);
         stubRestApi('getRepoTags').returns(Promise.resolve(tags));
 
@@ -420,7 +399,8 @@
           detail: 'tags',
         };
 
-        element._paramsChanged(params).then(() => { flush(done); });
+        await element._paramsChanged(params);
+        await flush();
       });
 
       test('_shownItems', () => {
@@ -482,16 +462,18 @@
     });
 
     suite('404', () => {
-      test('fires page-error', done => {
+      test('fires page-error', async () => {
         const response = {status: 404};
         stubRestApi('getRepoTags').callsFake(
             (filter, repo, reposTagsPerPage, opt_offset, errFn) => {
               errFn(response);
+              return Promise.resolve();
             });
 
+        const promise = mockPromise();
         addListenerForTest(document, 'page-error', e => {
           assert.deepEqual(e.detail.response, response);
-          done();
+          promise.resolve();
         });
 
         const params = {
@@ -501,6 +483,7 @@
           offset: 25,
         };
         element._paramsChanged(params);
+        await promise;
       });
     });
 
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
index 982fd8d..e87bb0d 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
@@ -46,8 +46,11 @@
   };
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = ListViewMixin(PolymerElement);
+
 @customElement('gr-repo-list')
-export class GrRepoList extends ListViewMixin(PolymerElement) {
+export class GrRepoList extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.js b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.js
index 4864af5..8fef4d0 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.js
@@ -53,17 +53,16 @@
   });
 
   suite('list with repos', () => {
-    setup(done => {
+    setup(async () => {
       repos = _.times(26, repoGenerator);
       stubRestApi('getRepos').returns(Promise.resolve(repos));
-      element._paramsChanged(value).then(() => { flush(done); });
+      await element._paramsChanged(value);
+      await flush();
     });
 
-    test('test for test repo in the list', done => {
-      flush(() => {
-        assert.equal(element._repos[1].id, 'test2');
-        done();
-      });
+    test('test for test repo in the list', async () => {
+      await flush();
+      assert.equal(element._repos[1].id, 'test2');
     });
 
     test('_shownRepos', () => {
@@ -84,10 +83,11 @@
   });
 
   suite('list with less then 25 repos', () => {
-    setup(done => {
+    setup(async () => {
       repos = _.times(25, repoGenerator);
       stubRestApi('getRepos').returns(Promise.resolve(repos));
-      element._paramsChanged(value).then(() => { flush(done); });
+      await element._paramsChanged(value);
+      await flush();
     });
 
     test('_shownRepos', () => {
@@ -113,17 +113,15 @@
       assert.isTrue(repoStub.lastCall.calledWithExactly('test', 25, 25));
     });
 
-    test('latest repos requested are always set', done => {
+    test('latest repos requested are always set', async () => {
       const repoStub = stubRestApi('getRepos');
       repoStub.withArgs('test').returns(Promise.resolve(repos));
       repoStub.withArgs('filter').returns(Promise.resolve(reposFiltered));
       element._filter = 'test';
 
       // Repos are not set because the element._filter differs.
-      element._getRepos('filter', 25, 0).then(() => {
-        assert.deepEqual(element._repos, []);
-        done();
-      });
+      await element._getRepos('filter', 25, 0);
+      assert.deepEqual(element._repos, []);
     });
 
     test('filter is case insensitive', async () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config-types.ts b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config-types.ts
index 6a96f55..d5515f9 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config-types.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config-types.ts
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 /**
- * @fileOverview This file contains interfaces shared between
+ * @fileoverview This file contains interfaces shared between
  * gr-repo-plugin-config.ts and nested editors
  * (e.g. gr-plugin-config-array-editor.ts)
  *
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
index 42060ee..02339cf 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
@@ -23,8 +23,8 @@
 import '../../shared/gr-select/gr-select';
 import '../../shared/gr-tooltip-content/gr-tooltip-content';
 import '../gr-plugin-config-array-editor/gr-plugin-config-array-editor';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 import {ConfigParameterInfoType} from '../../../constants/constants';
 import {
   ConfigParameterInfo,
@@ -57,7 +57,7 @@
 }
 
 @customElement('gr-repo-plugin-config')
-export class GrRepoPluginConfig extends GrLitElement {
+export class GrRepoPluginConfig extends LitElement {
   /**
    * Fired when the plugin config changes.
    *
@@ -149,7 +149,7 @@
           ?checked=${this._computeChecked(option.info.value)}
           @change=${this._handleBooleanChange}
           data-option-key=${option._key}
-          ?disabled=${option.info.editable}
+          ?disabled=${!option.info.editable}
           @click=${this._onTapPluginBoolean}
         ></paper-toggle-button>
       `;
@@ -158,7 +158,7 @@
         <gr-select value=${option.info.value} @change=${this._handleListChange}>
           <select
             data-option-key=${option._key}
-            ?disabled=${option.info.editable}
+            ?disabled=${!option.info.editable}
           >
             ${(option.info.permitted_values || []).map(
               value => html`<option value="${value}">${value}</option>`
@@ -176,14 +176,14 @@
           value=${option.info.value}
           @input=${this._handleStringChange}
           data-option-key="${option._key}"
-          ?disabled=${option.info.editable}
+          ?disabled=${!option.info.editable}
         >
           <input
             is="iron-input"
             value="${option.info.value}"
             @input=${this._handleStringChange}
             data-option-key="${option._key}"
-            ?disabled=${option.info.editable}
+            ?disabled=${!option.info.editable}
           />
         </iron-input>
       `;
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.js b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.js
index 17e8256..8c2e6b3 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.js
@@ -67,7 +67,7 @@
     test('ARRAY type option', async () => {
       element.pluginData = {
         name: 'testName',
-        config: {plugin: {value: 'test', type: 'ARRAY'}},
+        config: {plugin: {value: 'test', type: 'ARRAY', editable: true}},
       };
       await flush();
 
@@ -82,7 +82,7 @@
     test('BOOLEAN type option', async () => {
       element.pluginData = {
         name: 'testName',
-        config: {plugin: {value: 'true', type: 'BOOLEAN'}},
+        config: {plugin: {value: 'true', type: 'BOOLEAN', editable: true}},
       };
       await flush();
 
@@ -101,7 +101,7 @@
     test('INT/LONG/STRING type option', async () => {
       element.pluginData = {
         name: 'testName',
-        config: {plugin: {value: 'test', type: 'STRING'}},
+        config: {plugin: {value: 'test', type: 'STRING', editable: true}},
       };
       await flush();
 
@@ -122,7 +122,9 @@
       const permitted_values = ['test', 'newTest'];
       element.pluginData = {
         name: 'testName',
-        config: {plugin: {value: 'test', type: 'LIST', permitted_values}},
+        config: {plugin:
+          {value: 'test', type: 'LIST', editable: true, permitted_values},
+        },
       };
       await flush();
 
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
index d132267..cd5b095 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
@@ -20,6 +20,7 @@
 import '../../plugins/gr-endpoint-param/gr-endpoint-param';
 import '../../shared/gr-download-commands/gr-download-commands';
 import '../../shared/gr-select/gr-select';
+import '../../../styles/gr-font-styles';
 import '../../../styles/gr-form-styles';
 import '../../../styles/gr-subpage-styles';
 import '../../../styles/shared-styles';
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_html.ts b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_html.ts
index ef0e6b4..71abec0 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_html.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_html.ts
@@ -17,6 +17,9 @@
 import {html} from '@polymer/polymer/lib/utils/html-tag';
 
 export const htmlTemplate = html`
+  <style include="gr-font-styles">
+    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+  </style>
   <style include="shared-styles">
     /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
   </style>
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.js b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.js
index 9c3646a..f3df132 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.js
@@ -180,7 +180,7 @@
   });
 
   suite('already existing generic rule', () => {
-    setup(done => {
+    setup(async () => {
       element.group = 'Group Name';
       element.permission = 'submit';
       element.rule = {
@@ -195,11 +195,8 @@
       // Typically called on ready since elements will have properties defined
       // by the parent element.
       element._setupValues(element.rule);
-      flush();
-      flush(() => {
-        element.connectedCallback();
-        done();
-      });
+      await flush();
+      element.connectedCallback();
     });
 
     test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -295,7 +292,7 @@
   });
 
   suite('new edit rule', () => {
-    setup(done => {
+    setup(async () => {
       element.group = 'Group Name';
       element.permission = 'editTopicName';
       element.rule = {
@@ -303,12 +300,10 @@
       };
       element.section = 'refs/*';
       element._setupValues(element.rule);
-      flush();
+      await flush();
       element.rule.value.added = true;
-      flush(() => {
-        element.connectedCallback();
-        done();
-      });
+      await flush();
+      element.connectedCallback();
     });
 
     test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -348,7 +343,7 @@
   });
 
   suite('already existing rule with labels', () => {
-    setup(done => {
+    setup(async () => {
       element.label = {values: [
         {value: -2, text: 'This shall not be merged'},
         {value: -1, text: 'I would prefer this is not merged as is'},
@@ -369,11 +364,8 @@
       };
       element.section = 'refs/*';
       element._setupValues(element.rule);
-      flush();
-      flush(() => {
-        element.connectedCallback();
-        done();
-      });
+      await flush();
+      element.connectedCallback();
     });
 
     test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -406,7 +398,7 @@
   });
 
   suite('new rule with labels', () => {
-    setup(done => {
+    setup(async () => {
       sinon.spy(element, '_setDefaultRuleValues');
       element.label = {values: [
         {value: -2, text: 'This shall not be merged'},
@@ -422,12 +414,10 @@
       };
       element.section = 'refs/*';
       element._setupValues(element.rule);
-      flush();
+      await flush();
       element.rule.value.added = true;
-      flush(() => {
-        element.connectedCallback();
-        done();
-      });
+      await flush();
+      element.connectedCallback();
     });
 
     test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -468,7 +458,7 @@
   });
 
   suite('already existing push rule', () => {
-    setup(done => {
+    setup(async () => {
       element.group = 'Group Name';
       element.permission = 'push';
       element.rule = {
@@ -480,11 +470,8 @@
       };
       element.section = 'refs/*';
       element._setupValues(element.rule);
-      flush();
-      flush(() => {
-        element.connectedCallback();
-        done();
-      });
+      await flush();
+      element.connectedCallback();
     });
 
     test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -513,7 +500,7 @@
   });
 
   suite('new push rule', () => {
-    setup(done => {
+    setup(async () => {
       element.group = 'Group Name';
       element.permission = 'push';
       element.rule = {
@@ -521,12 +508,10 @@
       };
       element.section = 'refs/*';
       element._setupValues(element.rule);
-      flush();
+      await flush();
       element.rule.value.added = true;
-      flush(() => {
-        element.connectedCallback();
-        done();
-      });
+      await flush();
+      element.connectedCallback();
     });
 
     test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -557,7 +542,7 @@
   });
 
   suite('already existing edit rule', () => {
-    setup(done => {
+    setup(async () => {
       element.group = 'Group Name';
       element.permission = 'editTopicName';
       element.rule = {
@@ -569,11 +554,8 @@
       };
       element.section = 'refs/*';
       element._setupValues(element.rule);
-      flush();
-      flush(() => {
-        element.connectedCallback();
-        done();
-      });
+      await flush();
+      element.connectedCallback();
     });
 
     test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -590,10 +572,10 @@
       assert.isNotOk(element.root.querySelector('#labelMax'));
     });
 
-    test('modify value', () => {
+    test('modify value', async () => {
       assert.isNotOk(element.rule.value.modified);
       element.$.action.bindValue = false;
-      flush();
+      await flush();
       assert.isTrue(element.rule.value.modified);
 
       // The original value should now differ from the rule values.
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
index 50bbda9..313b91f 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
@@ -76,8 +76,11 @@
 // How many reviewers should be shown with an account-label?
 const PRIMARY_REVIEWERS_COUNT = 2;
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = ChangeTableMixin(PolymerElement);
+
 @customElement('gr-change-list-item')
-export class GrChangeListItem extends ChangeTableMixin(PolymerElement) {
+export class GrChangeListItem extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_html.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_html.ts
index 54d3911..355ef45 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_html.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_html.ts
@@ -66,7 +66,7 @@
     ></gr-repo-header>
     <gr-user-header
       user-id="[[_userId]]"
-      show-dashboard-link=""
+      showDashboardLink=""
       logged-in="[[_loggedIn]]"
       class$="[[_computeHeaderClass(_userId)]]"
     ></gr-user-header>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.js b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.js
index 445254a..86b2fd1 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.js
@@ -20,7 +20,7 @@
 import {page} from '../../../utils/page-wrapper-utils.js';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
 import 'lodash/lodash.js';
-import {stubRestApi} from '../../../test/test-utils.js';
+import {mockPromise, stubRestApi} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-change-list-view');
 
@@ -38,10 +38,8 @@
     element = basicFixture.instantiate();
   });
 
-  teardown(done => {
-    flush(() => {
-      done();
-    });
+  teardown(async () => {
+    await flush();
   });
 
   test('_computePage', () => {
@@ -126,128 +124,123 @@
     assert.isTrue(showStub.called);
   });
 
-  test('_userId query', done => {
+  test('_userId query', async () => {
     assert.isNull(element._userId);
     element._query = 'owner: foo@bar';
     element._changes = [{owner: {email: 'foo@bar'}}];
-    flush(() => {
-      assert.equal(element._userId, 'foo@bar');
+    await flush();
+    assert.equal(element._userId, 'foo@bar');
 
-      element._query = 'foo bar baz';
-      element._changes = [{owner: {email: 'foo@bar'}}];
-      assert.isNull(element._userId);
-
-      done();
-    });
+    element._query = 'foo bar baz';
+    element._changes = [{owner: {email: 'foo@bar'}}];
+    assert.isNull(element._userId);
   });
 
-  test('_userId query without email', done => {
+  test('_userId query without email', async () => {
     assert.isNull(element._userId);
     element._query = 'owner: foo@bar';
     element._changes = [{owner: {}}];
-    flush(() => {
-      assert.isNull(element._userId);
-      done();
-    });
+    await flush();
+    assert.isNull(element._userId);
   });
 
-  test('_repo query', done => {
+  test('_repo query', async () => {
     assert.isNull(element._repo);
     element._query = 'project: test-repo';
     element._changes = [{owner: {email: 'foo@bar'}, project: 'test-repo'}];
-    flush(() => {
-      assert.equal(element._repo, 'test-repo');
-      element._query = 'foo bar baz';
-      element._changes = [{owner: {email: 'foo@bar'}}];
-      assert.isNull(element._repo);
-      done();
-    });
+    await flush();
+    assert.equal(element._repo, 'test-repo');
+    element._query = 'foo bar baz';
+    element._changes = [{owner: {email: 'foo@bar'}}];
+    assert.isNull(element._repo);
   });
 
-  test('_repo query with open status', done => {
+  test('_repo query with open status', async () => {
     assert.isNull(element._repo);
     element._query = 'project:test-repo status:open';
     element._changes = [{owner: {email: 'foo@bar'}, project: 'test-repo'}];
-    flush(() => {
-      assert.equal(element._repo, 'test-repo');
-      element._query = 'foo bar baz';
-      element._changes = [{owner: {email: 'foo@bar'}}];
-      assert.isNull(element._repo);
-      done();
-    });
+    await flush();
+    assert.equal(element._repo, 'test-repo');
+    element._query = 'foo bar baz';
+    element._changes = [{owner: {email: 'foo@bar'}}];
+    assert.isNull(element._repo);
   });
 
   suite('query based navigation', () => {
     setup(() => {
     });
 
-    teardown(done => {
-      flush(() => {
-        sinon.restore();
-        done();
-      });
+    teardown(async () => {
+      await flush();
+      sinon.restore();
     });
 
-    test('Searching for a change ID redirects to change', done => {
+    test('Searching for a change ID redirects to change', async () => {
       const change = {_number: 1};
       sinon.stub(element, '_getChanges')
           .returns(Promise.resolve([change]));
+      const promise = mockPromise();
       sinon.stub(GerritNav, 'navigateToChange').callsFake(
           (url, opt_patchNum, opt_basePatchNum, opt_isEdit, opt_redirect) => {
             assert.equal(url, change);
             assert.isTrue(opt_redirect);
-            done();
+            promise.resolve();
           });
 
       element.params = {view: GerritNav.View.SEARCH, query: CHANGE_ID};
+      await promise;
     });
 
-    test('Searching for a change num redirects to change', done => {
+    test('Searching for a change num redirects to change', async () => {
       const change = {_number: 1};
       sinon.stub(element, '_getChanges')
           .returns(Promise.resolve([change]));
+      const promise = mockPromise();
       sinon.stub(GerritNav, 'navigateToChange').callsFake(
           (url, opt_patchNum, opt_basePatchNum, opt_isEdit, opt_redirect) => {
             assert.equal(url, change);
             assert.isTrue(opt_redirect);
-            done();
+            promise.resolve();
           });
 
       element.params = {view: GerritNav.View.SEARCH, query: '1'};
+      await promise;
     });
 
-    test('Commit hash redirects to change', done => {
+    test('Commit hash redirects to change', async () => {
       const change = {_number: 1};
       sinon.stub(element, '_getChanges')
           .returns(Promise.resolve([change]));
+      const promise = mockPromise();
       sinon.stub(GerritNav, 'navigateToChange').callsFake(
           (url, opt_patchNum, opt_basePatchNum, opt_isEdit, opt_redirect) => {
             assert.equal(url, change);
             assert.isTrue(opt_redirect);
-            done();
+            promise.resolve();
           });
 
       element.params = {view: GerritNav.View.SEARCH, query: COMMIT_HASH};
+      await promise;
     });
 
-    test('Searching for an invalid change ID searches', () => {
+    test('Searching for an invalid change ID searches', async () => {
       sinon.stub(element, '_getChanges')
           .returns(Promise.resolve([]));
       const stub = sinon.stub(GerritNav, 'navigateToChange');
 
       element.params = {view: GerritNav.View.SEARCH, query: CHANGE_ID};
-      flush();
+      await flush();
 
       assert.isFalse(stub.called);
     });
 
-    test('Change ID with multiple search results searches', () => {
+    test('Change ID with multiple search results searches', async () => {
       sinon.stub(element, '_getChanges')
           .returns(Promise.resolve([{}, {}]));
       const stub = sinon.stub(GerritNav, 'navigateToChange');
 
       element.params = {view: GerritNav.View.SEARCH, query: CHANGE_ID};
-      flush();
+      await flush();
 
       assert.isFalse(stub.called);
     });
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
index 5c0bab8..40674d4 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
@@ -62,13 +62,16 @@
   query?: string;
   results: ChangeInfo[];
 }
+
 export interface GrChangeList {
   $: {};
 }
+
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = ChangeTableMixin(KeyboardShortcutMixin(PolymerElement));
+
 @customElement('gr-change-list')
-export class GrChangeList extends ChangeTableMixin(
-  KeyboardShortcutMixin(PolymerElement)
-) {
+export class GrChangeList extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.ts
index 37d969e..c31da77 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.ts
@@ -63,7 +63,7 @@
               class="cell"
               colspan$="[[_computeColspan(changeSection, visibleChangeTableColumns, labelNames)]]"
             >
-              <h2>
+              <h2 class="heading-3">
                 <a
                   href$="[[_sectionHref(changeSection.query)]]"
                   class="section-title"
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.js b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.js
index 13490d7..0d2056a 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.js
@@ -19,7 +19,7 @@
 import './gr-change-list.js';
 import {afterNextRender} from '@polymer/polymer/lib/utils/render-status.js';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
-import {TestKeyboardShortcutBinder} from '../../../test/test-utils.js';
+import {mockPromise, TestKeyboardShortcutBinder} from '../../../test/test-utils.js';
 import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js';
 import {YOUR_TURN} from '../../core/gr-navigation/gr-navigation.js';
 
@@ -149,7 +149,7 @@
         {}, changeTableColumns, labelNames));
   });
 
-  test('keyboard shortcuts', done => {
+  test('keyboard shortcuts', async () => {
     sinon.stub(element, '_computeLabelNames');
     element.sections = [
       {results: new Array(1)},
@@ -161,7 +161,8 @@
       {_number: 1},
       {_number: 2},
     ];
-    flush();
+    await flush();
+    const promise = mockPromise();
     afterNextRender(element, () => {
       const elementItems = element.root.querySelectorAll(
           'gr-change-list-item');
@@ -192,8 +193,9 @@
       MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
       assert.equal(element.selectedIndex, 0);
 
-      done();
+      promise.resolve();
     });
+    await promise;
   });
 
   test('no changes', () => {
@@ -436,7 +438,7 @@
       element = basicFixture.instantiate();
     });
 
-    test('keyboard shortcuts', done => {
+    test('keyboard shortcuts', async () => {
       element.selectedIndex = 0;
       element.sections = [
         {
@@ -461,7 +463,8 @@
           ],
         },
       ];
-      flush();
+      await flush();
+      const promise = mockPromise();
       afterNextRender(element, () => {
         const elementItems = element.root.querySelectorAll(
             'gr-change-list-item');
@@ -499,8 +502,9 @@
         MockInteractions.pressAndReleaseKeyOn(element, 82); // 'r'
         assert.equal(change.reviewed, false,
             'Should mark change as unreviewed');
-        done();
+        promise.resolve();
       });
+      await promise;
     });
 
     test('_computeItemHighlight gives false for null account', () => {
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.ts b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.ts
index 055996ee..fd2a5d7 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.ts
@@ -19,8 +19,9 @@
 import '../../shared/gr-icons/gr-icons';
 import {fireEvent} from '../../../utils/event-util';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement} from 'lit/decorators';
+import {fontStyles} from '../../../styles/gr-font-styles';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -29,10 +30,11 @@
 }
 
 @customElement('gr-create-change-help')
-export class GrCreateChangeHelp extends GrLitElement {
+export class GrCreateChangeHelp extends LitElement {
   static override get styles() {
     return [
       sharedStyles,
+      fontStyles,
       css`
         :host {
           display: block;
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.ts b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.ts
index 1a359e5..e170a74 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.ts
@@ -18,7 +18,7 @@
 import '../../../test/common-test-setup-karma';
 import './gr-create-change-help';
 import {GrCreateChangeHelp} from './gr-create-change-help';
-import {queryAndAssert} from '../../../test/test-utils';
+import {mockPromise, queryAndAssert} from '../../../test/test-utils';
 import {GrButton} from '../../shared/gr-button/gr-button';
 import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
 
@@ -32,8 +32,10 @@
     await flush();
   });
 
-  test('Create change tap', done => {
-    element.addEventListener('create-tap', () => done());
+  test('Create change tap', async () => {
+    const promise = mockPromise();
+    element.addEventListener('create-tap', () => promise.resolve());
     MockInteractions.tap(queryAndAssert<GrButton>(element, 'gr-button'));
+    await promise;
   });
 });
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.ts b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.ts
index 171c1c3..6c9fa68 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.ts
@@ -20,8 +20,8 @@
 import '../../shared/gr-shell-command/gr-shell-command';
 import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property, query} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property, query} from 'lit/decorators';
 
 enum Commands {
   CREATE = 'git commit',
@@ -36,7 +36,7 @@
 }
 
 @customElement('gr-create-commands-dialog')
-export class GrCreateCommandsDialog extends GrLitElement {
+export class GrCreateCommandsDialog extends LitElement {
   @query('#commandsOverlay')
   commandsOverlay?: GrOverlay;
 
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.js b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.js
index 473d2b9..96a19e0 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.js
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.js
@@ -22,7 +22,7 @@
 import {changeIsOpen} from '../../../utils/change-util.js';
 import {ChangeStatus} from '../../../constants/constants.js';
 import {createAccountWithId} from '../../../test/test-data-generators.js';
-import {addListenerForTest, stubRestApi, isHidden} from '../../../test/test-utils.js';
+import {addListenerForTest, stubRestApi, isHidden, mockPromise} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-dashboard-view');
 
@@ -395,21 +395,23 @@
         'hide');
   });
 
-  test('404 page', done => {
+  test('404 page', async () => {
     const response = {status: 404};
     stubRestApi('getDashboard').callsFake(
         async (project, dashboard, errFn) => {
           errFn(response);
         });
+    const promise = mockPromise();
     addListenerForTest(document, 'page-error', e => {
       assert.strictEqual(e.detail.response, response);
-      paramsChangedPromise.then(done);
+      promise.resolve();
     });
     element.params = {
       view: GerritNav.View.DASHBOARD,
       project: 'project',
       dashboard: 'dashboard',
     };
+    await Promise.all([paramsChangedPromise, promise]);
   });
 
   test('params change triggers dashboardDisplayed()', async () => {
@@ -427,7 +429,7 @@
     assert.isTrue(element.reporting.dashboardDisplayed.calledOnce);
   });
 
-  test('selectedChangeIndex is derived from the params', () => {
+  test('selectedChangeIndex is derived from the params', async () => {
     stubRestApi('getDashboard').returns(Promise.resolve({
       title: 'title',
       sections: [],
@@ -443,9 +445,8 @@
     };
     flush();
     sinon.stub(element.reporting, 'dashboardDisplayed');
-    paramsChangedPromise.then(() => {
-      assert.equal(element._selectedChangeIndex, 23);
-    });
+    await paramsChangedPromise;
+    assert.equal(element._selectedChangeIndex, 23);
   });
 });
 
diff --git a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts
index 7b60715..beadea3 100644
--- a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts
@@ -21,12 +21,13 @@
 import {WebLinkInfo} from '../../../types/diff';
 import {appContext} from '../../../services/app-context';
 import {sharedStyles} from '../../../styles/shared-styles';
+import {fontStyles} from '../../../styles/gr-font-styles';
 import {dashboardHeaderStyles} from '../../../styles/dashboard-header-styles';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property, PropertyValues} from 'lit-element';
+import {LitElement, css, html, PropertyValues} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 @customElement('gr-repo-header')
-export class GrRepoHeader extends GrLitElement {
+export class GrRepoHeader extends LitElement {
   @property({type: String})
   repo?: RepoName;
 
@@ -42,6 +43,7 @@
     return [
       sharedStyles,
       dashboardHeaderStyles,
+      fontStyles,
       css`
         .browse {
           display: inline-block;
diff --git a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.ts b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.ts
index 4c42005..bc21dc0 100644
--- a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.ts
@@ -25,11 +25,12 @@
 import {appContext} from '../../../services/app-context';
 import {dashboardHeaderStyles} from '../../../styles/dashboard-header-styles';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property, PropertyValues} from 'lit-element';
+import {fontStyles} from '../../../styles/gr-font-styles';
+import {LitElement, css, html, PropertyValues} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 @customElement('gr-user-header')
-export class GrUserHeader extends GrLitElement {
+export class GrUserHeader extends LitElement {
   @property({type: String})
   userId?: AccountId;
 
@@ -51,6 +52,7 @@
     return [
       sharedStyles,
       dashboardHeaderStyles,
+      fontStyles,
       css`
         .status.hide,
         .name.hide,
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
index 081df40..891482d 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
@@ -1190,6 +1190,13 @@
     });
   }
 
+  showSubmitDialog() {
+    if (!this._canSubmitChange()) {
+      return;
+    }
+    this._showActionDialog(this.$.confirmSubmitDialog);
+  }
+
   _handleActionTap(e: MouseEvent) {
     e.preventDefault();
     let el = (dom(e) as EventApi).localTarget as Element;
diff --git a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
index 97d8a37..90b9a2d 100644
--- a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
@@ -14,9 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {html} from 'lit-html';
-import {css, customElement, property} from 'lit-element';
-import {GrLitElement} from '../../lit/gr-lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
+import {subscribe} from '../../lit/subscription-controller';
 import {sharedStyles} from '../../../styles/shared-styles';
 import {appContext} from '../../../services/app-context';
 import {
@@ -24,11 +24,13 @@
   aPluginHasRegistered$,
   CheckResult,
   CheckRun,
-  errorMessageLatest$,
+  ErrorMessages,
+  errorMessagesLatest$,
   loginCallbackLatest$,
-  someProvidersAreLoadingLatest$,
+  someProvidersAreLoadingFirstTime$,
+  topLevelActionsLatest$,
 } from '../../../services/checks/checks-model';
-import {Category, RunStatus} from '../../../api/checks';
+import {Action, Category, RunStatus} from '../../../api/checks';
 import {fireShowPrimaryTab} from '../../../utils/event-util';
 import '../../shared/gr-avatar/gr-avatar';
 import {
@@ -60,6 +62,8 @@
 import {ChecksTabState, CommentTabState} from '../../../types/events';
 import {spinnerStyles} from '../../../styles/gr-spinner-styles';
 import {modifierPressed} from '../../../utils/dom-util';
+import {DropdownLink} from '../../shared/gr-dropdown/gr-dropdown';
+import {fontStyles} from '../../../styles/gr-font-styles';
 
 export enum SummaryChipStyles {
   INFO = 'info',
@@ -78,7 +82,7 @@
 }
 
 @customElement('gr-summary-chip')
-export class GrSummaryChip extends GrLitElement {
+export class GrSummaryChip extends LitElement {
   @property()
   icon = '';
 
@@ -93,6 +97,7 @@
   static override get styles() {
     return [
       sharedStyles,
+      fontStyles,
       css`
         .summaryChip {
           color: var(--chip-color);
@@ -104,6 +109,10 @@
           border-radius: 12px;
           border: 1px solid gray;
           vertical-align: top;
+          /* centered position of 20px chips in 24px line-height inline flow */
+          vertical-align: top;
+          position: relative;
+          top: 2px;
         }
         iron-icon {
           width: var(--line-height-small);
@@ -164,7 +173,7 @@
 }
 
 @customElement('gr-checks-chip')
-export class GrChecksChip extends GrLitElement {
+export class GrChecksChip extends LitElement {
   @property()
   statusOrCategory?: Category | RunStatus;
 
@@ -173,6 +182,7 @@
 
   static override get styles() {
     return [
+      fontStyles,
       sharedStyles,
       css`
         :host {
@@ -187,7 +197,13 @@
             var(--spacing-s);
           border-radius: 12px;
           border: 1px solid gray;
+          /* centered position of 20px chips in 24px line-height inline flow */
           vertical-align: top;
+          position: relative;
+          top: 2px;
+        }
+        .checksChip:hover .text {
+          max-width: 240px;
         }
         .checksChip .text {
           display: inline-block;
@@ -315,7 +331,7 @@
 DETAILS_QUOTA.set(RunStatus.RUNNING, 2);
 
 @customElement('gr-change-summary')
-export class GrChangeSummary extends GrLitElement {
+export class GrChangeSummary extends LitElement {
   @property({type: Object})
   changeComments?: ChangeComments;
 
@@ -335,20 +351,30 @@
   someProvidersAreLoading = false;
 
   @property()
-  errorMessage?: string;
+  errorMessages: ErrorMessages = {};
 
   @property()
   loginCallback?: () => void;
 
+  @property()
+  actions: Action[] = [];
+
   private showAllChips = new Map<RunStatus | Category, boolean>();
 
+  private checksService = appContext.checksService;
+
   constructor() {
     super();
-    this.subscribe('runs', allRunsLatestPatchsetLatestAttempt$);
-    this.subscribe('showChecksSummary', aPluginHasRegistered$);
-    this.subscribe('someProvidersAreLoading', someProvidersAreLoadingLatest$);
-    this.subscribe('errorMessage', errorMessageLatest$);
-    this.subscribe('loginCallback', loginCallbackLatest$);
+    subscribe(this, allRunsLatestPatchsetLatestAttempt$, x => (this.runs = x));
+    subscribe(this, aPluginHasRegistered$, x => (this.showChecksSummary = x));
+    subscribe(
+      this,
+      someProvidersAreLoadingFirstTime$,
+      x => (this.someProvidersAreLoading = x)
+    );
+    subscribe(this, errorMessagesLatest$, x => (this.errorMessages = x));
+    subscribe(this, loginCallbackLatest$, x => (this.loginCallback = x));
+    subscribe(this, topLevelActionsLatest$, x => (this.actions = x));
   }
 
   static override get styles() {
@@ -372,7 +398,8 @@
         .login {
           display: flex;
           color: var(--primary-text-color);
-          padding: var(--spacing-s);
+          padding: 0 var(--spacing-s);
+          margin: var(--spacing-xs) 0;
           width: 490px;
         }
         div.error {
@@ -383,9 +410,17 @@
           width: 16px;
           height: 16px;
           position: relative;
-          top: 2px;
+          top: 4px;
           margin-right: var(--spacing-s);
         }
+        div.error .right {
+          overflow: hidden;
+        }
+        div.error .right .message {
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
         .login {
           justify-content: space-between;
           background: var(--info-background);
@@ -398,8 +433,8 @@
         }
         td.key {
           padding-right: var(--spacing-l);
-          padding-bottom: var(--spacing-m);
-          vertical-align: top;
+          padding-bottom: var(--spacing-s);
+          line-height: calc(var(--line-height-normal) + var(--spacing-s));
         }
         td.value {
           padding-right: var(--spacing-l);
@@ -428,27 +463,90 @@
           /* Making up for the 2px reduced height above. */
           top: 1px;
         }
+        .actions {
+          margin-left: calc(0px - var(--spacing-m));
+          line-height: var(--line-height-normal);
+        }
+        .actions gr-checks-action,
+        .actions gr-dropdown {
+          vertical-align: top;
+          --padding: 0 var(--spacing-m);
+        }
+        .actions #moreMessage {
+          display: none;
+        }
       `,
     ];
   }
 
-  renderChecksError() {
-    if (!this.errorMessage) return;
+  private renderActions() {
+    const actions = this.actions ?? [];
+    const summaryActions = actions.filter(a => a.summary).slice(0, 2);
+    if (summaryActions.length === 0) return;
+    const topActions = summaryActions.slice(0, 2);
+    const overflowActions = summaryActions.slice(2).map(action => {
+      return {...action, id: action.name};
+    });
+    const disabledActionIds = overflowActions
+      .filter(action => action.disabled)
+      .map(action => action.id);
+
     return html`
-      <div class="error zeroState">
-        <div class="left">
-          <iron-icon icon="gr-icons:error"></iron-icon>
-        </div>
-        <div class="right">
-          <div>Error while fetching check results</div>
-          <div>${this.errorMessage}</div>
-        </div>
+      <div class="actions">
+        ${topActions.map(this.renderAction)}
+        ${this.renderOverflow(overflowActions, disabledActionIds)}
       </div>
     `;
   }
 
+  private renderAction(action?: Action) {
+    if (!action) return;
+    return html`<gr-checks-action .action="${action}"></gr-checks-action>`;
+  }
+
+  private handleAction(e: CustomEvent<Action>) {
+    this.checksService.triggerAction(e.detail);
+  }
+
+  private renderOverflow(items: DropdownLink[], disabledIds: string[] = []) {
+    if (items.length === 0) return;
+    return html`
+      <gr-dropdown
+        id="moreActions"
+        link=""
+        vertical-offset="32"
+        horizontal-align="right"
+        @tap-item="${this.handleAction}"
+        .items="${items}"
+        .disabledIds="${disabledIds}"
+      >
+        <iron-icon icon="gr-icons:more-vert" aria-labelledby="moreMessage">
+        </iron-icon>
+        <span id="moreMessage">More</span>
+      </gr-dropdown>
+    `;
+  }
+
+  renderErrorMessages() {
+    return Object.entries(this.errorMessages).map(
+      ([plugin, message]) =>
+        html`
+          <div class="error zeroState">
+            <div class="left">
+              <iron-icon icon="gr-icons:error"></iron-icon>
+            </div>
+            <div class="right">
+              <div class="message" title="${message}">
+                Error while fetching results for ${plugin}: ${message}
+              </div>
+            </div>
+          </div>
+        `
+    );
+  }
+
   renderChecksLogin() {
-    if (this.errorMessage || !this.loginCallback) return;
+    if (!this.loginCallback) return;
     return html`
       <div class="login">
         <div class="left">
@@ -466,14 +564,14 @@
   }
 
   renderChecksZeroState() {
-    if (this.errorMessage || this.loginCallback) return;
+    if (Object.keys(this.errorMessages).length > 0) return;
+    if (this.loginCallback) return;
     if (this.runs.some(isRunningOrHasCompleted)) return;
     const msg = this.someProvidersAreLoading ? 'Loading results' : 'No results';
     return html`<span role="status" class="loading zeroState">${msg}</span>`;
   }
 
   renderChecksChipForCategory(category: Category) {
-    if (this.errorMessage || this.loginCallback) return;
     const runs = this.runs.filter(run => {
       if (hasResultsOf(run, category)) return true;
       return category === Category.SUCCESS && hasCompletedWithoutResults(run);
@@ -486,7 +584,6 @@
   }
 
   renderChecksChipRunning() {
-    if (this.errorMessage || this.loginCallback) return;
     const runs = this.runs.filter(isRunning);
     return this.renderChecksChipsExpanded(runs, RunStatus.RUNNING, () => []);
   }
@@ -616,7 +713,6 @@
             <td class="key">Checks</td>
             <td class="value">
               <div class="checksSummary">
-                ${this.renderChecksError()}${this.renderChecksLogin()}
                 ${this.renderChecksZeroState()}${this.renderChecksChipForCategory(
                   Category.ERROR
                 )}${this.renderChecksChipForCategory(
@@ -632,6 +728,7 @@
                   class="loadingSpin"
                   ?hidden="${!this.someProvidersAreLoading}"
                 ></span>
+                ${this.renderErrorMessages()}${this.renderChecksLogin()}${this.renderActions()}
               </div>
             </td>
           </tr>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index e8d07db..58a00f4 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -200,7 +200,7 @@
 
 const TRAILING_WHITESPACE_REGEX = /[ \t]+$/gm;
 
-const MSG_PREFIX = '#message-';
+const PREFIX = '#message-';
 
 const ReloadToastMessage = {
   NEWER_REVISION: 'A newer patch set has been uploaded',
@@ -239,8 +239,11 @@
 
 export type ChangeViewPatchRange = Partial<PatchRange>;
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-change-view')
-export class GrChangeView extends KeyboardShortcutMixin(PolymerElement) {
+export class GrChangeView extends base {
   static get template() {
     return htmlTemplate;
   }
@@ -575,6 +578,7 @@
       [Shortcut.DIFF_BASE_AGAINST_LEFT]: '_handleDiffBaseAgainstLeft',
       [Shortcut.DIFF_RIGHT_AGAINST_LATEST]: '_handleDiffRightAgainstLatest',
       [Shortcut.DIFF_BASE_AGAINST_LATEST]: '_handleDiffBaseAgainstLatest',
+      [Shortcut.OPEN_SUBMIT_DIALOG]: '_handleOpenSubmitDialog',
     };
   }
 
@@ -1182,10 +1186,12 @@
       this.restApiService.setInProjectLookup(value.changeNum, value.project);
     }
 
+    if (value.basePatchNum === undefined)
+      value.basePatchNum = ParentPatchSetNum;
+
     const patchChanged =
       this._patchRange &&
       value.patchNum !== undefined &&
-      value.basePatchNum !== undefined &&
       (this._patchRange.patchNum !== value.patchNum ||
         this._patchRange.basePatchNum !== value.basePatchNum);
 
@@ -1196,7 +1202,7 @@
 
     const patchRange: ChangeViewPatchRange = {
       patchNum: value.patchNum,
-      basePatchNum: value.basePatchNum || ParentPatchSetNum,
+      basePatchNum: value.basePatchNum,
     };
 
     this.$.fileList.collapseAllDiffs();
@@ -1311,7 +1317,7 @@
     assertIsDefined(this._change, '_change');
     if (!this._patchRange)
       throw new Error('missing required _patchRange property');
-    const hash = MSG_PREFIX + e.detail.id;
+    const hash = PREFIX + e.detail.id;
     const url = GerritNav.getUrlForChange(
       this._change,
       this._patchRange.patchNum,
@@ -1323,8 +1329,8 @@
   }
 
   _maybeScrollToMessage(hash: string) {
-    if (hash.startsWith(MSG_PREFIX) && this.messagesList) {
-      this.messagesList.scrollToMessage(hash.substr(MSG_PREFIX.length));
+    if (hash.startsWith(PREFIX) && this.messagesList) {
+      this.messagesList.scrollToMessage(hash.substr(PREFIX.length));
     }
   }
 
@@ -1508,6 +1514,15 @@
     this.$.metadata.editTopic();
   }
 
+  _handleOpenSubmitDialog(e: CustomKeyboardEvent) {
+    if (this.shouldSuppressKeyboardShortcut(e) || !this._submitEnabled) {
+      return;
+    }
+
+    e.preventDefault();
+    this.$.actions.showSubmitDialog();
+  }
+
   _handleDiffAgainstBase(e: CustomKeyboardEvent) {
     if (this.shouldSuppressKeyboardShortcut(e)) {
       return;
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
index 94d3117..fb80822 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
@@ -299,7 +299,7 @@
     .show-robot-comments {
       margin: var(--spacing-m);
     }
-    .patchInfo gr-thread-list {
+    .patchInfo gr-thread-list::part(threads) {
       padding: var(--spacing-l);
     }
   </style>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
index 5508ee0..6b63000 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
@@ -36,6 +36,7 @@
 
 import 'lodash/lodash';
 import {
+  mockPromise,
   stubRestApi,
   TestKeyboardShortcutBinder,
 } from '../../../test/test-utils';
@@ -389,10 +390,8 @@
     );
   });
 
-  teardown(done => {
-    flush(() => {
-      done();
-    });
+  teardown(async () => {
+    await flush();
   });
 
   test('_handleMessageAnchorTap', () => {
@@ -507,11 +506,11 @@
   });
 
   suite('plugins adding to file tab', () => {
-    setup(done => {
+    setup(async () => {
       element._changeNum = TEST_NUMERIC_CHANGE_ID;
       // Resolving it here instead of during setup() as other tests depend
       // on flush() not being called during setup.
-      flush(() => done());
+      await flush();
     });
 
     test('plugin added tab shows up as a dynamic endpoint', () => {
@@ -527,19 +526,17 @@
       assert.equal(paperTabs[2].dataset.name, 'change-view-tab-header-url');
     });
 
-    test('_setActivePrimaryTab switched tab correctly', done => {
+    test('_setActivePrimaryTab switched tab correctly', async () => {
       element._setActivePrimaryTab(
         new CustomEvent('', {
           detail: {tab: 'change-view-tab-header-url'},
         })
       );
-      flush(() => {
-        assert.equal(element._activeTabs[0], 'change-view-tab-header-url');
-        done();
-      });
+      await flush();
+      assert.equal(element._activeTabs[0], 'change-view-tab-header-url');
     });
 
-    test('show-primary-tab switched primary tab correctly', done => {
+    test('show-primary-tab switched primary tab correctly', async () => {
       element.dispatchEvent(
         new CustomEvent('show-primary-tab', {
           composed: true,
@@ -549,13 +546,11 @@
           },
         })
       );
-      flush(() => {
-        assert.equal(element._activeTabs[0], 'change-view-tab-header-url');
-        done();
-      });
+      await flush();
+      assert.equal(element._activeTabs[0], 'change-view-tab-header-url');
     });
 
-    test('param change should switch primary tab correctly', done => {
+    test('param change should switch primary tab correctly', async () => {
       assert.equal(element._activeTabs[0], PrimaryTab.FILES);
       const queryMap = new Map<string, string>();
       queryMap.set('tab', PrimaryTab.FINDINGS);
@@ -565,13 +560,11 @@
         ...element.params,
         queryMap,
       };
-      flush(() => {
-        assert.equal(element._activeTabs[0], PrimaryTab.FINDINGS);
-        done();
-      });
+      await flush();
+      assert.equal(element._activeTabs[0], PrimaryTab.FINDINGS);
     });
 
-    test('invalid param change should not switch primary tab', done => {
+    test('invalid param change should not switch primary tab', async () => {
       assert.equal(element._activeTabs[0], PrimaryTab.FILES);
       const queryMap = new Map<string, string>();
       queryMap.set('tab', 'random');
@@ -581,22 +574,18 @@
         ...element.params,
         queryMap,
       };
-      flush(() => {
-        assert.equal(element._activeTabs[0], PrimaryTab.FILES);
-        done();
-      });
+      await flush();
+      assert.equal(element._activeTabs[0], PrimaryTab.FILES);
     });
 
-    test('switching tab sets _selectedTabPluginEndpoint', done => {
+    test('switching tab sets _selectedTabPluginEndpoint', async () => {
       const paperTabs = element.shadowRoot!.querySelector('#primaryTabs')!;
       tap(paperTabs.querySelectorAll('paper-tab')[2]);
-      flush(() => {
-        assert.equal(
-          element._selectedTabPluginEndpoint,
-          'change-view-tab-content-url'
-        );
-        done();
-      });
+      await flush();
+      assert.equal(
+        element._selectedTabPluginEndpoint,
+        'change-view-tab-content-url'
+      );
     });
   });
 
@@ -653,28 +642,24 @@
       );
     });
 
-    test('A fires an error event when not logged in', done => {
+    test('A fires an error event when not logged in', async () => {
       sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(false));
       const loggedInErrorSpy = sinon.spy();
       element.addEventListener('show-auth-required', loggedInErrorSpy);
       pressAndReleaseKeyOn(element, 65, null, 'a');
-      flush(() => {
-        assert.isFalse(element.$.replyOverlay.opened);
-        assert.isTrue(loggedInErrorSpy.called);
-        done();
-      });
+      await flush();
+      assert.isFalse(element.$.replyOverlay.opened);
+      assert.isTrue(loggedInErrorSpy.called);
     });
 
-    test('shift A does not open reply overlay', done => {
+    test('shift A does not open reply overlay', async () => {
       sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
       pressAndReleaseKeyOn(element, 65, 'shift', 'a');
-      flush(() => {
-        assert.isFalse(element.$.replyOverlay.opened);
-        done();
-      });
+      await flush();
+      assert.isFalse(element.$.replyOverlay.opened);
     });
 
-    test('A toggles overlay when logged in', done => {
+    test('A toggles overlay when logged in', async () => {
       sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
       element._change = {
         ...createChangeViewChange(),
@@ -695,19 +680,17 @@
       const openSpy = sinon.spy(element, '_openReplyDialog');
 
       pressAndReleaseKeyOn(element, 65, null, 'a');
-      flush(() => {
-        assert.isTrue(element.$.replyOverlay.opened);
-        element.$.replyOverlay.close();
-        assert.isFalse(element.$.replyOverlay.opened);
-        assert(
-          openSpy.lastCall.calledWithExactly(
-            element.$.replyDialog.FocusTarget.ANY
-          ),
-          '_openReplyDialog should have been passed ANY'
-        );
-        assert.equal(openSpy.callCount, 1);
-        done();
-      });
+      await flush();
+      assert.isTrue(element.$.replyOverlay.opened);
+      element.$.replyOverlay.close();
+      assert.isFalse(element.$.replyOverlay.opened);
+      assert(
+        openSpy.lastCall.calledWithExactly(
+          element.$.replyDialog.FocusTarget.ANY
+        ),
+        '_openReplyDialog should have been passed ANY'
+      );
+      assert.equal(openSpy.callCount, 1);
     });
 
     test('fullscreen-overlay-opened hides content', () => {
@@ -785,28 +768,24 @@
       assert.isTrue(handleCollapse.called);
     });
 
-    test('X should expand all messages', done => {
-      flush(() => {
-        const handleExpand = sinon.stub(
-          element.messagesList!,
-          'handleExpandCollapse'
-        );
-        pressAndReleaseKeyOn(element, 88, null, 'x');
-        assert(handleExpand.calledWith(true));
-        done();
-      });
+    test('X should expand all messages', async () => {
+      await flush();
+      const handleExpand = sinon.stub(
+        element.messagesList!,
+        'handleExpandCollapse'
+      );
+      pressAndReleaseKeyOn(element, 88, null, 'x');
+      assert(handleExpand.calledWith(true));
     });
 
-    test('Z should collapse all messages', done => {
-      flush(() => {
-        const handleExpand = sinon.stub(
-          element.messagesList!,
-          'handleExpandCollapse'
-        );
-        pressAndReleaseKeyOn(element, 90, null, 'z');
-        assert(handleExpand.calledWith(false));
-        done();
-      });
+    test('Z should collapse all messages', async () => {
+      await flush();
+      const handleExpand = sinon.stub(
+        element.messagesList!,
+        'handleExpandCollapse'
+      );
+      pressAndReleaseKeyOn(element, 90, null, 'z');
+      assert(handleExpand.calledWith(false));
     });
 
     test('d should open download overlay', () => {
@@ -946,12 +925,10 @@
       );
     });
 
-    test('changing patchsets resets robot comments', done => {
+    test('changing patchsets resets robot comments', async () => {
       element.set('_change.current_revision', 'rev3');
-      flush(() => {
-        assert.equal(element._robotCommentThreads!.length, 1);
-        done();
-      });
+      await flush();
+      assert.equal(element._robotCommentThreads!.length, 1);
     });
 
     test('Show more button is hidden', () => {
@@ -959,15 +936,13 @@
     });
 
     suite('robot comments show more button', () => {
-      setup(done => {
+      setup(async () => {
         const arr = [];
         for (let i = 0; i <= 30; i++) {
           arr.push(...THREADS);
         }
         element._commentThreads = arr;
-        flush(() => {
-          done();
-        });
+        await flush();
       });
 
       test('Show more button is rendered', () => {
@@ -978,12 +953,10 @@
         );
       });
 
-      test('Clicking show more button renders all comments', done => {
+      test('Clicking show more button renders all comments', async () => {
         tap(element.shadowRoot!.querySelector('.show-robot-comments')!);
-        flush(() => {
-          assert.equal(element._robotCommentThreads!.length, 62);
-          done();
-        });
+        await flush();
+        assert.equal(element._robotCommentThreads!.length, 62);
       });
     });
   });
@@ -1005,14 +978,12 @@
     assert.isTrue(openDialogStub.called);
   });
 
-  test('fetches the server config on attached', done => {
-    flush(() => {
-      assert.equal(
-        element._serverConfig!.user.anonymous_coward_name,
-        'test coward name'
-      );
-      done();
-    });
+  test('fetches the server config on attached', async () => {
+    await flush();
+    assert.equal(
+      element._serverConfig!.user.anonymous_coward_name,
+      'test coward name'
+    );
   });
 
   test('_changeStatuses', () => {
@@ -1047,7 +1018,7 @@
   });
 
   suite('ChangeStatus revert', () => {
-    test('do not show any chip if no revert created', done => {
+    test('do not show any chip if no revert created', async () => {
       const change = {
         ...createChange(),
         messages: createChangeMessages(2),
@@ -1066,20 +1037,18 @@
       element._change = change;
       element._mergeable = true;
       element._submitEnabled = true;
-      flush();
+      await flush();
       element.computeRevertSubmitted(element._change);
-      flush(() => {
-        assert.isFalse(
-          element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
-        );
-        assert.isFalse(
-          element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
-        );
-        done();
-      });
+      await flush();
+      assert.isFalse(
+        element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
+      );
+      assert.isFalse(
+        element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
+      );
     });
 
-    test('do not show any chip if all reverts are abandoned', done => {
+    test('do not show any chip if all reverts are abandoned', async () => {
       const change = {
         ...createChange(),
         messages: createChangeMessages(2),
@@ -1106,20 +1075,18 @@
       element._change = change;
       element._mergeable = true;
       element._submitEnabled = true;
-      flush();
+      await flush();
       element.computeRevertSubmitted(element._change);
-      flush(() => {
-        assert.isFalse(
-          element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
-        );
-        assert.isFalse(
-          element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
-        );
-        done();
-      });
+      await flush();
+      assert.isFalse(
+        element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
+      );
+      assert.isFalse(
+        element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
+      );
     });
 
-    test('show revert created if no revert is merged', done => {
+    test('show revert created if no revert is merged', async () => {
       const change = {
         ...createChange(),
         messages: createChangeMessages(2),
@@ -1144,20 +1111,18 @@
       element._change = change;
       element._mergeable = true;
       element._submitEnabled = true;
-      flush();
+      await flush();
       element.computeRevertSubmitted(element._change);
-      flush(() => {
-        assert.isFalse(
-          element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
-        );
-        assert.isTrue(
-          element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
-        );
-        done();
-      });
+      await flush();
+      assert.isFalse(
+        element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
+      );
+      assert.isTrue(
+        element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
+      );
     });
 
-    test('show revert submitted if revert is merged', done => {
+    test('show revert submitted if revert is merged', async () => {
       const change = {
         ...createChange(),
         messages: createChangeMessages(2),
@@ -1179,17 +1144,15 @@
       element._change = change;
       element._mergeable = true;
       element._submitEnabled = true;
-      flush();
+      await flush();
       element.computeRevertSubmitted(element._change);
-      flush(() => {
-        assert.isFalse(
-          element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
-        );
-        assert.isTrue(
-          element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
-        );
-        done();
-      });
+      await flush();
+      assert.isFalse(
+        element._changeStatuses?.includes(ChangeStates.REVERT_CREATED)
+      );
+      assert.isTrue(
+        element._changeStatuses?.includes(ChangeStates.REVERT_SUBMITTED)
+      );
     });
   });
 
@@ -1343,17 +1306,15 @@
     assert.equal(element.viewState.diffMode, DiffViewMode.SIDE_BY_SIDE);
   });
 
-  test('diffMode defaults to side by side without preferences', done => {
+  test('diffMode defaults to side by side without preferences', async () => {
     stubRestApi('getPreferences').returns(Promise.resolve(createPreferences()));
     // No user prefs or diff view mode set.
 
-    element._setDiffViewMode()!.then(() => {
-      assert.equal(element.viewState.diffMode, DiffViewMode.SIDE_BY_SIDE);
-      done();
-    });
+    await element._setDiffViewMode()!;
+    assert.equal(element.viewState.diffMode, DiffViewMode.SIDE_BY_SIDE);
   });
 
-  test('diffMode defaults to preference when not already set', done => {
+  test('diffMode defaults to preference when not already set', async () => {
     stubRestApi('getPreferences').returns(
       Promise.resolve({
         ...createPreferences(),
@@ -1361,13 +1322,11 @@
       })
     );
 
-    element._setDiffViewMode()!.then(() => {
-      assert.equal(element.viewState.diffMode, DiffViewMode.UNIFIED);
-      done();
-    });
+    await element._setDiffViewMode()!;
+    assert.equal(element.viewState.diffMode, DiffViewMode.UNIFIED);
   });
 
-  test('existing diffMode overrides preference', done => {
+  test('existing diffMode overrides preference', async () => {
     element.viewState.diffMode = DiffViewMode.SIDE_BY_SIDE;
     stubRestApi('getPreferences').returns(
       Promise.resolve({
@@ -1375,10 +1334,8 @@
         default_diff_view: DiffViewMode.UNIFIED,
       })
     );
-    element._setDiffViewMode()!.then(() => {
-      assert.equal(element.viewState.diffMode, DiffViewMode.SIDE_BY_SIDE);
-      done();
-    });
+    await element._setDiffViewMode()!;
+    assert.equal(element.viewState.diffMode, DiffViewMode.SIDE_BY_SIDE);
   });
 
   test('don’t reload entire page when patchRange changes', async () => {
@@ -1486,17 +1443,15 @@
     assert.isTrue(recreateSpy.calledOnce);
   });
 
-  test('related changes are not updated after other action', done => {
+  test('related changes are not updated after other action', async () => {
     sinon.stub(element, 'loadData').callsFake(() => Promise.resolve());
-    flush();
+    await flush();
     const relatedChanges = element.shadowRoot!.querySelector(
       '#relatedChanges'
     ) as GrRelatedChangesList;
     sinon.stub(relatedChanges, 'reload');
-    element.loadData(true).then(() => {
-      assert.isFalse(navigateToChangeStub.called);
-      done();
-    });
+    await element.loadData(true);
+    assert.isFalse(navigateToChangeStub.called);
   });
 
   test('_computeCopyTextForTitle', () => {
@@ -1578,7 +1533,7 @@
     assert.equal(putStub.lastCall.args[1], '\n\n\n\n\n\n\n\n');
   });
 
-  test('topic is coalesced to null', done => {
+  test('topic is coalesced to null', async () => {
     sinon.stub(element, '_changeChanged');
     stubRestApi('getChangeDetail').returns(
       Promise.resolve({
@@ -1589,13 +1544,11 @@
       })
     );
 
-    element._getChangeDetail().then(() => {
-      assert.isNull(element._change!.topic);
-      done();
-    });
+    await element._getChangeDetail();
+    assert.isNull(element._change!.topic);
   });
 
-  test('commit sha is populated from getChangeDetail', done => {
+  test('commit sha is populated from getChangeDetail', async () => {
     sinon.stub(element, '_changeChanged');
     stubRestApi('getChangeDetail').callsFake(() =>
       Promise.resolve({
@@ -1606,10 +1559,8 @@
       })
     );
 
-    element._getChangeDetail().then(() => {
-      assert.equal('foo', element._commitInfo!.commit);
-      done();
-    });
+    await element._getChangeDetail();
+    assert.equal('foo', element._commitInfo!.commit);
   });
 
   test('edit is added to change', () => {
@@ -1693,43 +1644,39 @@
     assert.equal(element._getBasePatchNum(_change2, _patchRange), 'PARENT');
   });
 
-  test('_openReplyDialog called with `ANY` when coming from tap event', done => {
-    flush(() => {
-      const openStub = sinon.stub(element, '_openReplyDialog');
-      tap(element.$.replyBtn);
-      assert(
-        openStub.lastCall.calledWithExactly(
-          element.$.replyDialog.FocusTarget.ANY
-        ),
-        '_openReplyDialog should have been passed ANY'
-      );
-      assert.equal(openStub.callCount, 1);
-      done();
-    });
+  test('_openReplyDialog called with `ANY` when coming from tap event', async () => {
+    await flush();
+    const openStub = sinon.stub(element, '_openReplyDialog');
+    tap(element.$.replyBtn);
+    assert(
+      openStub.lastCall.calledWithExactly(
+        element.$.replyDialog.FocusTarget.ANY
+      ),
+      '_openReplyDialog should have been passed ANY'
+    );
+    assert.equal(openStub.callCount, 1);
   });
 
   test(
     '_openReplyDialog called with `BODY` when coming from message reply' +
       'event',
-    done => {
-      flush(() => {
-        const openStub = sinon.stub(element, '_openReplyDialog');
-        element.messagesList!.dispatchEvent(
-          new CustomEvent('reply', {
-            detail: {message: {message: 'text'}},
-            composed: true,
-            bubbles: true,
-          })
-        );
-        assert(
-          openStub.lastCall.calledWithExactly(
-            element.$.replyDialog.FocusTarget.BODY
-          ),
-          '_openReplyDialog should have been passed BODY'
-        );
-        assert.equal(openStub.callCount, 1);
-        done();
-      });
+    async () => {
+      await flush();
+      const openStub = sinon.stub(element, '_openReplyDialog');
+      element.messagesList!.dispatchEvent(
+        new CustomEvent('reply', {
+          detail: {message: {message: 'text'}},
+          composed: true,
+          bubbles: true,
+        })
+      );
+      assert(
+        openStub.lastCall.calledWithExactly(
+          element.$.replyDialog.FocusTarget.BODY
+        ),
+        '_openReplyDialog should have been passed BODY'
+      );
+      assert.equal(openStub.callCount, 1);
     }
   );
 
@@ -1771,7 +1718,7 @@
     assert.isNull(element._getUrlParameter('test'));
   });
 
-  test('revert dialog opened with revert param', done => {
+  test('revert dialog opened with revert param', async () => {
     const awaitPluginsLoadedStub = sinon
       .stub(getPluginLoader(), 'awaitPluginsLoaded')
       .callsFake(() => Promise.resolve());
@@ -1797,10 +1744,15 @@
       return param;
     });
 
-    sinon.stub(element.$.actions, 'showRevertDialog').callsFake(done);
+    const promise = mockPromise();
+
+    sinon
+      .stub(element.$.actions, 'showRevertDialog')
+      .callsFake(() => promise.resolve());
 
     element._maybeShowRevertDialog();
     assert.isTrue(awaitPluginsLoadedStub.called);
+    await promise;
   });
 
   suite('reply dialog tests', () => {
@@ -1823,7 +1775,7 @@
       );
     });
 
-    test('show reply dialog on open-reply-dialog event', done => {
+    test('show reply dialog on open-reply-dialog event', async () => {
       const openReplyDialogStub = sinon.stub(element, '_openReplyDialog');
       element.dispatchEvent(
         new CustomEvent('open-reply-dialog', {
@@ -1832,10 +1784,8 @@
           detail: {},
         })
       );
-      flush(() => {
-        assert.isTrue(openReplyDialogStub.calledOnce);
-        done();
-      });
+      await flush();
+      assert.isTrue(openReplyDialogStub.calledOnce);
     });
 
     test('reply from comment adds quote text', () => {
@@ -1887,13 +1837,11 @@
     });
   });
 
-  test('reply button is disabled until server config is loaded', done => {
+  test('reply button is disabled until server config is loaded', async () => {
     assert.isTrue(element._replyDisabled);
     // fetches the server config on attached
-    flush(() => {
-      assert.isFalse(element._replyDisabled);
-      done();
-    });
+    await flush();
+    assert.isFalse(element._replyDisabled);
   });
 
   test('header class computation', () => {
@@ -1901,19 +1849,17 @@
     assert.equal(element._computeHeaderClass(true), 'header editMode');
   });
 
-  test('_maybeScrollToMessage', done => {
-    flush(() => {
-      const scrollStub = sinon.stub(element.messagesList!, 'scrollToMessage');
+  test('_maybeScrollToMessage', async () => {
+    await flush();
+    const scrollStub = sinon.stub(element.messagesList!, 'scrollToMessage');
 
-      element._maybeScrollToMessage('');
-      assert.isFalse(scrollStub.called);
-      element._maybeScrollToMessage('message');
-      assert.isFalse(scrollStub.called);
-      element._maybeScrollToMessage('#message-TEST');
-      assert.isTrue(scrollStub.called);
-      assert.equal(scrollStub.lastCall.args[0], 'TEST');
-      done();
-    });
+    element._maybeScrollToMessage('');
+    assert.isFalse(scrollStub.called);
+    element._maybeScrollToMessage('message');
+    assert.isFalse(scrollStub.called);
+    element._maybeScrollToMessage('#message-TEST');
+    assert.isTrue(scrollStub.called);
+    assert.equal(scrollStub.lastCall.args[0], 'TEST');
   });
 
   test('topic update reloads related changes', () => {
@@ -2172,94 +2118,93 @@
       };
     });
 
-    test('edit exists in revisions', done => {
+    test('edit exists in revisions', async () => {
+      const promise = mockPromise();
       sinon.stub(GerritNav, 'navigateToChange').callsFake((...args) => {
         assert.equal(args.length, 2);
         assert.equal(args[1], EditPatchSetNum); // patchNum
-        done();
+        promise.resolve();
       });
 
       element.set('_change.revisions.rev2', {
         _number: EditPatchSetNum,
       });
-      flush();
+      await flush();
 
       fireEdit();
+      await promise;
     });
 
-    test('no edit exists in revisions, non-latest patchset', done => {
+    test('no edit exists in revisions, non-latest patchset', async () => {
+      const promise = mockPromise();
       sinon.stub(GerritNav, 'navigateToChange').callsFake((...args) => {
         assert.equal(args.length, 4);
         assert.equal(args[1], 1 as PatchSetNum); // patchNum
         assert.equal(args[3], true); // opt_isEdit
-        done();
+        promise.resolve();
       });
 
       element.set('_change.revisions.rev2', {_number: 2});
       element._patchRange = {patchNum: 1 as RevisionPatchSetNum};
-      flush();
+      await flush();
 
       fireEdit();
+      await promise;
     });
 
-    test('no edit exists in revisions, latest patchset', done => {
+    test('no edit exists in revisions, latest patchset', async () => {
+      const promise = mockPromise();
       sinon.stub(GerritNav, 'navigateToChange').callsFake((...args) => {
         assert.equal(args.length, 4);
         // No patch should be specified when patchNum == latest.
         assert.isNotOk(args[1]); // patchNum
         assert.equal(args[3], true); // opt_isEdit
-        done();
+        promise.resolve();
       });
 
       element.set('_change.revisions.rev2', {_number: 2});
       element._patchRange = {patchNum: 2 as RevisionPatchSetNum};
-      flush();
+      await flush();
 
       fireEdit();
+      await promise;
     });
   });
 
-  test('_handleStopEditTap', done => {
+  test('_handleStopEditTap', async () => {
     element._change = {
       ...createChangeViewChange(),
     };
     sinon.stub(element.$.metadata, '_computeLabelNames');
     navigateToChangeStub.restore();
+    const promise = mockPromise();
     sinon.stub(GerritNav, 'navigateToChange').callsFake((...args) => {
       assert.equal(args.length, 2);
       assert.equal(args[1], 1 as PatchSetNum); // patchNum
-      done();
+      promise.resolve();
     });
 
     element._patchRange = {patchNum: 1 as RevisionPatchSetNum};
     element.$.actions.dispatchEvent(
       new CustomEvent('stop-edit-tap', {bubbles: false})
     );
+    await promise;
   });
 
   suite('plugin endpoints', () => {
-    test('endpoint params', done => {
+    test('endpoint params', async () => {
       element._change = {...createChangeViewChange(), labels: {}};
       element._selectedRevision = createRevision();
-      let hookEl: HTMLElement;
-      let plugin: PluginApi;
-      pluginApi.install(
-        p => {
-          plugin = p;
-          plugin
-            .hook('change-view-integration')
-            .getLastAttached()
-            .then(el => (hookEl = el));
-        },
-        '0.1',
-        'http://some/plugins/url.js'
-      );
-      flush(() => {
-        assert.strictEqual((hookEl as any).plugin, plugin);
-        assert.strictEqual((hookEl as any).change, element._change);
-        assert.strictEqual((hookEl as any).revision, element._selectedRevision);
-        done();
-      });
+      const promise = mockPromise();
+      pluginApi.install(promise.resolve, '0.1', 'http://some/plugins/url.js');
+      await flush();
+      const plugin: PluginApi = (await promise) as PluginApi;
+      const hookEl = await plugin
+        .hook('change-view-integration')
+        .getLastAttached();
+      assert.strictEqual((hookEl as any).plugin, plugin);
+      assert.strictEqual((hookEl as any).change, element._change);
+      assert.strictEqual((hookEl as any).revision, element._selectedRevision);
     });
   });
 
@@ -2328,7 +2273,7 @@
         .returns(Promise.resolve([undefined, undefined, undefined]));
     });
 
-    test("don't report changeDisplayed on reply", done => {
+    test("don't report changeDisplayed on reply", async () => {
       const changeDisplayStub = sinon.stub(
         appContext.reportingService,
         'changeDisplayed'
@@ -2338,11 +2283,9 @@
         'changeFullyLoaded'
       );
       element._handleReplySent();
-      flush(() => {
-        assert.isFalse(changeDisplayStub.called);
-        assert.isFalse(changeFullyLoadedStub.called);
-        done();
-      });
+      await flush();
+      assert.isFalse(changeDisplayStub.called);
+      assert.isFalse(changeFullyLoadedStub.called);
     });
 
     test('report changeDisplayed on _paramsChanged', async () => {
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts
index 796b35d..1052c91 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts
@@ -19,8 +19,8 @@
 import {GerritNav} from '../../core/gr-navigation/gr-navigation';
 import {ChangeInfo, CommitInfo, ServerInfo} from '../../../types/common';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -29,7 +29,7 @@
 }
 
 @customElement('gr-commit-info')
-export class GrCommitInfo extends GrLitElement {
+export class GrCommitInfo extends LitElement {
   // TODO(TS): can not use `?` here as @computed require dependencies as
   // not optional
   @property({type: Object})
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts
index 0954b7f..4fd42b0 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts
@@ -35,10 +35,11 @@
   }
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-confirm-abandon-dialog')
-export class GrConfirmAbandonDialog extends KeyboardShortcutMixin(
-  PolymerElement
-) {
+export class GrConfirmAbandonDialog extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.js b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.js
index 1034674..90fddaa 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.js
@@ -111,22 +111,20 @@
       flush();
     });
 
-    test('cherry pick topic submit', done => {
+    test('cherry pick topic submit', async () => {
       element.branch = 'master';
       const executeChangeActionStub = stubRestApi(
           'executeChangeAction').returns(Promise.resolve([]));
       MockInteractions.tap(element.shadowRoot.
           querySelector('gr-dialog').$.confirm);
-      flush(() => {
-        const args = executeChangeActionStub.args[0];
-        assert.equal(args[0], 1);
-        assert.equal(args[1], 'POST');
-        assert.equal(args[2], '/cherrypick');
-        assert.equal(args[4].destination, 'master');
-        assert.isTrue(args[4].allow_conflicts);
-        assert.isTrue(args[4].allow_empty);
-        done();
-      });
+      await flush();
+      const args = executeChangeActionStub.args[0];
+      assert.equal(args[0], 1);
+      assert.equal(args[1], 'POST');
+      assert.equal(args[2], '/cherrypick');
+      assert.equal(args[4].destination, 'master');
+      assert.isTrue(args[4].allow_conflicts);
+      assert.isTrue(args[4].allow_empty);
     });
 
     test('deselecting a change removes it from being cherry picked', () => {
@@ -171,18 +169,16 @@
       ), 'error');
     });
 
-    test('submit button is blocked while cherry picks is running', done => {
+    test('submit button is blocked while cherry picks is running', async () => {
       const confirmButton = element.shadowRoot.querySelector('gr-dialog').$
           .confirm;
       assert.isTrue(confirmButton.hasAttribute('disabled'));
       element.branch = 'b';
-      flush();
+      await flush();
       assert.isFalse(confirmButton.hasAttribute('disabled'));
       element.updateStatus(changes[0], {status: 'RUNNING'});
-      flush(() => {
-        assert.isTrue(confirmButton.hasAttribute('disabled'));
-        done();
-      });
+      await flush();
+      assert.isTrue(confirmButton.hasAttribute('disabled'));
     });
   });
 
@@ -192,12 +188,11 @@
     assert.isTrue(focusStub.called);
   });
 
-  test('_getProjectBranchesSuggestions non-empty', done => {
-    element._getProjectBranchesSuggestions('test-branch').then(branches => {
-      assert.equal(branches.length, 1);
-      assert.equal(branches[0].name, 'test-branch');
-      done();
-    });
+  test('_getProjectBranchesSuggestions non-empty', async () => {
+    const branches = await element._getProjectBranchesSuggestions(
+        'test-branch');
+    assert.equal(branches.length, 1);
+    assert.equal(branches[0].name, 'test-branch');
   });
 });
 
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
index eb4053a..c196706 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
@@ -27,8 +27,11 @@
 
 const SUGGESTIONS_LIMIT = 15;
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-confirm-move-dialog')
-export class GrConfirmMoveDialog extends KeyboardShortcutMixin(PolymerElement) {
+export class GrConfirmMoveDialog extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.js b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.js
index 36a2ad3..10844f5 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.js
@@ -50,26 +50,22 @@
     assert.equal(element.message, myNewMessage);
   });
 
-  test('_getProjectBranchesSuggestions empty', done => {
-    element._getProjectBranchesSuggestions('nonexistent').then(branches => {
-      assert.equal(branches.length, 0);
-      done();
-    });
+  test('_getProjectBranchesSuggestions empty', async () => {
+    const branches = await element._getProjectBranchesSuggestions(
+        'nonexistent');
+    assert.equal(branches.length, 0);
   });
 
-  test('_getProjectBranchesSuggestions non-empty', done => {
-    element._getProjectBranchesSuggestions('test-branch').then(branches => {
-      assert.equal(branches.length, 1);
-      assert.equal(branches[0].name, 'test-branch');
-      done();
-    });
+  test('_getProjectBranchesSuggestions non-empty', async () => {
+    const branches = await element._getProjectBranchesSuggestions(
+        'test-branch');
+    assert.equal(branches.length, 1);
+    assert.equal(branches[0].name, 'test-branch');
   });
 
-  test('_getProjectBranchesSuggestions input empty string', done => {
-    element._getProjectBranchesSuggestions('').then(branches => {
-      assert.equal(branches.length, 0);
-      done();
-    });
+  test('_getProjectBranchesSuggestions input empty string', async () => {
+    const branches = await element._getProjectBranchesSuggestions('');
+    assert.equal(branches.length, 0);
   });
 });
 
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts
index 95169ca..c705319 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts
@@ -25,11 +25,12 @@
 import {pluralize} from '../../../utils/string-util';
 import {CommentThread, isUnresolved} from '../../../utils/comment-util';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property, query} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property, query} from 'lit/decorators';
+import {fontStyles} from '../../../styles/gr-font-styles';
 
 @customElement('gr-confirm-submit-dialog')
-export class GrConfirmSubmitDialog extends GrLitElement {
+export class GrConfirmSubmitDialog extends LitElement {
   @query('#dialog')
   dialog?: GrDialog;
 
@@ -60,6 +61,7 @@
   static get styles() {
     return [
       sharedStyles,
+      fontStyles,
       css`
         #dialog {
           min-width: 40em;
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
index 968800d..d0865c9 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
@@ -14,6 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import '../../../styles/gr-font-styles';
 import '../../../styles/shared-styles';
 import '../../shared/gr-download-commands/gr-download-commands';
 import {PolymerElement} from '@polymer/polymer/polymer-element';
@@ -41,8 +42,11 @@
   };
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-download-dialog')
-export class GrDownloadDialog extends KeyboardShortcutMixin(PolymerElement) {
+export class GrDownloadDialog extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_html.ts b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_html.ts
index 6e40f84..097fb0e 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_html.ts
@@ -17,6 +17,9 @@
 import {html} from '@polymer/polymer/lib/utils/html-tag';
 
 export const htmlTemplate = html`
+  <style include="gr-font-styles">
+    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+  </style>
   <style include="shared-styles">
     :host {
       display: block;
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
index ff303e3..bdc6a43 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
@@ -60,8 +60,11 @@
   };
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-file-list-header')
-export class GrFileListHeader extends KeyboardShortcutMixin(PolymerElement) {
+export class GrFileListHeader extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js
index dd70678..08e5e3b 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js
@@ -34,10 +34,8 @@
     element = basicFixture.instantiate();
   });
 
-  teardown(done => {
-    flush(() => {
-      done();
-    });
+  teardown(async () => {
+    await flush();
   });
 
   test('Diff preferences hidden when no prefs or diffPrefsDisabled', () => {
@@ -77,21 +75,19 @@
     assert.isTrue(element._collapseAllDiffs.called);
   });
 
-  test('show/hide diffs disabled for large amounts of files', done => {
+  test('show/hide diffs disabled for large amounts of files', async () => {
     const computeSpy = sinon.spy(element, '_fileListActionsVisible');
     element._files = [];
     element.changeNum = '42';
     element.basePatchNum = 'PARENT';
     element.patchNum = '2';
     element.shownFileCount = 1;
-    flush(() => {
-      assert.isTrue(computeSpy.lastCall.returnValue);
-      _.times(element._maxFilesForBulkActions + 1, () => {
-        element.shownFileCount = element.shownFileCount + 1;
-      });
-      assert.isFalse(computeSpy.lastCall.returnValue);
-      done();
+    await flush();
+    assert.isTrue(computeSpy.lastCall.returnValue);
+    _.times(element._maxFilesForBulkActions + 1, () => {
+      element.shownFileCount = element.shownFileCount + 1;
     });
+    assert.isFalse(computeSpy.lastCall.returnValue);
   });
 
   test('fileViewActions are properly hidden', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
index 0427e9e..8d0c203 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
@@ -172,8 +172,11 @@
  * @property {number} lines_inserted - fallback to 0 if not present in api
  */
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-file-list')
-export class GrFileList extends KeyboardShortcutMixin(PolymerElement) {
+export class GrFileList extends base {
   static get template() {
     return htmlTemplate;
   }
@@ -1140,7 +1143,7 @@
     // Polymer 2: check for undefined
     if (
       change === undefined ||
-      patchRange === undefined ||
+      !patchRange?.patchNum ||
       path === undefined ||
       editMode === undefined
     ) {
@@ -1434,11 +1437,9 @@
     );
 
     // Find the paths introduced by the new index splices:
-    const newFiles = record.indexSplices
-      .map(splice =>
-        splice.object.slice(splice.index, splice.index + splice.addedCount)
-      )
-      .reduce((acc, paths) => acc.concat(paths), []);
+    const newFiles = record.indexSplices.flatMap(splice =>
+      splice.object.slice(splice.index, splice.index + splice.addedCount)
+    );
 
     // Required so that the newly created diff view is included in this.diffs.
     flush();
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
index 79bc9f6..e11d3a3 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
@@ -93,7 +93,7 @@
   });
 
   suite('basic tests', () => {
-    setup(done => {
+    setup(async () => {
       stubRestApi('getDiffComments').returns(Promise.resolve({}));
       stubRestApi('getDiffRobotComments').returns(Promise.resolve({}));
       stubRestApi('getDiffDrafts').returns(Promise.resolve({}));
@@ -118,7 +118,6 @@
       };
       saveStub = sinon.stub(element, '_saveReviewedState').callsFake(
           () => Promise.resolve());
-      done();
     });
 
     test('correct number of files are shown', () => {
@@ -942,14 +941,15 @@
       assert.equal(collapseStub.lastCall.args[0].length, 1);
     });
 
-    test('_expandedFilesChanged', done => {
+    test('_expandedFilesChanged', async () => {
       sinon.stub(element, '_reviewFile');
       const path = 'path/to/my/file.txt';
+      const promise = mockPromise();
       const diffs = [{
         path,
         style: {},
         reload() {
-          done();
+          promise.resolve();
         },
         prefetchDiff() {},
         cancel() {},
@@ -963,6 +963,7 @@
       }];
       sinon.stub(element, 'diffs').get(() => diffs);
       element.push('_expandedFiles', {path});
+      await promise;
     });
 
     test('_clearCollapsedDiffs', () => {
@@ -1523,7 +1524,7 @@
       return diffs;
     }
 
-    setup(done => {
+    setup(async () => {
       stubRestApi('getPreferences').returns(Promise.resolve({}));
       stubRestApi('getDiffComments').returns(Promise.resolve({}));
       stubRestApi('getDiffRobotComments').returns(Promise.resolve({}));
@@ -1568,13 +1569,12 @@
         patchNum: 2,
       };
       sinon.stub(window, 'fetch').callsFake(() => Promise.resolve());
-      flush();
-      done();
+      await flush();
     });
 
     test('cursor with individually opened files', async () => {
       MockInteractions.keyUpOn(element, 73, null, 'i');
-      flush();
+      await flush();
       let diffs = await renderAndGetNewDiffs(0);
       const diffStops = diffs[0].getCursorStops();
 
@@ -1587,13 +1587,13 @@
       // Tapping content on a line selects the line number.
       MockInteractions.tap(dom(
           diffStops[10]).querySelectorAll('.contentText')[0]);
-      flush();
+      await flush();
       assert.isTrue(diffStops[10].classList.contains('target-row'));
 
       // Keyboard shortcuts are still moving the file cursor, not the diff
       // cursor.
       MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
-      flush();
+      await flush();
       assert.isTrue(diffStops[10].classList.contains('target-row'));
       assert.isFalse(diffStops[11].classList.contains('target-row'));
 
@@ -1601,7 +1601,7 @@
       assert.equal(element.fileCursor.index, 1);
 
       MockInteractions.keyUpOn(element, 73, null, 'i');
-      flush();
+      await flush();
       diffs = await renderAndGetNewDiffs(1);
 
       // Two diffs should be rendered.
@@ -1616,7 +1616,7 @@
 
     test('cursor with toggle all files', async () => {
       MockInteractions.keyUpOn(element, 73, 'shift', 'i');
-      flush();
+      await flush();
 
       const diffs = await renderAndGetNewDiffs(0);
       const diffStops = diffs[0].getCursorStops();
@@ -1630,13 +1630,13 @@
       // Tapping content on a line selects the line number.
       MockInteractions.tap(dom(
           diffStops[10]).querySelectorAll('.contentText')[0]);
-      flush();
+      await flush();
       assert.isTrue(diffStops[10].classList.contains('target-row'));
 
       // Keyboard shortcuts are still moving the file cursor, not the diff
       // cursor.
       MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
-      flush();
+      await flush();
       assert.isFalse(diffStops[10].classList.contains('target-row'));
       assert.isTrue(diffStops[11].classList.contains('target-row'));
 
@@ -1661,9 +1661,9 @@
             element.root.querySelectorAll('.row:not(.header-row)');
       });
 
-      test('n key with some files expanded and no shift key', () => {
+      test('n key with some files expanded and no shift key', async () => {
         MockInteractions.keyUpOn(fileRows[0], 73, null, 'i');
-        flush();
+        await flush();
 
         // Handle N key should return before calling diff cursor functions.
         MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'n');
@@ -1675,9 +1675,9 @@
         assert.equal(element.filesExpanded, 'some');
       });
 
-      test('n key with some files expanded and shift key', () => {
+      test('n key with some files expanded and shift key', async () => {
         MockInteractions.keyUpOn(fileRows[0], 73, null, 'i');
-        flush();
+        await flush();
         assert.equal(nextChunkStub.callCount, 0);
 
         MockInteractions.pressAndReleaseKeyOn(element, 78, 'shift', 'n');
@@ -1689,9 +1689,9 @@
         assert.equal(element.filesExpanded, 'some');
       });
 
-      test('n key without all files expanded and shift key', () => {
+      test('n key without all files expanded and shift key', async () => {
         MockInteractions.keyUpOn(fileRows[0], 73, 'shift', 'i');
-        flush();
+        await flush();
 
         MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'n');
         assert.isTrue(nKeySpy.called);
@@ -1702,9 +1702,9 @@
         assert.isTrue(element._showInlineDiffs);
       });
 
-      test('n key without all files expanded and no shift key', () => {
+      test('n key without all files expanded and no shift key', async () => {
         MockInteractions.keyUpOn(fileRows[0], 73, 'shift', 'i');
-        flush();
+        await flush();
 
         MockInteractions.pressAndReleaseKeyOn(element, 78, 'shift', 'n');
         assert.isTrue(nKeySpy.called);
@@ -1716,7 +1716,7 @@
       });
     });
 
-    test('_openSelectedFile behavior', () => {
+    test('_openSelectedFile behavior', async () => {
       const _filesByPath = element._filesByPath;
       element.set('_filesByPath', {});
       const navStub = sinon.stub(GerritNav, 'navigateToDiff');
@@ -1725,7 +1725,7 @@
       assert.isFalse(navStub.called);
 
       element.set('_filesByPath', _filesByPath);
-      flush();
+      await flush();
       // Navigates when a file is selected.
       element._openSelectedFile();
       assert.isTrue(navStub.called);
@@ -1753,7 +1753,7 @@
     });
 
     suite('editMode behavior', () => {
-      test('reviewed checkbox', () => {
+      test('reviewed checkbox', async () => {
         element._reviewFile.restore();
         const saveReviewStub = sinon.stub(element, '_saveReviewedState');
 
@@ -1762,7 +1762,7 @@
         assert.isTrue(saveReviewStub.calledOnce);
 
         element.editMode = true;
-        flush();
+        await flush();
 
         MockInteractions.pressAndReleaseKeyOn(element, 82, null, 'r');
         assert.isTrue(saveReviewStub.calledOnce);
@@ -1778,13 +1778,13 @@
       });
     });
 
-    test('editing actions', () => {
+    test('editing actions', async () => {
       // Edit controls are guarded behind a dom-if initially and not rendered.
       assert.isNotOk(dom(element.root)
           .querySelector('gr-edit-file-controls'));
 
       element.editMode = true;
-      flush();
+      await flush();
 
       // Commit message should not have edit controls.
       const editControls =
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts
index c11e484..d50e00f 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts
@@ -16,6 +16,7 @@
  */
 import '@polymer/iron-input/iron-input';
 import '../../../styles/shared-styles';
+import '../../../styles/gr-font-styles';
 import '../../shared/gr-button/gr-button';
 import {PolymerElement} from '@polymer/polymer/polymer-element';
 import {htmlTemplate} from './gr-included-in-dialog_html';
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_html.ts b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_html.ts
index 2029209..674b7e7 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_html.ts
@@ -17,6 +17,9 @@
 import {html} from '@polymer/polymer/lib/utils/html-tag';
 
 export const htmlTemplate = html`
+  <style include="gr-font-styles">
+    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+  </style>
   <style include="shared-styles">
     :host {
       background-color: var(--dialog-background-color);
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.ts
index 283a133..4e02155 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.ts
@@ -76,7 +76,7 @@
     ]);
   });
 
-  test('_computeGroups with .bindValue', done => {
+  test('_computeGroups with .bindValue', async () => {
     queryAndAssert<IronInputElement>(element, '#filterInput')!.bindValue =
       'stable-3.2';
     const includedIn = {branches: [], tags: []} as IncludedInInfo;
@@ -84,14 +84,10 @@
       'master' as BranchName,
       'stable-3.2' as BranchName
     );
-
-    setTimeout(() => {
-      const filterText = element._filterText;
-      assert.deepEqual(element._computeGroups(includedIn, filterText), [
-        {title: 'Branches', items: ['stable-3.2']},
-      ]);
-
-      done();
-    });
+    await flush();
+    const filterText = element._filterText;
+    assert.deepEqual(element._computeGroups(includedIn, filterText), [
+      {title: 'Branches', items: ['stable-3.2']},
+    ]);
   });
 });
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js
index 3c9b7c7..e7ff236 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js
@@ -23,7 +23,7 @@
 suite('gr-label-row-score tests', () => {
   let element;
 
-  setup(done => {
+  setup(async () => {
     element = basicFixture.instantiate();
     element.labels = {
       'Code-Review': {
@@ -80,7 +80,7 @@
       value: '+1',
     };
 
-    flush(done);
+    await flush();
   });
 
   function checkAriaCheckedValid() {
@@ -241,7 +241,7 @@
     checkAriaCheckedValid();
   });
 
-  test('without permitted labels', () => {
+  test('without permitted labels', async () => {
     element.permittedLabels = {
       Verified: [
         '-1',
@@ -249,22 +249,22 @@
         '+1',
       ],
     };
-    flush();
+    await flush();
     assert.isOk(element.$.labelSelector);
     assert.isFalse(element.$.labelSelector.hidden);
 
     element.permittedLabels = {};
-    flush();
+    await flush();
     assert.isOk(element.$.labelSelector);
     assert.isTrue(element.$.labelSelector.hidden);
 
     element.permittedLabels = {Verified: []};
-    flush();
+    await flush();
     assert.isOk(element.$.labelSelector);
     assert.isTrue(element.$.labelSelector.hidden);
   });
 
-  test('asymmetrical labels', done => {
+  test('asymmetrical labels', async () => {
     element.permittedLabels = {
       'Code-Review': [
         '-2',
@@ -278,35 +278,32 @@
         '+1',
       ],
     };
-    flush(() => {
-      assert.strictEqual(element.$.labelSelector
-          .items.length, 2);
-      assert.strictEqual(
-          element.root.querySelectorAll('.placeholder').length,
-          3);
+    await flush();
+    assert.strictEqual(element.$.labelSelector
+        .items.length, 2);
+    assert.strictEqual(
+        element.root.querySelectorAll('.placeholder').length,
+        3);
 
-      element.permittedLabels = {
-        'Code-Review': [
-          ' 0',
-          '+1',
-        ],
-        'Verified': [
-          '-2',
-          '-1',
-          ' 0',
-          '+1',
-          '+2',
-        ],
-      };
-      flush(() => {
-        assert.strictEqual(element.$.labelSelector
-            .items.length, 5);
-        assert.strictEqual(
-            element.root.querySelectorAll('.placeholder').length,
-            0);
-        done();
-      });
-    });
+    element.permittedLabels = {
+      'Code-Review': [
+        ' 0',
+        '+1',
+      ],
+      'Verified': [
+        '-2',
+        '-1',
+        ' 0',
+        '+1',
+        '+2',
+      ],
+    };
+    await flush();
+    assert.strictEqual(element.$.labelSelector
+        .items.length, 5);
+    assert.strictEqual(
+        element.root.querySelectorAll('.placeholder').length,
+        0);
   });
 
   test('default_value', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
index 5e5e01e..b4e85a9 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
@@ -180,7 +180,7 @@
   @property({
     type: String,
     computed:
-      '_computeMessageContentExpanded(message.message,' +
+      '_computeMessageContentExpanded(_expanded, message.message,' +
       ' message.accounts_in_message,' +
       ' message.tag)',
   })
@@ -240,10 +240,12 @@
   }
 
   _computeMessageContentExpanded(
+    expanded: boolean,
     content?: string,
     accountsInMessage?: AccountInfo[],
     tag?: ReviewInputTag
   ) {
+    if (!expanded) return '';
     return this._computeMessageContent(true, content, accountsInMessage, tag);
   }
 
@@ -277,9 +279,11 @@
     tag?: ReviewInputTag,
     commentThreads?: CommentThread[]
   ) {
+    // Content is under text-overflow, so it's always shorten
+    const shortenedContent = content?.substring(0, 1000);
     const summary = this._computeMessageContent(
       false,
-      content,
+      shortenedContent,
       accountsInMessage,
       tag
     );
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
index fadf5b2..ddbd22e 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
@@ -245,13 +245,13 @@
       <template is="dom-if" if="[[message.message]]">
         <div class="content messageContent">
           <div class="message hideOnOpen">[[_messageContentCollapsed]]</div>
-          <gr-formatted-text
-            noTrailingMargin
-            class="message hideOnCollapsed"
-            content="[[_messageContentExpanded]]"
-            config="[[_projectConfig.commentlinks]]"
-          ></gr-formatted-text>
           <template is="dom-if" if="[[_expanded]]">
+            <gr-formatted-text
+              noTrailingMargin
+              class="message hideOnCollapsed"
+              content="[[_messageContentExpanded]]"
+              config="[[_projectConfig.commentlinks]]"
+            ></gr-formatted-text>
             <template is="dom-if" if="[[_messageContentExpanded]]">
               <div
                 class="replyActionContainer"
@@ -281,8 +281,8 @@
             </template>
             <gr-thread-list
               change="[[change]]"
-              hidden$="[[!message.commentThreads.length]]"
-              threads="[[message.commentThreads]]"
+              hidden$="[[!commentThreads.length]]"
+              threads="[[commentThreads]]"
               change-num="[[changeNum]]"
               logged-in="[[_loggedIn]]"
               hide-dropdown
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
index cae7a5a..90d2049 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
@@ -26,6 +26,7 @@
   createRevisions,
 } from '../../../test/test-data-generators';
 import {
+  mockPromise,
   query,
   queryAll,
   queryAndAssert,
@@ -58,13 +59,13 @@
   let element: GrMessage;
 
   suite('when admin and logged in', () => {
-    setup(done => {
+    setup(async () => {
       stubRestApi('getIsAdmin').returns(Promise.resolve(true));
       element = basicFixture.instantiate();
-      flush(done);
+      await flush();
     });
 
-    test('reply event', done => {
+    test('reply event', async () => {
       element.message = {
         ...createChangeMessage(),
         id: '47c43261_55aa2c41' as ChangeMessageId,
@@ -79,18 +80,20 @@
         expanded: true,
       };
 
+      const promise = mockPromise();
       element.addEventListener('reply', (e: CustomEvent<ReplyEventDetail>) => {
         assert.deepEqual(e.detail.message, element.message);
-        done();
+        promise.resolve();
       });
-      flush();
+      await flush();
       assert.isFalse(
         queryAndAssert<HTMLElement>(element, '.replyActionContainer').hidden
       );
       tap(queryAndAssert(element, '.replyBtn'));
+      await promise;
     });
 
-    test('can see delete button', () => {
+    test('can see delete button', async () => {
       element.message = {
         ...createChangeMessage(),
         id: '47c43261_55aa2c41' as ChangeMessageId,
@@ -105,11 +108,11 @@
         expanded: true,
       };
 
-      flush();
+      await flush();
       assert.isFalse(queryAndAssert<HTMLElement>(element, '.deleteBtn').hidden);
     });
 
-    test('delete change message', done => {
+    test('delete change message', async () => {
       element.changeNum = 314159 as NumericChangeId;
       element.message = {
         ...createChangeMessage(),
@@ -125,6 +128,7 @@
         expanded: true,
       };
 
+      const promise = mockPromise();
       element.addEventListener(
         'change-message-deleted',
         (e: CustomEvent<ChangeMessageDeletedEventDetail>) => {
@@ -132,14 +136,15 @@
           assert.isFalse(
             (queryAndAssert(element, '.deleteBtn') as GrButton).disabled
           );
-          done();
+          promise.resolve();
         }
       );
-      flush();
+      await flush();
       tap(queryAndAssert(element, '.deleteBtn'));
       assert.isTrue(
         (queryAndAssert(element, '.deleteBtn') as GrButton).disabled
       );
+      await promise;
     });
 
     test('autogenerated prefix hiding', () => {
@@ -565,11 +570,11 @@
   });
 
   suite('when not logged in', () => {
-    setup(done => {
+    setup(async () => {
       stubRestApi('getLoggedIn').returns(Promise.resolve(false));
       stubRestApi('getIsAdmin').returns(Promise.resolve(false));
       element = basicFixture.instantiate();
-      flush(done);
+      await flush();
     });
 
     test('reply and delete button should be hidden', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
index fae624e..e458611 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
@@ -210,8 +210,11 @@
   };
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-messages-list')
-export class GrMessagesList extends KeyboardShortcutMixin(PolymerElement) {
+export class GrMessagesList extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-change.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-change.ts
index b3730fd..d34600e 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-change.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-change.ts
@@ -14,9 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {html} from 'lit-html';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {customElement, property, css} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 import {sharedStyles} from '../../../styles/shared-styles';
 import {
   ChangeInfo,
@@ -27,7 +26,7 @@
 import {isChangeInfo} from '../../../utils/change-util';
 
 @customElement('gr-related-change')
-export class GrRelatedChange extends GrLitElement {
+export class GrRelatedChange extends LitElement {
   @property()
   change?: ChangeInfo | RelatedChangeAndCommitInfo;
 
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
index dd86ea1..74f20f2 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
@@ -14,14 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {html, nothing} from 'lit-html';
 import './gr-related-change';
 import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
 import '../../plugins/gr-endpoint-param/gr-endpoint-param';
 import '../../plugins/gr-endpoint-slot/gr-endpoint-slot';
-import {classMap} from 'lit-html/directives/class-map';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {customElement, property, css, state, TemplateResult} from 'lit-element';
+import {classMap} from 'lit/directives/class-map';
+import {LitElement, css, html, nothing, TemplateResult} from 'lit';
+import {customElement, property, state} from 'lit/decorators';
 import {sharedStyles} from '../../../styles/shared-styles';
 import {
   SubmittedTogetherInfo,
@@ -61,7 +60,7 @@
 }
 
 @customElement('gr-related-changes-list')
-export class GrRelatedChangesList extends GrLitElement {
+export class GrRelatedChangesList extends LitElement {
   @property()
   change?: ParsedChangeInfo;
 
@@ -666,7 +665,7 @@
 }
 
 @customElement('gr-related-collapse')
-export class GrRelatedCollapse extends GrLitElement {
+export class GrRelatedCollapse extends LitElement {
   @property()
   override title = '';
 
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.ts
index 15bc6bf..e38e0ae 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.ts
@@ -74,9 +74,9 @@
         v: boolean;
       }>
     ) {
-      return instructions
-        .map(inst => Array.from({length: inst.len}, () => inst.v))
-        .reduce((acc, val) => acc.concat(val), []);
+      return instructions.flatMap(inst =>
+        Array.from({length: inst.len}, () => inst.v)
+      );
     }
 
     function checkShowWhenCollapsed(
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index 89df2cb..17036e6 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -163,8 +163,11 @@
   };
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-reply-dialog')
-export class GrReplyDialog extends KeyboardShortcutMixin(PolymerElement) {
+export class GrReplyDialog extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
index d70c7ba..e3bbd9e 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
@@ -1037,45 +1037,39 @@
     });
   });
 
-  test('getlabelValue returns value', done => {
-    flush(() => {
-      const el = queryAndAssert(
-        queryAndAssert(element, 'gr-label-scores'),
-        'gr-label-score-row[name="Verified"]'
-      ) as GrLabelScoreRow;
-      el.setSelectedValue('-1');
-      assert.equal('-1', element.getLabelValue('Verified'));
-      done();
-    });
+  test('getlabelValue returns value', async () => {
+    await flush();
+    const el = queryAndAssert(
+      queryAndAssert(element, 'gr-label-scores'),
+      'gr-label-score-row[name="Verified"]'
+    ) as GrLabelScoreRow;
+    el.setSelectedValue('-1');
+    assert.equal('-1', element.getLabelValue('Verified'));
   });
 
-  test('getlabelValue when no score is selected', done => {
-    flush(() => {
-      const el = queryAndAssert(
-        queryAndAssert(element, 'gr-label-scores'),
-        'gr-label-score-row[name="Code-Review"]'
-      ) as GrLabelScoreRow;
-      el.setSelectedValue('-1');
-      assert.strictEqual(element.getLabelValue('Verified'), ' 0');
-      done();
-    });
+  test('getlabelValue when no score is selected', async () => {
+    await flush();
+    const el = queryAndAssert(
+      queryAndAssert(element, 'gr-label-scores'),
+      'gr-label-score-row[name="Code-Review"]'
+    ) as GrLabelScoreRow;
+    el.setSelectedValue('-1');
+    assert.strictEqual(element.getLabelValue('Verified'), ' 0');
   });
 
-  test('setlabelValue', done => {
+  test('setlabelValue', async () => {
     element._account = {_account_id: 1 as AccountId};
-    flush(() => {
-      const label = 'Verified';
-      const value = '+1';
-      element.setLabelValue(label, value);
+    await flush();
+    const label = 'Verified';
+    const value = '+1';
+    element.setLabelValue(label, value);
 
-      const labels = (
-        queryAndAssert(element, '#labelScores') as GrLabelScores
-      ).getLabelValues();
-      assert.deepEqual(labels, {
-        'Code-Review': 0,
-        Verified: 1,
-      });
-      done();
+    const labels = (
+      queryAndAssert(element, '#labelScores') as GrLabelScores
+    ).getLabelValues();
+    assert.deepEqual(labels, {
+      'Code-Review': 0,
+      Verified: 1,
     });
   });
 
@@ -1347,7 +1341,7 @@
     assert.isTrue(eraseDraftCommentStub.calledWith(location));
   });
 
-  test('400 converts to human-readable server-error', done => {
+  test('400 converts to human-readable server-error', async () => {
     stubRestApi('saveChangeReview').callsFake(
       (_changeNum, _patchNum, _review, errFn) => {
         errFn!(
@@ -1360,34 +1354,35 @@
       }
     );
 
+    const promise = mockPromise();
     const listener = (event: Event) => {
       if (event.target !== document) return;
       (event as CustomEvent).detail.response.text().then((body: string) => {
         if (body === 'human readable') {
-          done();
+          promise.resolve();
         }
       });
     };
     addListenerForTest(document, 'server-error', listener);
 
-    flush(() => {
-      element.send(false, false);
-    });
+    await flush();
+    element.send(false, false);
+    await promise;
   });
 
-  test('non-json 400 is treated as a normal server-error', done => {
+  test('non-json 400 is treated as a normal server-error', async () => {
     stubRestApi('saveChangeReview').callsFake(
       (_changeNum, _patchNum, _review, errFn) => {
         errFn!(cloneableResponse(400, 'Comment validation error!') as Response);
         return Promise.resolve(new Response());
       }
     );
-
+    const promise = mockPromise();
     const listener = (event: Event) => {
       if (event.target !== document) return;
       (event as CustomEvent).detail.response.text().then((body: string) => {
         if (body === 'Comment validation error!') {
-          done();
+          promise.resolve();
         }
       });
     };
@@ -1395,9 +1390,9 @@
 
     // Async tick is needed because iron-selector content is distributed and
     // distributed content requires an observer to be set up.
-    flush(() => {
-      element.send(false, false);
-    });
+    await flush();
+    element.send(false, false);
+    await promise;
   });
 
   test('filterReviewerSuggestion', () => {
@@ -1494,29 +1489,30 @@
     assert.strictEqual(element._chooseFocusTarget(), element.FocusTarget.BODY);
   });
 
-  test('only send labels that have changed', done => {
-    flush(() => {
-      stubSaveReview((review: ReviewInput) => {
-        assert.deepEqual(review?.labels, {
-          'Code-Review': 0,
-          Verified: -1,
-        });
+  test('only send labels that have changed', async () => {
+    await flush();
+    stubSaveReview((review: ReviewInput) => {
+      assert.deepEqual(review?.labels, {
+        'Code-Review': 0,
+        Verified: -1,
       });
-
-      element.addEventListener('send', () => {
-        done();
-      });
-      // Without wrapping this test in flush(), the below two calls to
-      // tap() cause a race in some situations in shadow DOM.
-      // The send button can be tapped before the others, causing the test to
-      // fail.
-      const el = queryAndAssert(
-        queryAndAssert(element, 'gr-label-scores'),
-        'gr-label-score-row[name="Verified"]'
-      ) as GrLabelScoreRow;
-      el.setSelectedValue('-1');
-      tap(queryAndAssert(element, '.send'));
     });
+
+    const promise = mockPromise();
+    element.addEventListener('send', () => {
+      promise.resolve();
+    });
+    // Without wrapping this test in flush(), the below two calls to
+    // tap() cause a race in some situations in shadow DOM.
+    // The send button can be tapped before the others, causing the test to
+    // fail.
+    const el = queryAndAssert(
+      queryAndAssert(element, 'gr-label-scores'),
+      'gr-label-score-row[name="Verified"]'
+    ) as GrLabelScoreRow;
+    el.setSelectedValue('-1');
+    tap(queryAndAssert(element, '.send'));
+    await promise;
   });
 
   test('moving from cc to reviewer', () => {
@@ -1811,14 +1807,14 @@
     stubSaveReview(() => undefined);
     element.addEventListener('send', () => assert.fail('wrongly called'));
     pressAndReleaseKeyOn(element, 13, null, 'enter');
-    flush();
   });
 
-  test('emit send on ctrl+enter key', done => {
+  test('emit send on ctrl+enter key', async () => {
     stubSaveReview(() => undefined);
-    element.addEventListener('send', () => done());
+    const promise = mockPromise();
+    element.addEventListener('send', () => promise.resolve());
     pressAndReleaseKeyOn(element, 13, 'ctrl', 'enter');
-    flush();
+    await promise;
   });
 
   test('_computeMessagePlaceholder', () => {
@@ -1840,7 +1836,7 @@
     );
   });
 
-  test('_handle400Error reviewers and CCs', done => {
+  test('_handle400Error reviewers and CCs', async () => {
     const error1 = 'error 1';
     const error2 = 'error 2';
     const error3 = 'error 3';
@@ -1862,49 +1858,48 @@
           },
         },
       });
+    const promise = mockPromise();
     const listener = (e: Event) => {
       (e as CustomEvent).detail.response.text().then((text: string) => {
         assert.equal(text, [error1, error2, error3].join(', '));
-        done();
+        promise.resolve();
       });
     };
     addListenerForTest(document, 'server-error', listener);
     element._handle400Error(cloneableResponse(400, text) as Response);
+    await promise;
   });
 
-  test('fires height change when the drafts comments load', done => {
+  test('fires height change when the drafts comments load', async () => {
     // Flush DOM operations before binding to the autogrow event so we don't
     // catch the events fired from the initial layout.
-    flush(() => {
-      const autoGrowHandler = sinon.stub();
-      element.addEventListener('autogrow', autoGrowHandler);
-      element.draftCommentThreads = [];
-      flush(() => {
-        assert.isTrue(autoGrowHandler.called);
-        done();
-      });
-    });
+    await flush();
+    const autoGrowHandler = sinon.stub();
+    element.addEventListener('autogrow', autoGrowHandler);
+    element.draftCommentThreads = [];
+    await flush();
+    assert.isTrue(autoGrowHandler.called);
   });
 
   suite('start review and save buttons', () => {
     let sendStub: sinon.SinonStub;
 
-    setup(() => {
+    setup(async () => {
       sendStub = sinon.stub(element, 'send').callsFake(() => Promise.resolve());
       element.canBeStarted = true;
       // Flush to make both Start/Save buttons appear in DOM.
-      flush();
+      await flush();
     });
 
-    test('start review sets ready', () => {
+    test('start review sets ready', async () => {
       tap(queryAndAssert(element, '.send'));
-      flush();
+      await flush();
       assert.isTrue(sendStub.calledWith(true, true));
     });
 
-    test("save review doesn't set ready", () => {
+    test("save review doesn't set ready", async () => {
       tap(queryAndAssert(element, '.save'));
-      flush();
+      await flush();
       assert.isTrue(sendStub.calledWith(true, false));
     });
   });
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.ts b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.ts
index c579a59..058007b 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.ts
@@ -57,6 +57,10 @@
       vertical-align: top;
       display: inline-block;
     }
+    gr-vote-chip {
+      --gr-vote-chip-width: 16px;
+      --gr-vote-chip-height: 16px;
+    }
   </style>
   <div class="container">
     <div>
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts b/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts
new file mode 100644
index 0000000..7ca7226
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts
@@ -0,0 +1,91 @@
+/**
+ * @license
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import '../../../styles/gr-font-styles';
+import '../../shared/gr-hovercard/gr-hovercard-shared-style';
+import {PolymerElement} from '@polymer/polymer/polymer-element';
+import {customElement, property} from '@polymer/decorators';
+import {HovercardBehaviorMixin} from '../../shared/gr-hovercard/gr-hovercard-behavior';
+import {htmlTemplate} from './gr-submit-requirement-hovercard_html';
+import {
+  AccountInfo,
+  SubmitRequirementResultInfo,
+  SubmitRequirementStatus,
+} from '../../../api/rest-api';
+import {
+  extractAssociatedLabels,
+  iconForStatus,
+} from '../../../utils/label-util';
+import {ParsedChangeInfo} from '../../../types/types';
+import {Label} from '../gr-change-requirements/gr-change-requirements';
+
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = HovercardBehaviorMixin(PolymerElement);
+
+@customElement('gr-submit-requirement-hovercard')
+export class GrHovercardRun extends base {
+  static get template() {
+    return htmlTemplate;
+  }
+
+  @property({type: Object})
+  requirement?: SubmitRequirementResultInfo;
+
+  @property({type: Object})
+  change?: ParsedChangeInfo;
+
+  @property({type: Object})
+  account?: AccountInfo;
+
+  @property({type: Boolean})
+  mutable?: boolean;
+
+  @property({type: Array, computed: 'computeLabels(change, requirement)'})
+  _labels: Label[] = [];
+
+  computeLabels(
+    change?: ParsedChangeInfo,
+    requirement?: SubmitRequirementResultInfo
+  ) {
+    if (!requirement) return [];
+    const requirementLabels = extractAssociatedLabels(requirement);
+    const labels = change?.labels ?? {};
+
+    const allLabels: Label[] = [];
+
+    for (const label of Object.keys(labels)) {
+      if (requirementLabels.includes(label)) {
+        allLabels.push({
+          labelName: label,
+          icon: '',
+          style: '',
+          labelInfo: labels[label],
+        });
+      }
+    }
+    return allLabels;
+  }
+
+  computeIcon(status: SubmitRequirementStatus) {
+    return iconForStatus(status);
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'gr-submit-requirement-hovercard': GrHovercardRun;
+  }
+}
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard_html.ts b/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard_html.ts
new file mode 100644
index 0000000..6adb29e
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard_html.ts
@@ -0,0 +1,117 @@
+/**
+ * @license
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {html} from '@polymer/polymer/lib/utils/html-tag';
+
+export const htmlTemplate = html`
+  <style include="gr-font-styles">
+    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+  </style>
+  <style include="gr-hovercard-shared-style">
+    #container {
+      min-width: 356px;
+      max-width: 356px;
+      padding: var(--spacing-xl) 0 var(--spacing-m) 0;
+    }
+    section.label {
+      display: table-row;
+    }
+    .label-title {
+      min-width: 10em;
+      padding-top: var(--spacing-s);
+    }
+    .label-value {
+      padding-top: var(--spacing-s);
+    }
+    .label-title,
+    .label-value {
+      display: table-cell;
+      vertical-align: top;
+    }
+    .row {
+      display: flex;
+      margin-top: var(--spacing-s);
+    }
+    .title {
+      color: var(--deemphasized-text-color);
+      margin-right: var(--spacing-m);
+    }
+    div.section {
+      margin: 0 var(--spacing-xl) var(--spacing-m) var(--spacing-xl);
+      display: flex;
+    }
+    div.sectionIcon iron-icon {
+      position: relative;
+      top: 2px;
+      width: 20px;
+      height: 20px;
+    }
+    iron-icon.check {
+      color: var(--success-foreground);
+    }
+    iron-icon.close {
+      color: var(--warning-foreground);
+    }
+  </style>
+  <div id="container" role="tooltip" tabindex="-1">
+    <div class="section">
+      <div class="sectionIcon">
+        <iron-icon
+          class$="[[computeIcon(requirement.status)]]"
+          icon="gr-icons:[[computeIcon(requirement.status)]]"
+        ></iron-icon>
+      </div>
+      <div class="sectionContent">
+        <h3 class="name heading-3">
+          <span>[[requirement.name]]</span>
+        </h3>
+      </div>
+    </div>
+    <div class="section">
+      <div class="sectionIcon">
+        <iron-icon class="small" icon="gr-icons:info-outline"></iron-icon>
+      </div>
+      <div class="sectionContent">
+        <div class="row">
+          <div class="title">Status</div>
+          <div>[[requirement.status]]</div>
+        </div>
+      </div>
+    </div>
+    <div class="section">
+      <template is="dom-repeat" items="[[_labels]]">
+        <section class="label">
+          <div class="label-title">
+            <gr-limited-text
+              class="name"
+              limit="25"
+              text="[[item.labelName]]"
+            ></gr-limited-text>
+          </div>
+          <div class="label-value">
+            <gr-label-info
+              change="{{change}}"
+              account="[[account]]"
+              mutable="[[mutable]]"
+              label="[[item.labelName]]"
+              label-info="[[item.labelInfo]]"
+            ></gr-label-info>
+          </div>
+        </section>
+      </template>
+    </div>
+  </div>
+`;
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
index 47f6e85..286f24a 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
@@ -14,20 +14,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property} from 'lit-element';
+import '../gr-submit-requirement-hovercard/gr-submit-requirement-hovercard';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 import {ParsedChangeInfo} from '../../../types/types';
 import {
   AccountInfo,
+  isDetailedLabelInfo,
+  LabelNameToInfoMap,
   SubmitRequirementResultInfo,
   SubmitRequirementStatus,
 } from '../../../api/rest-api';
-import {assertNever} from '../../../utils/common-util';
-import {extractAssociatedLabels} from '../../../utils/change-metadata-util';
-import {Label} from '../gr-change-requirements/gr-change-requirements';
+import {unique} from '../../../utils/common-util';
+import {
+  extractAssociatedLabels,
+  iconForStatus,
+} from '../../../utils/label-util';
 
 @customElement('gr-submit-requirements')
-export class GrSubmitRequirements extends GrLitElement {
+export class GrSubmitRequirements extends LitElement {
   @property({type: Object})
   change?: ParsedChangeInfo;
 
@@ -61,11 +66,13 @@
           padding: var(--spacing-s) 0 0 0;
         }
         .title,
-        .value,
         .status {
           display: table-cell;
           vertical-align: top;
         }
+        .value {
+          display: inline-flex;
+        }
         .status {
           width: var(--line-height-small);
           padding: var(--spacing-s) var(--spacing-m) 0
@@ -75,10 +82,10 @@
           width: var(--line-height-small);
           height: var(--line-height-small);
         }
-        iron-icon.satisfied {
+        iron-icon.check {
           color: var(--success-foreground);
         }
-        iron-icon.unsatisfied {
+        iron-icon.close {
           color: var(--warning-foreground);
         }
         .testing {
@@ -106,6 +113,12 @@
     return html`<h3 class="metadata-title">Submit Requirements</h3>
       ${submit_requirements.map(
         requirement => html`<section>
+          <gr-submit-requirement-hovercard
+            .requirement="${requirement}"
+            .change="${this.change}"
+            .account="${this.account}"
+            .mutable="${this.mutable}"
+          ></gr-submit-requirement-hovercard>
           <div class="status">${this.renderStatus(requirement.status)}</div>
           <div class="title">
             <gr-limited-text
@@ -114,62 +127,75 @@
               text="${requirement.name}"
             ></gr-limited-text>
           </div>
-          <div class="value">${this.renderLabels(requirement)}</div>
+          <div class="value">${this.renderVotes(requirement)}</div>
         </section>`
+      )}
+      ${this.renderTriggerVotes(
+        submit_requirements
       )}${this.renderFakeControls()}`;
   }
 
   renderStatus(status: SubmitRequirementStatus) {
-    let grIcon: string;
-    switch (status) {
-      case SubmitRequirementStatus.SATISFIED:
-        grIcon = 'gr-icons:check';
-        break;
-      case SubmitRequirementStatus.UNSATISFIED:
-        grIcon = 'gr-icons:close';
-        break;
-      case SubmitRequirementStatus.OVERRIDDEN:
-        grIcon = 'gr-icons:warning';
-        break;
-      case SubmitRequirementStatus.NOT_APPLICABLE:
-        grIcon = 'gr-icons:info';
-        break;
-      default:
-        assertNever(status, `Unsupported status: ${status}`);
-    }
+    const icon = iconForStatus(status);
     return html`<iron-icon
-      class=${status.toLowerCase()}
-      icon="${grIcon}"
+      class="${icon}"
+      icon="gr-icons:${icon}"
     ></iron-icon>`;
   }
 
-  renderLabels(requirement: SubmitRequirementResultInfo) {
+  renderVotes(requirement: SubmitRequirementResultInfo) {
     const requirementLabels = extractAssociatedLabels(requirement);
     const labels = this.change?.labels ?? {};
 
-    const allLabels: Label[] = [];
+    return Object.keys(labels)
+      .filter(label => requirementLabels.includes(label))
+      .map(label => this.renderLabelVote(label, labels));
+  }
 
-    for (const label of Object.keys(labels)) {
-      if (requirementLabels.includes(label)) {
-        allLabels.push({
-          labelName: label,
-          icon: '',
-          style: '',
-          labelInfo: labels[label],
-        });
-      }
-    }
-    return allLabels.map(
-      label => html`<gr-label-info
-        .change="${this.change}"
-        .account="${this.account}"
-        .mutable="${this.mutable}"
-        label="${label.labelName}"
-        .labelInfo="${label.labelInfo}"
-      ></gr-label-info>`
+  renderLabelVote(label: string, labels: LabelNameToInfoMap) {
+    const labelInfo = labels[label];
+    if (!isDetailedLabelInfo(labelInfo)) return;
+    const uniqueApprovals = (labelInfo.all ?? [])
+      .filter(
+        (approvalInfo, index, array) =>
+          index === array.findIndex(other => other.value === approvalInfo.value)
+      )
+      .sort((a, b) => -(a.value ?? 0) + (b.value ?? 0));
+    return uniqueApprovals.map(
+      approvalInfo =>
+        html`<gr-vote-chip
+          .vote="${approvalInfo}"
+          .label="${labelInfo}"
+          .more="${(labelInfo.all ?? []).filter(
+            other => other.value === approvalInfo.value
+          ).length > 1}"
+        ></gr-vote-chip>`
     );
   }
 
+  renderTriggerVotes(submitReqs: SubmitRequirementResultInfo[]) {
+    const labels = this.change?.labels ?? {};
+    const allLabels = Object.keys(labels);
+    const labelAssociatedWithSubmitReqs = submitReqs
+      .flatMap(req => extractAssociatedLabels(req))
+      .filter(unique);
+    const triggerVotes = allLabels.filter(
+      label => !labelAssociatedWithSubmitReqs.includes(label)
+    );
+    if (!triggerVotes.length) return;
+    return html`<h3 class="metadata-title">Trigger Votes</h3>
+      ${triggerVotes.map(
+        label => html`${label}:
+          <gr-label-info
+            .change="${this.change}"
+            .account="${this.account}"
+            .mutable="${this.mutable}"
+            label="${label}"
+            .labelInfo="${labels[label]}"
+          ></gr-label-info>`
+      )}`;
+  }
+
   renderFakeControls() {
     return html`
       <div class="testing">
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_html.ts b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_html.ts
index daf2643..4d97fec 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_html.ts
@@ -115,7 +115,7 @@
       </template>
     </div>
   </template>
-  <div id="threads">
+  <div id="threads" part="threads">
     <template
       is="dom-if"
       if="[[_showEmptyThreadsMessage(threads, _displayedThreads, unresolvedOnly)]]"
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.js b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.js
index fe99918..b478ef5 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.js
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.js
@@ -37,7 +37,7 @@
         .filter(e => e.style.display !== 'none');
   }
 
-  setup(done => {
+  setup(async () => {
     element = basicFixture.instantiate();
     element.changeNum = 123;
     element.change = {
@@ -267,9 +267,7 @@
     ];
 
     // use flush to render all (bypass initial-count set on dom-repeat)
-    flush(() => {
-      done();
-    });
+    await flush();
   });
 
   test('draft dropdown item only appears when logged in', () => {
@@ -289,14 +287,13 @@
     assert.equal(getVisibleThreads().length, element.threads.length);
   });
 
-  test('show unresolved threads if unresolvedOnly is set', done => {
+  test('show unresolved threads if unresolvedOnly is set', async () => {
     element.unresolvedOnly = true;
-    flush();
+    await flush();
     const unresolvedThreads = element.threads.filter(t => t.comments.some(
         c => c.unresolved
     ));
     assert.equal(getVisibleThreads().length, unresolvedThreads.length);
-    done();
   });
 
   test('showing file name takes visible threads into account', () => {
@@ -646,11 +643,9 @@
   });
 
   suite('hideDropdown', () => {
-    setup(done => {
+    setup(async () => {
       element.hideDropdown = true;
-      flush(() => {
-        done();
-      });
+      await flush();
     });
 
     test('toggle buttons are hidden', () => {
@@ -660,11 +655,9 @@
   });
 
   suite('empty thread', () => {
-    setup(done => {
+    setup(async () => {
       element.threads = [];
-      flush(() => {
-        done();
-      });
+      await flush();
     });
 
     test('default empty message should show', () => {
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-action.ts b/polygerrit-ui/app/elements/checks/gr-checks-action.ts
index 4633c0e..87cab46 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-action.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-action.ts
@@ -14,21 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {html} from 'lit-html';
-import {css, customElement, property} from 'lit-element';
-import {GrLitElement} from '../lit/gr-lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 import {Action} from '../../api/checks';
 import {checkRequiredProperty} from '../../utils/common-util';
-import {fireActionTriggered} from '../../services/checks/checks-util';
+import {appContext} from '../../services/app-context';
 
 @customElement('gr-checks-action')
-export class GrChecksAction extends GrLitElement {
+export class GrChecksAction extends LitElement {
   @property()
   action!: Action;
 
   @property()
   eventTarget?: EventTarget;
 
+  private checksService = appContext.checksService;
+
   override connectedCallback() {
     super.connectedCallback();
     checkRequiredProperty(this.action, 'action');
@@ -42,20 +43,13 @@
           white-space: nowrap;
         }
         gr-button {
-          /* It is not fully understood why this is needed, but otherwise the
-             paper-tooltip may render under some iron-icons of the content
-             below. Maybe this has to do with a z-index:0 setting for
-             paper-button, such that a stacking context is created. And the high
-             z-index of the paper-tooltip will then only be interpreted within
-             that stacking context. */
-          z-index: 1;
           --padding: var(--spacing-s) var(--spacing-m);
         }
-        gr-button paper-tooltip {
+        paper-tooltip {
           text-transform: none;
           text-align: center;
           white-space: normal;
-          width: 200px;
+          max-width: 200px;
         }
       `,
     ];
@@ -70,19 +64,23 @@
         @click="${(e: Event) => this.handleClick(e)}"
       >
         ${this.action.name}
-        <paper-tooltip
-          ?hidden="${!this.action.tooltip}"
-          offset="5"
-          fit-to-visible-bounds="true"
-          >${this.action.tooltip}</paper-tooltip
-        >
       </gr-button>
+      ${this.renderTooltip()}
+    `;
+  }
+
+  private renderTooltip() {
+    if (!this.action.tooltip) return;
+    return html`
+      <paper-tooltip offset="5" fit-to-visible-bounds="true">
+        ${this.action.tooltip}
+      </paper-tooltip>
     `;
   }
 
   handleClick(e: Event) {
     e.stopPropagation();
-    fireActionTriggered(this.eventTarget ?? this, this.action);
+    this.checksService.triggerAction(this.action);
   }
 }
 
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-attempt.ts b/polygerrit-ui/app/elements/checks/gr-checks-attempt.ts
index 6de92e1..b4d87ae 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-attempt.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-attempt.ts
@@ -14,14 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {html} from 'lit-html';
-import {css, customElement, property} from 'lit-element';
-import {GrLitElement} from '../lit/gr-lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 import {CheckRun} from '../../services/checks/checks-model';
 import {ordinal} from '../../utils/string-util';
 
 @customElement('gr-checks-attempt')
-class GrChecksAttempt extends GrLitElement {
+class GrChecksAttempt extends LitElement {
   @property()
   run?: CheckRun;
 
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index 08f3907..de334be 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -14,20 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {html} from 'lit-html';
-import {classMap} from 'lit-html/directives/class-map';
-import {repeat} from 'lit-html/directives/repeat';
-import {ifDefined} from 'lit-html/directives/if-defined';
-import {
-  css,
-  customElement,
-  property,
-  PropertyValues,
-  query,
-  state,
-  TemplateResult,
-} from 'lit-element';
-import {GrLitElement} from '../lit/gr-lit-element';
+import {classMap} from 'lit/directives/class-map';
+import {repeat} from 'lit/directives/repeat';
+import {ifDefined} from 'lit/directives/if-defined';
+import {LitElement, css, html, PropertyValues, TemplateResult} from 'lit';
+import {customElement, property, query, state} from 'lit/decorators';
 import './gr-checks-action';
 import '@polymer/paper-tooltip/paper-tooltip';
 import {
@@ -49,7 +40,6 @@
 } from '../../services/checks/checks-model';
 import {
   allResults,
-  fireActionTriggered,
   firstPrimaryLink,
   hasCompletedWithoutResults,
   iconFor,
@@ -81,9 +71,12 @@
   valueString,
 } from '../../utils/label-util';
 import {GerritNav} from '../core/gr-navigation/gr-navigation';
+import {DropdownLink} from '../shared/gr-dropdown/gr-dropdown';
+import {subscribe} from '../lit/subscription-controller';
+import {fontStyles} from '../../styles/gr-font-styles';
 
 @customElement('gr-result-row')
-class GrResultRow extends GrLitElement {
+class GrResultRow extends LitElement {
   @query('td.nameCol div.name')
   nameEl?: HTMLElement;
 
@@ -102,9 +95,11 @@
   @property()
   labels?: LabelNameToInfoMap;
 
+  private checksService = appContext.checksService;
+
   constructor() {
     super();
-    this.subscribe('labels', labels$);
+    subscribe(this, labels$, x => (this.labels = x));
   }
 
   static override get styles() {
@@ -501,7 +496,7 @@
   }
 
   private handleAction(e: CustomEvent<Action>) {
-    fireActionTriggered(this, e.detail);
+    this.checksService.triggerAction(e.detail);
   }
 
   private renderAction(action?: Action) {
@@ -531,14 +526,14 @@
     return html`<div class="tag ${tag.color}">
       <span>${tag.name}</span>
       <paper-tooltip offset="5" fit-to-visible-bounds="true">
-        A category tag for this check result
+        ${tag.tooltip ?? 'A category tag for this check result'}
       </paper-tooltip>
     </div>`;
   }
 }
 
 @customElement('gr-result-expanded')
-class GrResultExpanded extends GrLitElement {
+class GrResultExpanded extends LitElement {
   @property()
   result?: RunResult;
 
@@ -570,7 +565,7 @@
 
   constructor() {
     super();
-    this.subscribe('repoConfig', repoConfig$);
+    subscribe(this, repoConfig$, x => (this.repoConfig = x));
   }
 
   override render() {
@@ -678,7 +673,7 @@
 );
 
 @customElement('gr-checks-results')
-export class GrChecksResults extends GrLitElement {
+export class GrChecksResults extends LitElement {
   @query('#filterInput')
   filterInput?: HTMLInputElement;
 
@@ -743,17 +738,26 @@
 
   constructor() {
     super();
-    this.subscribe('actions', topLevelActionsSelected$);
-    this.subscribe('links', topLevelLinksSelected$);
-    this.subscribe('checksPatchsetNumber', checksSelectedPatchsetNumber$);
-    this.subscribe('latestPatchsetNumber', latestPatchNum$);
-    this.subscribe('someProvidersAreLoading', someProvidersAreLoadingSelected$);
+    subscribe(this, topLevelActionsSelected$, x => (this.actions = x));
+    subscribe(this, topLevelLinksSelected$, x => (this.links = x));
+    subscribe(
+      this,
+      checksSelectedPatchsetNumber$,
+      x => (this.checksPatchsetNumber = x)
+    );
+    subscribe(this, latestPatchNum$, x => (this.latestPatchsetNumber = x));
+    subscribe(
+      this,
+      someProvidersAreLoadingSelected$,
+      x => (this.someProvidersAreLoading = x)
+    );
   }
 
   static override get styles() {
     return [
       sharedStyles,
       spinnerStyles,
+      fontStyles,
       css`
         :host {
           display: block;
@@ -1011,11 +1015,7 @@
         </div>
         <div class="headerBottomRow">
           <div class="left">${this.renderFilter()}</div>
-          <div class="right">
-            ${this.renderLinks()}
-            <div class="space"></div>
-            ${this.renderActions()}
-          </div>
+          <div class="right">${this.renderLinksAndActions()}</div>
         </div>
       </div>
       <div class="body">
@@ -1026,9 +1026,8 @@
       </div>`;
   }
 
-  private renderLinks() {
+  private renderLinksAndActions() {
     const links = this.links ?? [];
-    if (links.length === 0) return;
     const primaryLinks = links
       .filter(a => a.primary)
       // Showing the same icons twice without text is super confusing.
@@ -1038,14 +1037,7 @@
       )
       .slice(0, 4);
     const overflowLinks = links.filter(a => !primaryLinks.includes(a));
-    return html`
-      ${primaryLinks.map(this.renderLink)}
-      ${this.renderOverflowLinks(overflowLinks)}
-    `;
-  }
-
-  private renderOverflowLinks(overflowLinks: Link[]) {
-    const items = overflowLinks.map(link => {
+    const overflowLinkItems = overflowLinks.map(link => {
       return {
         ...link,
         id: link.tooltip,
@@ -1054,18 +1046,27 @@
         tooltip: undefined,
       };
     });
+
+    const actions = this.actions ?? [];
+    const primaryActions = actions.filter(a => a.primary).slice(0, 2);
+    const overflowActions = actions.filter(a => !primaryActions.includes(a));
+    const overflowActionItems = overflowActions.map(action => {
+      return {...action, id: action.name};
+    });
+    const disabledActions = overflowActionItems
+      .filter(action => action.disabled)
+      .map(action => action.id);
+
     return html`
-      <gr-dropdown
-        id="moreLinks"
-        link=""
-        vertical-offset="32"
-        horizontal-align="right"
-        .items="${items}"
-      >
-        <iron-icon icon="gr-icons:more-vert" aria-labelledby="moreMessage">
-        </iron-icon>
-        <span id="moreMessage">More</span>
-      </gr-dropdown>
+      ${primaryLinks.map(this.renderLink)}
+      ${primaryLinks.length > 0 && primaryActions.length > 0
+        ? html`<div class="space"></div>`
+        : ''}
+      ${primaryActions.map(this.renderAction)}
+      ${this.renderOverflow(
+        [...overflowLinkItems, ...overflowActionItems],
+        disabledActions
+      )}
     `;
   }
 
@@ -1082,26 +1083,8 @@
     >`;
   }
 
-  private renderActions() {
-    const actions = this.actions ?? [];
-    if (actions.length === 0) return;
-    const primaryActions = actions.filter(a => a.primary).slice(0, 2);
-    const overflowActions = actions.filter(a => !primaryActions.includes(a));
-    return html`
-      ${this.renderAction(primaryActions[0])}
-      ${this.renderAction(primaryActions[1])}
-      ${this.renderOverflowActions(overflowActions)}
-    `;
-  }
-
-  private renderOverflowActions(overflowActions: Action[]) {
-    const items = overflowActions.map(action => {
-      return {...action, id: action.name};
-    });
-    if (!items || items.length === 0) return;
-    const disabledItems = items
-      .filter(action => action.disabled)
-      .map(action => action.id);
+  private renderOverflow(items: DropdownLink[], disabledIds: string[] = []) {
+    if (items.length === 0) return;
     return html`
       <gr-dropdown
         id="moreActions"
@@ -1110,7 +1093,7 @@
         horizontal-align="right"
         @tap-item="${this.handleAction}"
         .items="${items}"
-        .disabledIds="${disabledItems}"
+        .disabledIds="${disabledIds}"
       >
         <iron-icon icon="gr-icons:more-vert" aria-labelledby="moreMessage">
         </iron-icon>
@@ -1120,7 +1103,7 @@
   }
 
   private handleAction(e: CustomEvent<Action>) {
-    fireActionTriggered(this, e.detail);
+    this.checksService.triggerAction(e.detail);
   }
 
   private renderAction(action?: Action) {
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
index 250b035..d296a46 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
@@ -14,25 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {html, nothing} from 'lit-html';
-import {classMap} from 'lit-html/directives/class-map';
+import {classMap} from 'lit/directives/class-map';
 import './gr-hovercard-run';
-import {
-  css,
-  customElement,
-  property,
-  PropertyValues,
-  query,
-  state,
-} from 'lit-element';
-import {GrLitElement} from '../lit/gr-lit-element';
+import {LitElement, css, html, nothing, PropertyValues} from 'lit';
+import {customElement, property, query, state} from 'lit/decorators';
 import './gr-checks-attempt';
 import {Action, Link, RunStatus} from '../../api/checks';
 import {sharedStyles} from '../../styles/shared-styles';
 import {
   AttemptDetail,
   compareByWorstCategory,
-  fireActionTriggered,
   iconFor,
   iconForRun,
   PRIMARY_STATUS_ACTIONS,
@@ -43,17 +34,15 @@
   allRunsSelectedPatchset$,
   CheckRun,
   ChecksPatchset,
-  errorMessageLatest$,
+  ErrorMessages,
+  errorMessagesLatest$,
   fakeActions,
   fakeLinks,
   fakeRun0,
   fakeRun1,
   fakeRun2,
   fakeRun3,
-  fakeRun4_1,
-  fakeRun4_2,
-  fakeRun4_3,
-  fakeRun4_4,
+  fakeRun4Att,
   loginCallbackLatest$,
   updateStateSetResults,
 } from '../../services/checks/checks-model';
@@ -68,9 +57,11 @@
 import {charsOnly} from '../../utils/string-util';
 import {appContext} from '../../services/app-context';
 import {KnownExperimentId} from '../../services/flags/flags';
+import {subscribe} from '../lit/subscription-controller';
+import {fontStyles} from '../../styles/gr-font-styles';
 
 @customElement('gr-checks-run')
-export class GrChecksRun extends GrLitElement {
+export class GrChecksRun extends LitElement {
   static override get styles() {
     return [
       sharedStyles,
@@ -92,6 +83,15 @@
           overflow: hidden;
           white-space: nowrap;
           text-overflow: ellipsis;
+          flex-shrink: 1;
+        }
+        .middle {
+          /* extra space must go between middle and right */
+          flex-grow: 1;
+          white-space: nowrap;
+        }
+        .middle gr-checks-attempt {
+          margin-left: var(--spacing-s);
         }
         .name {
           font-weight: var(--font-weight-bold);
@@ -151,6 +151,7 @@
              Alternatively we could have set the vertical padding to 0, but
              that would not have been a nice click target. */
           margin: calc(0px - var(--spacing-s));
+          margin-left: var(--spacing-s);
         }
         .attemptDetails {
           padding-bottom: var(--spacing-s);
@@ -244,6 +245,8 @@
           <iron-icon class="${icon}" icon="gr-icons:${icon}"></iron-icon>
           ${this.renderAdditionalIcon()}
           <span class="name">${this.run.checkName}</span>
+        </div>
+        <div class="middle">
           <gr-checks-attempt .run="${this.run}"></gr-checks-attempt>
           ${this.renderStatusLink()}
         </div>
@@ -353,7 +356,7 @@
 }
 
 @customElement('gr-checks-runs')
-export class GrChecksRuns extends GrLitElement {
+export class GrChecksRuns extends LitElement {
   @query('#filterInput')
   filterInput?: HTMLInputElement;
 
@@ -380,7 +383,7 @@
   tabState?: ChecksTabState;
 
   @property()
-  errorMessage?: string;
+  errorMessages: ErrorMessages = {};
 
   @property()
   loginCallback?: () => void;
@@ -389,16 +392,19 @@
 
   private flagService = appContext.flagsService;
 
+  private checksService = appContext.checksService;
+
   constructor() {
     super();
-    this.subscribe('runs', allRunsSelectedPatchset$);
-    this.subscribe('errorMessage', errorMessageLatest$);
-    this.subscribe('loginCallback', loginCallbackLatest$);
+    subscribe(this, allRunsSelectedPatchset$, x => (this.runs = x));
+    subscribe(this, errorMessagesLatest$, x => (this.errorMessages = x));
+    subscribe(this, loginCallbackLatest$, x => (this.loginCallback = x));
   }
 
   static override get styles() {
     return [
       sharedStyles,
+      fontStyles,
       css`
         :host {
           display: block;
@@ -469,6 +475,7 @@
           padding: var(--spacing-m);
           color: var(--primary-text-color);
           margin-top: var(--spacing-m);
+          max-width: 400px;
         }
         .error {
           display: flex;
@@ -522,7 +529,7 @@
         <div class="flex-space"></div>
         ${this.renderTitleButtons()} ${this.renderCollapseButton()}
       </h2>
-      ${this.renderError()} ${this.renderSignIn()}
+      ${this.renderErrors()} ${this.renderSignIn()}
       <input
         id="filterInput"
         type="text"
@@ -536,23 +543,26 @@
     `;
   }
 
-  private renderError() {
-    if (!this.errorMessage) return;
-    return html`
-      <div class="error">
-        <div class="left">
-          <iron-icon icon="gr-icons:error"></iron-icon>
-        </div>
-        <div class="right">
-          <div>Error while fetching check results</div>
-          <div>${this.errorMessage}</div>
-        </div>
-      </div>
-    `;
+  private renderErrors() {
+    return Object.entries(this.errorMessages).map(
+      ([plugin, message]) =>
+        html`
+          <div class="error">
+            <div class="left">
+              <iron-icon icon="gr-icons:error"></iron-icon>
+            </div>
+            <div class="right">
+              <div class="message">
+                Error while fetching results for ${plugin}:<br />${message}
+              </div>
+            </div>
+          </div>
+        `
+    );
   }
 
   private renderSignIn() {
-    if (this.errorMessage || !this.loginCallback) return;
+    if (!this.loginCallback) return;
     return html`
       <div class="login">
         <div>
@@ -598,7 +608,7 @@
         has-tooltip="${runButtonDisabled}"
         ?disabled="${runButtonDisabled}"
         @click="${() => {
-          actions.forEach(action => fireActionTriggered(this, action));
+          actions.forEach(action => this.checksService.triggerAction(action));
         }}"
         >Run Selected</gr-button
       >
@@ -652,13 +662,7 @@
     updateStateSetResults('f1', [fakeRun1], [], [], ChecksPatchset.LATEST);
     updateStateSetResults('f2', [fakeRun2], [], [], ChecksPatchset.LATEST);
     updateStateSetResults('f3', [fakeRun3], [], [], ChecksPatchset.LATEST);
-    updateStateSetResults(
-      'f4',
-      [fakeRun4_1, fakeRun4_2, fakeRun4_3, fakeRun4_4],
-      [],
-      [],
-      ChecksPatchset.LATEST
-    );
+    updateStateSetResults('f4', fakeRun4Att, [], [], ChecksPatchset.LATEST);
   }
 
   toggle(
@@ -748,11 +752,7 @@
         <gr-button link @click="${() => this.toggle('f3', [fakeRun3])}"
           >3</gr-button
         >
-        <gr-button
-          link
-          @click="${() => {
-            this.toggle('f4', [fakeRun4_1, fakeRun4_2, fakeRun4_3, fakeRun4_4]);
-          }}"
+        <gr-button link @click="${() => this.toggle('f4', fakeRun4Att)}}"
           >4</gr-button
         >
         <gr-button link @click="${this.all}">all</gr-button>
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-styles.ts b/polygerrit-ui/app/elements/checks/gr-checks-styles.ts
index 9a1605e..dbc2bec 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-styles.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-styles.ts
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import {css} from 'lit-element';
+import {css} from 'lit';
 
 // Mark the file as a module. Otherwise typescript assumes this is a script
 // and $_documentContainer is a global variable.
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
index 1e98dfa..688667a 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
@@ -14,9 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {html} from 'lit-html';
-import {css, customElement, property, PropertyValues, state} from 'lit-element';
-import {GrLitElement} from '../lit/gr-lit-element';
+import {LitElement, css, html, PropertyValues} from 'lit';
+import {customElement, property, state} from 'lit/decorators';
 import {Action} from '../../api/checks';
 import {
   CheckResult,
@@ -32,17 +31,15 @@
 import {ActionTriggeredEvent} from '../../services/checks/checks-util';
 import {AttemptSelectedEvent, RunSelectedEvent} from './gr-checks-util';
 import {ChecksTabState} from '../../types/events';
-import {fireAlert, fireEvent} from '../../utils/event-util';
 import {appContext} from '../../services/app-context';
-import {from, timer} from 'rxjs';
-import {takeUntil} from 'rxjs/operators';
+import {subscribe} from '../lit/subscription-controller';
 
 /**
  * The "Checks" tab on the Gerrit change page. Gets its data from plugins that
  * have registered with the Checks Plugin API.
  */
 @customElement('gr-checks-tab')
-export class GrChecksTab extends GrLitElement {
+export class GrChecksTab extends LitElement {
   @property()
   runs: CheckRun[] = [];
 
@@ -75,11 +72,15 @@
 
   constructor() {
     super();
-    this.subscribe('runs', allRunsSelectedPatchset$);
-    this.subscribe('results', allResultsSelected$);
-    this.subscribe('checksPatchsetNumber', checksSelectedPatchsetNumber$);
-    this.subscribe('latestPatchsetNumber', latestPatchNum$);
-    this.subscribe('changeNum', changeNum$);
+    subscribe(this, allRunsSelectedPatchset$, x => (this.runs = x));
+    subscribe(this, allResultsSelected$, x => (this.results = x));
+    subscribe(
+      this,
+      checksSelectedPatchsetNumber$,
+      x => (this.checksPatchsetNumber = x)
+    );
+    subscribe(this, latestPatchNum$, x => (this.latestPatchsetNumber = x));
+    subscribe(this, changeNum$, x => (this.changeNum = x));
 
     this.addEventListener('action-triggered', (e: ActionTriggeredEvent) =>
       this.handleActionTriggered(e.detail.action, e.detail.run)
@@ -139,35 +140,7 @@
   }
 
   handleActionTriggered(action: Action, run?: CheckRun) {
-    if (!this.changeNum) return;
-    const patchSet = this.checksPatchsetNumber ?? this.latestPatchsetNumber;
-    if (!patchSet) return;
-    const promise = action.callback(
-      this.changeNum,
-      patchSet,
-      run?.attempt,
-      run?.externalId,
-      run?.checkName,
-      action.name
-    );
-    // If plugins return undefined or not a promise, then show no toast.
-    if (!promise?.then) return;
-
-    fireAlert(this, `Triggering action '${action.name}' ...`);
-    from(promise)
-      // If the action takes longer than 5 seconds, then most likely the
-      // user is either not interested or the result not relevant anymore.
-      .pipe(takeUntil(timer(5000)))
-      .subscribe(result => {
-        if (result.errorMessage || result.message) {
-          fireAlert(this, `${result.message ?? result.errorMessage}`);
-        } else {
-          fireEvent(this, 'hide-alert');
-        }
-        if (result.shouldReload) {
-          this.checksService.reloadForCheck(run?.checkName);
-        }
-      });
+    this.checksService.triggerAction(action, run);
   }
 
   handleRunSelected(e: RunSelectedEvent) {
diff --git a/polygerrit-ui/app/elements/checks/gr-hovercard-run.ts b/polygerrit-ui/app/elements/checks/gr-hovercard-run.ts
index 2316a05..d26856c 100644
--- a/polygerrit-ui/app/elements/checks/gr-hovercard-run.ts
+++ b/polygerrit-ui/app/elements/checks/gr-hovercard-run.ts
@@ -15,13 +15,15 @@
  * limitations under the License.
  */
 import './gr-checks-styles';
-import {hovercardBehaviorMixin} from '../shared/gr-hovercard/gr-hovercard-behavior';
+import '../../styles/gr-font-styles';
+import {HovercardBehaviorMixin} from '../shared/gr-hovercard/gr-hovercard-behavior';
 import {PolymerElement} from '@polymer/polymer/polymer-element';
 import {htmlTemplate} from './gr-hovercard-run_html';
 import {customElement, property} from '@polymer/decorators';
 import './gr-checks-action';
 import {CheckRun} from '../../services/checks/checks-model';
 import {
+  AttemptDetail,
   iconFor,
   runActions,
   worstCategory,
@@ -30,8 +32,11 @@
 import {RunStatus} from '../../api/checks';
 import {ordinal} from '../../utils/string-util';
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = HovercardBehaviorMixin(PolymerElement);
+
 @customElement('gr-hovercard-run')
-export class GrHovercardRun extends hovercardBehaviorMixin(PolymerElement) {
+export class GrHovercardRun extends base {
   static get template() {
     return htmlTemplate;
   }
@@ -56,6 +61,13 @@
     return ordinal(attempt);
   }
 
+  computeAttempts(run?: CheckRun): AttemptDetail[] {
+    const details = run?.attemptDetails ?? [];
+    const more =
+      details.length > 7 ? [{icon: 'more-horiz', attempt: undefined}] : [];
+    return [...more, ...details.slice(-7)];
+  }
+
   computeChipIcon(run?: CheckRun) {
     if (run?.status === RunStatus.COMPLETED) return 'check';
     if (run?.status === RunStatus.RUNNING) return 'timelapse';
diff --git a/polygerrit-ui/app/elements/checks/gr-hovercard-run_html.ts b/polygerrit-ui/app/elements/checks/gr-hovercard-run_html.ts
index 5b2e24a..49a1416 100644
--- a/polygerrit-ui/app/elements/checks/gr-hovercard-run_html.ts
+++ b/polygerrit-ui/app/elements/checks/gr-hovercard-run_html.ts
@@ -17,6 +17,9 @@
 import {html} from '@polymer/polymer/lib/utils/html-tag';
 
 export const htmlTemplate = html`
+  <style include="gr-font-styles">
+    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+  </style>
   <style include="gr-checks-styles">
     /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
   </style>
@@ -30,6 +33,9 @@
       display: flex;
       margin-top: var(--spacing-s);
     }
+    .attempts.row {
+      flex-wrap: wrap;
+    }
     .chipRow {
       display: flex;
       margin-top: var(--spacing-s);
@@ -91,7 +97,7 @@
       margin-right: var(--spacing-s);
       color: var(--deemphasized-text-color);
       text-align: center;
-      width: 20px;
+      width: 24px;
       font-size: var(--font-size-small);
     }
     div.action {
@@ -151,9 +157,9 @@
         <iron-icon class="small" icon="gr-icons:arrow-forward"></iron-icon>
       </div>
       <div class="sectionContent">
-        <div hidden$="[[hideAttempts(run)]]" class="row">
+        <div hidden$="[[hideAttempts(run)]]" class="attempts row">
           <div class="title">Attempt</div>
-          <template is="dom-repeat" items="[[run.attemptDetails]]">
+          <template is="dom-repeat" items="[[computeAttempts(run)]]">
             <div>
               <div class="attemptIcon">
                 <iron-icon
@@ -216,7 +222,10 @@
     </div>
     <template is="dom-repeat" items="[[computeActions(run)]]">
       <div class="action">
-        <gr-checks-action action="[[item]]"></gr-checks-action>
+        <gr-checks-action
+          event-target="[[_target]]"
+          action="[[item]]"
+        ></gr-checks-action>
       </div>
     </template>
   </div>
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.ts b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.ts
index 881eb28..0dbe823a 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.ts
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.ts
@@ -25,8 +25,8 @@
   DropdownLink,
 } from '../../shared/gr-dropdown/gr-dropdown';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 const INTERPOLATE_URL_PATTERN = /\${([\w]+)}/g;
 
@@ -37,7 +37,7 @@
 }
 
 @customElement('gr-account-dropdown')
-export class GrAccountDropdown extends GrLitElement {
+export class GrAccountDropdown extends LitElement {
   @property({type: Object})
   account?: AccountInfo;
 
@@ -111,17 +111,17 @@
       <gr-dropdown
         link=""
         .items="${this.links}"
-        .top-content="${this.topContent}"
+        .topContent="${this.topContent}"
         @tap-item-shortcuts=${this._handleShortcutsTap}
-        .horizontal-align="right"
+        .horizontalAlign=${'right'}
       >
-        <span ?hidden=${this._hasAvatars}
+        <span ?hidden="${this._hasAvatars}"
           >${this._accountName(this.account)}</span
         >
         <gr-avatar
           .account="${this.account}"
           ?hidden=${!this._hasAvatars}
-          .imageSize="56"
+          .imageSize=${56}
           aria-label="Account avatar"
         ></gr-avatar>
       </gr-dropdown>`;
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.ts b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.ts
index 0aaf05a..80ebf2d 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.ts
@@ -30,6 +30,7 @@
 } from '../../../test/test-data-generators';
 import {tap} from '@polymer/iron-test-helpers/mock-interactions';
 import {AccountId} from '../../../types/common';
+import {waitUntil} from '../../../test/test-utils';
 
 const basicFixture = fixtureFromElement('gr-error-manager');
 
@@ -62,7 +63,7 @@
       });
     });
 
-    test('does not show auth error on 403 by default', done => {
+    test('does not show auth error on 403 by default', async () => {
       const showAuthErrorStub = sinon.stub(element, '_showAuthErrorAlert');
       const responseText = Promise.resolve('server says no.');
       element.dispatchEvent(
@@ -79,13 +80,11 @@
           bubbles: true,
         })
       );
-      flush(() => {
-        assert.isFalse(showAuthErrorStub.calledOnce);
-        done();
-      });
+      await flush();
+      assert.isFalse(showAuthErrorStub.calledOnce);
     });
 
-    test('show auth required for 403 with auth error and not authed before', done => {
+    test('show auth required for 403 with auth error and not authed before', async () => {
       const showAuthErrorStub = sinon.stub(element, '_showAuthErrorAlert');
       const responseText = Promise.resolve('Authentication required\n');
       getLoggedInStub.returns(Promise.resolve(true));
@@ -103,10 +102,8 @@
           bubbles: true,
         })
       );
-      flush(() => {
-        assert.isTrue(showAuthErrorStub.calledOnce);
-        done();
-      });
+      await flush();
+      assert.isTrue(showAuthErrorStub.calledOnce);
     });
 
     test('recheck auth for 403 with auth error if authed before', async () => {
@@ -148,7 +145,7 @@
       );
     });
 
-    test('show normal Error', done => {
+    test('show normal Error', async () => {
       const showErrorSpy = sinon.spy(element, '_showErrorDialog');
       const textSpy = sinon.spy(() => Promise.resolve('ZOMG'));
       element.dispatchEvent(
@@ -160,13 +157,9 @@
       );
 
       assert.isTrue(textSpy.called);
-      flush(() => {
-        assert.isTrue(showErrorSpy.calledOnce);
-        assert.isTrue(
-          showErrorSpy.lastCall.calledWithExactly('Error 500: ZOMG')
-        );
-        done();
-      });
+      await flush();
+      assert.isTrue(showErrorSpy.calledOnce);
+      assert.isTrue(showErrorSpy.lastCall.calledWithExactly('Error 500: ZOMG'));
     });
 
     test('constructServerErrorMsg', () => {
@@ -206,7 +199,7 @@
       );
     });
 
-    test('extract trace id from headers if exists', done => {
+    test('extract trace id from headers if exists', async () => {
       const textSpy = sinon.spy(() => Promise.resolve('500'));
       const headers = new Headers();
       headers.set('X-Gerrit-Trace', 'xxxx');
@@ -223,16 +216,14 @@
           bubbles: true,
         })
       );
-      flush(() => {
-        assert.equal(
-          element.$.errorDialog.text,
-          'Error 500: 500\nTrace Id: xxxx'
-        );
-        done();
-      });
+      await flush();
+      assert.equal(
+        element.$.errorDialog.text,
+        'Error 500: 500\nTrace Id: xxxx'
+      );
     });
 
-    test('suppress TOO_MANY_FILES error', done => {
+    test('suppress TOO_MANY_FILES error', async () => {
       const showAlertStub = sinon.stub(element, '_showAlert');
       const textSpy = sinon.spy(() =>
         Promise.resolve('too many files to find conflicts')
@@ -246,13 +237,11 @@
       );
 
       assert.isTrue(textSpy.called);
-      flush(() => {
-        assert.isFalse(showAlertStub.called);
-        done();
-      });
+      await flush();
+      assert.isFalse(showAlertStub.called);
     });
 
-    test('suppress CONFLICTS_OPERATOR_IS_NOT_SUPPORTED error', done => {
+    test('suppress CONFLICTS_OPERATOR_IS_NOT_SUPPORTED error', async () => {
       const showAlertStub = sinon.stub(element, '_showAlert');
       const textSpy = sinon.spy(() =>
         Promise.resolve("'conflicts:' operator is not supported by server")
@@ -266,13 +255,11 @@
       );
 
       assert.isTrue(textSpy.called);
-      flush(() => {
-        assert.isFalse(showAlertStub.called);
-        done();
-      });
+      await flush();
+      assert.isFalse(showAlertStub.called);
     });
 
-    test('show network error', done => {
+    test('show network error', async () => {
       const showAlertStub = sinon.stub(element, '_showAlert');
       element.dispatchEvent(
         new CustomEvent('network-error', {
@@ -281,13 +268,11 @@
           bubbles: true,
         })
       );
-      flush(() => {
-        assert.isTrue(showAlertStub.calledOnce);
-        assert.isTrue(
-          showAlertStub.lastCall.calledWithExactly('Server unavailable')
-        );
-        done();
-      });
+      await flush();
+      assert.isTrue(showAlertStub.calledOnce);
+      assert.isTrue(
+        showAlertStub.lastCall.calledWithExactly('Server unavailable')
+      );
     });
 
     test('_canOverride alerts', () => {
@@ -515,7 +500,7 @@
       // promises on server-error handler and flush only flushes one
       assert.equal(fetchStub.callCount, 2);
       await flush();
-
+      await waitUntil(() => toastSpy.calledOnce);
       let toast = toastSpy.lastCall.returnValue;
       assert.include(toast.shadowRoot.textContent, 'Credentials expired.');
       assert.include(toast.shadowRoot.textContent, 'Refresh credentials');
@@ -533,6 +518,7 @@
       );
 
       await flush();
+      assert.isTrue(toastSpy.calledOnce);
       toast = toastSpy.lastCall.returnValue;
       assert.isOk(toast);
       assert.include(toast.shadowRoot.textContent, 'Credentials expired.');
@@ -574,7 +560,7 @@
       assert.equal(element._lastCredentialCheck, 999999);
     });
 
-    test('refreshes with same credentials', done => {
+    test('refreshes with same credentials', async () => {
       const accountPromise = Promise.resolve({
         ...createAccountDetailWithId(1234),
       });
@@ -590,12 +576,10 @@
       element._refreshingCredentials = true;
       element._checkSignedIn();
 
-      flush(() => {
-        assert.isFalse(requestCheckStub.called);
-        assert.isTrue(handleRefreshStub.called);
-        assert.isFalse(reloadStub.called);
-        done();
-      });
+      await flush();
+      assert.isFalse(requestCheckStub.called);
+      assert.isTrue(handleRefreshStub.called);
+      assert.isFalse(reloadStub.called);
     });
 
     test('_showAlert hides existing alerts', () => {
diff --git a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.ts b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.ts
index c820ec4..1ae0992 100644
--- a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.ts
+++ b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.ts
@@ -14,9 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {html} from 'lit-html';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, property} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -25,7 +24,7 @@
 }
 
 @customElement('gr-key-binding-display')
-export class GrKeyBindingDisplay extends GrLitElement {
+export class GrKeyBindingDisplay extends LitElement {
   static override get styles() {
     return [
       css`
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
index 68b696e..0b191f1 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
@@ -17,6 +17,7 @@
 import '../../shared/gr-button/gr-button';
 import '../gr-key-binding-display/gr-key-binding-display';
 import '../../../styles/shared-styles';
+import '../../../styles/gr-font-styles';
 import {PolymerElement} from '@polymer/polymer/polymer-element';
 import {htmlTemplate} from './gr-keyboard-shortcuts-dialog_html';
 import {
@@ -38,10 +39,11 @@
   shortcuts?: SectionView;
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-keyboard-shortcuts-dialog')
-export class GrKeyboardShortcutsDialog extends KeyboardShortcutMixin(
-  PolymerElement
-) {
+export class GrKeyboardShortcutsDialog extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_html.ts b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_html.ts
index 3576dfe..4992daa 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_html.ts
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_html.ts
@@ -17,6 +17,9 @@
 import {html} from '@polymer/polymer/lib/utils/html-tag';
 
 export const htmlTemplate = html`
+  <style include="gr-font-styles">
+    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+  </style>
   <style include="shared-styles">
     :host {
       display: block;
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts
index 89e81d4..b8f2630 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts
@@ -309,8 +309,8 @@
   changeNum: NumericChangeId;
   project: RepoName;
   path?: string;
-  patchNum?: PatchSetNum | null;
-  basePatchNum?: BasePatchSetNum | null;
+  patchNum?: PatchSetNum;
+  basePatchNum?: BasePatchSetNum;
   lineNum?: number | string;
   leftSide?: boolean;
   commentId?: UrlEncodedCommentId;
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
index ac4ac64..4a6f009 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
@@ -57,6 +57,7 @@
   RepoName,
   ServerInfo,
   UrlEncodedCommentId,
+  ParentPatchSetNum,
 } from '../../../types/common';
 import {
   AppElement,
@@ -296,8 +297,8 @@
 >;
 
 interface PatchRangeParams {
-  patchNum?: PatchSetNum | null;
-  basePatchNum?: BasePatchSetNum | null;
+  patchNum?: PatchSetNum;
+  basePatchNum?: BasePatchSetNum;
 }
 
 @customElement('gr-router')
@@ -671,7 +672,7 @@
     if (params.patchNum) {
       range = `${params.patchNum}`;
     }
-    if (params.basePatchNum) {
+    if (params.basePatchNum && params.basePatchNum !== ParentPatchSetNum) {
       range = `${params.basePatchNum}..${range}`;
     }
     return range;
@@ -714,23 +715,22 @@
    *
    */
   _normalizePatchRangeParams(params: PatchRangeParams) {
-    if (params.basePatchNum === null || params.basePatchNum === undefined) {
+    if (params.basePatchNum === undefined) {
       return false;
     }
-    const hasPatchNum =
-      params.patchNum !== null && params.patchNum !== undefined;
+    const hasPatchNum = params.patchNum !== undefined;
     let needsRedirect = false;
 
     // Diffing a patch against itself is invalid, so if the base and revision
     // patches are equal clear the base.
     if (params.patchNum && params.basePatchNum === params.patchNum) {
       needsRedirect = true;
-      params.basePatchNum = null;
+      params.basePatchNum = ParentPatchSetNum;
     } else if (!hasPatchNum) {
       // Regexes set basePatchNum instead of patchNum when only one is
       // specified. Redirect is not needed in this case.
       params.patchNum = params.basePatchNum;
-      params.basePatchNum = null;
+      params.basePatchNum = ParentPatchSetNum;
     }
     return needsRedirect;
   }
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
index ff27bbd..4c7855b 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
@@ -22,6 +22,7 @@
 import {stubBaseUrl, stubRestApi, addListenerForTest} from '../../../test/test-utils.js';
 import {_testOnly_RoutePattern} from './gr-router.js';
 import {GerritView} from '../../../services/router/router-model.js';
+import {ParentPatchSetNum} from '../../../types/common.js';
 
 const basicFixture = fixtureFromElement('gr-router');
 
@@ -600,7 +601,7 @@
         const params = {basePatchNum: 4, patchNum: 4};
         const needsRedirect = element._normalizePatchRangeParams(params);
         assert.isTrue(needsRedirect);
-        assert.isNotOk(params.basePatchNum);
+        assert.equal(params.basePatchNum, ParentPatchSetNum);
         assert.equal(params.patchNum, 4);
       });
 
@@ -608,7 +609,7 @@
         const params = {basePatchNum: 4};
         const needsRedirect = element._normalizePatchRangeParams(params);
         assert.isFalse(needsRedirect);
-        assert.isNotOk(params.basePatchNum);
+        assert.equal(params.basePatchNum, ParentPatchSetNum);
         assert.equal(params.patchNum, 4);
       });
     });
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
index 7c65c3f..2dc737e 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
@@ -70,7 +70,6 @@
   'has:draft',
   'has:edit',
   'has:star',
-  'has:stars',
   'has:unresolved',
   'hashtag:',
   'inhashtag:',
@@ -78,6 +77,7 @@
   'is:',
   'is:abandoned',
   'is:assigned',
+  'is:cherrypick',
   'is:closed',
   'is:ignored',
   'is:merge',
@@ -110,6 +110,7 @@
   'reviewer:',
   'reviewer:self',
   'reviewerin:',
+  'rule:',
   'size:',
   'star:',
   'status:',
@@ -147,8 +148,11 @@
   };
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-search-bar')
-export class GrSearchBar extends KeyboardShortcutMixin(PolymerElement) {
+export class GrSearchBar extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts
index 3b22d1d..b5b0124 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts
@@ -22,6 +22,7 @@
 import {
   TestKeyboardShortcutBinder,
   stubRestApi,
+  mockPromise,
 } from '../../../test/test-utils';
 import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
 import {_testOnly_clearDocsBaseUrlCache} from '../../../utils/url-util';
@@ -47,9 +48,9 @@
     TestKeyboardShortcutBinder.pop();
   });
 
-  setup(done => {
+  setup(async () => {
     element = basicFixture.instantiate();
-    flush(done);
+    await flush();
   });
 
   test('value is propagated to _inputVal', () => {
@@ -62,10 +63,11 @@
       ? document.activeElement!.shadowRoot.activeElement
       : document.activeElement;
 
-  test('enter in search input fires event', done => {
+  test('enter in search input fires event', async () => {
+    const promise = mockPromise();
     element.addEventListener('handle-search', () => {
       assert.notEqual(getActiveElement(), element.$.searchInput);
-      done();
+      promise.resolve();
     });
     element.value = 'test';
     MockInteractions.pressAndReleaseKeyOn(
@@ -74,6 +76,7 @@
       null,
       'enter'
     );
+    await promise;
   });
 
   test('input blurred after commit', () => {
@@ -176,7 +179,7 @@
       });
     });
 
-    test('Autocompletes groups', done => {
+    test('Autocompletes groups', async () => {
       sinon
         .stub(element, 'groupSuggestions')
         .callsFake(() =>
@@ -185,13 +188,11 @@
             {text: 'ownerin:gerrit'},
           ])
         );
-      element._getSearchSuggestions('ownerin:pol').then(s => {
-        assert.equal(s[0].value, 'ownerin:Polygerrit');
-        done();
-      });
+      const s = await element._getSearchSuggestions('ownerin:pol');
+      assert.equal(s[0].value, 'ownerin:Polygerrit');
     });
 
-    test('Autocompletes projects', done => {
+    test('Autocompletes projects', async () => {
       sinon
         .stub(element, 'projectSuggestions')
         .callsFake(() =>
@@ -201,27 +202,21 @@
             {text: 'project:gerrittest'},
           ])
         );
-      element._getSearchSuggestions('project:pol').then(s => {
-        assert.equal(s[0].value, 'project:Polygerrit');
-        done();
-      });
+      const s = await element._getSearchSuggestions('project:pol');
+      assert.equal(s[0].value, 'project:Polygerrit');
     });
 
-    test('Autocompletes simple searches', done => {
-      element._getSearchSuggestions('is:o').then(s => {
-        assert.equal(s[0].name, 'is:open');
-        assert.equal(s[0].value, 'is:open');
-        assert.equal(s[1].name, 'is:owner');
-        assert.equal(s[1].value, 'is:owner');
-        done();
-      });
+    test('Autocompletes simple searches', async () => {
+      const s = await element._getSearchSuggestions('is:o');
+      assert.equal(s[0].name, 'is:open');
+      assert.equal(s[0].value, 'is:open');
+      assert.equal(s[1].name, 'is:owner');
+      assert.equal(s[1].value, 'is:owner');
     });
 
-    test('Does not autocomplete with no match', done => {
-      element._getSearchSuggestions('asdasdasdasd').then(s => {
-        assert.equal(s.length, 0);
-        done();
-      });
+    test('Does not autocomplete with no match', async () => {
+      const s = await element._getSearchSuggestions('asdasdasdasd');
+      assert.equal(s.length, 0);
     });
 
     test('Autocompletes without is:mergable when disabled', async () => {
@@ -235,7 +230,7 @@
     'REF_UPDATED_AND_CHANGE_REINDEX',
   ].forEach(mergeability => {
     suite(`mergeability as ${mergeability}`, () => {
-      setup(done => {
+      setup(async () => {
         stubRestApi('getConfig').returns(
           Promise.resolve({
             ...createServerInfo(),
@@ -248,24 +243,22 @@
         );
 
         element = basicFixture.instantiate();
-        flush(done);
+        await flush();
       });
 
-      test('Autocompltes with is:mergable when enabled', done => {
-        element._getSearchSuggestions('is:mergeab').then(s => {
-          assert.equal(s.length, 2);
-          assert.equal(s[0].name, 'is:mergeable');
-          assert.equal(s[0].value, 'is:mergeable');
-          assert.equal(s[1].name, '-is:mergeable');
-          assert.equal(s[1].value, '-is:mergeable');
-          done();
-        });
+      test('Autocompltes with is:mergable when enabled', async () => {
+        const s = await element._getSearchSuggestions('is:mergeab');
+        assert.equal(s.length, 2);
+        assert.equal(s[0].name, 'is:mergeable');
+        assert.equal(s[0].value, 'is:mergeable');
+        assert.equal(s[1].name, '-is:mergeable');
+        assert.equal(s[1].value, '-is:mergeable');
       });
     });
   });
 
   suite('doc url', () => {
-    setup(done => {
+    setup(async () => {
       stubRestApi('getConfig').returns(
         Promise.resolve({
           ...createServerInfo(),
@@ -278,7 +271,7 @@
 
       _testOnly_clearDocsBaseUrlCache();
       element = basicFixture.instantiate();
-      flush(done);
+      await flush();
     });
 
     test('compute help doc url with correct path', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
index 6dd67e4..1908df0 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
@@ -41,7 +41,6 @@
 import {ParsedChangeInfo} from '../../../types/types';
 import {GrButton} from '../../shared/gr-button/gr-button';
 import {TokenHighlightLayer} from '../gr-diff-builder/token-highlight-layer';
-import {KnownExperimentId} from '../../../services/flags/flags';
 
 export interface GrApplyFixDialog {
   $: {
@@ -99,11 +98,7 @@
   })
   _disableApplyFixButton = false;
 
-  layers = appContext.flagsService.isEnabled(
-    KnownExperimentId.TOKEN_HIGHLIGHTING
-  )
-    ? [new TokenHighlightLayer(this)]
-    : [];
+  layers = [new TokenHighlightLayer(this)];
 
   private refitOverlay?: () => void;
 
diff --git a/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts b/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
index ff275ad..af9c9d4 100644
--- a/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
+++ b/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
@@ -31,14 +31,8 @@
 import {fire} from '../../../utils/event-util';
 import {DiffInfo} from '../../../types/diff';
 import {assertIsDefined} from '../../../utils/common-util';
-import {
-  css,
-  customElement,
-  html,
-  LitElement,
-  property,
-  TemplateResult,
-} from 'lit-element';
+import {css, html, LitElement, TemplateResult} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 import {
   ContextButtonType,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
index 196567e..44b0b8b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
@@ -751,7 +751,7 @@
     let outputEl;
     let keyLocations;
 
-    setup(done => {
+    setup(async () => {
       const prefs = {
         line_length: 10,
         show_tabs: true,
@@ -786,11 +786,11 @@
         return builder;
       });
       element.diff = {content};
-      element.render(keyLocations, prefs).then(done);
+      await element.render(keyLocations, prefs);
     });
 
-    test('addColumns is called', done => {
-      element.render(keyLocations, {}).then(done);
+    test('addColumns is called', async () => {
+      await element.render(keyLocations, {});
       assert.isTrue(element._builder.addColumns.called);
     });
 
@@ -812,15 +812,13 @@
       assert.strictEqual(sections[1], section[1]);
     });
 
-    test('render-start and render-content are fired', done => {
+    test('render-start and render-content are fired', async () => {
       const dispatchEventStub = sinon.stub(element, 'dispatchEvent');
-      element.render(keyLocations, {}).then(() => {
-        const firedEventTypes = dispatchEventStub.getCalls()
-            .map(c => c.args[0].type);
-        assert.include(firedEventTypes, 'render-start');
-        assert.include(firedEventTypes, 'render-content');
-        done();
-      });
+      await element.render(keyLocations, {});
+      const firedEventTypes = dispatchEventStub.getCalls()
+          .map(c => c.args[0].type);
+      assert.include(firedEventTypes, 'render-start');
+      assert.include(firedEventTypes, 'render-content');
     });
 
     test('cancel', () => {
@@ -837,7 +835,7 @@
     let prefs;
     let keyLocations;
 
-    setup(done => {
+    setup(async () => {
       element = mockDiffFixture.instantiate();
       diff = getMockDiffResponse();
       element.diff = diff;
@@ -849,10 +847,8 @@
       };
       keyLocations = {left: {}, right: {}};
 
-      element.render(keyLocations, prefs).then(() => {
-        builder = element._builder;
-        done();
-      });
+      await element.render(keyLocations, prefs);
+      builder = element._builder;
     });
 
     test('aria-labels on added line numbers', () => {
@@ -986,34 +982,30 @@
       assert.isTrue(lineNumberEl.classList.contains('right'));
     });
 
-    test('_getLineNumberEl unified left', done => {
+    test('_getLineNumberEl unified left', async () => {
       // Re-render as unified:
       element.viewMode = 'UNIFIED_DIFF';
-      element.render(keyLocations, prefs).then(() => {
-        builder = element._builder;
+      await element.render(keyLocations, prefs);
+      builder = element._builder;
 
-        const contentEl = builder.getContentByLine(5, 'left',
-            element.$.diffTable);
-        const lineNumberEl = builder._getLineNumberEl(contentEl, 'left');
-        assert.isTrue(lineNumberEl.classList.contains('lineNum'));
-        assert.isTrue(lineNumberEl.classList.contains('left'));
-        done();
-      });
+      const contentEl = builder.getContentByLine(5, 'left',
+          element.$.diffTable);
+      const lineNumberEl = builder._getLineNumberEl(contentEl, 'left');
+      assert.isTrue(lineNumberEl.classList.contains('lineNum'));
+      assert.isTrue(lineNumberEl.classList.contains('left'));
     });
 
-    test('_getLineNumberEl unified right', done => {
+    test('_getLineNumberEl unified right', async () => {
       // Re-render as unified:
       element.viewMode = 'UNIFIED_DIFF';
-      element.render(keyLocations, prefs).then(() => {
-        builder = element._builder;
+      await element.render(keyLocations, prefs);
+      builder = element._builder;
 
-        const contentEl = builder.getContentByLine(5, 'right',
-            element.$.diffTable);
-        const lineNumberEl = builder._getLineNumberEl(contentEl, 'right');
-        assert.isTrue(lineNumberEl.classList.contains('lineNum'));
-        assert.isTrue(lineNumberEl.classList.contains('right'));
-        done();
-      });
+      const contentEl = builder.getContentByLine(5, 'right',
+          element.$.diffTable);
+      const lineNumberEl = builder._getLineNumberEl(contentEl, 'right');
+      assert.isTrue(lineNumberEl.classList.contains('lineNum'));
+      assert.isTrue(lineNumberEl.classList.contains('right'));
     });
 
     test('_getNextContentOnSide side-by-side left', () => {
@@ -1040,44 +1032,38 @@
       assert.equal(nextElem.textContent, expectedNextString);
     });
 
-    test('_getNextContentOnSide unified left', done => {
+    test('_getNextContentOnSide unified left', async () => {
       // Re-render as unified:
       element.viewMode = 'UNIFIED_DIFF';
-      element.render(keyLocations, prefs).then(() => {
-        builder = element._builder;
+      await element.render(keyLocations, prefs);
+      builder = element._builder;
 
-        const startElem = builder.getContentByLine(5, 'left',
-            element.$.diffTable);
-        const expectedStartString = diff.content[2].ab[0];
-        const expectedNextString = diff.content[2].ab[1];
-        assert.equal(startElem.textContent, expectedStartString);
+      const startElem = builder.getContentByLine(5, 'left',
+          element.$.diffTable);
+      const expectedStartString = diff.content[2].ab[0];
+      const expectedNextString = diff.content[2].ab[1];
+      assert.equal(startElem.textContent, expectedStartString);
 
-        const nextElem = builder._getNextContentOnSide(startElem,
-            'left');
-        assert.equal(nextElem.textContent, expectedNextString);
-
-        done();
-      });
+      const nextElem = builder._getNextContentOnSide(startElem,
+          'left');
+      assert.equal(nextElem.textContent, expectedNextString);
     });
 
-    test('_getNextContentOnSide unified right', done => {
+    test('_getNextContentOnSide unified right', async () => {
       // Re-render as unified:
       element.viewMode = 'UNIFIED_DIFF';
-      element.render(keyLocations, prefs).then(() => {
-        builder = element._builder;
+      await element.render(keyLocations, prefs);
+      builder = element._builder;
 
-        const startElem = builder.getContentByLine(5, 'right',
-            element.$.diffTable);
-        const expectedStartString = diff.content[1].b[0];
-        const expectedNextString = diff.content[1].b[1];
-        assert.equal(startElem.textContent, expectedStartString);
+      const startElem = builder.getContentByLine(5, 'right',
+          element.$.diffTable);
+      const expectedStartString = diff.content[1].b[0];
+      const expectedNextString = diff.content[1].b[1];
+      assert.equal(startElem.textContent, expectedStartString);
 
-        const nextElem = builder._getNextContentOnSide(startElem,
-            'right');
-        assert.equal(nextElem.textContent, expectedNextString);
-
-        done();
-      });
+      const nextElem = builder._getNextContentOnSide(startElem,
+          'right');
+      assert.equal(nextElem.textContent, expectedNextString);
     });
 
     test('escaping HTML', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
index 057b926..bd7dc29 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
@@ -39,6 +39,7 @@
       numberOfCells: 4,
       movedOutIndex: 1,
       movedInIndex: 3,
+      lineNumberCols: [0, 2],
     };
   }
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts
index b2dfa61..a7b3a42 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts
@@ -38,6 +38,7 @@
       numberOfCells: 3,
       movedOutIndex: 2,
       movedInIndex: 2,
+      lineNumberCols: [0, 1],
     };
   }
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
index 869faa8..294a78e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
@@ -683,6 +683,7 @@
     numberOfCells: number;
     movedOutIndex: number;
     movedInIndex: number;
+    lineNumberCols: number[];
   };
 
   /**
@@ -776,7 +777,7 @@
 
   _buildMoveControls(group: GrDiffGroup) {
     const movedIn = group.adds.length > 0;
-    const {numberOfCells, movedOutIndex, movedInIndex} =
+    const {numberOfCells, movedOutIndex, movedInIndex, lineNumberCols} =
       this._getMoveControlsConfig();
 
     let controlsClass;
@@ -794,6 +795,9 @@
     const cells = [...Array(numberOfCells).keys()].map(() =>
       this._createElement('td')
     );
+    lineNumberCols.forEach(index => {
+      cells[index].classList.add('moveControlsLineNumCol');
+    });
 
     const moveRangeHeader = this._createElement('gr-range-header');
     moveRangeHeader.setAttribute('icon', 'gr-icons:move-item');
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
index 36e92b9..4cc98d0a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
@@ -31,7 +31,7 @@
 /** CSS class for the currently hovered token. */
 const CSS_HIGHLIGHT = 'token-highlight';
 
-const HOVER_DELAY_MS = 200;
+export const HOVER_DELAY_MS = 200;
 
 const LINE_LENGTH_LIMIT = 500;
 
@@ -42,11 +42,10 @@
 const TOKEN_OCCURRENCES_LIMIT = 1000;
 
 /**
- * Token highlighting is only useful for code on-screen, so don't bother
- * highlighting tokens that are further away than this threshold from where the
- * user is hovering.
+ * Token highlighting is only useful for code on-screen, so we only highlight
+ * the nearest set of tokens up to this limit.
  */
-const LINE_DISTANCE_THRESHOLD = 100;
+const TOKEN_HIGHLIGHT_LIMIT = 100;
 
 /**
  * When a user hovers over a token in the diff, then this layer makes sure that
@@ -97,8 +96,8 @@
   private updateTokenTask?: DelayedTask;
 
   constructor(container: HTMLElement = document.documentElement) {
-    container.addEventListener('click', _ => {
-      this.handleMouseClick();
+    container.addEventListener('click', e => {
+      this.handleContainerClick(e);
     });
   }
 
@@ -137,10 +136,10 @@
       // garbage collected along with the element itself once it is not attached
       // to the DOM anymore and no references exist anymore.
       el.addEventListener('mouseover', e => {
-        this.handleMouseOver(e);
+        this.handleTokenMouseOver(e);
       });
       el.addEventListener('mouseout', e => {
-        this.handleMouseOut(e);
+        this.handleTokenMouseOut(e);
       });
     }
   }
@@ -162,7 +161,7 @@
     numbers.add(Number(lineNumber));
   }
 
-  private handleMouseOut(e: MouseEvent) {
+  private handleTokenMouseOut(e: MouseEvent) {
     // If there's no ongoing hover-task, terminate early.
     if (!this.updateTokenTask?.isActive()) return;
     if (e.buttons > 0 || this.interferesWithSelection()) return;
@@ -176,7 +175,7 @@
     }
   }
 
-  private handleMouseOver(e: MouseEvent) {
+  private handleTokenMouseOver(e: MouseEvent) {
     if (e.buttons > 0 || this.interferesWithSelection()) return;
     const {
       line,
@@ -195,8 +194,13 @@
     );
   }
 
-  private handleMouseClick() {
+  private handleContainerClick(e: MouseEvent) {
     if (this.interferesWithSelection()) return;
+    // Ignore the click if the click is on a token.
+    // We can't use e.target becauses it gets retargetted to the container as
+    // it's a shadow dom.
+    const {element} = this.findTokenAncestor(e.composedPath()[0]);
+    if (element) return;
     this.hoveredElement = undefined;
     this.updateTokenTask?.cancel();
     this.updateTokenHighlight(undefined, 0);
@@ -206,18 +210,6 @@
     return window.getSelection()?.type === 'Range';
   }
 
-  private updateTokenHighlight(
-    newHighlight: string | undefined,
-    newLineNumber: number
-  ) {
-    const oldHighlight = this.currentHighlight;
-    const oldLineNumber = this.currentHighlightLineNumber;
-    this.currentHighlight = newHighlight;
-    this.currentHighlightLineNumber = newLineNumber;
-    this.notifyForToken(oldHighlight, oldLineNumber);
-    this.notifyForToken(newHighlight, newLineNumber);
-  }
-
   findTokenAncestor(el?: EventTarget | Element | null): {
     token?: string;
     line: number;
@@ -247,20 +239,62 @@
     return (linesLeft?.size ?? 0) + (linesRight?.size ?? 0);
   }
 
+  private updateTokenHighlight(
+    newHighlight: string | undefined,
+    newLineNumber: number
+  ) {
+    if (
+      this.currentHighlight === newHighlight &&
+      this.currentHighlightLineNumber === newLineNumber
+    )
+      return;
+    const oldHighlight = this.currentHighlight;
+    const oldLineNumber = this.currentHighlightLineNumber;
+    this.currentHighlight = newHighlight;
+    this.currentHighlightLineNumber = newLineNumber;
+
+    this.notifyForToken(oldHighlight, oldLineNumber);
+    this.notifyForToken(newHighlight, newLineNumber);
+  }
+
+  getSortedLinesForSide(
+    lineMapping: Map<string, Set<number>>,
+    token: string | undefined,
+    lineNumber: number
+  ): Array<number> {
+    if (!token) return [];
+    const lineSet = lineMapping.get(token);
+    if (!lineSet) return [];
+    const lines = [...lineSet];
+    lines.sort((a, b) => {
+      const da = Math.abs(a - lineNumber);
+      const db = Math.abs(b - lineNumber);
+      // For equal distance, prefer lines later in the file over earlier in the
+      // file. This ensures total ordering.
+      if (da === db) return b - a;
+      // Compare the distance to lineNumber.
+      return da - db;
+    });
+    return lines.slice(0, TOKEN_HIGHLIGHT_LIMIT);
+  }
+
   notifyForToken(token: string | undefined, lineNumber: number) {
-    if (!token) return;
-    const linesLeft = this.tokenToLinesLeft.get(token);
-    linesLeft?.forEach(line => {
-      if (Math.abs(line - lineNumber) < LINE_DISTANCE_THRESHOLD) {
-        this.notifyListeners(line, Side.LEFT);
-      }
-    });
-    const linesRight = this.tokenToLinesRight.get(token);
-    linesRight?.forEach(line => {
-      if (Math.abs(line - lineNumber) < LINE_DISTANCE_THRESHOLD) {
-        this.notifyListeners(line, Side.RIGHT);
-      }
-    });
+    const leftLines = this.getSortedLinesForSide(
+      this.tokenToLinesLeft,
+      token,
+      lineNumber
+    );
+    for (const line of leftLines) {
+      this.notifyListeners(line, Side.LEFT);
+    }
+    const rightLines = this.getSortedLinesForSide(
+      this.tokenToLinesRight,
+      token,
+      lineNumber
+    );
+    for (const line of rightLines) {
+      this.notifyListeners(line, Side.RIGHT);
+    }
   }
 
   addListener(listener: DiffLayerListener) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts
new file mode 100644
index 0000000..d606e01
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts
@@ -0,0 +1,301 @@
+/**
+ * @license
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import '../../../test/common-test-setup-karma';
+import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
+import {Side} from '../../../api/diff';
+import {GrDiffLine, GrDiffLineType} from '../gr-diff/gr-diff-line.js';
+import {HOVER_DELAY_MS, TokenHighlightLayer} from './token-highlight-layer';
+import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
+import sinon from 'sinon/pkg/sinon-esm';
+import {html, render} from 'lit-html';
+import {_testOnly_allTasks} from '../../../utils/async-util';
+import {queryAndAssert} from '../../../test/test-utils';
+
+// MockInteractions.makeMouseEvent always sets buttons to 1.
+function dispatchMouseEvent(
+  type: string,
+  xy: {x: number; y: number},
+  node: Element
+) {
+  const props = {
+    bubbles: true,
+    cancellable: true,
+    composed: true,
+    clientX: xy.x,
+    clientY: xy.y,
+    buttons: 0,
+  };
+  node.dispatchEvent(new MouseEvent(type, props));
+}
+
+class MockListener {
+  private results: any[][] = [];
+
+  notify(...args: any[]) {
+    this.results.push(args);
+  }
+
+  shift() {
+    return this.results.shift();
+  }
+
+  flush() {
+    this.results = [];
+  }
+
+  get pending(): number {
+    return this.results.length;
+  }
+}
+
+suite('token-highlight-layer', () => {
+  let container: HTMLElement;
+  let listener: MockListener;
+  let highlighter: TokenHighlightLayer;
+
+  setup(async () => {
+    listener = new MockListener();
+    container = document.createElement('div');
+    document.body.appendChild(container);
+    highlighter = new TokenHighlightLayer(container);
+    highlighter.addListener((...args) => listener.notify(...args));
+  });
+
+  teardown(() => {
+    document.body.removeChild(container);
+  });
+
+  function annotate(el: HTMLElement, side: Side = Side.LEFT, line = 1) {
+    const diffLine = new GrDiffLine(GrDiffLineType.BOTH);
+    diffLine.afterNumber = line;
+    diffLine.beforeNumber = line;
+    highlighter.annotate(el, document.createElement('div'), diffLine, side);
+    return el;
+  }
+
+  let uniqueId = 0;
+  function createLineId() {
+    uniqueId++;
+    return `line-${uniqueId.toString()}`;
+  }
+
+  function createLine(text: string, line = 1): HTMLElement {
+    const lineId = createLineId();
+    const template = html`
+      <div class="line">
+        <div data-value=${line} class="lineNum"></div>
+        <div id=${lineId} class="line-content">${text}</div>
+      </div>
+    `;
+    const div = document.createElement('div');
+    render(template, div);
+    container.appendChild(div);
+    const el = queryAndAssert(container, `#${lineId}`);
+    return el as HTMLElement;
+  }
+
+  suite('annotate', () => {
+    function assertAnnotation(
+      args: any[],
+      el: HTMLElement,
+      start: number,
+      length: number,
+      cssClass: string
+    ) {
+      assert.equal(args[0], el);
+      assert.equal(args[1], start);
+      assert.equal(args[2], length);
+      assert.equal(args[3], cssClass);
+    }
+
+    test('annotate adds css token', () => {
+      const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
+      const el = createLine('these are words');
+      annotate(el);
+      assert.isTrue(annotateElementStub.calledThrice);
+      assertAnnotation(annotateElementStub.args[0], el, 0, 5, 'tk-these token');
+      assertAnnotation(annotateElementStub.args[1], el, 6, 3, 'tk-are token');
+      assertAnnotation(
+        annotateElementStub.args[2],
+        el,
+        10,
+        5,
+        'tk-words token'
+      );
+    });
+
+    test('annotate adds mouse handlers', () => {
+      const el = createLine('these are words');
+      const addEventListenerStub = sinon.stub(el, 'addEventListener');
+      annotate(el);
+      assert.isTrue(addEventListenerStub.calledTwice);
+      assert.equal(addEventListenerStub.args[0][0], 'mouseover');
+      assert.equal(addEventListenerStub.args[1][0], 'mouseout');
+    });
+
+    test('annotate does not add mouse handlers without words', () => {
+      const el = createLine('  ');
+      const addEventListenerStub = sinon.stub(el, 'addEventListener');
+      annotate(el);
+      assert.isFalse(addEventListenerStub.called);
+    });
+
+    test('annotate adds mouse handlers for longest word', () => {
+      const el = createLine('w'.repeat(100));
+      const addEventListenerStub = sinon.stub(el, 'addEventListener');
+      annotate(el);
+      assert.isTrue(addEventListenerStub.called);
+    });
+
+    test('annotate does not add mouse handlers for long words', () => {
+      const el = createLine('w'.repeat(101));
+      const addEventListenerStub = sinon.stub(el, 'addEventListener');
+      annotate(el);
+      assert.isFalse(addEventListenerStub.called);
+    });
+  });
+
+  suite('highlight', () => {
+    test('highlighting hover delay', async () => {
+      const clock = sinon.useFakeTimers();
+      const line1 = createLine('two words');
+      annotate(line1);
+      const line2 = createLine('three words');
+      annotate(line2, Side.RIGHT, 2);
+      const words1 = queryAndAssert(line1, '.tk-words');
+      assert.isTrue(words1.classList.contains('token'));
+      dispatchMouseEvent(
+        'mouseover',
+        MockInteractions.middleOfNode(words1),
+        words1
+      );
+
+      assert.equal(listener.pending, 0);
+      assert.equal(_testOnly_allTasks.size, 1);
+
+      // Too early for hover behavior to trigger.
+      clock.tick(100);
+      assert.equal(listener.pending, 0);
+      assert.equal(_testOnly_allTasks.size, 1);
+
+      // After a total of HOVER_DELAY_MS ms the hover behavior should trigger.
+      clock.tick(HOVER_DELAY_MS - 100);
+      assert.equal(listener.pending, 2);
+      assert.equal(_testOnly_allTasks.size, 0);
+      assert.deepEqual(listener.shift(), [1, 1, Side.LEFT]);
+      assert.deepEqual(listener.shift(), [2, 2, Side.RIGHT]);
+    });
+
+    test('highlighting spans many lines', async () => {
+      const clock = sinon.useFakeTimers();
+      const line1 = createLine('two words');
+      annotate(line1);
+      const line2 = createLine('three words');
+      annotate(line2, Side.RIGHT, 1000);
+      const words1 = queryAndAssert(line1, '.tk-words');
+      assert.isTrue(words1.classList.contains('token'));
+      dispatchMouseEvent(
+        'mouseover',
+        MockInteractions.middleOfNode(words1),
+        words1
+      );
+
+      assert.equal(listener.pending, 0);
+
+      // After a total of HOVER_DELAY_MS ms the hover behavior should trigger.
+      clock.tick(HOVER_DELAY_MS);
+      assert.equal(listener.pending, 2);
+      assert.equal(_testOnly_allTasks.size, 0);
+      assert.deepEqual(listener.shift(), [1, 1, Side.LEFT]);
+      assert.deepEqual(listener.shift(), [1000, 1000, Side.RIGHT]);
+    });
+
+    test('highlighting mouse out before delay', async () => {
+      const clock = sinon.useFakeTimers();
+      const line1 = createLine('two words');
+      annotate(line1);
+      const line2 = createLine('three words', 2);
+      annotate(line2, Side.RIGHT, 2);
+      const words1 = queryAndAssert(line1, '.tk-words');
+      assert.isTrue(words1.classList.contains('token'));
+      dispatchMouseEvent(
+        'mouseover',
+        MockInteractions.middleOfNode(words1),
+        words1
+      );
+      assert.equal(listener.pending, 0);
+      clock.tick(100);
+      // Mouse out after 100ms but before hover delay.
+      dispatchMouseEvent(
+        'mouseout',
+        MockInteractions.middleOfNode(words1),
+        words1
+      );
+      assert.equal(listener.pending, 0);
+      clock.tick(HOVER_DELAY_MS - 100);
+      assert.equal(listener.pending, 0);
+      assert.equal(_testOnly_allTasks.size, 0);
+    });
+
+    test('clicking clears highlight', async () => {
+      const clock = sinon.useFakeTimers();
+      const line1 = createLine('two words');
+      annotate(line1);
+      const line2 = createLine('three words', 2);
+      annotate(line2, Side.RIGHT, 2);
+      const words1 = queryAndAssert(line1, '.tk-words');
+      assert.isTrue(words1.classList.contains('token'));
+      dispatchMouseEvent(
+        'mouseover',
+        MockInteractions.middleOfNode(words1),
+        words1
+      );
+      assert.equal(listener.pending, 0);
+      clock.tick(HOVER_DELAY_MS);
+      assert.equal(listener.pending, 2);
+      listener.flush();
+      assert.equal(listener.pending, 0);
+      MockInteractions.click(container);
+      assert.equal(listener.pending, 2);
+      assert.deepEqual(listener.shift(), [1, 1, Side.LEFT]);
+      assert.deepEqual(listener.shift(), [2, 2, Side.RIGHT]);
+    });
+
+    test('clicking on word does not clear highlight', async () => {
+      const clock = sinon.useFakeTimers();
+      const line1 = createLine('two words');
+      annotate(line1);
+      const line2 = createLine('three words', 2);
+      annotate(line2, Side.RIGHT, 2);
+      const words1 = queryAndAssert(line1, '.tk-words');
+      assert.isTrue(words1.classList.contains('token'));
+      dispatchMouseEvent(
+        'mouseover',
+        MockInteractions.middleOfNode(words1),
+        words1
+      );
+      assert.equal(listener.pending, 0);
+      clock.tick(HOVER_DELAY_MS);
+      assert.equal(listener.pending, 2);
+      listener.flush();
+      assert.equal(listener.pending, 0);
+      MockInteractions.click(words1);
+      assert.equal(listener.pending, 0);
+    });
+  });
+});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js
index 3e63b0a..3b604eb 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js
@@ -19,7 +19,7 @@
 import '../gr-diff/gr-diff.js';
 import './gr-diff-cursor.js';
 import {html} from '@polymer/polymer/lib/utils/html-tag.js';
-import {listenOnce} from '../../../test/test-utils.js';
+import {listenOnce, mockPromise} from '../../../test/test-utils.js';
 import {getMockDiffResponse} from '../../../test/mocks/diff-response.js';
 import {createDefaultDiffPrefs} from '../../../constants/constants.js';
 import {GrDiffCursor} from './gr-diff-cursor.js';
@@ -33,7 +33,7 @@
   let diffElement;
   let diff;
 
-  setup(done => {
+  setup(async () => {
     diffElement = basicFixture.instantiate();
     cursor = new GrDiffCursor();
 
@@ -48,17 +48,19 @@
       meta: {patchRange: undefined},
     };
     diffElement.path = 'some/path.ts';
+    const promise = mockPromise();
     const setupDone = () => {
       cursor._updateStops();
       cursor.moveToFirstChunk();
       diffElement.removeEventListener('render', setupDone);
-      done();
+      promise.resolve();
     };
     diffElement.addEventListener('render', setupDone);
 
     diff = getMockDiffResponse();
     diffElement.prefs = createDefaultDiffPrefs();
     diffElement.diff = diff;
+    await promise;
   });
 
   test('diff cursor functionality (side-by-side)', () => {
@@ -215,15 +217,17 @@
   });
 
   suite('unified diff', () => {
-    setup(done => {
+    setup(async () => {
+      const promise = mockPromise();
       // We must allow the diff to re-render after setting the viewMode.
       const renderHandler = function() {
         diffElement.removeEventListener('render', renderHandler);
         cursor.reInitCursor();
-        done();
+        promise.resolve();
       };
       diffElement.addEventListener('render', renderHandler);
       diffElement.viewMode = 'UNIFIED_DIFF';
+      await promise;
     });
 
     test('diff cursor functionality (unified)', () => {
@@ -312,11 +316,12 @@
   });
 
   suite('moved chunks without line range)', () => {
-    setup(done => {
+    setup(async () => {
+      const promise = mockPromise();
       const renderHandler = function() {
         diffElement.removeEventListener('render', renderHandler);
         cursor.reInitCursor();
-        done();
+        promise.resolve();
       };
       diffElement.addEventListener('render', renderHandler);
       diffElement.diff = {...diff, content: [
@@ -352,6 +357,7 @@
           ],
         },
       ]};
+      await promise;
     });
 
     test('renders moveControls with simple descriptions', () => {
@@ -363,11 +369,12 @@
   });
 
   suite('moved chunks (moveDetails)', () => {
-    setup(done => {
+    setup(async () => {
+      const promise = mockPromise();
       const renderHandler = function() {
         diffElement.removeEventListener('render', renderHandler);
         cursor.reInitCursor();
-        done();
+        promise.resolve();
       };
       diffElement.addEventListener('render', renderHandler);
       diffElement.diff = {...diff, content: [
@@ -403,6 +410,7 @@
           ],
         },
       ]};
+      await promise;
     });
 
     test('renders moveControls with simple descriptions', () => {
@@ -412,37 +420,41 @@
       assert.equal(movedOut.textContent, 'Moved to lines 2 - 4');
     });
 
-    test('startLineAnchor of movedIn chunk fires events', done => {
+    test('startLineAnchor of movedIn chunk fires events', async () => {
       const [movedIn] = diffElement.root
           .querySelectorAll('.dueToMove .moveControls');
       const [startLineAnchor] = movedIn.querySelectorAll('a');
 
+      const promise = mockPromise();
       const onMovedLinkClicked = e => {
         assert.deepEqual(e.detail, {lineNum: 4, side: 'left'});
-        done();
+        promise.resolve();
       };
       assert.equal(startLineAnchor.textContent, '4');
       startLineAnchor
           .addEventListener('moved-link-clicked', onMovedLinkClicked);
       MockInteractions.click(startLineAnchor);
+      await promise;
     });
 
-    test('endLineAnchor of movedOut fires events', done => {
+    test('endLineAnchor of movedOut fires events', async () => {
       const [, movedOut] = diffElement.root
           .querySelectorAll('.dueToMove .moveControls');
       const [, endLineAnchor] = movedOut.querySelectorAll('a');
 
+      const promise = mockPromise();
       const onMovedLinkClicked = e => {
         assert.deepEqual(e.detail, {lineNum: 4, side: 'right'});
-        done();
+        promise.resolve();
       };
       assert.equal(endLineAnchor.textContent, '4');
       endLineAnchor.addEventListener('moved-link-clicked', onMovedLinkClicked);
       MockInteractions.click(endLineAnchor);
+      await promise;
     });
   });
 
-  test('initialLineNumber not provided', done => {
+  test('initialLineNumber not provided', async () => {
     let scrollBehaviorDuringMove;
     const moveToNumStub = sinon.stub(cursor, 'moveToLineNumber');
     const moveToChunkStub = sinon.stub(cursor, 'moveToFirstChunk')
@@ -450,6 +462,7 @@
           scrollBehaviorDuringMove = cursor.cursorManager.scrollMode;
         });
 
+    const promise = mockPromise();
     function renderHandler() {
       diffElement.removeEventListener('render', renderHandler);
       cursor.reInitCursor();
@@ -457,19 +470,21 @@
       assert.isTrue(moveToChunkStub.called);
       assert.equal(scrollBehaviorDuringMove, 'never');
       assert.equal(cursor.cursorManager.scrollMode, 'keep-visible');
-      done();
+      promise.resolve();
     }
     diffElement.addEventListener('render', renderHandler);
     diffElement._diffChanged(getMockDiffResponse());
+    await promise;
   });
 
-  test('initialLineNumber provided', done => {
+  test('initialLineNumber provided', async () => {
     let scrollBehaviorDuringMove;
     const moveToNumStub = sinon.stub(cursor, 'moveToLineNumber')
         .callsFake(() => {
           scrollBehaviorDuringMove = cursor.cursorManager.scrollMode;
         });
     const moveToChunkStub = sinon.stub(cursor, 'moveToFirstChunk');
+    const promise = mockPromise();
     function renderHandler() {
       diffElement.removeEventListener('render', renderHandler);
       cursor.reInitCursor();
@@ -479,13 +494,14 @@
       assert.equal(moveToNumStub.lastCall.args[1], 'right');
       assert.equal(scrollBehaviorDuringMove, 'keep-visible');
       assert.equal(cursor.cursorManager.scrollMode, 'keep-visible');
-      done();
+      promise.resolve();
     }
     diffElement.addEventListener('render', renderHandler);
     cursor.initialLineNumber = 10;
     cursor.side = 'right';
 
     diffElement._diffChanged(getMockDiffResponse());
+    await promise;
   });
 
   test('getTargetDiffElement', () => {
@@ -502,31 +518,35 @@
       diffElement.loggedIn = true;
     });
 
-    test('adds new draft for selected line on the left', done => {
+    test('adds new draft for selected line on the left', async () => {
       cursor.moveToLineNumber(2, 'left');
+      const promise = mockPromise();
       diffElement.addEventListener('create-comment', e => {
         const {lineNum, range, side} = e.detail;
         assert.equal(lineNum, 2);
         assert.equal(range, undefined);
         assert.equal(side, 'left');
-        done();
+        promise.resolve();
       });
       cursor.createCommentInPlace();
+      await promise;
     });
 
-    test('adds draft for selected line on the right', done => {
+    test('adds draft for selected line on the right', async () => {
       cursor.moveToLineNumber(4, 'right');
+      const promise = mockPromise();
       diffElement.addEventListener('create-comment', e => {
         const {lineNum, range, side} = e.detail;
         assert.equal(lineNum, 4);
         assert.equal(range, undefined);
         assert.equal(side, 'right');
-        done();
+        promise.resolve();
       });
       cursor.createCommentInPlace();
+      await promise;
     });
 
-    test('creates comment for range if selected', done => {
+    test('creates comment for range if selected', async () => {
       const someRange = {
         start_line: 2,
         start_character: 3,
@@ -537,14 +557,16 @@
         side: 'right',
         range: someRange,
       };
+      const promise = mockPromise();
       diffElement.addEventListener('create-comment', e => {
         const {lineNum, range, side} = e.detail;
         assert.equal(lineNum, 6);
         assert.equal(range, someRange);
         assert.equal(side, 'right');
-        done();
+        promise.resolve();
       });
       cursor.createCommentInPlace();
+      await promise;
     });
 
     test('ignores call if nothing is selected', () => {
@@ -596,15 +618,13 @@
     assert.equal(cursor._findRowByNumberAndFile(5, 'left'), row);
   });
 
-  test('expand context updates stops', done => {
+  test('expand context updates stops', async () => {
     sinon.spy(cursor, '_updateStops');
     MockInteractions.tap(diffElement.shadowRoot
         .querySelector('gr-context-controls').shadowRoot
         .querySelector('.showContext'));
-    flush(() => {
-      assert.isTrue(cursor._updateStops.called);
-      done();
-    });
+    await flush();
+    assert.isTrue(cursor._updateStops.called);
   });
 
   test('updates stops when loading changes', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation.ts b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation.ts
index 7420dc8..5e81871 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation.ts
@@ -29,6 +29,7 @@
    *
    */
   getLength(node: Node) {
+    if (node instanceof Comment) return 0;
     return this.getStringLength(node.textContent || '');
   },
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.js b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.js
index 65f5e07..d8295a5 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.js
@@ -252,6 +252,24 @@
           '0<test-wrapper>123456789<span></span>0</test-wrapper>123456789');
     });
 
+    test('handles comment nodes', () => {
+      const container = document.createElement('div');
+      container.appendChild(document.createComment('comment1'));
+      container.appendChild(document.createTextNode('0123456789'));
+      container.appendChild(document.createComment('comment2'));
+      container.appendChild(document.createElement('span'));
+      container.appendChild(document.createTextNode('0123456789'));
+      GrAnnotation.annotateWithElement(
+          container, 1, 10, {tagName: 'test-wrapper'});
+
+      assert.equal(
+          container.innerHTML,
+          '<!--comment1-->' +
+          '0<test-wrapper>123456789' +
+          '<!--comment2-->' +
+          '<span></span>0</test-wrapper>123456789');
+    });
+
     test('sets sanitized attributes', () => {
       const container = document.createElement('div');
       container.textContent = fullText;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
index b791b55..fa3ddf4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
@@ -50,7 +50,6 @@
   Base64ImageFile,
   BlameInfo,
   ChangeInfo,
-  CommentRange,
   EditPatchSetNum,
   NumericChangeId,
   ParentPatchSetNum,
@@ -93,7 +92,7 @@
 import {Subject} from 'rxjs';
 import {RenderPreferences} from '../../../api/diff';
 
-const MSG_EMPTY_BLAME = 'No blame information for this diff.';
+const EMPTY_BLAME = 'No blame information for this diff.';
 
 const EVENT_AGAINST_PARENT = 'diff-against-parent';
 const EVENT_ZERO_REBASE = 'rebase-percent-zero';
@@ -413,11 +412,7 @@
 
   private _getLayers(path: string): DiffLayer[] {
     const layers = [];
-    if (
-      appContext.flagsService.isEnabled(KnownExperimentId.TOKEN_HIGHLIGHTING)
-    ) {
-      layers.push(new TokenHighlightLayer(this));
-    }
+    layers.push(new TokenHighlightLayer(this));
     layers.push(this.syntaxLayer);
     // Get layers from plugins (if any).
     layers.push(...this.jsAPI.getDiffLayers(path));
@@ -569,8 +564,8 @@
       .getBlame(this.changeNum, this.patchRange.patchNum, this.path, true)
       .then(blame => {
         if (!blame || !blame.length) {
-          fireAlert(this, MSG_EMPTY_BLAME);
-          return Promise.reject(MSG_EMPTY_BLAME);
+          fireAlert(this, EMPTY_BLAME);
+          return Promise.reject(EMPTY_BLAME);
         }
 
         this._blame = blame;
@@ -730,14 +725,15 @@
   }
 
   _threadsChanged(threads: CommentThread[]) {
-    // Currently, the only way this is ever changed here is when the initial
-    // threads are loaded, so it's okay performance wise to clear the threads
-    // and recreate them. If this changes in future, we might want to reuse
-    // some DOM nodes here.
-    this._clearThreads();
+    const threadEls = new Set<Object>();
     for (const thread of threads) {
-      const threadEl = this._createThreadElement(thread);
-      this._attachThreadElement(threadEl);
+      threadEls.add(this._getOrCreateThread(thread));
+    }
+    // Remove all threads that are no longer existing.
+    for (const threadEl of this.getThreadEls()) {
+      if (threadEls.has(threadEl)) continue;
+      const parent = threadEl.parentNode;
+      if (parent) parent.removeChild(threadEl);
     }
     const portedThreadsCount = threads.filter(thread => thread.ported).length;
     const portedThreadsWithoutRange = threads.filter(
@@ -786,14 +782,15 @@
         ? CommentSide.PARENT
         : CommentSide.REVISION;
     if (!this.canCommentOnPatchSetNum(patchNum)) return;
-    const threadEl = this._getOrCreateThread(
-      patchNum,
-      lineNum,
-      side,
-      commentSide,
+    const threadEl = this._getOrCreateThread({
+      comments: [],
       path,
-      range
-    );
+      diffSide: side,
+      commentSide,
+      patchNum,
+      line: lineNum,
+      range,
+    });
     threadEl.addOrEditDraft(lineNum, range);
 
     this.reporting.recordDraftInteraction();
@@ -829,26 +826,13 @@
    * Gets or creates a comment thread at a given location.
    * May provide a range, to get/create a range comment.
    */
-  _getOrCreateThread(
-    patchNum: PatchSetNum,
-    lineNum: LineNumber | undefined,
-    diffSide: Side,
-    commentSide: CommentSide,
-    path: string,
-    range?: CommentRange
-  ): GrCommentThread {
-    let threadEl = this._getThreadEl(lineNum, diffSide, range);
+  _getOrCreateThread(thread: CommentThread): GrCommentThread {
+    let threadEl = this._getThreadEl(thread);
     if (!threadEl) {
-      threadEl = this._createThreadElement({
-        comments: [],
-        path,
-        diffSide,
-        commentSide,
-        patchNum,
-        line: lineNum,
-        range,
-      });
+      threadEl = this._createThreadElement(thread);
       this._attachThreadElement(threadEl);
+    } else {
+      this._updateThreadElement(threadEl, thread);
     }
     return threadEl;
   }
@@ -871,6 +855,11 @@
       'slot',
       `${thread.diffSide}-${thread.line || 'LOST'}`
     );
+    this._updateThreadElement(threadEl, thread);
+    return threadEl;
+  }
+
+  _updateThreadElement(threadEl: GrCommentThread, thread: CommentThread) {
     threadEl.comments = thread.comments;
     threadEl.diffSide = thread.diffSide;
     threadEl.isOnParent = thread.commentSide === CommentSide.PARENT;
@@ -896,34 +885,29 @@
     else threadEl.lineNum = thread.line !== 'FILE' ? thread.line : undefined;
     threadEl.projectName = this.projectName;
     threadEl.range = thread.range;
-    return threadEl;
   }
 
   /**
    * Gets a comment thread element at a given location.
    * May provide a range, to get a range comment.
    */
-  _getThreadEl(
-    lineNum: LineNumber | undefined,
-    commentSide: Side,
-    range?: CommentRange
-  ): GrCommentThread | null {
+  _getThreadEl(thread: CommentThread): GrCommentThread | null {
     let line: LineInfo;
-    if (commentSide === Side.LEFT) {
-      line = {beforeNumber: lineNum};
-    } else if (commentSide === Side.RIGHT) {
-      line = {afterNumber: lineNum};
+    if (thread.diffSide === Side.LEFT) {
+      line = {beforeNumber: thread.line};
+    } else if (thread.diffSide === Side.RIGHT) {
+      line = {afterNumber: thread.line};
     } else {
-      throw new Error(`Unknown side: ${commentSide}`);
+      throw new Error(`Unknown side: ${thread.diffSide}`);
     }
     function matchesRange(threadEl: GrCommentThread) {
-      return rangesEqual(getRange(threadEl), range);
+      return rangesEqual(getRange(threadEl), thread.range);
     }
 
     const filteredThreadEls = this._filterThreadElsForLocation(
       this.getThreadEls(),
       line,
-      commentSide
+      thread.diffSide
     ).filter(matchesRange);
     return filteredThreadEls.length ? filteredThreadEls[0] : null;
   }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
index 42aa160..b24b3ba 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
@@ -23,7 +23,11 @@
 import {Side, createDefaultDiffPrefs} from '../../../constants/constants.js';
 import {createChange} from '../../../test/test-data-generators.js';
 import {CoverageType} from '../../../types/types.js';
-import {addListenerForTest, stubRestApi} from '../../../test/test-utils.js';
+import {
+  addListenerForTest,
+  mockPromise,
+  stubRestApi,
+} from '../../../test/test-utils.js';
 import {EditPatchSetNum, ParentPatchSetNum} from '../../../types/common.js';
 import {_testOnly_resetState} from '../../../services/comments/comments-model.js';
 
@@ -64,14 +68,13 @@
   });
 
   suite('render reporting', () => {
-    test('starts total and content timer on render-start', done => {
+    test('starts total and content timer on render-start', () => {
       element.dispatchEvent(
           new CustomEvent('render-start', {bubbles: true, composed: true}));
       assert.isTrue(element.reporting.time.calledWithExactly(
           'Diff Total Render'));
       assert.isTrue(element.reporting.time.calledWithExactly(
           'Diff Content Render'));
-      done();
     });
 
     test('ends content timer on render-content', () => {
@@ -215,25 +218,23 @@
     });
   });
 
-  test('prefetch getDiff', done => {
+  test('prefetch getDiff', async () => {
     const diffRestApiStub = stubRestApi('getDiff')
         .returns(Promise.resolve({content: []}));
     element.changeNum = 123;
     element.patchRange = {basePatchNum: 1, patchNum: 2};
     element.path = 'file.txt';
     element.prefetchDiff();
-    element._getDiff().then(() =>{
-      assert.isTrue(diffRestApiStub.calledOnce);
-      done();
-    });
+    await element._getDiff();
+    assert.isTrue(diffRestApiStub.calledOnce);
   });
 
-  test('_getDiff handles null diff responses', done => {
+  test('_getDiff handles null diff responses', async () => {
     stubRestApi('getDiff').returns(Promise.resolve(null));
     element.changeNum = 123;
     element.patchRange = {basePatchNum: 1, patchNum: 2};
     element.path = 'file.txt';
-    element._getDiff().then(done);
+    await element._getDiff();
   });
 
   test('reload resolves on error', () => {
@@ -311,7 +312,7 @@
       };
     });
 
-    test('renders image diffs with same file name', done => {
+    test('renders image diffs with same file name', async () => {
       const mockDiff = {
         meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
         meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg',
@@ -342,6 +343,7 @@
         },
       }));
 
+      const promise = mockPromise();
       const rendered = () => {
         // Recognizes that it should be an image diff.
         assert.isTrue(element.isImageDiff);
@@ -377,7 +379,7 @@
           leftLoaded = true;
           if (rightLoaded) {
             element.removeEventListener('render', rendered);
-            done();
+            promise.resolve();
           }
         });
 
@@ -390,7 +392,7 @@
           rightLoaded = true;
           if (leftLoaded) {
             element.removeEventListener('render', rendered);
-            done();
+            promise.resolve();
           }
         });
       };
@@ -398,9 +400,10 @@
       element.addEventListener('render', rendered);
       element.prefs = createDefaultDiffPrefs();
       element.reload();
+      await promise;
     });
 
-    test('renders image diffs with a different file name', done => {
+    test('renders image diffs with a different file name', async () => {
       const mockDiff = {
         meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
         meta_b: {name: 'carrot2.jpg', content_type: 'image/jpeg',
@@ -431,6 +434,7 @@
         },
       }));
 
+      const promise = mockPromise();
       const rendered = () => {
         // Recognizes that it should be an image diff.
         assert.isTrue(element.isImageDiff);
@@ -468,7 +472,7 @@
           leftLoaded = true;
           if (rightLoaded) {
             element.removeEventListener('render', rendered);
-            done();
+            promise.resolve();
           }
         });
 
@@ -481,7 +485,7 @@
           rightLoaded = true;
           if (leftLoaded) {
             element.removeEventListener('render', rendered);
-            done();
+            promise.resolve();
           }
         });
       };
@@ -489,9 +493,10 @@
       element.addEventListener('render', rendered);
       element.prefs = createDefaultDiffPrefs();
       element.reload();
+      await promise;
     });
 
-    test('renders added image', done => {
+    test('renders added image', async () => {
       const mockDiff = {
         meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg',
           lines: 560},
@@ -517,6 +522,7 @@
         },
       }));
 
+      const promise = mockPromise();
       element.addEventListener('render', () => {
         // Recognizes that it should be an image diff.
         assert.isTrue(element.isImageDiff);
@@ -530,14 +536,15 @@
 
         assert.isNotOk(leftImage);
         assert.isOk(rightImage);
-        done();
+        promise.resolve();
       });
 
       element.prefs = createDefaultDiffPrefs();
       element.reload();
+      await promise;
     });
 
-    test('renders removed image', done => {
+    test('renders removed image', async () => {
       const mockDiff = {
         meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg',
           lines: 560},
@@ -563,6 +570,7 @@
         revisionImage: null,
       }));
 
+      const promise = mockPromise();
       element.addEventListener('render', () => {
         // Recognizes that it should be an image diff.
         assert.isTrue(element.isImageDiff);
@@ -576,14 +584,15 @@
 
         assert.isOk(leftImage);
         assert.isNotOk(rightImage);
-        done();
+        promise.resolve();
       });
 
       element.prefs = createDefaultDiffPrefs();
       element.reload();
+      await promise;
     });
 
-    test('does not render disallowed image type', done => {
+    test('does not render disallowed image type', async () => {
       const mockDiff = {
         meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg-evil',
           lines: 560},
@@ -611,6 +620,7 @@
         revisionImage: null,
       }));
 
+      const promise = mockPromise();
       element.addEventListener('render', () => {
         // Recognizes that it should be an image diff.
         assert.isTrue(element.isImageDiff);
@@ -619,11 +629,12 @@
         const leftImage =
             element.$.diff.$.diffTable.querySelector('td.left img');
         assert.isNotOk(leftImage);
-        done();
+        promise.resolve();
       });
 
       element.prefs = createDefaultDiffPrefs();
       element.reload();
+      await promise;
     });
   });
 
@@ -1259,7 +1270,7 @@
     test('gr-diff-host provides syntax highlighting layer', async () => {
       stubRestApi('getDiff').returns(Promise.resolve({content: []}));
       await element.reload();
-      assert.equal(element.$.diff.layers[0], element.syntaxLayer);
+      assert.equal(element.$.diff.layers[1], element.syntaxLayer);
     });
 
     test('rendering normal-sized diff does not disable syntax', () => {
@@ -1313,7 +1324,7 @@
     test('gr-diff-host provides syntax highlighting layer', async () => {
       stubRestApi('getDiff').returns(Promise.resolve({content: []}));
       await element.reload();
-      assert.equal(element.$.diff.layers[0], element.syntaxLayer);
+      assert.equal(element.$.diff.layers[1], element.syntaxLayer);
     });
 
     test('syntax layer should be disabled', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts
index cf825ff..fbdcb69 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts
@@ -28,18 +28,10 @@
 import {GrLibLoader} from '../../shared/gr-lib-loader/gr-lib-loader';
 import {RESEMBLEJS_LIBRARY_CONFIG} from '../../shared/gr-lib-loader/resemblejs_config';
 
-import {
-  css,
-  customElement,
-  html,
-  LitElement,
-  property,
-  PropertyValues,
-  query,
-  state,
-} from 'lit-element';
-import {classMap} from 'lit-html/directives/class-map';
-import {StyleInfo, styleMap} from 'lit-html/directives/style-map';
+import {css, html, LitElement, PropertyValues} from 'lit';
+import {customElement, property, query, state} from 'lit/decorators';
+import {classMap} from 'lit/directives/class-map';
+import {StyleInfo, styleMap} from 'lit/directives/style-map';
 
 import {
   createEvent,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-overview-image.ts b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-overview-image.ts
index b1ea72f..28e6d82 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-overview-image.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-overview-image.ts
@@ -14,17 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {
-  css,
-  customElement,
-  html,
-  LitElement,
-  property,
-  PropertyValues,
-  query,
-  state,
-} from 'lit-element';
-import {StyleInfo, styleMap} from 'lit-html/directives/style-map';
+import {css, html, LitElement, PropertyValues} from 'lit';
+import {customElement, property, query, state} from 'lit/decorators';
+import {StyleInfo, styleMap} from 'lit/directives/style-map';
 import {ImageDiffAction} from '../../../api/diff';
 
 import {createEvent, Dimensions, fitToFrame, Point, Rect} from './util';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-zoomed-image.ts b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-zoomed-image.ts
index 671b858..66d4671 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-zoomed-image.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-zoomed-image.ts
@@ -14,16 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {
-  css,
-  customElement,
-  html,
-  LitElement,
-  property,
-  PropertyValues,
-  state,
-} from 'lit-element';
-import {StyleInfo, styleMap} from 'lit-html/directives/style-map';
+import {css, html, LitElement, PropertyValues} from 'lit';
+import {customElement, property, state} from 'lit/decorators';
+import {StyleInfo, styleMap} from 'lit/directives/style-map';
 import {Rect} from './util';
 
 /**
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog_html.ts b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog_html.ts
index 787fe30..85edc12 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog_html.ts
@@ -49,12 +49,12 @@
   </style>
   <gr-overlay id="diffPrefsOverlay" with-backdrop="">
     <div role="dialog" aria-labelledby="diffPreferencesTitle">
-      <h1
-        class$="diffHeader [[_computeHeaderClass(_diffPrefsChanged)]]"
+      <h3
+        class$="heading-3 diffHeader [[_computeHeaderClass(_diffPrefsChanged)]]"
         id="diffPreferencesTitle"
       >
         Diff Preferences
-      </h1>
+      </h3>
       <gr-diff-preferences
         id="diffPreferences"
         diff-prefs="{{_editableDiffPrefs}}"
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index 61259d1..b1d71cc 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -112,8 +112,8 @@
 import {Subject} from 'rxjs';
 
 const ERR_REVIEW_STATUS = 'Couldn’t change file review status.';
-const MSG_LOADING_BLAME = 'Loading blame...';
-const MSG_LOADED_BLAME = 'Blame loaded';
+const LOADING_BLAME = 'Loading blame...';
+const LOADED_BLAME = 'Blame loaded';
 
 // Time in which pressing n key again after the toast navigates to next file
 const NAVIGATE_TO_NEXT_FILE_TIMEOUT_MS = 5000;
@@ -140,8 +140,11 @@
   };
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-diff-view')
-export class GrDiffView extends KeyboardShortcutMixin(PolymerElement) {
+export class GrDiffView extends base {
   static get template() {
     return htmlTemplate;
   }
@@ -1664,12 +1667,12 @@
 
   _loadBlame() {
     this._isBlameLoading = true;
-    fireAlert(this, MSG_LOADING_BLAME);
+    fireAlert(this, LOADING_BLAME);
     this.$.diffHost
       .loadBlame()
       .then(() => {
         this._isBlameLoading = false;
-        fireAlert(this, MSG_LOADED_BLAME);
+        fireAlert(this, LOADED_BLAME);
       })
       .catch(() => {
         this._isBlameLoading = false;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
index 033b886..d9c4ba2 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
@@ -678,23 +678,21 @@
       assert.isNotOk(args[3]);
     });
 
-    test('A fires an error event when not logged in', done => {
+    test('A fires an error event when not logged in', async () => {
       const changeNavStub = sinon.stub(GerritNav, 'navigateToChange');
       sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(false));
       const loggedInErrorSpy = sinon.spy();
       element.addEventListener('show-auth-required', loggedInErrorSpy);
       MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
-      flush(() => {
-        assert.isTrue(changeNavStub.notCalled, 'The `a` keyboard shortcut ' +
-          'should only work when the user is logged in.');
-        assert.isNull(window.sessionStorage.getItem(
-            'changeView.showReplyDialog'));
-        assert.isTrue(loggedInErrorSpy.called);
-        done();
-      });
+      await flush();
+      assert.isTrue(changeNavStub.notCalled, 'The `a` keyboard shortcut ' +
+        'should only work when the user is logged in.');
+      assert.isNull(window.sessionStorage.getItem(
+          'changeView.showReplyDialog'));
+      assert.isTrue(loggedInErrorSpy.called);
     });
 
-    test('A navigates to change with logged in', done => {
+    test('A navigates to change with logged in', async () => {
       element._changeNum = '42';
       element._patchRange = {
         basePatchNum: 5,
@@ -712,41 +710,38 @@
       const loggedInErrorSpy = sinon.spy();
       element.addEventListener('show-auth-required', loggedInErrorSpy);
       MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
-      flush(() => {
-        assert.isTrue(element.changeViewState.showReplyDialog);
-        assert(changeNavStub.lastCall.calledWithExactly(element._change, 10,
-            5), 'Should navigate to /c/42/5..10');
-        assert.isFalse(loggedInErrorSpy.called);
-        done();
-      });
+      await flush();
+      assert.isTrue(element.changeViewState.showReplyDialog);
+      assert(changeNavStub.lastCall.calledWithExactly(element._change, 10,
+          5), 'Should navigate to /c/42/5..10');
+      assert.isFalse(loggedInErrorSpy.called);
     });
 
-    test('A navigates to change with old patch number with logged in', done => {
-      element._changeNum = '42';
-      element._patchRange = {
-        basePatchNum: PARENT,
-        patchNum: 1,
-      };
-      element._change = {
-        _number: 42,
-        revisions: {
-          a: {_number: 1, commit: {parents: []}},
-          b: {_number: 2, commit: {parents: []}},
-        },
-      };
-      const changeNavStub = sinon.stub(GerritNav, 'navigateToChange');
-      sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
-      const loggedInErrorSpy = sinon.spy();
-      element.addEventListener('show-auth-required', loggedInErrorSpy);
-      MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
-      flush(() => {
-        assert.isTrue(element.changeViewState.showReplyDialog);
-        assert(changeNavStub.lastCall.calledWithExactly(element._change, 1,
-            PARENT), 'Should navigate to /c/42/1');
-        assert.isFalse(loggedInErrorSpy.called);
-        done();
-      });
-    });
+    test('A navigates to change with old patch number with logged in',
+        async () => {
+          element._changeNum = '42';
+          element._patchRange = {
+            basePatchNum: PARENT,
+            patchNum: 1,
+          };
+          element._change = {
+            _number: 42,
+            revisions: {
+              a: {_number: 1, commit: {parents: []}},
+              b: {_number: 2, commit: {parents: []}},
+            },
+          };
+          const changeNavStub = sinon.stub(GerritNav, 'navigateToChange');
+          sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
+          const loggedInErrorSpy = sinon.spy();
+          element.addEventListener('show-auth-required', loggedInErrorSpy);
+          MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
+          await flush();
+          assert.isTrue(element.changeViewState.showReplyDialog);
+          assert(changeNavStub.lastCall.calledWithExactly(element._change, 1,
+              PARENT), 'Should navigate to /c/42/1');
+          assert.isFalse(loggedInErrorSpy.called);
+        });
 
     test('keyboard shortcuts with patch range', () => {
       element._changeNum = '42';
@@ -860,7 +855,7 @@
       assert.isTrue(changeNavStub.calledOnce);
     });
 
-    test('edit should redirect to edit page', done => {
+    test('edit should redirect to edit page', async () => {
       element._loggedIn = true;
       element._path = 't.txt';
       element._patchRange = {
@@ -877,23 +872,21 @@
         },
       };
       const redirectStub = sinon.stub(GerritNav, 'navigateToRelativeUrl');
-      flush(() => {
-        const editBtn = element.shadowRoot
-            .querySelector('.editButton gr-button');
-        assert.isTrue(!!editBtn);
-        MockInteractions.tap(editBtn);
-        assert.isTrue(redirectStub.called);
-        assert.isTrue(redirectStub.lastCall.calledWithExactly(
-            GerritNav.getEditUrlForDiff(
-                element._change,
-                element._path,
-                element._patchRange.patchNum
-            )));
-        done();
-      });
+      await flush();
+      const editBtn = element.shadowRoot
+          .querySelector('.editButton gr-button');
+      assert.isTrue(!!editBtn);
+      MockInteractions.tap(editBtn);
+      assert.isTrue(redirectStub.called);
+      assert.isTrue(redirectStub.lastCall.calledWithExactly(
+          GerritNav.getEditUrlForDiff(
+              element._change,
+              element._path,
+              element._patchRange.patchNum
+          )));
     });
 
-    test('edit should redirect to edit page with line number', done => {
+    test('edit should redirect to edit page with line number', async () => {
       const lineNumber = 42;
       element._loggedIn = true;
       element._path = 't.txt';
@@ -913,21 +906,19 @@
       sinon.stub(element.cursor, 'getAddress')
           .returns({number: lineNumber, isLeftSide: false});
       const redirectStub = sinon.stub(GerritNav, 'navigateToRelativeUrl');
-      flush(() => {
-        const editBtn = element.shadowRoot
-            .querySelector('.editButton gr-button');
-        assert.isTrue(!!editBtn);
-        MockInteractions.tap(editBtn);
-        assert.isTrue(redirectStub.called);
-        assert.isTrue(redirectStub.lastCall.calledWithExactly(
-            GerritNav.getEditUrlForDiff(
-                element._change,
-                element._path,
-                element._patchRange.patchNum,
-                lineNumber
-            )));
-        done();
-      });
+      await flush();
+      const editBtn = element.shadowRoot
+          .querySelector('.editButton gr-button');
+      assert.isTrue(!!editBtn);
+      MockInteractions.tap(editBtn);
+      assert.isTrue(redirectStub.called);
+      assert.isTrue(redirectStub.lastCall.calledWithExactly(
+          GerritNav.getEditUrlForDiff(
+              element._change,
+              element._path,
+              element._patchRange.patchNum,
+              lineNumber
+          )));
     });
 
     function isEditVisibile({loggedIn, changeStatus}) {
@@ -1294,7 +1285,7 @@
       assert.isFalse(saveReviewedStub.called);
     });
 
-    test('hash is determined from params', done => {
+    test('hash is determined from params', async () => {
       sinon.stub(element.$.diffHost, 'reload');
       sinon.stub(element, '_initLineOfInterestAndCursor');
 
@@ -1308,10 +1299,8 @@
         hash: 10,
       };
 
-      flush(() => {
-        assert.isTrue(element._initLineOfInterestAndCursor.calledOnce);
-        done();
-      });
+      await flush();
+      assert.isTrue(element._initLineOfInterestAndCursor.calledOnce);
     });
 
     test('diff mode selector correctly toggles the diff', () => {
@@ -1360,15 +1349,13 @@
       assert.equal(element._getDiffViewMode(), 'SIDE_BY_SIDE');
     });
 
-    test('diff mode selector should be hidden for binary', done => {
+    test('diff mode selector should be hidden for binary', async () => {
       element._diff = {binary: true, content: []};
 
-      flush(() => {
-        const diffModeSelector = element.shadowRoot
-            .querySelector('.diffModeSelector');
-        assert.isTrue(diffModeSelector.classList.contains('hide'));
-        done();
-      });
+      await flush();
+      const diffModeSelector = element.shadowRoot
+          .querySelector('.diffModeSelector');
+      assert.isTrue(diffModeSelector.classList.contains('hide'));
     });
 
     suite('_commitRange', () => {
@@ -1400,7 +1387,7 @@
             change));
       });
 
-      test('uses the patchNum and basePatchNum ', done => {
+      test('uses the patchNum and basePatchNum ', async () => {
         element.params = {
           view: GerritNav.View.DIFF,
           changeNum: '42',
@@ -1409,16 +1396,14 @@
           path: '/COMMIT_MSG',
         };
         element._change = change;
-        flush(() => {
-          assert.deepEqual(element._commitRange, {
-            baseCommit: 'commit-sha-2',
-            commit: 'commit-sha-4',
-          });
-          done();
+        await flush();
+        assert.deepEqual(element._commitRange, {
+          baseCommit: 'commit-sha-2',
+          commit: 'commit-sha-4',
         });
       });
 
-      test('uses the parent when there is no base patch num ', done => {
+      test('uses the parent when there is no base patch num ', async () => {
         element.params = {
           view: GerritNav.View.DIFF,
           changeNum: '42',
@@ -1426,12 +1411,10 @@
           path: '/COMMIT_MSG',
         };
         element._change = change;
-        flush(() => {
-          assert.deepEqual(element._commitRange, {
-            commit: 'commit-sha-5',
-            baseCommit: 'sha-5-parent',
-          });
-          done();
+        await flush();
+        assert.deepEqual(element._commitRange, {
+          commit: 'commit-sha-5',
+          baseCommit: 'sha-5-parent',
         });
       });
     });
@@ -1921,7 +1904,7 @@
       ]);
     });
 
-    test('File change should trigger navigateToDiff once', done => {
+    test('File change should trigger navigateToDiff once', async () => {
       element._files = getFilesFromFileList(['file1', 'file2', 'file3']);
       sinon.stub(element, '_initLineOfInterestAndCursor');
       sinon.stub(GerritNav, 'navigateToDiff');
@@ -1942,7 +1925,7 @@
         ...createChange(),
         revisions: createRevisions(1),
       };
-      flush();
+      await flush();
       assert.isTrue(GerritNav.navigateToDiff.notCalled);
 
       // Switch to file2
@@ -1964,7 +1947,6 @@
 
       // No extra call
       assert.isTrue(GerritNav.navigateToDiff.calledOnce);
-      done();
     });
 
     test('_computeDownloadDropdownLinks', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
index 79ef38a..83b0aad 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
@@ -69,10 +69,9 @@
     /* Provides the option to add side borders (left and right) to the line number column. */
     td.left,
     td.right,
+    td.moveControlsLineNumCol,
     td.contextLineNum {
-      border-width: var(--line-number-border-width, 0);
-      border-style: solid;
-      border-color: var(--line-number-border-color, var(--border-color, unset));
+      box-shadow: var(--line-number-box-shadow, unset);
     }
 
     /*
@@ -112,6 +111,7 @@
       width: 100%;
       height: 100%;
       background-color: var(--diff-blank-background-color);
+      box-shadow: var(--line-number-box-shadow, unset);
     }
     td.lineNum {
       vertical-align: top;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
index c59ceb7..9ee779c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
@@ -23,7 +23,7 @@
 import {_setHiddenScroll} from '../../../scripts/hiddenscroll.js';
 import {runA11yAudit} from '../../../test/a11y-test-utils.js';
 import '@polymer/paper-button/paper-button.js';
-import {stubRestApi} from '../../../test/test-utils.js';
+import {mockPromise, stubRestApi} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-diff');
 
@@ -232,7 +232,9 @@
         };
       });
 
-      test('renders image diffs with same file name', done => {
+      test('renders image diffs with same file name', async () => {
+        const leftRendered = mockPromise();
+        const rightRendered = mockPromise();
         const rendered = () => {
           // Recognizes that it should be an image diff.
           assert.isTrue(element.isImageDiff);
@@ -256,19 +258,12 @@
           assert.isNotOk(rightLabelName);
           assert.isNotOk(leftLabelName);
 
-          let leftLoaded = false;
-          let rightLoaded = false;
-
           leftImage.addEventListener('load', () => {
             assert.isOk(leftImage);
             assert.equal(leftImage.getAttribute('src'),
                 'data:image/bmp;base64,' + mockFile1.body);
             assert.equal(leftLabelContent.textContent, '1\u00d71 image/bmp');// \u00d7 - '×'
-            leftLoaded = true;
-            if (rightLoaded) {
-              element.removeEventListener('render', rendered);
-              done();
-            }
+            leftRendered.resolve();
           });
 
           rightImage.addEventListener('load', () => {
@@ -277,11 +272,7 @@
                 'data:image/bmp;base64,' + mockFile2.body);
             assert.equal(rightLabelContent.textContent, '1\u00d71 image/bmp');// \u00d7 - '×'
 
-            rightLoaded = true;
-            if (leftLoaded) {
-              element.removeEventListener('render', rendered);
-              done();
-            }
+            rightRendered.resolve();
           });
         };
 
@@ -305,9 +296,11 @@
           content: [{skip: 66}],
           binary: true,
         };
+        await Promise.all([leftRendered, rightRendered]);
+        element.removeEventListener('render', rendered);
       });
 
-      test('renders image diffs with a different file name', done => {
+      test('renders image diffs with a different file name', async () => {
         const mockDiff = {
           meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
           meta_b: {name: 'carrot2.jpg', content_type: 'image/jpeg',
@@ -324,7 +317,8 @@
           content: [{skip: 66}],
           binary: true,
         };
-
+        const leftRendered = mockPromise();
+        const rightRendered = mockPromise();
         const rendered = () => {
           // Recognizes that it should be an image diff.
           assert.isTrue(element.isImageDiff);
@@ -350,19 +344,12 @@
           assert.equal(leftLabelName.textContent, mockDiff.meta_a.name);
           assert.equal(rightLabelName.textContent, mockDiff.meta_b.name);
 
-          let leftLoaded = false;
-          let rightLoaded = false;
-
           leftImage.addEventListener('load', () => {
             assert.isOk(leftImage);
             assert.equal(leftImage.getAttribute('src'),
                 'data:image/bmp;base64,' + mockFile1.body);
             assert.equal(leftLabelContent.textContent, '1\u00d71 image/bmp');// \u00d7 - '×'
-            leftLoaded = true;
-            if (rightLoaded) {
-              element.removeEventListener('render', rendered);
-              done();
-            }
+            leftRendered.resolve();
           });
 
           rightImage.addEventListener('load', () => {
@@ -371,11 +358,7 @@
                 'data:image/bmp;base64,' + mockFile2.body);
             assert.equal(rightLabelContent.textContent, '1\u00d71 image/bmp');// \u00d7 - '×'
 
-            rightLoaded = true;
-            if (leftLoaded) {
-              element.removeEventListener('render', rendered);
-              done();
-            }
+            rightRendered.resolve();
           });
         };
 
@@ -386,9 +369,11 @@
         element.revisionImage = mockFile2;
         element.revisionImage._name = mockDiff.meta_b.name;
         element.diff = mockDiff;
+        await Promise.all([leftRendered, rightRendered]);
+        element.removeEventListener('render', rendered);
       });
 
-      test('renders added image', done => {
+      test('renders added image', async () => {
         const mockDiff = {
           meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg',
             lines: 560},
@@ -405,27 +390,27 @@
           binary: true,
         };
 
-        function rendered() {
-          // Recognizes that it should be an image diff.
-          assert.isTrue(element.isImageDiff);
-          assert.instanceOf(
-              element.$.diffBuilder._builder, GrDiffBuilderImage);
-
-          const leftImage = element.$.diffTable.querySelector('td.left img');
-          const rightImage = element.$.diffTable.querySelector('td.right img');
-
-          assert.isNotOk(leftImage);
-          assert.isOk(rightImage);
-          done();
-          element.removeEventListener('render', rendered);
-        }
+        const promise = mockPromise();
+        function rendered() { promise.resolve(); }
         element.addEventListener('render', rendered);
 
         element.revisionImage = mockFile2;
         element.diff = mockDiff;
+        await promise;
+        element.removeEventListener('render', rendered);
+        // Recognizes that it should be an image diff.
+        assert.isTrue(element.isImageDiff);
+        assert.instanceOf(
+            element.$.diffBuilder._builder, GrDiffBuilderImage);
+
+        const leftImage = element.$.diffTable.querySelector('td.left img');
+        const rightImage = element.$.diffTable.querySelector('td.right img');
+
+        assert.isNotOk(leftImage);
+        assert.isOk(rightImage);
       });
 
-      test('renders removed image', done => {
+      test('renders removed image', async () => {
         const mockDiff = {
           meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg',
             lines: 560},
@@ -441,28 +426,27 @@
           content: [{skip: 66}],
           binary: true,
         };
-
-        function rendered() {
-          // Recognizes that it should be an image diff.
-          assert.isTrue(element.isImageDiff);
-          assert.instanceOf(
-              element.$.diffBuilder._builder, GrDiffBuilderImage);
-
-          const leftImage = element.$.diffTable.querySelector('td.left img');
-          const rightImage = element.$.diffTable.querySelector('td.right img');
-
-          assert.isOk(leftImage);
-          assert.isNotOk(rightImage);
-          done();
-          element.removeEventListener('render', rendered);
-        }
+        const promise = mockPromise();
+        function rendered() { promise.resolve(); }
         element.addEventListener('render', rendered);
 
         element.baseImage = mockFile1;
         element.diff = mockDiff;
+        await promise;
+        element.removeEventListener('render', rendered);
+        // Recognizes that it should be an image diff.
+        assert.isTrue(element.isImageDiff);
+        assert.instanceOf(
+            element.$.diffBuilder._builder, GrDiffBuilderImage);
+
+        const leftImage = element.$.diffTable.querySelector('td.left img');
+        const rightImage = element.$.diffTable.querySelector('td.right img');
+
+        assert.isOk(leftImage);
+        assert.isNotOk(rightImage);
       });
 
-      test('does not render disallowed image type', done => {
+      test('does not render disallowed image type', async () => {
         const mockDiff = {
           meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg-evil',
             lines: 560},
@@ -480,50 +464,54 @@
         };
         mockFile1.type = 'image/jpeg-evil';
 
-        function rendered() {
-          // Recognizes that it should be an image diff.
-          assert.isTrue(element.isImageDiff);
-          assert.instanceOf(
-              element.$.diffBuilder._builder, GrDiffBuilderImage);
-          const leftImage = element.$.diffTable.querySelector('td.left img');
-          assert.isNotOk(leftImage);
-          done();
-          element.removeEventListener('render', rendered);
-        }
+        const promise = mockPromise();
+        function rendered() { promise.resolve(); }
         element.addEventListener('render', rendered);
 
         element.baseImage = mockFile1;
         element.diff = mockDiff;
+        await promise;
+        element.removeEventListener('render', rendered);
+        // Recognizes that it should be an image diff.
+        assert.isTrue(element.isImageDiff);
+        assert.instanceOf(
+            element.$.diffBuilder._builder, GrDiffBuilderImage);
+        const leftImage = element.$.diffTable.querySelector('td.left img');
+        assert.isNotOk(leftImage);
       });
     });
 
-    test('_handleTap lineNum', done => {
+    test('_handleTap lineNum', async () => {
       const addDraftStub = sinon.stub(element, 'addDraftAtLine');
       const el = document.createElement('div');
       el.className = 'lineNum';
+      const promise = mockPromise();
       el.addEventListener('click', e => {
         element._handleTap(e);
         assert.isTrue(addDraftStub.called);
         assert.equal(addDraftStub.lastCall.args[0], el);
-        done();
+        promise.resolve();
       });
       el.click();
+      await promise;
     });
 
-    test('_handleTap context', done => {
+    test('_handleTap context', async () => {
       const showContextStub =
           sinon.stub(element.$.diffBuilder, 'showContext');
       const el = document.createElement('div');
       el.className = 'showContext';
+      const promise = mockPromise();
       el.addEventListener('click', e => {
         element._handleDiffContextExpanded(e);
         assert.isTrue(showContextStub.called);
-        done();
+        promise.resolve();
       });
       el.click();
+      await promise;
     });
 
-    test('_handleTap content', done => {
+    test('_handleTap content', async () => {
       const content = document.createElement('div');
       const lineEl = document.createElement('div');
       lineEl.className = 'lineNum';
@@ -534,13 +522,15 @@
       const selectStub = sinon.stub(element, '_selectLine');
 
       content.className = 'content';
+      const promise = mockPromise();
       content.addEventListener('click', e => {
         element._handleTap(e);
         assert.isTrue(selectStub.called);
         assert.equal(selectStub.lastCall.args[0], lineEl);
-        done();
+        promise.resolve();
       });
       content.click();
+      await promise;
     });
 
     suite('getCursorStops', () => {
@@ -816,41 +806,47 @@
       element.noRenderOnPrefsChange = true;
     });
 
-    test('large render w/ context = 10', done => {
+    test('large render w/ context = 10', async () => {
       element.prefs = {...MINIMAL_PREFS, context: 10};
+      const promise = mockPromise();
       function rendered() {
         assert.isTrue(renderStub.called);
         assert.isFalse(element._showWarning);
-        done();
+        promise.resolve();
         element.removeEventListener('render', rendered);
       }
       element.addEventListener('render', rendered);
       element._renderDiffTable();
+      await promise;
     });
 
-    test('large render w/ whole file and bypass', done => {
+    test('large render w/ whole file and bypass', async () => {
       element.prefs = {...MINIMAL_PREFS, context: -1};
       element._safetyBypass = 10;
+      const promise = mockPromise();
       function rendered() {
         assert.isTrue(renderStub.called);
         assert.isFalse(element._showWarning);
-        done();
+        promise.resolve();
         element.removeEventListener('render', rendered);
       }
       element.addEventListener('render', rendered);
       element._renderDiffTable();
+      await promise;
     });
 
-    test('large render w/ whole file and no bypass', done => {
+    test('large render w/ whole file and no bypass', async () => {
       element.prefs = {...MINIMAL_PREFS, context: -1};
+      const promise = mockPromise();
       function rendered() {
         assert.isFalse(renderStub.called);
         assert.isTrue(element._showWarning);
-        done();
+        promise.resolve();
         element.removeEventListener('render', rendered);
       }
       element.addEventListener('render', rendered);
       element._renderDiffTable();
+      await promise;
     });
 
     test('toggles expand context using bypass', async () => {
@@ -1219,16 +1215,18 @@
     assert.equal(element.getDiffLength(diff), 52);
   });
 
-  test('`render` event has contentRendered field in detail', done => {
+  test('`render` event has contentRendered field in detail', async () => {
     element = basicFixture.instantiate();
     element.prefs = {};
     sinon.stub(element.$.diffBuilder, 'render')
         .returns(Promise.resolve());
+    const promise = mockPromise();
     element.addEventListener('render', event => {
       assert.isTrue(event.detail.contentRendered);
-      done();
+      promise.resolve();
     });
     element._renderDiffTable();
+    await promise;
   });
 
   test('_prefsEqual', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js
index 89b8b4a..0fe1fe2 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js
@@ -196,7 +196,7 @@
   });
 
   test('_computeBaseDropdownContent called when changeComments update',
-      done => {
+      async () => {
         element.revisions = [
           {commit: {parents: []}},
           {commit: {parents: []}},
@@ -212,15 +212,14 @@
         ];
         element.patchNum = 2;
         element.basePatchNum = 'PARENT';
-        flush();
+        await flush();
 
         // Should be recomputed for each available patch
         sinon.stub(element, '_computeBaseDropdownContent');
         assert.equal(element._computeBaseDropdownContent.callCount, 0);
         element.changeComments = new ChangeComments();
-        flush();
+        await flush();
         assert.equal(element._computeBaseDropdownContent.callCount, 1);
-        done();
       });
 
   test('_computePatchDropdownContent called when basePatchNum updates', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-range-header/gr-range-header.ts b/polygerrit-ui/app/elements/diff/gr-range-header/gr-range-header.ts
index 776b954..1c2c074 100644
--- a/polygerrit-ui/app/elements/diff/gr-range-header/gr-range-header.ts
+++ b/polygerrit-ui/app/elements/diff/gr-range-header/gr-range-header.ts
@@ -14,9 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 /**
  * Represents a header (label) for a code chunk whenever showing
@@ -25,7 +24,7 @@
  * like long comments and moved in/out chunks.
  */
 @customElement('gr-range-header')
-export class GrRangeHeader extends GrLitElement {
+export class GrRangeHeader extends LitElement {
   @property({type: String})
   icon?: string;
 
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts b/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts
index f82290b..bc3c054 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts
@@ -17,13 +17,13 @@
 
 import '../gr-range-header/gr-range-header';
 import {CommentRange} from '../../../types/common';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 import {sharedStyles} from '../../../styles/shared-styles';
 import {grRangedCommentTheme} from '../gr-ranged-comment-themes/gr-ranged-comment-theme';
 
 @customElement('gr-ranged-comment-hint')
-export class GrRangedCommentHint extends GrLitElement {
+export class GrRangedCommentHint extends LitElement {
   @property({type: Object})
   range?: CommentRange;
 
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-themes/gr-ranged-comment-theme.ts b/polygerrit-ui/app/elements/diff/gr-ranged-comment-themes/gr-ranged-comment-theme.ts
index c3fadf5..fb20a91 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-themes/gr-ranged-comment-theme.ts
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-themes/gr-ranged-comment-theme.ts
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import {css} from 'lit-element';
+import {css} from 'lit';
 
 // Mark the file as a module. Otherwise typescript assumes this is a script
 // and $_documentContainer is a global variable.
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js
index b8c3c16..c907a80 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js
@@ -120,7 +120,7 @@
     assert.isFalse(annotationSpy.called);
   });
 
-  test('process on empty diff does nothing', done => {
+  test('process on empty diff does nothing', async () => {
     element.diff = {
       meta_a: {content_type: 'application/json'},
       meta_b: {content_type: 'application/json'},
@@ -128,17 +128,14 @@
     };
     const processNextSpy = sinon.spy(element, '_processNextLine');
 
-    const processPromise = element.process();
+    await element.process();
 
-    processPromise.then(() => {
-      assert.isFalse(processNextSpy.called);
-      assert.equal(element.baseRanges.length, 0);
-      assert.equal(element.revisionRanges.length, 0);
-      done();
-    });
+    assert.isFalse(processNextSpy.called);
+    assert.equal(element.baseRanges.length, 0);
+    assert.equal(element.revisionRanges.length, 0);
   });
 
-  test('process for unsupported languages does nothing', done => {
+  test('process for unsupported languages does nothing', async () => {
     element.diff = {
       meta_a: {content_type: 'text/x+objective-cobol-plus-plus'},
       meta_b: {content_type: 'application/not-a-real-language'},
@@ -146,33 +143,27 @@
     };
     const processNextSpy = sinon.spy(element, '_processNextLine');
 
-    const processPromise = element.process();
+    await element.process();
 
-    processPromise.then(() => {
-      assert.isFalse(processNextSpy.called);
-      assert.equal(element.baseRanges.length, 0);
-      assert.equal(element.revisionRanges.length, 0);
-      done();
-    });
+    assert.isFalse(processNextSpy.called);
+    assert.equal(element.baseRanges.length, 0);
+    assert.equal(element.revisionRanges.length, 0);
   });
 
-  test('process while disabled does nothing', done => {
+  test('process while disabled does nothing', async () => {
     const processNextSpy = sinon.spy(element, '_processNextLine');
     element.enabled = false;
     const loadHLJSSpy = sinon.spy(element, '_loadHLJS');
 
-    const processPromise = element.process();
+    await element.process();
 
-    processPromise.then(() => {
-      assert.isFalse(processNextSpy.called);
-      assert.equal(element.baseRanges.length, 0);
-      assert.equal(element.revisionRanges.length, 0);
-      assert.isFalse(loadHLJSSpy.called);
-      done();
-    });
+    assert.isFalse(processNextSpy.called);
+    assert.equal(element.baseRanges.length, 0);
+    assert.equal(element.revisionRanges.length, 0);
+    assert.isFalse(loadHLJSSpy.called);
   });
 
-  test('process highlight ipsum', done => {
+  test('process highlight ipsum', async () => {
     element.diff.meta_a.content_type = 'application/json';
     element.diff.meta_b.content_type = 'application/json';
 
@@ -180,65 +171,61 @@
     window.hljs = mockHLJS;
     const highlightSpy = sinon.spy(mockHLJS, 'highlight');
     const processNextSpy = sinon.spy(element, '_processNextLine');
-    const processPromise = element.process();
+    await element.process();
 
-    processPromise.then(() => {
-      const linesA = diff.meta_a.lines;
-      const linesB = diff.meta_b.lines;
+    const linesA = diff.meta_a.lines;
+    const linesB = diff.meta_b.lines;
 
-      assert.isTrue(processNextSpy.called);
-      assert.equal(element.baseRanges.length, linesA);
-      assert.equal(element.revisionRanges.length, linesB);
+    assert.isTrue(processNextSpy.called);
+    assert.equal(element.baseRanges.length, linesA);
+    assert.equal(element.revisionRanges.length, linesB);
 
-      assert.equal(highlightSpy.callCount, linesA + linesB);
+    assert.equal(highlightSpy.callCount, linesA + linesB);
 
-      // The first line of both sides have a range.
-      let ranges = [element.baseRanges[0], element.revisionRanges[0]];
-      for (const range of ranges) {
-        assert.equal(range.length, 1);
-        assert.equal(range[0].className,
-            'gr-diff gr-syntax gr-syntax-string');
-        assert.equal(range[0].start, 'lorem '.length);
-        assert.equal(range[0].length, 'ipsum'.length);
-      }
-
-      // There are no ranges from ll.1-12 on the left and ll.1-11 on the
-      // right.
-      ranges = element.baseRanges.slice(1, 12)
-          .concat(element.revisionRanges.slice(1, 11));
-
-      for (const range of ranges) {
-        assert.equal(range.length, 0);
-      }
-
-      // There should be another pair of ranges on l.13 for the left and
-      // l.12 for the right.
-      ranges = [element.baseRanges[13], element.revisionRanges[12]];
-
-      for (const range of ranges) {
-        assert.equal(range.length, 1);
-        assert.equal(range[0].className,
-            'gr-diff gr-syntax gr-syntax-string');
-        assert.equal(range[0].start, 32);
-        assert.equal(range[0].length, 'ipsum'.length);
-      }
-
-      // The next group should have a similar instance on either side.
-
-      let range = element.baseRanges[15];
+    // The first line of both sides have a range.
+    let ranges = [element.baseRanges[0], element.revisionRanges[0]];
+    for (const range of ranges) {
       assert.equal(range.length, 1);
-      assert.equal(range[0].className, 'gr-diff gr-syntax gr-syntax-string');
-      assert.equal(range[0].start, 34);
+      assert.equal(range[0].className,
+          'gr-diff gr-syntax gr-syntax-string');
+      assert.equal(range[0].start, 'lorem '.length);
       assert.equal(range[0].length, 'ipsum'.length);
+    }
 
-      range = element.revisionRanges[14];
+    // There are no ranges from ll.1-12 on the left and ll.1-11 on the
+    // right.
+    ranges = element.baseRanges.slice(1, 12)
+        .concat(element.revisionRanges.slice(1, 11));
+
+    for (const range of ranges) {
+      assert.equal(range.length, 0);
+    }
+
+    // There should be another pair of ranges on l.13 for the left and
+    // l.12 for the right.
+    ranges = [element.baseRanges[13], element.revisionRanges[12]];
+
+    for (const range of ranges) {
       assert.equal(range.length, 1);
-      assert.equal(range[0].className, 'gr-diff gr-syntax gr-syntax-string');
-      assert.equal(range[0].start, 35);
+      assert.equal(range[0].className,
+          'gr-diff gr-syntax gr-syntax-string');
+      assert.equal(range[0].start, 32);
       assert.equal(range[0].length, 'ipsum'.length);
+    }
 
-      done();
-    });
+    // The next group should have a similar instance on either side.
+
+    let range = element.baseRanges[15];
+    assert.equal(range.length, 1);
+    assert.equal(range[0].className, 'gr-diff gr-syntax gr-syntax-string');
+    assert.equal(range[0].start, 34);
+    assert.equal(range[0].length, 'ipsum'.length);
+
+    range = element.revisionRanges[14];
+    assert.equal(range.length, 1);
+    assert.equal(range[0].className, 'gr-diff gr-syntax gr-syntax-string');
+    assert.equal(range[0].start, 35);
+    assert.equal(range[0].length, 'ipsum'.length);
   });
 
   test('init calls cancel', () => {
diff --git a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts
index 90c8383..91882ab 100644
--- a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts
+++ b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts
@@ -29,8 +29,11 @@
 import {fireTitleChange} from '../../../utils/event-util';
 import {appContext} from '../../../services/app-context';
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = ListViewMixin(PolymerElement);
+
 @customElement('gr-documentation-search')
-export class GrDocumentationSearch extends ListViewMixin(PolymerElement) {
+export class GrDocumentationSearch extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts
index 9c196c7..5267b2d 100644
--- a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts
+++ b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts
@@ -47,28 +47,24 @@
   });
 
   suite('list with searches for documentation', () => {
-    setup(done => {
+    setup(async () => {
       documentationSearches = _.times(26, documentationGenerator);
       stubRestApi('getDocumentationSearches').returns(
         Promise.resolve(documentationSearches)
       );
-      element._paramsChanged(value).then(() => {
-        flush(done);
-      });
+      await element._paramsChanged(value);
+      await flush();
     });
 
-    test('test for test repo in the list', done => {
-      flush(() => {
-        assert.equal(
-          element._documentationSearches![0].title,
-          'Gerrit Code Review - REST API Developers Notes1'
-        );
-        assert.equal(
-          element._documentationSearches![0].url,
-          'Documentation/dev-rest-api.html'
-        );
-        done();
-      });
+    test('test for test repo in the list', async () => {
+      assert.equal(
+        element._documentationSearches![0].title,
+        'Gerrit Code Review - REST API Developers Notes1'
+      );
+      assert.equal(
+        element._documentationSearches![0].url,
+        'Documentation/dev-rest-api.html'
+      );
     });
   });
 
@@ -88,14 +84,14 @@
   });
 
   suite('loading', () => {
-    test('correct contents are displayed', () => {
+    test('correct contents are displayed', async () => {
       assert.isTrue(element._loading);
       assert.equal(element.computeLoadingClass(element._loading), 'loading');
       assert.equal(getComputedStyle(element.$.loading).display, 'block');
 
       element._loading = false;
 
-      flush();
+      await flush();
       assert.equal(element.computeLoadingClass(element._loading), '');
       assert.equal(getComputedStyle(element.$.loading).display, 'none');
     });
diff --git a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.ts b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.ts
index b04e6cf..480b8fe 100644
--- a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.ts
+++ b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.ts
@@ -16,8 +16,8 @@
  */
 
 import {sharedStyles} from '../../../styles/shared-styles';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -26,7 +26,7 @@
 }
 
 @customElement('gr-default-editor')
-export class GrDefaultEditor extends GrLitElement {
+export class GrDefaultEditor extends LitElement {
   /**
    * Fired when the content of the editor changes.
    *
diff --git a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.ts b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.ts
index 8a483fc..6b7ce34 100644
--- a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.ts
+++ b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.ts
@@ -18,7 +18,7 @@
 import '../../../test/common-test-setup-karma';
 import './gr-default-editor';
 import {GrDefaultEditor} from './gr-default-editor';
-import {queryAndAssert} from '../../../test/test-utils';
+import {mockPromise, queryAndAssert} from '../../../test/test-utils';
 
 const basicFixture = fixtureFromElement('gr-default-editor');
 
@@ -31,13 +31,15 @@
     await flush();
   });
 
-  test('fires content-change event', done => {
+  test('fires content-change event', async () => {
     const textarea = queryAndAssert<HTMLTextAreaElement>(element, '#textarea');
+    const promise = mockPromise();
     element.addEventListener('content-change', e => {
       assert.equal((e as CustomEvent).detail.value, 'test');
-      done();
+      promise.resolve();
     });
     textarea.value = 'test';
     textarea.dispatchEvent(new Event('input', {bubbles: true, composed: true}));
+    await promise;
   });
 });
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
index 18e6e75..2e1fc21 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
@@ -402,15 +402,13 @@
     });
   });
 
-  test('openOpenDialog', done => {
-    element.openOpenDialog('test/path.cpp').then(() => {
-      assert.isFalse(element.$.openDialog.hasAttribute('hidden'));
-      assert.equal(
-        element.$.openDialog!.querySelector('gr-autocomplete')!.text,
-        'test/path.cpp'
-      );
-      done();
-    });
+  test('openOpenDialog', async () => {
+    await element.openOpenDialog('test/path.cpp');
+    assert.isFalse(element.$.openDialog.hasAttribute('hidden'));
+    assert.equal(
+      element.$.openDialog!.querySelector('gr-autocomplete')!.text,
+      'test/path.cpp'
+    );
   });
 
   test('_getDialogFromEvent', () => {
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
index ce4c313..1255256 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
@@ -18,8 +18,8 @@
 import '../../shared/gr-dropdown/gr-dropdown';
 import {GrEditConstants} from '../gr-edit-constants';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 interface EditAction {
   label: string;
@@ -27,7 +27,7 @@
 }
 
 @customElement('gr-edit-file-controls')
-export class GrEditFileControls extends GrLitElement {
+export class GrEditFileControls extends LitElement {
   /**
    * Fired when an action in the overflow menu is tapped.
    *
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
index f5dc610..6f2d27e 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
@@ -68,8 +68,11 @@
   };
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-editor-view')
-export class GrEditorView extends KeyboardShortcutMixin(PolymerElement) {
+export class GrEditorView extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts
index 5b04a81..f591ab2 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts
@@ -19,7 +19,7 @@
 import {GrEditorView} from './gr-editor-view';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation';
 import {HttpMethod} from '../../../constants/constants';
-import {stubRestApi, stubStorage} from '../../../test/test-utils';
+import {mockPromise, stubRestApi, stubStorage} from '../../../test/test-utils';
 import {
   EditPatchSetNum,
   NumericChangeId,
@@ -348,14 +348,16 @@
     });
   });
 
-  test('_showAlert', done => {
+  test('_showAlert', async () => {
+    const promise = mockPromise();
     element.addEventListener('show-alert', e => {
       assert.deepEqual(e.detail, {message: 'test message'});
       assert.isTrue(e.bubbles);
-      done();
+      promise.resolve();
     });
 
     element._showAlert('test message');
+    await promise;
   });
 
   test('_viewEditInChangeView', () => {
diff --git a/polygerrit-ui/app/elements/gr-app-element.ts b/polygerrit-ui/app/elements/gr-app-element.ts
index 88ed0dc..46a3e84 100644
--- a/polygerrit-ui/app/elements/gr-app-element.ts
+++ b/polygerrit-ui/app/elements/gr-app-element.ts
@@ -102,9 +102,12 @@
   restamp: boolean;
 };
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 // TODO(TS): implement AppElement interface from gr-app-types.ts
 @customElement('gr-app-element')
-export class GrAppElement extends KeyboardShortcutMixin(PolymerElement) {
+export class GrAppElement extends base {
   static get template() {
     return htmlTemplate;
   }
@@ -349,6 +352,7 @@
     this.bindShortcut(Shortcut.TOGGLE_CHANGE_STAR, 's:keydown');
     this.bindShortcut(Shortcut.REFRESH_CHANGE_LIST, 'shift+r:keyup');
     this.bindShortcut(Shortcut.EDIT_TOPIC, 't');
+    this.bindShortcut(Shortcut.OPEN_SUBMIT_DIALOG, 'shift+s');
 
     this.bindShortcut(Shortcut.OPEN_REPLY_DIALOG, 'a:keyup');
     this.bindShortcut(Shortcut.OPEN_DOWNLOAD_DIALOG, 'd:keyup');
diff --git a/polygerrit-ui/app/elements/lit/gr-lit-element.ts b/polygerrit-ui/app/elements/lit/gr-lit-element.ts
deleted file mode 100644
index d0b68e3..0000000
--- a/polygerrit-ui/app/elements/lit/gr-lit-element.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the 'License');
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an 'AS IS' BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {LitElement} from 'lit-element';
-import {Observable, Subject} from 'rxjs';
-import {takeUntil} from 'rxjs/operators';
-
-/**
- * Base class for Gerrit's lit-elements.
- *
- * Adds basic functionality that we want to have available in all Gerrit's
- * components.
- */
-export abstract class GrLitElement extends LitElement {
-  disconnected$ = new Subject();
-
-  /**
-   * Hooks up an element property with an observable. Apart from subscribing it
-   * makes sure that you are unsubscribed when the component is disconnected.
-   * And it requests a template check when a new value comes in.
-   *
-   * Should be called from connectedCallback() such that you will be
-   * re-subscribed when the component is re-connected.
-   *
-   * TODO: Maybe distinctUntilChanged should be applied to obs$?
-   */
-  subscribe<Key extends keyof this>(prop: Key, obs$: Observable<this[Key]>) {
-    obs$.pipe(takeUntil(this.disconnected$)).subscribe(value => {
-      const oldValue = this[prop];
-      this[prop] = value;
-      this.requestUpdate(prop, oldValue);
-    });
-  }
-
-  override disconnectedCallback() {
-    this.disconnected$.next();
-    super.disconnectedCallback();
-  }
-}
diff --git a/polygerrit-ui/app/elements/lit/gr-lit-element_test.ts b/polygerrit-ui/app/elements/lit/gr-lit-element_test.ts
deleted file mode 100644
index 9b7b0e2..0000000
--- a/polygerrit-ui/app/elements/lit/gr-lit-element_test.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../../test/common-test-setup-karma';
-import {html, customElement} from 'lit-element';
-import {GrLitElement} from './gr-lit-element';
-
-@customElement('test-gr-lit-element')
-export class TestGrLitElement extends GrLitElement {
-  render() {
-    return html`<span>test</span>`;
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    'test-gr-lit-element': GrLitElement;
-  }
-}
-
-suite('gr-lit-element test', () => {
-  test('is defined', () => {
-    const el = document.createElement('test-gr-lit-element');
-    assert.instanceOf(el, TestGrLitElement);
-  });
-});
diff --git a/polygerrit-ui/app/elements/lit/subscription-controller.ts b/polygerrit-ui/app/elements/lit/subscription-controller.ts
new file mode 100644
index 0000000..ab4ed64
--- /dev/null
+++ b/polygerrit-ui/app/elements/lit/subscription-controller.ts
@@ -0,0 +1,48 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {ReactiveController, ReactiveControllerHost} from 'lit';
+import {Observable, Subscription} from 'rxjs';
+
+/**
+ * Enables components to simply hook up a property with an Observable like so:
+ *
+ * subscribe(this, obs$, x => (this.prop = x));
+ */
+export function subscribe<T>(
+  host: ReactiveControllerHost,
+  obs$: Observable<T>,
+  setProp: (t: T) => void
+) {
+  host.addController(new SubscriptionController(obs$, setProp));
+}
+
+export class SubscriptionController<T> implements ReactiveController {
+  private sub?: Subscription;
+
+  constructor(
+    private readonly obs$: Observable<T>,
+    private readonly setProp: (t: T) => void
+  ) {}
+
+  hostConnected() {
+    this.sub = this.obs$.subscribe(this.setProp);
+  }
+
+  hostDisconnected() {
+    this.sub?.unsubscribe();
+  }
+}
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.js b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.js
index 8138ff0..1be5e82 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.js
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.js
@@ -51,7 +51,7 @@
   let decorationHookWithSlot;
   let replacementHook;
 
-  setup(done => {
+  setup(async () => {
     resetPlugins();
     container = basicFixture.instantiate();
     pluginApi.install(p => plugin = p, '0.1',
@@ -68,7 +68,7 @@
         'second', 'other-module', {replace: true});
     // Mimic all plugins loaded.
     getPluginLoader().loadPlugins([]);
-    flush(done);
+    await flush();
   });
 
   teardown(() => {
@@ -135,58 +135,52 @@
         });
   });
 
-  test('late registration', done => {
+  test('late registration', async () => {
     plugin.registerCustomComponent('banana', 'noob-noob');
-    flush(() => {
-      const element =
-          container.querySelector('gr-endpoint-decorator[name="banana"]');
-      const module = Array.from(element.root.children).find(
-          element => element.nodeName === 'NOOB-NOOB');
-      assert.isOk(module);
-      done();
-    });
+    await flush();
+    const element =
+        container.querySelector('gr-endpoint-decorator[name="banana"]');
+    const module = Array.from(element.root.children).find(
+        element => element.nodeName === 'NOOB-NOOB');
+    assert.isOk(module);
   });
 
-  test('two modules', done => {
+  test('two modules', async () => {
     plugin.registerCustomComponent('banana', 'mod-one');
     plugin.registerCustomComponent('banana', 'mod-two');
-    flush(() => {
-      const element =
-          container.querySelector('gr-endpoint-decorator[name="banana"]');
-      const module1 = Array.from(element.root.children).find(
-          element => element.nodeName === 'MOD-ONE');
-      assert.isOk(module1);
-      const module2 = Array.from(element.root.children).find(
-          element => element.nodeName === 'MOD-TWO');
-      assert.isOk(module2);
-      done();
-    });
+    await flush();
+    const element =
+        container.querySelector('gr-endpoint-decorator[name="banana"]');
+    const module1 = Array.from(element.root.children).find(
+        element => element.nodeName === 'MOD-ONE');
+    assert.isOk(module1);
+    const module2 = Array.from(element.root.children).find(
+        element => element.nodeName === 'MOD-TWO');
+    assert.isOk(module2);
   });
 
-  test('late param setup', done => {
+  test('late param setup', async () => {
     const element =
         container.querySelector('gr-endpoint-decorator[name="banana"]');
     const param = element.querySelector('gr-endpoint-param');
     param['value'] = undefined;
     plugin.registerCustomComponent('banana', 'noob-noob');
-    flush(() => {
-      let module = Array.from(element.root.children).find(
-          element => element.nodeName === 'NOOB-NOOB');
-      // Module waits for param to be defined.
-      assert.isNotOk(module);
-      const value = {abc: 'def'};
-      param.value = value;
-      flush(() => {
-        module = Array.from(element.root.children).find(
-            element => element.nodeName === 'NOOB-NOOB');
-        assert.isOk(module);
-        assert.strictEqual(module['someParam'], value);
-        done();
-      });
-    });
+    await flush();
+    let module = Array.from(element.root.children).find(
+        element => element.nodeName === 'NOOB-NOOB');
+    // Module waits for param to be defined.
+    assert.isNotOk(module);
+    const value = {abc: 'def'};
+    param.value = value;
+
+    await flush();
+    module = Array.from(element.root.children).find(
+        element => element.nodeName === 'NOOB-NOOB');
+    assert.isOk(module);
+    assert.strictEqual(module['someParam'], value);
   });
 
-  test('param is bound', done => {
+  test('param is bound', async () => {
     const element =
         container.querySelector('gr-endpoint-decorator[name="banana"]');
     const param = element.querySelector('gr-endpoint-param');
@@ -194,13 +188,11 @@
     const value2 = {def: 'abc'};
     param.value = value1;
     plugin.registerCustomComponent('banana', 'noob-noob');
-    flush(() => {
-      const module = Array.from(element.root.children).find(
-          element => element.nodeName === 'NOOB-NOOB');
-      assert.strictEqual(module['someParam'], value1);
-      param.value = value2;
-      assert.strictEqual(module['someParam'], value2);
-      done();
-    });
+    await flush();
+    const module = Array.from(element.root.children).find(
+        element => element.nodeName === 'NOOB-NOOB');
+    assert.strictEqual(module['someParam'], value1);
+    param.value = value2;
+    assert.strictEqual(module['someParam'], value2);
   });
 });
diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.js b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.js
index 6291e64..4e3d657 100644
--- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.js
+++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.js
@@ -19,6 +19,7 @@
 import {addListener} from '@polymer/polymer/lib/utils/gestures.js';
 import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
 import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
+import {mockPromise} from '../../../test/test-utils.js';
 
 Polymer({
   is: 'gr-event-helper-some-element',
@@ -47,11 +48,13 @@
     instance = plugin.eventHelper(element);
   });
 
-  test('onTap()', done => {
+  test('onTap()', async () => {
+    const promise = mockPromise();
     instance.onTap(() => {
-      done();
+      promise.resolve();
     });
     MockInteractions.tap(element);
+    await promise;
   });
 
   test('onTap() cancel', () => {
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.ts b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.ts
index e4d84e3..988366c 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.ts
@@ -14,13 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {customElement, property, PropertyValues} from 'lit-element';
+import {LitElement, PropertyValues} from 'lit';
+import {customElement, property} from 'lit/decorators';
 import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
 import {ServerInfo} from '../../../types/common';
 
 @customElement('gr-plugin-host')
-export class GrPluginHost extends GrLitElement {
+export class GrPluginHost extends LitElement {
   @property({type: Object})
   config?: ServerInfo;
 
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.ts b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.ts
index a186f70..342cf83 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.ts
@@ -36,11 +36,9 @@
     assert.isOk(element);
   });
 
-  test('open uses open() from gr-overlay', done => {
-    element.open().then(() => {
-      assert.isTrue(overlayOpen.called);
-      done();
-    });
+  test('open uses open() from gr-overlay', async () => {
+    await element.open();
+    assert.isTrue(overlayOpen.called);
   });
 
   test('close uses close() from gr-overlay', () => {
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.js b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.js
index 8a7788f..2889333 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.js
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.js
@@ -56,27 +56,23 @@
       instance = new GrPopupInterface(plugin);
     });
 
-    test('open', done => {
-      instance.open().then(api => {
-        assert.strictEqual(api, instance);
-        const manual = document.createElement('div');
-        manual.id = 'foobar';
-        manual.innerHTML = 'manual content';
-        api._getElement().appendChild(manual);
-        flush();
-        assert.equal(
-            container.querySelector('#foobar').textContent, 'manual content');
-        done();
-      });
+    test('open', async () => {
+      const api = await instance.open();
+      assert.strictEqual(api, instance);
+      const manual = document.createElement('div');
+      manual.id = 'foobar';
+      manual.innerHTML = 'manual content';
+      api._getElement().appendChild(manual);
+      await flush();
+      assert.equal(
+          container.querySelector('#foobar').textContent, 'manual content');
     });
 
-    test('close', done => {
-      instance.open().then(api => {
-        assert.isTrue(api._getElement().node.opened);
-        api.close();
-        assert.isFalse(api._getElement().node.opened);
-        done();
-      });
+    test('close', async () => {
+      const api = await instance.open();
+      assert.isTrue(api._getElement().node.opened);
+      api.close();
+      assert.isFalse(api._getElement().node.opened);
     });
   });
 
@@ -85,21 +81,16 @@
       instance = new GrPopupInterface(plugin, 'gr-user-test-popup');
     });
 
-    test('open', done => {
-      instance.open().then(api => {
-        assert.isNotNull(
-            container.querySelector('gr-user-test-popup'));
-        done();
-      });
+    test('open', async () => {
+      await instance.open();
+      assert.isNotNull(container.querySelector('gr-user-test-popup'));
     });
 
-    test('close', done => {
-      instance.open().then(api => {
-        assert.isTrue(api._getElement().node.opened);
-        api.close();
-        assert.isFalse(api._getElement().node.opened);
-        done();
-      });
+    test('close', async () => {
+      const api = await instance.open();
+      assert.isTrue(api._getElement().node.opened);
+      api.close();
+      assert.isFalse(api._getElement().node.opened);
     });
   });
 });
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
index 4a4862b..ab27b1e 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
@@ -157,7 +157,7 @@
       statusStub = stubRestApi('setAccountStatus').returns(Promise.resolve());
     });
 
-    test('name', done => {
+    test('name', async () => {
       assert.isTrue(element.nameMutable);
       assert.isFalse(element.hasUnsavedChanges);
 
@@ -167,18 +167,15 @@
       assert.isFalse(statusChangedSpy.called);
       assert.isTrue(element.hasUnsavedChanges);
 
-      element.save().then(() => {
-        assert.isFalse(usernameStub.called);
-        assert.isTrue(nameStub.called);
-        assert.isFalse(statusStub.called);
-        nameStub.lastCall.returnValue.then(() => {
-          assert.equal(nameStub.lastCall.args[0], 'new name');
-          done();
-        });
-      });
+      await element.save();
+      assert.isFalse(usernameStub.called);
+      assert.isTrue(nameStub.called);
+      assert.isFalse(statusStub.called);
+      await nameStub.lastCall.returnValue;
+      assert.equal(nameStub.lastCall.args[0], 'new name');
     });
 
-    test('username', done => {
+    test('username', async () => {
       element.set('_account.username', '');
       element._hasUsernameChange = false;
       assert.isTrue(element.usernameMutable);
@@ -189,18 +186,15 @@
       assert.isFalse(statusChangedSpy.called);
       assert.isTrue(element.hasUnsavedChanges);
 
-      element.save().then(() => {
-        assert.isTrue(usernameStub.called);
-        assert.isFalse(nameStub.called);
-        assert.isFalse(statusStub.called);
-        usernameStub.lastCall.returnValue.then(() => {
-          assert.equal(usernameStub.lastCall.args[0], 'new username');
-          done();
-        });
-      });
+      await element.save();
+      assert.isTrue(usernameStub.called);
+      assert.isFalse(nameStub.called);
+      assert.isFalse(statusStub.called);
+      await usernameStub.lastCall.returnValue;
+      assert.equal(usernameStub.lastCall.args[0], 'new username');
     });
 
-    test('status', done => {
+    test('status', async () => {
       assert.isFalse(element.hasUnsavedChanges);
 
       element.set('_account.status', 'new status');
@@ -209,15 +203,12 @@
       assert.isTrue(statusChangedSpy.called);
       assert.isTrue(element.hasUnsavedChanges);
 
-      element.save().then(() => {
-        assert.isFalse(usernameStub.called);
-        assert.isTrue(statusStub.called);
-        assert.isFalse(nameStub.called);
-        statusStub.lastCall.returnValue.then(() => {
-          assert.equal(statusStub.lastCall.args[0], 'new status');
-          done();
-        });
-      });
+      await element.save();
+      assert.isFalse(usernameStub.called);
+      assert.isTrue(statusStub.called);
+      assert.isFalse(nameStub.called);
+      await statusStub.lastCall.returnValue;
+      assert.equal(statusStub.lastCall.args[0], 'new status');
     });
   });
 
@@ -239,7 +230,7 @@
       stubRestApi('setAccountUsername').returns(Promise.resolve());
     });
 
-    test('set name and status', done => {
+    test('set name and status', async () => {
       assert.isTrue(element.nameMutable);
       assert.isFalse(element.hasUnsavedChanges);
 
@@ -253,16 +244,13 @@
 
       assert.isTrue(element.hasUnsavedChanges);
 
-      element.save().then(() => {
-        assert.isTrue(statusStub.called);
-        assert.isTrue(nameStub.called);
+      await element.save();
+      assert.isTrue(statusStub.called);
+      assert.isTrue(nameStub.called);
 
-        assert.equal(nameStub.lastCall.args[0], 'new name');
+      assert.equal(nameStub.lastCall.args[0], 'new name');
 
-        assert.equal(statusStub.lastCall.args[0], 'new status');
-
-        done();
-      });
+      assert.equal(statusStub.lastCall.args[0], 'new status');
     });
   });
 
@@ -277,7 +265,7 @@
       statusStub = stubRestApi('setAccountStatus').returns(Promise.resolve());
     });
 
-    test('read full name but set status', done => {
+    test('read full name but set status', async () => {
       const section = element.$.nameSection;
       const displaySpan = section.querySelectorAll('.value')[0];
       const inputSpan = section.querySelectorAll('.value')[1];
@@ -296,13 +284,10 @@
 
       assert.isTrue(element.hasUnsavedChanges);
 
-      element.save().then(() => {
-        assert.isTrue(statusStub.called);
-        statusStub.lastCall.returnValue.then(() => {
-          assert.equal(statusStub.lastCall.args[0], 'new status');
-          done();
-        });
-      });
+      await element.save();
+      assert.isTrue(statusStub.called);
+      await statusStub.lastCall.returnValue;
+      assert.equal(statusStub.lastCall.args[0], 'new status');
     });
   });
 
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts
index 41fc71a..eca38d9 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts
@@ -20,7 +20,8 @@
 import {appContext} from '../../../services/app-context';
 import {formStyles} from '../../../styles/gr-form-styles';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {css, customElement, html, property, LitElement} from 'lit-element';
+import {css, html, LitElement} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 @customElement('gr-agreements-list')
 export class GrAgreementsList extends LitElement {
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts
index 6c942b0..b79e448 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts
@@ -26,8 +26,11 @@
 import {ServerInfo} from '../../../types/common';
 import {appContext} from '../../../services/app-context';
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = ChangeTableMixin(PolymerElement);
+
 @customElement('gr-change-table-editor')
-export class GrChangeTableEditor extends ChangeTableMixin(PolymerElement) {
+export class GrChangeTableEditor extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
index 76047f4..5b757e6 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
@@ -16,6 +16,7 @@
  */
 
 import '@polymer/iron-input/iron-input';
+import '../../../styles/gr-font-styles';
 import '../../../styles/gr-form-styles';
 import '../../../styles/shared-styles';
 import '../../shared/gr-button/gr-button';
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_html.ts b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_html.ts
index 4800e5b..ce95ccb 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_html.ts
@@ -17,6 +17,9 @@
 import {html} from '@polymer/polymer/lib/utils/html-tag';
 
 export const htmlTemplate = html`
+  <style include="gr-font-styles">
+    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+  </style>
   <style include="shared-styles">
     h1 {
       margin-bottom: var(--spacing-m);
diff --git a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.js b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.js
index 8820748..ba736a2 100644
--- a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.js
+++ b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.js
@@ -17,7 +17,7 @@
 
 import '../../../test/common-test-setup-karma.js';
 import './gr-gpg-editor.js';
-import {stubRestApi} from '../../../test/test-utils.js';
+import {mockPromise, stubRestApi} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-gpg-editor');
 
@@ -25,7 +25,7 @@
   let element;
   let keys;
 
-  setup(done => {
+  setup(async () => {
     const fingerprint1 = '0192 723D 42D1 0C5B 32A6  E1E0 9350 9E4B AFC8 A49B';
     const fingerprint2 = '0196 723D 42D1 0C5B 32A6  E1E0 9350 9E4B AFC8 A49B';
     keys = {
@@ -55,7 +55,8 @@
 
     element = basicFixture.instantiate();
 
-    element.loadData().then(() => { flush(done); });
+    await element.loadData();
+    await flush();
   });
 
   test('renders', () => {
@@ -70,7 +71,7 @@
     assert.equal(cells[0].textContent, 'AED9B59C');
   });
 
-  test('remove key', done => {
+  test('remove key', async () => {
     const lastKey = keys[Object.keys(keys)[1]];
 
     const saveStub = stubRestApi('deleteAccountGPGKey')
@@ -91,13 +92,11 @@
     assert.isTrue(element.hasUnsavedChanges);
     assert.isFalse(saveStub.called);
 
-    element.save().then(() => {
-      assert.isTrue(saveStub.called);
-      assert.equal(saveStub.lastCall.args[0], Object.keys(keys)[1]);
-      assert.equal(element._keysToRemove.length, 0);
-      assert.isFalse(element.hasUnsavedChanges);
-      done();
-    });
+    await element.save();
+    assert.isTrue(saveStub.called);
+    assert.equal(saveStub.lastCall.args[0], Object.keys(keys)[1]);
+    assert.equal(element._keysToRemove.length, 0);
+    assert.isFalse(element.hasUnsavedChanges);
   });
 
   test('show key', () => {
@@ -113,7 +112,7 @@
     assert.isTrue(openSpy.called);
   });
 
-  test('add key', done => {
+  test('add key', async () => {
     const newKeyString =
         '-----BEGIN PGP PUBLIC KEY BLOCK-----' +
         '\nVersion: BCPG v1.52\n\t<key 3>';
@@ -138,11 +137,12 @@
     assert.isFalse(element.$.addButton.disabled);
     assert.isFalse(element.$.newKey.disabled);
 
+    const promise = mockPromise();
     element._handleAddKey().then(() => {
       assert.isTrue(element.$.addButton.disabled);
       assert.isFalse(element.$.newKey.disabled);
       assert.equal(element._keys.length, 2);
-      done();
+      promise.resolve();
     });
 
     assert.isTrue(element.$.addButton.disabled);
@@ -150,9 +150,10 @@
 
     assert.isTrue(addStub.called);
     assert.deepEqual(addStub.lastCall.args[0], {add: [newKeyString]});
+    await promise;
   });
 
-  test('add invalid key', done => {
+  test('add invalid key', async () => {
     const newKeyString = 'not even close to valid';
 
     const addStub = stubRestApi(
@@ -164,11 +165,12 @@
     assert.isFalse(element.$.addButton.disabled);
     assert.isFalse(element.$.newKey.disabled);
 
+    const promise = mockPromise();
     element._handleAddKey().then(() => {
       assert.isFalse(element.$.addButton.disabled);
       assert.isFalse(element.$.newKey.disabled);
       assert.equal(element._keys.length, 2);
-      done();
+      promise.resolve();
     });
 
     assert.isTrue(element.$.addButton.disabled);
@@ -176,6 +178,7 @@
 
     assert.isTrue(addStub.called);
     assert.deepEqual(addStub.lastCall.args[0], {add: [newKeyString]});
+    await promise;
   });
 });
 
diff --git a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.ts b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.ts
index 2757454..8f1706d 100644
--- a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.ts
+++ b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.ts
@@ -20,8 +20,8 @@
 import {appContext} from '../../../services/app-context';
 import {formStyles} from '../../../styles/gr-form-styles';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, state} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, state} from 'lit/decorators';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -29,7 +29,7 @@
   }
 }
 @customElement('gr-group-list')
-export class GrGroupList extends GrLitElement {
+export class GrGroupList extends LitElement {
   @state()
   protected _groups: GroupInfo[] = [];
 
diff --git a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.ts b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.ts
index 3dce295..a1534af 100644
--- a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.ts
@@ -28,7 +28,7 @@
   let element: GrGroupList;
   let groups: GroupInfo[];
 
-  setup(done => {
+  setup(async () => {
     groups = [
       {
         url: 'some url',
@@ -58,9 +58,8 @@
 
     element = basicFixture.instantiate();
 
-    element.loadData().then(() => {
-      flush(done);
-    });
+    await element.loadData();
+    await flush();
   });
 
   test('renders', async () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.ts b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.ts
index ccf8a01..6354c9a 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.ts
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.ts
@@ -21,8 +21,8 @@
 import {appContext} from '../../../services/app-context';
 import {formStyles} from '../../../styles/gr-form-styles';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property, query} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property, query} from 'lit/decorators';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -31,7 +31,7 @@
 }
 
 @customElement('gr-http-password')
-export class GrHttpPassword extends GrLitElement {
+export class GrHttpPassword extends LitElement {
   @query('#generatedPasswordOverlay')
   generatedPasswordOverlay?: GrOverlay;
 
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.ts b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.ts
index 004f18f..eab8d2e 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.ts
@@ -35,7 +35,7 @@
   let account: AccountDetailInfo;
   let config: ServerInfo;
 
-  setup(done => {
+  setup(async () => {
     account = {...createAccountDetailWithId(), username: 'user name'};
     config = createServerInfo();
 
@@ -43,9 +43,8 @@
     stubRestApi('getConfig').returns(Promise.resolve(config));
 
     element = basicFixture.instantiate();
-    element.loadData().then(() => {
-      flush(done);
-    });
+    await element.loadData();
+    await flush();
   });
 
   test('generate password', () => {
@@ -76,12 +75,10 @@
     assert.isNull(element._passwordUrl);
   });
 
-  test('with http_password_url', done => {
+  test('with http_password_url', async () => {
     config.auth.http_password_url = 'http://example.com/';
-    element.loadData().then(() => {
-      assert.isNotNull(element._passwordUrl);
-      assert.equal(element._passwordUrl, config.auth.http_password_url);
-      done();
-    });
+    await element.loadData();
+    assert.isNotNull(element._passwordUrl);
+    assert.equal(element._passwordUrl, config.auth.http_password_url);
   });
 });
diff --git a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.ts b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.ts
index d1a4f8f..9d8dcc5 100644
--- a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.ts
@@ -88,13 +88,11 @@
     assert.isTrue(element.filterIdentities(ids[1]));
   });
 
-  test('delete id', done => {
+  test('delete id', async () => {
     element._idName = 'mailto:gerrit2@example.com';
     const loadDataStub = sinon.stub(element, 'loadData');
-    element._handleDeleteItemConfirm().then(() => {
-      assert.isTrue(loadDataStub.called);
-      done();
-    });
+    await element._handleDeleteItemConfirm();
+    assert.isTrue(loadDataStub.called);
   });
 
   test('_handleDeleteItem opens modal', () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.js b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.js
index 19852d9..1ca4852 100644
--- a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.js
+++ b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.js
@@ -46,7 +46,7 @@
     MockInteractions.tap(button);
   }
 
-  setup(done => {
+  setup(async () => {
     element = basicFixture.instantiate();
     menu = [
       {url: '/first/url', name: 'first name', target: '_blank'},
@@ -55,7 +55,7 @@
     ];
     element.set('menuItems', menu);
     flush$0();
-    flush(done);
+    await flush();
   });
 
   test('renders', () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.ts b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.ts
index 6c21e58..07a2e51 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.ts
@@ -93,38 +93,34 @@
     return promise;
   }
 
-  test('fires the close event on close', done => {
-    close().then(done);
+  test('fires the close event on close', async () => {
+    await close();
   });
 
-  test('fires the close event on save', done => {
-    close(() =>
+  test('fires the close event on save', async () => {
+    await close(() =>
       MockInteractions.tap(queryAndAssert(element, '#saveButton'))
-    ).then(done);
+    );
   });
 
-  test('saves account details', done => {
-    flush(() => {
-      element.$.name.value = 'new name';
+  test('saves account details', async () => {
+    await flush();
+    element.$.name.value = 'new name';
 
-      element.set('_account.username', '');
-      element._hasUsernameChange = false;
-      assert.isTrue(element._usernameMutable);
+    element.set('_account.username', '');
+    element._hasUsernameChange = false;
+    assert.isTrue(element._usernameMutable);
 
-      element.set('_username', 'new username');
+    element.set('_username', 'new username');
 
-      // Nothing should be committed yet.
-      assert.equal(account.name, 'name');
-      assert.isNotOk(account.username);
+    // Nothing should be committed yet.
+    assert.equal(account.name, 'name');
+    assert.isNotOk(account.username);
 
-      // Save and verify new values are committed.
-      save()
-        .then(() => {
-          assert.equal(account.name, 'new name');
-          assert.equal(account.username, 'new username');
-        })
-        .then(done);
-    });
+    // Save and verify new values are committed.
+    await save();
+    assert.equal(account.name, 'new name');
+    assert.equal(account.username, 'new username');
   });
 
   test('save btn disabled', () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.ts
index ecb02eb..64409d5 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.ts
@@ -14,9 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
+import {formStyles} from '../../../styles/gr-form-styles';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -25,7 +25,7 @@
 }
 
 @customElement('gr-settings-item')
-export class GrSettingsItem extends GrLitElement {
+export class GrSettingsItem extends LitElement {
   @property({type: String})
   anchor?: string;
 
@@ -34,6 +34,7 @@
 
   static override get styles() {
     return [
+      formStyles,
       css`
         :host {
           display: block;
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.ts
index 0748379..9e4ea0a 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.ts
@@ -16,8 +16,8 @@
  */
 import {pageNavStyles} from '../../../styles/gr-page-nav-styles';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {customElement, html, property} from 'lit-element';
+import {LitElement, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -26,7 +26,7 @@
 }
 
 @customElement('gr-settings-menu-item')
-export class GrSettingsMenuItem extends GrLitElement {
+export class GrSettingsMenuItem extends LitElement {
   @property({type: String})
   href?: string;
 
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
index 91b2ed6..453bc3f 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
@@ -16,6 +16,7 @@
  */
 import '@polymer/iron-input/iron-input';
 import '@polymer/paper-toggle-button/paper-toggle-button';
+import '../../../styles/gr-font-styles';
 import '../../../styles/gr-form-styles';
 import '../../../styles/gr-menu-page-styles';
 import '../../../styles/gr-page-nav-styles';
@@ -136,8 +137,11 @@
   };
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = ChangeTableMixin(PolymerElement);
+
 @customElement('gr-settings-view')
-export class GrSettingsView extends ChangeTableMixin(PolymerElement) {
+export class GrSettingsView extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_html.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_html.ts
index 167417d..1ed0d57 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_html.ts
@@ -17,6 +17,9 @@
 import {html} from '@polymer/polymer/lib/utils/html-tag';
 
 export const htmlTemplate = html`
+  <style include="gr-font-styles">
+    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+  </style>
   <style include="shared-styles">
     :host {
       color: var(--primary-text-color);
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.ts
index 19aeccb..26705ee 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.ts
@@ -93,7 +93,7 @@
     );
   }
 
-  setup(done => {
+  setup(async () => {
     account = {
       ...createAccountDetailWithId(123),
       name: 'user name',
@@ -126,7 +126,8 @@
     element = basicFixture.instantiate();
 
     // Allow the element to render.
-    element._testOnly_loadingPromise?.then(done);
+    if (element._testOnly_loadingPromise)
+      await element._testOnly_loadingPromise;
   });
 
   test('theme changing', () => {
@@ -164,7 +165,7 @@
     assert.equal(titleChangedStub.getCall(0).args[0].detail.title, 'Settings');
   });
 
-  test('user preferences', done => {
+  test('user preferences', async () => {
     // Rendered with the expected preferences selected.
     assert.equal(
       Number(
@@ -269,14 +270,12 @@
     });
 
     // Save the change.
-    element._handleSavePreferences().then(() => {
-      assert.isFalse(element._prefsChanged);
-      assert.isFalse(element._menuChanged);
-      done();
-    });
+    await element._handleSavePreferences();
+    assert.isFalse(element._prefsChanged);
+    assert.isFalse(element._menuChanged);
   });
 
-  test('publish comments on push', done => {
+  test('publish comments on push', async () => {
     const publishCommentsOnPush = valueOf(
       'Publish comments on push',
       'preferences'
@@ -292,14 +291,12 @@
     });
 
     // Save the change.
-    element._handleSavePreferences().then(() => {
-      assert.isFalse(element._prefsChanged);
-      assert.isFalse(element._menuChanged);
-      done();
-    });
+    await element._handleSavePreferences();
+    assert.isFalse(element._prefsChanged);
+    assert.isFalse(element._menuChanged);
   });
 
-  test('set new changes work-in-progress', done => {
+  test('set new changes work-in-progress', async () => {
     const newChangesWorkInProgress = valueOf(
       'Set new changes to "work in progress" by default',
       'preferences'
@@ -315,14 +312,12 @@
     });
 
     // Save the change.
-    element._handleSavePreferences().then(() => {
-      assert.isFalse(element._prefsChanged);
-      assert.isFalse(element._menuChanged);
-      done();
-    });
+    await element._handleSavePreferences();
+    assert.isFalse(element._prefsChanged);
+    assert.isFalse(element._menuChanged);
   });
 
-  test('menu', done => {
+  test('menu', async () => {
     assert.isFalse(element._menuChanged);
     assert.isFalse(element._prefsChanged);
 
@@ -349,12 +344,10 @@
       return Promise.resolve(new Response());
     });
 
-    element._handleSaveMenu().then(() => {
-      assert.isFalse(element._menuChanged);
-      assert.isFalse(element._prefsChanged);
-      assertMenusEqual(element.prefs.my, element._localMenu);
-      done();
-    });
+    await element._handleSaveMenu();
+    assert.isFalse(element._menuChanged);
+    assert.isFalse(element._prefsChanged);
+    assertMenusEqual(element.prefs.my, element._localMenu);
   });
 
   test('add email validation', () => {
@@ -388,7 +381,7 @@
     assert.isFalse(addEmailStub.called);
   });
 
-  test('add email does save valid', done => {
+  test('add email does save valid', async () => {
     const addEmailStub = stubAddAccountEmail(201);
 
     assert.isFalse(element._addingEmail);
@@ -401,13 +394,11 @@
     assert.isTrue(addEmailStub.called);
 
     assert.isTrue(addEmailStub.called);
-    addEmailStub.lastCall.returnValue.then(() => {
-      assert.isOk(element._lastSentVerificationEmail);
-      done();
-    });
+    await addEmailStub.lastCall.returnValue;
+    assert.isOk(element._lastSentVerificationEmail);
   });
 
-  test('add email does not set last-email if error', done => {
+  test('add email does not set last-email if error', async () => {
     const addEmailStub = stubAddAccountEmail(500);
 
     assert.isNotOk(element._lastSentVerificationEmail);
@@ -416,10 +407,8 @@
     element._handleAddEmailButton();
 
     assert.isTrue(addEmailStub.called);
-    addEmailStub.lastCall.returnValue.then(() => {
-      assert.isNotOk(element._lastSentVerificationEmail);
-      done();
-    });
+    await addEmailStub.lastCall.returnValue;
+    assert.isNotOk(element._lastSentVerificationEmail);
   });
 
   test('emails are loaded without emailToken', () => {
@@ -450,7 +439,7 @@
     assert.isTrue(element.prefs.legacycid_in_change_table);
   });
 
-  test('reset menu item back to default', done => {
+  test('reset menu item back to default', async () => {
     const originalMenu = {
       ...createDefaultPreferences(),
       my: [
@@ -471,10 +460,8 @@
 
     element.set('_localMenu', updatedMenu);
 
-    element._handleResetMenuButton().then(() => {
-      assertMenusEqual(element._localMenu, originalMenu.my);
-      done();
-    });
+    await element._handleResetMenuButton();
+    assertMenusEqual(element._localMenu, originalMenu.my);
   });
 
   test('test that reset button is called', () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.js b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.js
index 8f99baa..cd2c1df 100644
--- a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.js
+++ b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.js
@@ -17,7 +17,7 @@
 
 import '../../../test/common-test-setup-karma.js';
 import './gr-ssh-editor.js';
-import {stubRestApi} from '../../../test/test-utils.js';
+import {mockPromise, stubRestApi} from '../../../test/test-utils.js';
 
 const basicFixture = fixtureFromElement('gr-ssh-editor');
 
@@ -25,7 +25,7 @@
   let element;
   let keys;
 
-  setup(done => {
+  setup(async () => {
     keys = [{
       seq: 1,
       ssh_public_key: 'ssh-rsa <key 1> comment-one@machine-one',
@@ -46,7 +46,8 @@
 
     element = basicFixture.instantiate();
 
-    element.loadData().then(() => { flush(done); });
+    await element.loadData();
+    await flush();
   });
 
   test('renders', () => {
@@ -61,7 +62,7 @@
     assert.equal(cells[0].textContent, keys[1].comment);
   });
 
-  test('remove key', done => {
+  test('remove key', async () => {
     const lastKey = keys[1];
 
     const saveStub = stubRestApi('deleteAccountSSHKey')
@@ -82,13 +83,11 @@
     assert.isTrue(element.hasUnsavedChanges);
     assert.isFalse(saveStub.called);
 
-    element.save().then(() => {
-      assert.isTrue(saveStub.called);
-      assert.equal(saveStub.lastCall.args[0], lastKey.seq);
-      assert.equal(element._keysToRemove.length, 0);
-      assert.isFalse(element.hasUnsavedChanges);
-      done();
-    });
+    await element.save();
+    assert.isTrue(saveStub.called);
+    assert.equal(saveStub.lastCall.args[0], lastKey.seq);
+    assert.equal(element._keysToRemove.length, 0);
+    assert.isFalse(element.hasUnsavedChanges);
   });
 
   test('show key', () => {
@@ -104,7 +103,7 @@
     assert.isTrue(openSpy.called);
   });
 
-  test('add key', done => {
+  test('add key', async () => {
     const newKeyString = 'ssh-rsa <key 3> comment-three@machine-three';
     const newKeyObject = {
       seq: 3,
@@ -124,11 +123,12 @@
     assert.isFalse(element.$.addButton.disabled);
     assert.isFalse(element.$.newKey.disabled);
 
+    const promise = mockPromise();
     element._handleAddKey().then(() => {
       assert.isTrue(element.$.addButton.disabled);
       assert.isFalse(element.$.newKey.disabled);
       assert.equal(element._keys.length, 3);
-      done();
+      promise.resolve();
     });
 
     assert.isTrue(element.$.addButton.disabled);
@@ -136,9 +136,10 @@
 
     assert.isTrue(addStub.called);
     assert.equal(addStub.lastCall.args[0], newKeyString);
+    await promise;
   });
 
-  test('add invalid key', done => {
+  test('add invalid key', async () => {
     const newKeyString = 'not even close to valid';
 
     const addStub = stubRestApi(
@@ -150,11 +151,12 @@
     assert.isFalse(element.$.addButton.disabled);
     assert.isFalse(element.$.newKey.disabled);
 
+    const promise = mockPromise();
     element._handleAddKey().then(() => {
       assert.isFalse(element.$.addButton.disabled);
       assert.isFalse(element.$.newKey.disabled);
       assert.equal(element._keys.length, 2);
-      done();
+      promise.resolve();
     });
 
     assert.isTrue(element.$.addButton.disabled);
@@ -162,6 +164,7 @@
 
     assert.isTrue(addStub.called);
     assert.equal(addStub.lastCall.args[0], newKeyString);
+    await promise;
   });
 });
 
diff --git a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.ts b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.ts
index cb4b86d..c0580f6 100644
--- a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.ts
@@ -28,7 +28,7 @@
 suite('gr-watched-projects-editor tests', () => {
   let element: GrWatchedProjectsEditor;
 
-  setup(done => {
+  setup(async () => {
     const projects = [
       {
         project: 'project a',
@@ -69,9 +69,8 @@
 
     element = basicFixture.instantiate();
 
-    element.loadData().then(() => {
-      flush(done);
-    });
+    await element.loadData();
+    await flush();
   });
 
   test('renders', () => {
@@ -102,27 +101,21 @@
     assert.equal(checkedKeys[2], 'notify_all_comments');
   });
 
-  test('_getProjectSuggestions empty', done => {
-    element._getProjectSuggestions('nonexistent').then(projects => {
-      assert.equal(projects.length, 0);
-      done();
-    });
+  test('_getProjectSuggestions empty', async () => {
+    const projects = await element._getProjectSuggestions('nonexistent');
+    assert.equal(projects.length, 0);
   });
 
-  test('_getProjectSuggestions non-empty', done => {
-    element._getProjectSuggestions('the project').then(projects => {
-      assert.equal(projects.length, 1);
-      assert.equal(projects[0].name, 'the project');
-      done();
-    });
+  test('_getProjectSuggestions non-empty', async () => {
+    const projects = await element._getProjectSuggestions('the project');
+    assert.equal(projects.length, 1);
+    assert.equal(projects[0].name, 'the project');
   });
 
-  test('_getProjectSuggestions non-empty with two letter project', done => {
-    element._getProjectSuggestions('th').then(projects => {
-      assert.equal(projects.length, 1);
-      assert.equal(projects[0].name, 'the project');
-      done();
-    });
+  test('_getProjectSuggestions non-empty with two letter project', async () => {
+    const projects = await element._getProjectSuggestions('th');
+    assert.equal(projects.length, 1);
+    assert.equal(projects[0].name, 'the project');
   });
 
   test('_canAddProject', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
index a3650b1..66103fd 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
@@ -19,12 +19,12 @@
 import '../gr-icons/gr-icons';
 import {AccountInfo, ChangeInfo} from '../../../types/common';
 import {appContext} from '../../../services/app-context';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property} from 'lit-element';
-import {classMap} from 'lit-html/directives/class-map';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
+import {classMap} from 'lit/directives/class-map';
 
 @customElement('gr-account-chip')
-export class GrAccountChip extends GrLitElement {
+export class GrAccountChip extends LitElement {
   /**
    * Fired to indicate a key was pressed while this chip was focused.
    *
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
index 16f5e8c..f746c29 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
@@ -26,14 +26,14 @@
 import {fireEvent} from '../../../utils/event-util';
 import {isInvolved} from '../../../utils/change-util';
 import {ShowAlertEventDetail} from '../../../types/events';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property, state} from 'lit-element';
-import {classMap} from 'lit-html/directives/class-map';
+import {LitElement, css, html} from 'lit';
+import {customElement, property, state} from 'lit/decorators';
+import {classMap} from 'lit/directives/class-map';
 import {modifierPressed} from '../../../utils/dom-util';
 import {getRemovedByIconClickReason} from '../../../utils/attention-set-util';
 
 @customElement('gr-account-label')
-export class GrAccountLabel extends GrLitElement {
+export class GrAccountLabel extends LitElement {
   @property({type: Object})
   account?: AccountInfo;
 
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
index 97b3b34..b302bee 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
@@ -18,11 +18,11 @@
 import '../gr-account-label/gr-account-label';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation';
 import {AccountInfo, ChangeInfo} from '../../../types/common';
-import {css, customElement, html, property} from 'lit-element';
-import {GrLitElement} from '../../lit/gr-lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 @customElement('gr-account-link')
-export class GrAccountLink extends GrLitElement {
+export class GrAccountLink extends LitElement {
   @property({type: String})
   voteableText?: string;
 
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts
index 220f5fd..895c99b 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts
@@ -18,8 +18,8 @@
 import '../../../styles/shared-styles';
 import {getRootElement} from '../../../scripts/rootElement';
 import {ErrorType} from '../../../types/types';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {customElement, property, css, html} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 import {sharedStyles} from '../../../styles/shared-styles';
 
 declare global {
@@ -29,7 +29,7 @@
 }
 
 @customElement('gr-alert')
-export class GrAlert extends GrLitElement {
+export class GrAlert extends LitElement {
   static get styles() {
     return [
       sharedStyles,
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
index ac6c99e..a629d0e 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
@@ -52,11 +52,14 @@
   selected: HTMLElement | null;
 }
 
-@customElement('gr-autocomplete-dropdown')
-export class GrAutocompleteDropdown extends IronFitMixin(
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = IronFitMixin(
   KeyboardShortcutMixin(PolymerElement),
   IronFitBehavior as IronFitBehavior
-) {
+);
+
+@customElement('gr-autocomplete-dropdown')
+export class GrAutocompleteDropdown extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
index 7595a17..3f52650 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
@@ -64,8 +64,11 @@
 export type AutocompleteCommitEvent =
   CustomEvent<AutocompleteCommitEventDetail>;
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-autocomplete')
-export class GrAutocomplete extends KeyboardShortcutMixin(PolymerElement) {
+export class GrAutocomplete extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.ts b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.ts
index 6dd3a00..33bf6c6 100644
--- a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.ts
+++ b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.ts
@@ -18,11 +18,11 @@
 import {getPluginLoader} from '../gr-js-api-interface/gr-plugin-loader';
 import {AccountInfo} from '../../../types/common';
 import {appContext} from '../../../services/app-context';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 @customElement('gr-avatar')
-export class GrAvatar extends GrLitElement {
+export class GrAvatar extends LitElement {
   @property({type: Object})
   account?: AccountInfo;
 
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts b/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
index af185f2..1ece10a 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
@@ -37,8 +37,11 @@
   }
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = TooltipMixin(PolymerElement);
+
 @customElement('gr-button')
-export class GrButton extends TooltipMixin(PolymerElement) {
+export class GrButton extends base {
   static get template() {
     return htmlTemplate;
   }
@@ -81,12 +84,12 @@
   @property({
     computed: 'computeAriaDisabled(disabled, loading)',
     reflectToAttribute: true,
-    type: Boolean,
+    type: String,
   })
-  ariaDisabled!: boolean;
+  ariaDisabled!: string;
 
   computeAriaDisabled() {
-    return this._disabled;
+    return this._disabled ? 'true' : 'false';
   }
 
   computePaperButtonClass(voteChip?: boolean) {
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
index 7a4885b..39a87a2 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
@@ -54,10 +54,13 @@
 import {CustomKeyboardEvent} from '../../../types/events';
 import {LineNumber, FILE} from '../../diff/gr-diff/gr-diff-line';
 import {GrButton} from '../gr-button/gr-button';
-import {KnownExperimentId} from '../../../services/flags/flags';
 import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
 import {RenderPreferences} from '../../../api/diff';
-import {check, assertIsDefined} from '../../../utils/common-util';
+import {
+  check,
+  assertIsDefined,
+  queryAndAssert,
+} from '../../../utils/common-util';
 import {fireAlert, waitForEventOnce} from '../../../utils/event-util';
 import {GrSyntaxLayer} from '../../diff/gr-syntax-layer/gr-syntax-layer';
 import {StorageLocation} from '../../../services/storage/gr-storage';
@@ -76,8 +79,11 @@
   };
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-comment-thread')
-export class GrCommentThread extends KeyboardShortcutMixin(PolymerElement) {
+export class GrCommentThread extends base {
   // KeyboardShortcutMixin Not used in this element rather other elements tests
 
   static get template() {
@@ -205,8 +211,6 @@
 
   private readonly reporting = appContext.reportingService;
 
-  private readonly flagsService = appContext.flagsService;
-
   private readonly commentsService = appContext.commentsService;
 
   readonly storage = appContext.storageService;
@@ -267,6 +271,7 @@
       const resizeObserver = new ResizeObserver(
         (_entries: ResizeObserverEntry[], observer: ResizeObserver) => {
           if (this.offsetHeight > 0) {
+            queryAndAssert<HTMLDivElement>(this, '.comment-box').focus();
             this.scrollIntoView();
           }
           observer.unobserve(this);
@@ -312,7 +317,6 @@
     draft.__editing = true;
     draft.unresolved = unresolved === false ? unresolved : true;
     this.commentsService.addDraft(draft);
-    this.push('comments', draft);
   }
 
   _getDiffUrlForPath(
@@ -353,9 +357,7 @@
   _getLayers(diff?: DiffInfo) {
     if (!diff) return [];
     const layers = [];
-    if (this.flagsService.isEnabled(KnownExperimentId.TOKEN_HIGHLIGHTING)) {
-      layers.push(new TokenHighlightLayer(this));
-    }
+    layers.push(new TokenHighlightLayer(this));
     layers.push(this.syntaxLayer);
     return layers;
   }
@@ -548,14 +550,11 @@
     }
 
     this.commentsService.addDraft(reply);
-    this.push('comments', reply);
 
     if (!isEditing) {
-      // Allow the reply to render in the dom-repeat.
-      setTimeout(() => {
-        const commentEl = this._commentElWithDraftID(reply.__draftID);
-        if (commentEl) commentEl.save();
-      }, 1);
+      assertIsDefined(this.changeNum, 'changeNum');
+      assertIsDefined(this.patchNum, 'patchNum');
+      this.restApiService.saveDiffDraft(this.changeNum, this.patchNum, reply);
     }
   }
 
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts
index 5c01467..bf9a9dc 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts
@@ -143,7 +143,10 @@
     <h3 class="assistive-tech-only">
       [[_computeAriaHeading(_orderedComments)]]
     </h3>
-    <div class$="[[_computeHostClass(unresolved, isRobotComment)]] comment-box">
+    <div
+      class$="[[_computeHostClass(unresolved, isRobotComment)]] comment-box"
+      tabindex="0"
+    >
       <template
         id="commentList"
         is="dom-repeat"
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts
index 5f860f4..ecd9731 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts
@@ -23,7 +23,6 @@
   sortComments,
   UIComment,
   UIRobot,
-  isDraft,
   UIDraft,
 } from '../../../utils/comment-util';
 import {GrCommentThread} from './gr-comment-thread';
@@ -46,11 +45,13 @@
 } from '@polymer/iron-test-helpers/mock-interactions';
 import {html} from '@polymer/polymer/lib/utils/html-tag';
 import {
+  mockPromise,
   stubComments,
   stubReporting,
   stubRestApi,
 } from '../../../test/test-utils';
 import {_testOnly_resetState} from '../../../services/comments/comments-model';
+import {SinonStub} from 'sinon';
 
 const basicFixture = fixtureFromElement('gr-comment-thread');
 
@@ -235,16 +236,14 @@
       assert.equal(element._hideActions(showActions, robotComment), true);
     });
 
-    test('setting project name loads the project config', done => {
+    test('setting project name loads the project config', async () => {
       const projectName = 'foo/bar/baz' as RepoName;
       const getProjectStub = stubRestApi('getProjectConfig').returns(
         Promise.resolve({} as ConfigInfo)
       );
       element.projectName = projectName;
-      flush(() => {
-        assert.isTrue(getProjectStub.calledWithExactly(projectName as never));
-        done();
-      });
+      await flush();
+      assert.isTrue(getProjectStub.calledWithExactly(projectName as never));
     });
 
     test('optionally show file path', () => {
@@ -314,10 +313,12 @@
 
 suite('comment action tests with unresolved thread', () => {
   let element: GrCommentThread;
-
+  let addDraftServiceStub: SinonStub;
+  let saveDiffDraftStub: SinonStub;
   setup(() => {
+    addDraftServiceStub = stubComments('addDraft');
     stubRestApi('getLoggedIn').returns(Promise.resolve(false));
-    stubRestApi('saveDiffDraft').returns(
+    saveDiffDraftStub = stubRestApi('saveDiffDraft').returns(
       Promise.resolve({
         headers: {} as Headers,
         redirected: false,
@@ -373,12 +374,11 @@
     const replyBtn = element.$.replyBtn;
     tap(replyBtn);
     flush();
-
-    const drafts = element._orderedComments.filter(c => isDraft(c));
-    assert.equal(drafts.length, 1);
-    assert.notOk(drafts[0].message, 'message should be empty');
+    const draft = addDraftServiceStub.firstCall.args[0];
+    assert.isOk(draft);
+    assert.notOk(draft.message, 'message should be empty');
     assert.equal(
-      drafts[0].in_reply_to,
+      draft.in_reply_to,
       'baf0414d_60047215' as UrlEncodedCommentId as UrlEncodedCommentId
     );
     assert.isTrue(reportStub.calledOnce);
@@ -393,11 +393,10 @@
     tap(quoteBtn);
     flush();
 
-    const drafts = element._orderedComments.filter(c => isDraft(c));
-    assert.equal(drafts.length, 1);
-    assert.equal(drafts[0].message, '> is this a crossover episode!?\n\n');
+    const draft = addDraftServiceStub.firstCall.args[0];
+    assert.equal(draft.message, '> is this a crossover episode!?\n\n');
     assert.equal(
-      drafts[0].in_reply_to,
+      draft.in_reply_to,
       'baf0414d_60047215' as UrlEncodedCommentId as UrlEncodedCommentId
     );
     assert.isTrue(reportStub.calledOnce);
@@ -427,20 +426,16 @@
     tap(quoteBtn);
     flush();
 
-    const drafts = element._orderedComments.filter(c => isDraft(c));
-    assert.equal(drafts.length, 1);
+    const draft = addDraftServiceStub.firstCall.args[0];
     assert.equal(
-      drafts[0].message,
+      draft.message,
       '> is this a crossover episode!?\n> It might be!\n\n'
     );
-    assert.equal(
-      drafts[0].in_reply_to,
-      'baf0414d_60047215' as UrlEncodedCommentId
-    );
+    assert.equal(draft.in_reply_to, 'baf0414d_60047215' as UrlEncodedCommentId);
     assert.isTrue(reportStub.calledOnce);
   });
 
-  test('ack', done => {
+  test('ack', async () => {
     const reportStub = stubReporting('recordDraftInteraction');
     element.changeNum = 42 as NumericChangeId;
     element.patchNum = 1 as PatchSetNum;
@@ -451,22 +446,17 @@
     const ackBtn = element.shadowRoot?.querySelector('#ackBtn');
     assert.isOk(ackBtn);
     tap(ackBtn!);
-    flush(() => {
-      const drafts = element.comments.filter(c => isDraft(c));
-      assert.equal(drafts.length, 1);
-      assert.equal(drafts[0].message, 'Ack');
-      assert.equal(
-        drafts[0].in_reply_to,
-        'baf0414d_60047215' as UrlEncodedCommentId
-      );
-      assert.equal(drafts[0].unresolved, false);
-      assert.isTrue(reportStub.calledOnce);
-      done();
-    });
+    await flush();
+    const draft = addDraftServiceStub.firstCall.args[0];
+    assert.equal(draft.message, 'Ack');
+    assert.equal(draft.in_reply_to, 'baf0414d_60047215' as UrlEncodedCommentId);
+    assert.equal(draft.unresolved, false);
+    assert.isTrue(reportStub.calledOnce);
   });
 
-  test('done', done => {
+  test('done', async () => {
     const reportStub = stubReporting('recordDraftInteraction');
+    assert.isFalse(saveDiffDraftStub.called);
     element.changeNum = 42 as NumericChangeId;
     element.patchNum = 1 as PatchSetNum;
     const commentEl = element.shadowRoot?.querySelector('gr-comment');
@@ -475,21 +465,16 @@
     const doneBtn = element.shadowRoot?.querySelector('#doneBtn');
     assert.isOk(doneBtn);
     tap(doneBtn!);
-    flush(() => {
-      const drafts = element.comments.filter(c => isDraft(c));
-      assert.equal(drafts.length, 1);
-      assert.equal(drafts[0].message, 'Done');
-      assert.equal(
-        drafts[0].in_reply_to,
-        'baf0414d_60047215' as UrlEncodedCommentId
-      );
-      assert.isFalse(drafts[0].unresolved);
-      assert.isTrue(reportStub.calledOnce);
-      done();
-    });
+    await flush();
+    const draft = addDraftServiceStub.firstCall.args[0];
+    assert.equal(draft.message, 'Done');
+    assert.equal(draft.in_reply_to, 'baf0414d_60047215' as UrlEncodedCommentId);
+    assert.isFalse(draft.unresolved);
+    assert.isTrue(reportStub.calledOnce);
+    assert.isTrue(saveDiffDraftStub.called);
   });
 
-  test('save', done => {
+  test('save', async () => {
     element.changeNum = 42 as NumericChangeId;
     element.patchNum = 1 as PatchSetNum;
     element.path = '/path/to/file.txt';
@@ -498,30 +483,28 @@
 
     element.shadowRoot?.querySelector('gr-comment')?._fireSave();
 
-    flush(() => {
-      assert.equal(element.rootId, 'baf0414d_60047215' as UrlEncodedCommentId);
-      done();
-    });
+    await flush();
+    assert.equal(element.rootId, 'baf0414d_60047215' as UrlEncodedCommentId);
   });
 
-  test('please fix', done => {
+  test('please fix', async () => {
     element.changeNum = 42 as NumericChangeId;
     element.patchNum = 1 as PatchSetNum;
     const commentEl = element.shadowRoot?.querySelector('gr-comment');
     assert.ok(commentEl);
+    const promise = mockPromise();
     commentEl!.addEventListener('create-fix-comment', () => {
-      const drafts = element._orderedComments.filter(c => isDraft(c));
-      assert.equal(drafts.length, 1);
+      const draft = addDraftServiceStub.firstCall.args[0];
       assert.equal(
-        drafts[0].message,
+        draft.message,
         '> is this a crossover episode!?\n\nPlease fix.'
       );
       assert.equal(
-        drafts[0].in_reply_to,
+        draft.in_reply_to,
         'baf0414d_60047215' as UrlEncodedCommentId
       );
-      assert.isTrue(drafts[0].unresolved);
-      done();
+      assert.isTrue(draft.unresolved);
+      promise.resolve();
     });
     commentEl!.dispatchEvent(
       new CustomEvent('create-fix-comment', {
@@ -530,9 +513,10 @@
         bubbles: false,
       })
     );
+    await promise;
   });
 
-  test('discard', done => {
+  test('discard', async () => {
     element.changeNum = 42 as NumericChangeId;
     element.patchNum = 1 as PatchSetNum;
     element.path = '/path/to/file.txt';
@@ -545,37 +529,43 @@
         'it’s pronouced jiff, not giff'
       )
     );
-    flush();
+    await flush();
 
     const draftEl = element.root?.querySelectorAll('gr-comment')[1];
     assert.ok(draftEl);
     draftEl?._fireSave(); // tell the model about the draft
+    const promise = mockPromise();
     draftEl!.addEventListener('comment-discard', () => {
-      flush();
       assert.isTrue(deleteDraftStub.called);
-      done();
+      promise.resolve();
     });
     draftEl!._fireDiscard();
+    await promise;
   });
 
-  test('discard with a single comment still fires event with previous rootId', done => {
+  test('discard with a single comment still fires event with previous rootId', async () => {
     element.changeNum = 42 as NumericChangeId;
     element.patchNum = 1 as PatchSetNum;
     element.path = '/path/to/file.txt';
     element.comments = [];
     element.addOrEditDraft(1 as LineNumber);
+    const draft = addDraftServiceStub.firstCall.args[0];
+    element.comments = [draft];
     flush();
     const rootId = element.rootId;
     assert.isOk(rootId);
-
+    flush();
     const draftEl = element.root?.querySelectorAll('gr-comment')[0];
     assert.ok(draftEl);
     const deleteDraftStub = stubComments('deleteDraft');
+    const promise = mockPromise();
     draftEl!.addEventListener('comment-discard', () => {
       assert.isTrue(deleteDraftStub.called);
-      done();
+      promise.resolve();
     });
     draftEl!._fireDiscard();
+    await promise;
+    assert.isTrue(deleteDraftStub.called);
   });
 
   test('comment-update', () => {
@@ -644,6 +634,8 @@
 
     test('comment in_reply_to is either null or most recent comment', () => {
       element._createReplyComment('dummy', true);
+      const draft = addDraftServiceStub.firstCall.args[0];
+      element.comments = [...element.comments, draft];
       flush();
       assert.equal(element._orderedComments.length, 5);
       assert.equal(
@@ -655,6 +647,8 @@
     test('resolvable comments', () => {
       assert.isFalse(element.unresolved);
       element._createReplyComment('dummy', true, true);
+      const draft = addDraftServiceStub.firstCall.args[0];
+      element.comments = [...element.comments, draft];
       flush();
       assert.isTrue(element.unresolved);
     });
@@ -703,18 +697,23 @@
 
   test('addDraft sets unresolved state correctly', () => {
     let unresolved = true;
+    let draft;
     element.comments = [];
     element.path = 'abcd';
     element.addDraft(undefined, undefined, unresolved);
-    assert.equal(element.comments[0].unresolved, true);
+    draft = addDraftServiceStub.lastCall.args[0];
+    assert.equal(draft.unresolved, true);
 
     unresolved = false; // comment should get added as actually resolved.
     element.comments = [];
     element.addDraft(undefined, undefined, unresolved);
-    assert.equal(element.comments[0].unresolved, false);
+    draft = addDraftServiceStub.lastCall.args[0];
+    assert.equal(draft.unresolved, false);
+
     element.comments = [];
     element.addDraft();
-    assert.equal(element.comments[0].unresolved, true);
+    draft = addDraftServiceStub.lastCall.args[0];
+    assert.equal(draft.unresolved, true);
   });
 
   test('_newDraft with root', () => {
@@ -734,13 +733,16 @@
     element.comments = [];
     element.path = 'abcd';
     element.addOrEditDraft(1);
+    const draft = addDraftServiceStub.firstCall.args[0];
+    element.comments = [draft];
+    flush();
     assert.equal(element.comments.length, 1);
     // Mock a submitted comment.
     element.comments[0].id = (element.comments[0] as UIDraft)
       .__draftID as UrlEncodedCommentId;
     delete (element.comments[0] as UIDraft).__draft;
     element.addOrEditDraft(1);
-    assert.equal(element.comments.length, 2);
+    assert.equal(addDraftServiceStub.callCount, 2);
   });
 
   test('unresolved label', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index 2c2d454..418cd0e 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -100,8 +100,11 @@
   };
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-comment')
-export class GrComment extends KeyboardShortcutMixin(PolymerElement) {
+export class GrComment extends base {
   static get template() {
     return htmlTemplate;
   }
@@ -125,6 +128,12 @@
    */
 
   /**
+   * Fired when this comment is edited.
+   *
+   * @event comment-edit
+   */
+
+  /**
    * Fired when this comment is saved.
    *
    * @event comment-save
@@ -548,6 +557,17 @@
     return {comment: this.comment, patchNum: this.patchNum};
   }
 
+  _fireEdit() {
+    if (this.comment) this.commentsService.editDraft(this.comment);
+    this.dispatchEvent(
+      new CustomEvent('comment-edit', {
+        detail: this._getEventPayload(),
+        composed: true,
+        bubbles: true,
+      })
+    );
+  }
+
   _fireSave() {
     if (this.comment) this.commentsService.addDraft(this.comment);
     this.dispatchEvent(
@@ -659,7 +679,11 @@
   }
 
   _messageTextChanged(_: string, oldValue: string) {
-    if (!this.comment || (this.comment && this.comment.id)) {
+    // Only store comments that are being edited in local storage.
+    if (
+      !this.comment ||
+      (this.comment.id && (!isDraft(this.comment) || !this.comment.__editing))
+    ) {
       return;
     }
 
@@ -667,33 +691,32 @@
       ? this.comment.patch_set
       : this._getPatchNum();
     const {path, line, range} = this.comment;
-    if (path) {
-      this.storeTask = debounce(
-        this.storeTask,
-        () => {
-          const message = this._messageText;
-          if (this.changeNum === undefined) {
-            throw new Error('undefined changeNum');
-          }
-          const commentLocation: StorageLocation = {
-            changeNum: this.changeNum,
-            patchNum,
-            path,
-            line,
-            range,
-          };
+    if (!path) return;
+    this.storeTask = debounce(
+      this.storeTask,
+      () => {
+        const message = this._messageText;
+        if (this.changeNum === undefined) {
+          throw new Error('undefined changeNum');
+        }
+        const commentLocation: StorageLocation = {
+          changeNum: this.changeNum,
+          patchNum,
+          path,
+          line,
+          range,
+        };
 
-          if ((!message || !message.length) && oldValue) {
-            // If the draft has been modified to be empty, then erase the storage
-            // entry.
-            this.storage.eraseDraftComment(commentLocation);
-          } else {
-            this.storage.setDraftComment(commentLocation, message);
-          }
-        },
-        STORAGE_DEBOUNCE_INTERVAL
-      );
-    }
+        if ((!message || !message.length) && oldValue) {
+          // If the draft has been modified to be empty, then erase the storage
+          // entry.
+          this.storage.eraseDraftComment(commentLocation);
+        } else {
+          this.storage.setDraftComment(commentLocation, message);
+        }
+      },
+      STORAGE_DEBOUNCE_INTERVAL
+    );
   }
 
   _handleAnchorClick(e: Event) {
@@ -715,6 +738,7 @@
     e.preventDefault();
     if (this.comment?.message) this._messageText = this.comment.message;
     this.editing = true;
+    this._fireEdit();
     this.reporting.recordDraftInteraction();
   }
 
@@ -722,9 +746,7 @@
     e.preventDefault();
 
     // Ignore saves started while already saving.
-    if (this.disabled) {
-      return;
-    }
+    if (this.disabled) return;
     const timingLabel = this.comment?.id
       ? REPORT_UPDATE_DRAFT
       : REPORT_CREATE_DRAFT;
@@ -737,17 +759,16 @@
 
   _handleCancel(e: Event) {
     e.preventDefault();
-
-    if (
-      !this.comment?.message ||
-      this.comment.message.trim().length === 0 ||
-      !this.comment.id
-    ) {
+    if (!this.comment) return;
+    if (!this.comment.id) {
+      // Ensures we update the discarded draft message before deleting the draft
+      this.set('comment.message', this._messageText);
       this._fireDiscard();
-      return;
+    } else {
+      this.set('comment.__editing', false);
+      this.commentsService.cancelDraft(this.comment);
+      this.editing = false;
     }
-    this._messageText = this.comment.message;
-    this.editing = false;
   }
 
   _fireDiscard() {
@@ -947,9 +968,15 @@
       return;
     }
 
-    // Only apply local drafts to comments that haven't been saved
-    // remotely, and haven't been given a default message already.
-    if (!comment || comment.id || comment.message || !comment.path) {
+    // Only apply local drafts to comments that are drafts and are currently
+    // being edited.
+    if (
+      !comment ||
+      !comment.path ||
+      comment.message ||
+      !isDraft(comment) ||
+      !comment.__editing
+    ) {
       return;
     }
 
@@ -962,7 +989,7 @@
     });
 
     if (draft) {
-      this.set('comment.message', draft.message);
+      this._messageText = draft.message || '';
     }
   }
 
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
index cb09125..54d8ee8 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
@@ -28,6 +28,7 @@
   query,
   isVisible,
   stubReporting,
+  mockPromise,
 } from '../../../test/test-utils';
 import {
   AccountId,
@@ -155,7 +156,7 @@
       });
     });
 
-    test('message is not retrieved from storage when other edits', done => {
+    test('message is not retrieved from storage when missing path', async () => {
       const storageStub = stubStorage('getDraftComment');
       const loadSpy = sinon.spy(element, '_loadLocalDraft');
 
@@ -168,14 +169,34 @@
         },
         line: 5,
       };
-      flush(() => {
-        assert.isTrue(loadSpy.called);
-        assert.isFalse(storageStub.called);
-        done();
-      });
+      await flush();
+      assert.isTrue(loadSpy.called);
+      assert.isFalse(storageStub.called);
     });
 
-    test('message is retrieved from storage when no other edits', done => {
+    test('message is not retrieved from storage when message present', async () => {
+      const storageStub = stubStorage('getDraftComment');
+      const loadSpy = sinon.spy(element, '_loadLocalDraft');
+
+      element.changeNum = 1 as NumericChangeId;
+      element.patchNum = 1 as PatchSetNum;
+      element.comment = {
+        author: {
+          name: 'Mr. Peanutbutter',
+          email: 'tenn1sballchaser@aol.com' as EmailAddress,
+        },
+        message: 'This is a message',
+        line: 5,
+        path: 'test',
+        __editing: true,
+        __draft: true,
+      };
+      await flush();
+      assert.isTrue(loadSpy.called);
+      assert.isFalse(storageStub.called);
+    });
+
+    test('message is retrieved from storage for drafts in edit', async () => {
       const storageStub = stubStorage('getDraftComment');
       const loadSpy = sinon.spy(element, '_loadLocalDraft');
 
@@ -188,12 +209,12 @@
         },
         line: 5,
         path: 'test',
+        __editing: true,
+        __draft: true,
       };
-      flush(() => {
-        assert.isTrue(loadSpy.called);
-        assert.isTrue(storageStub.called);
-        done();
-      });
+      await flush();
+      assert.isTrue(loadSpy.called);
+      assert.isTrue(storageStub.called);
     });
 
     test('_getPatchNum', () => {
@@ -315,7 +336,7 @@
       );
     });
 
-    test('delete comment', done => {
+    test('delete comment', async () => {
       const stub = stubRestApi('deleteComment').returns(
         Promise.resolve({
           id: '1' as UrlEncodedCommentId,
@@ -333,24 +354,21 @@
         )
       );
       tap(queryAndAssert(element, '.action.delete'));
-      flush(() => {
-        openSpy.lastCall.returnValue.then(() => {
-          const dialog = element.confirmDeleteOverlay?.querySelector(
-            '#confirmDeleteComment'
-          ) as GrConfirmDeleteCommentDialog;
-          dialog.message = 'removal reason';
-          element._handleConfirmDeleteComment();
-          assert.isTrue(
-            stub.calledWith(
-              42 as NumericChangeId,
-              1 as PatchSetNum,
-              'baf0414d_60047215' as UrlEncodedCommentId,
-              'removal reason'
-            )
-          );
-          done();
-        });
-      });
+      await flush();
+      await openSpy.lastCall.returnValue;
+      const dialog = element.confirmDeleteOverlay?.querySelector(
+        '#confirmDeleteComment'
+      ) as GrConfirmDeleteCommentDialog;
+      dialog.message = 'removal reason';
+      element._handleConfirmDeleteComment();
+      assert.isTrue(
+        stub.calledWith(
+          42 as NumericChangeId,
+          1 as PatchSetNum,
+          'baf0414d_60047215' as UrlEncodedCommentId,
+          'removal reason'
+        )
+      );
     });
 
     suite('draft update reporting', () => {
@@ -417,6 +435,7 @@
 
     test('edit reports interaction', () => {
       const reportStub = stubReporting('recordDraftInteraction');
+      sinon.stub(element, '_fireEdit');
       element.draft = true;
       flush();
       tap(queryAndAssert(element, '.edit'));
@@ -437,7 +456,7 @@
       assert.isTrue(reportStub.calledOnce);
     });
 
-    test('failed save draft request', done => {
+    test('failed save draft request', async () => {
       element.draft = true;
       element.changeNum = 1 as NumericChangeId;
       element.patchNum = 1 as PatchSetNum;
@@ -449,46 +468,42 @@
         ...createComment(),
         id: 'abc_123' as UrlEncodedCommentId,
       });
-      flush(() => {
-        let args = updateRequestStub.lastCall.args;
-        assert.deepEqual(args, [0, true]);
-        assert.equal(
-          element._getSavingMessage(...args),
-          __testOnly_UNSAVED_MESSAGE
-        );
-        assert.equal(
-          (queryAndAssert(element, '.draftLabel') as HTMLSpanElement).innerText,
-          'DRAFT(Failed to save)'
-        );
-        assert.isTrue(
-          isVisible(queryAndAssert(element, '.save')),
-          'save is visible'
-        );
-        diffDraftStub.returns(Promise.resolve({...new Response(), ok: true}));
-        element._saveDraft({
-          ...createComment(),
-          id: 'abc_123' as UrlEncodedCommentId,
-        });
-        flush(() => {
-          args = updateRequestStub.lastCall.args;
-          assert.deepEqual(args, [0]);
-          assert.equal(element._getSavingMessage(...args), 'All changes saved');
-          assert.equal(
-            (queryAndAssert(element, '.draftLabel') as HTMLSpanElement)
-              .innerText,
-            'DRAFT'
-          );
-          assert.isFalse(
-            isVisible(queryAndAssert(element, '.save')),
-            'save is not visible'
-          );
-          assert.isFalse(element._unableToSave);
-          done();
-        });
+      await flush();
+      let args = updateRequestStub.lastCall.args;
+      assert.deepEqual(args, [0, true]);
+      assert.equal(
+        element._getSavingMessage(...args),
+        __testOnly_UNSAVED_MESSAGE
+      );
+      assert.equal(
+        (queryAndAssert(element, '.draftLabel') as HTMLSpanElement).innerText,
+        'DRAFT(Failed to save)'
+      );
+      assert.isTrue(
+        isVisible(queryAndAssert(element, '.save')),
+        'save is visible'
+      );
+      diffDraftStub.returns(Promise.resolve({...new Response(), ok: true}));
+      element._saveDraft({
+        ...createComment(),
+        id: 'abc_123' as UrlEncodedCommentId,
       });
+      await flush();
+      args = updateRequestStub.lastCall.args;
+      assert.deepEqual(args, [0]);
+      assert.equal(element._getSavingMessage(...args), 'All changes saved');
+      assert.equal(
+        (queryAndAssert(element, '.draftLabel') as HTMLSpanElement).innerText,
+        'DRAFT'
+      );
+      assert.isFalse(
+        isVisible(queryAndAssert(element, '.save')),
+        'save is not visible'
+      );
+      assert.isFalse(element._unableToSave);
     });
 
-    test('failed save draft request with promise failure', done => {
+    test('failed save draft request with promise failure', async () => {
       element.draft = true;
       element.changeNum = 1 as NumericChangeId;
       element.patchNum = 1 as PatchSetNum;
@@ -500,43 +515,39 @@
         ...createComment(),
         id: 'abc_123' as UrlEncodedCommentId,
       });
-      flush(() => {
-        let args = updateRequestStub.lastCall.args;
-        assert.deepEqual(args, [0, true]);
-        assert.equal(
-          element._getSavingMessage(...args),
-          __testOnly_UNSAVED_MESSAGE
-        );
-        assert.equal(
-          (queryAndAssert(element, '.draftLabel') as HTMLSpanElement).innerText,
-          'DRAFT(Failed to save)'
-        );
-        assert.isTrue(
-          isVisible(queryAndAssert(element, '.save')),
-          'save is visible'
-        );
-        diffDraftStub.returns(Promise.resolve({...new Response(), ok: true}));
-        element._saveDraft({
-          ...createComment(),
-          id: 'abc_123' as UrlEncodedCommentId,
-        });
-        flush(() => {
-          args = updateRequestStub.lastCall.args;
-          assert.deepEqual(args, [0]);
-          assert.equal(element._getSavingMessage(...args), 'All changes saved');
-          assert.equal(
-            (queryAndAssert(element, '.draftLabel') as HTMLSpanElement)
-              .innerText,
-            'DRAFT'
-          );
-          assert.isFalse(
-            isVisible(queryAndAssert(element, '.save')),
-            'save is not visible'
-          );
-          assert.isFalse(element._unableToSave);
-          done();
-        });
+      await flush();
+      let args = updateRequestStub.lastCall.args;
+      assert.deepEqual(args, [0, true]);
+      assert.equal(
+        element._getSavingMessage(...args),
+        __testOnly_UNSAVED_MESSAGE
+      );
+      assert.equal(
+        (queryAndAssert(element, '.draftLabel') as HTMLSpanElement).innerText,
+        'DRAFT(Failed to save)'
+      );
+      assert.isTrue(
+        isVisible(queryAndAssert(element, '.save')),
+        'save is visible'
+      );
+      diffDraftStub.returns(Promise.resolve({...new Response(), ok: true}));
+      element._saveDraft({
+        ...createComment(),
+        id: 'abc_123' as UrlEncodedCommentId,
       });
+      await flush();
+      args = updateRequestStub.lastCall.args;
+      assert.deepEqual(args, [0]);
+      assert.equal(element._getSavingMessage(...args), 'All changes saved');
+      assert.equal(
+        (queryAndAssert(element, '.draftLabel') as HTMLSpanElement).innerText,
+        'DRAFT'
+      );
+      assert.isFalse(
+        isVisible(queryAndAssert(element, '.save')),
+        'save is not visible'
+      );
+      assert.isFalse(element._unableToSave);
     });
   });
 
@@ -579,10 +590,11 @@
         __draftID: 'temp_draft_id',
         path: '/path/to/file',
         line: 5,
+        id: undefined,
       };
     });
 
-    test('button visibility states', () => {
+    test('button visibility states', async () => {
       element.showActions = false;
       assert.isTrue(
         queryAndAssert(element, '.humanActions').hasAttribute('hidden')
@@ -600,7 +612,7 @@
       );
 
       element.draft = true;
-      flush();
+      await flush();
       assert.isTrue(
         isVisible(queryAndAssert(element, '.edit')),
         'edit is visible'
@@ -629,7 +641,7 @@
       );
 
       element.editing = true;
-      flush();
+      await flush();
       assert.isFalse(
         isVisible(queryAndAssert(element, '.edit')),
         'edit is not visible'
@@ -659,7 +671,7 @@
 
       element.draft = false;
       element.editing = false;
-      flush();
+      await flush();
       assert.isFalse(
         isVisible(queryAndAssert(element, '.edit')),
         'edit is not visible'
@@ -686,7 +698,7 @@
       element.comment!.id = 'foo' as UrlEncodedCommentId;
       element.draft = true;
       element.editing = true;
-      flush();
+      await flush();
       assert.isTrue(
         isVisible(queryAndAssert(element, '.cancel')),
         'cancel is visible'
@@ -726,14 +738,14 @@
       element.set(['comment', 'robot_run_id'], 'text');
       element.editing = false;
       element.collapsed = false;
-      flush();
+      await flush();
       assert.isTrue(
         queryAndAssert(element, '.robotRun.link').textContent === 'Run Details'
       );
 
       // A robot comment with run ID and url should display a link.
       element.set(['comment', 'url'], '/path/to/run');
-      flush();
+      await flush();
       assert.notEqual(
         getComputedStyle(queryAndAssert(element, '.robotRun.link')).display,
         'none'
@@ -745,7 +757,8 @@
       );
     });
 
-    test('collapsible drafts', () => {
+    test('collapsible drafts', async () => {
+      const fireEditStub = sinon.stub(element, '_fireEdit');
       assert.isTrue(element.collapsed);
       assert.isFalse(
         isVisible(queryAndAssert(element, 'gr-formatted-text')),
@@ -780,9 +793,10 @@
       // When the edit button is pressed, should still see the actions
       // and also textarea
       element.draft = true;
-      flush();
+      await flush();
       tap(queryAndAssert(element, '.edit'));
-      flush();
+      await flush();
+      assert.isTrue(fireEditStub.called);
       assert.isFalse(element.collapsed);
       assert.isFalse(
         isVisible(queryAndAssert(element, 'gr-formatted-text')),
@@ -837,7 +851,7 @@
       );
     });
 
-    test('robot comment layout', done => {
+    test('robot comment layout', async () => {
       const comment = {
         robot_id: 'happy_robot_id' as RobotId,
         url: '/robot/comment',
@@ -849,36 +863,32 @@
       };
       element.comment = comment;
       element.collapsed = false;
-      flush(() => {
-        let runIdMessage;
-        runIdMessage = queryAndAssert(element, '.runIdMessage') as HTMLElement;
-        assert.isFalse((runIdMessage as HTMLElement).hidden);
+      await flush;
+      let runIdMessage;
+      runIdMessage = queryAndAssert(element, '.runIdMessage') as HTMLElement;
+      assert.isFalse((runIdMessage as HTMLElement).hidden);
 
-        const runDetailsLink = queryAndAssert(
-          element,
-          '.robotRunLink'
-        ) as HTMLAnchorElement;
-        assert.isTrue(
-          runDetailsLink.href.indexOf((element.comment as UIRobot).url!) !== -1
-        );
+      const runDetailsLink = queryAndAssert(
+        element,
+        '.robotRunLink'
+      ) as HTMLAnchorElement;
+      assert.isTrue(
+        runDetailsLink.href.indexOf((element.comment as UIRobot).url!) !== -1
+      );
 
-        const robotServiceName = queryAndAssert(element, '.robotName');
-        assert.equal(robotServiceName.textContent?.trim(), 'happy_robot_id');
+      const robotServiceName = queryAndAssert(element, '.robotName');
+      assert.equal(robotServiceName.textContent?.trim(), 'happy_robot_id');
 
-        const authorName = queryAndAssert(element, '.robotId');
-        assert.isTrue(
-          (authorName as HTMLDivElement).innerText === 'Happy Robot'
-        );
+      const authorName = queryAndAssert(element, '.robotId');
+      assert.isTrue((authorName as HTMLDivElement).innerText === 'Happy Robot');
 
-        element.collapsed = true;
-        flush();
-        runIdMessage = queryAndAssert(element, '.runIdMessage');
-        assert.isTrue((runIdMessage as HTMLDivElement).hidden);
-        done();
-      });
+      element.collapsed = true;
+      await flush();
+      runIdMessage = queryAndAssert(element, '.runIdMessage');
+      assert.isTrue((runIdMessage as HTMLDivElement).hidden);
     });
 
-    test('author name fallback to email', done => {
+    test('author name fallback to email', async () => {
       const comment = {
         url: '/robot/comment',
         author: {
@@ -888,17 +898,16 @@
       };
       element.comment = comment;
       element.collapsed = false;
-      flush(() => {
-        const authorName = queryAndAssert(
-          queryAndAssert(element, 'gr-account-label'),
-          'span.name'
-        ) as HTMLSpanElement;
-        assert.equal(authorName.innerText.trim(), 'test@test.com');
-        done();
-      });
+      await flush();
+      const authorName = queryAndAssert(
+        queryAndAssert(element, 'gr-account-label'),
+        'span.name'
+      ) as HTMLSpanElement;
+      assert.equal(authorName.innerText.trim(), 'test@test.com');
     });
 
-    test('patchset level comment', done => {
+    test('patchset level comment', async () => {
+      const fireEditStub = sinon.stub(element, '_fireEdit');
       const comment = {
         ...element.comment,
         path: SpecialFilePath.PATCHSET_LEVEL_COMMENTS,
@@ -906,25 +915,26 @@
         range: undefined,
       };
       element.comment = comment;
-      flush();
+      await flush();
       tap(queryAndAssert(element, '.edit'));
+      assert.isTrue(fireEditStub.called);
       assert.isTrue(element.editing);
 
       element._messageText = 'hello world';
       const eraseMessageDraftSpy = spyStorage('eraseDraftComment');
       const mockEvent = {...new Event('click'), preventDefault: sinon.stub()};
       element._handleSave(mockEvent);
-      flush(() => {
-        assert.isTrue(eraseMessageDraftSpy.called);
-        done();
-      });
+      await flush();
+      assert.isTrue(eraseMessageDraftSpy.called);
     });
 
-    test('draft creation/cancellation', done => {
+    test('draft creation/cancellation', async () => {
+      const fireEditStub = sinon.stub(element, '_fireEdit');
       assert.isFalse(element.editing);
       element.draft = true;
-      flush();
+      await flush();
       tap(queryAndAssert(element, '.edit'));
+      assert.isTrue(fireEditStub.called);
       assert.isTrue(element.editing);
 
       element.comment!.message = '';
@@ -945,37 +955,41 @@
       element.addEventListener('comment-update', updateStub);
 
       let numDiscardEvents = 0;
+      const promise = mockPromise();
       element.addEventListener('comment-discard', () => {
         numDiscardEvents++;
         assert.isFalse(eraseMessageDraftSpy.called);
         if (numDiscardEvents === 2) {
           assert.isFalse(updateStub.called);
-          done();
+          promise.resolve();
         }
       });
       tap(queryAndAssert(element, '.cancel'));
-      flush();
+      await flush();
       element._messageText = '';
       element.editing = true;
-      flush();
+      await flush();
       pressAndReleaseKeyOn(element.textarea!, 27); // esc
+      await promise;
     });
 
-    test('draft discard removes message from storage', done => {
+    test('draft discard removes message from storage', async () => {
       element._messageText = '';
       const eraseMessageDraftSpy = sinon.spy(
         element,
         '_eraseDraftCommentFromStorage'
       );
 
+      const promise = mockPromise();
       element.addEventListener('comment-discard', () => {
         assert.isTrue(eraseMessageDraftSpy.called);
-        done();
+        promise.resolve();
       });
       element._handleDiscard({
         ...new Event('click'),
         preventDefault: sinon.stub(),
       });
+      await promise;
     });
 
     test('storage is cleared only after save success', () => {
@@ -1022,22 +1036,24 @@
       assert.equal(element._computeSaveDisabled('', comment, false), true);
     });
 
-    test('ctrl+s saves comment', done => {
+    test('ctrl+s saves comment', async () => {
+      const promise = mockPromise();
       const stub = sinon.stub(element, 'save').callsFake(() => {
         assert.isTrue(stub.called);
         stub.restore();
-        done();
+        promise.resolve();
         return Promise.resolve();
       });
       element._messageText = 'is that the horse from horsing around??';
       element.editing = true;
-      flush();
+      await flush();
       pressAndReleaseKeyOn(element.textarea!.$.textarea.textarea, 83, 'ctrl'); // 'ctrl + s'
+      await promise;
     });
 
-    test('draft saving/editing', done => {
+    test('draft saving/editing', async () => {
       const dispatchEventStub = sinon.stub(element, 'dispatchEvent');
-
+      const fireEditStub = sinon.stub(element, '_fireEdit');
       const clock: SinonFakeTimers = sinon.useFakeTimers();
       const tickAndFlush = async (repetitions: number) => {
         for (let i = 1; i <= repetitions; i++) {
@@ -1047,8 +1063,9 @@
       };
 
       element.draft = true;
-      flush();
+      await flush();
       tap(queryAndAssert(element, '.edit'));
+      assert.isTrue(fireEditStub.called);
       tickAndFlush(1);
       element._messageText = 'good news, everyone!';
       tickAndFlush(1);
@@ -1056,7 +1073,7 @@
       assert.isTrue(dispatchEventStub.calledTwice);
 
       element._messageText = 'good news, everyone!';
-      flush();
+      await flush();
       assert.isTrue(dispatchEventStub.calledTwice);
 
       tap(queryAndAssert(element, '.save'));
@@ -1066,68 +1083,62 @@
         'Element should be disabled when creating draft.'
       );
 
-      element
-        ._xhrPromise!.then(draft => {
-          const evt = dispatchEventStub.lastCall.args[0] as CustomEvent<{
-            comment: DraftInfo;
-          }>;
-          assert.equal(evt.type, 'comment-save');
+      let draft = await element._xhrPromise!;
+      const evt = dispatchEventStub.lastCall.args[0] as CustomEvent<{
+        comment: DraftInfo;
+      }>;
+      assert.equal(evt.type, 'comment-save');
 
-          const expectedDetail = {
-            comment: {
-              ...createComment(),
-              __draft: true,
-              __draftID: 'temp_draft_id',
-              id: 'baf0414d_40572e03' as UrlEncodedCommentId,
-              line: 5,
-              message: 'saved!',
-              path: '/path/to/file',
-              updated: '2015-12-08 21:52:36.177000000' as Timestamp,
-            },
-            patchNum: 1 as PatchSetNum,
-          };
+      const expectedDetail = {
+        comment: {
+          ...createComment(),
+          __draft: true,
+          __draftID: 'temp_draft_id',
+          id: 'baf0414d_40572e03' as UrlEncodedCommentId,
+          line: 5,
+          message: 'saved!',
+          path: '/path/to/file',
+          updated: '2015-12-08 21:52:36.177000000' as Timestamp,
+        },
+        patchNum: 1 as PatchSetNum,
+      };
 
-          assert.deepEqual(evt.detail, expectedDetail);
-          assert.isFalse(
-            element.disabled,
-            'Element should be enabled when done creating draft.'
-          );
-          assert.equal(draft.message, 'saved!');
-          assert.isFalse(element.editing);
-        })
-        .then(() => {
-          tap(queryAndAssert(element, '.edit'));
-          element._messageText =
-            'You’ll be delivering a package to Chapek 9, ' +
-            'a world where humans are killed on sight.';
-          tap(queryAndAssert(element, '.save'));
-          assert.isTrue(
-            element.disabled,
-            'Element should be disabled when updating draft.'
-          );
-
-          element._xhrPromise!.then(draft => {
-            assert.isFalse(
-              element.disabled,
-              'Element should be enabled when done updating draft.'
-            );
-            assert.equal(draft.message, 'saved!');
-            assert.isFalse(element.editing);
-            dispatchEventStub.restore();
-            done();
-          });
-        });
+      assert.deepEqual(evt.detail, expectedDetail);
+      assert.isFalse(
+        element.disabled,
+        'Element should be enabled when done creating draft.'
+      );
+      assert.equal(draft.message, 'saved!');
+      assert.isFalse(element.editing);
+      tap(queryAndAssert(element, '.edit'));
+      assert.isTrue(fireEditStub.calledTwice);
+      element._messageText =
+        'You’ll be delivering a package to Chapek 9, ' +
+        'a world where humans are killed on sight.';
+      tap(queryAndAssert(element, '.save'));
+      assert.isTrue(
+        element.disabled,
+        'Element should be disabled when updating draft.'
+      );
+      draft = await element._xhrPromise!;
+      assert.isFalse(
+        element.disabled,
+        'Element should be enabled when done updating draft.'
+      );
+      assert.equal(draft.message, 'saved!');
+      assert.isFalse(element.editing);
+      dispatchEventStub.restore();
     });
 
-    test('draft prevent save when disabled', () => {
+    test('draft prevent save when disabled', async () => {
       const saveStub = sinon.stub(element, 'save').returns(Promise.resolve());
       element.showActions = true;
       element.draft = true;
-      flush();
+      await flush();
       tap(element.$.header);
       tap(queryAndAssert(element, '.edit'));
       element._messageText = 'good news, everyone!';
-      flush();
+      await flush();
 
       element.disabled = true;
       tap(queryAndAssert(element, '.save'));
@@ -1138,14 +1149,16 @@
       assert.isTrue(saveStub.calledOnce);
     });
 
-    test('proper event fires on resolve, comment is not saved', done => {
+    test('proper event fires on resolve, comment is not saved', async () => {
       const save = sinon.stub(element, 'save');
+      const promise = mockPromise();
       element.addEventListener('comment-update', e => {
         assert.isTrue(e.detail.comment.unresolved);
         assert.isFalse(save.called);
-        done();
+        promise.resolve();
       });
       tap(queryAndAssert(element, '.resolve input'));
+      await promise;
     });
 
     test('resolved comment state indicated by checkbox', () => {
@@ -1209,7 +1222,7 @@
       });
     });
 
-    test('cancelling an unsaved draft discards, persists in storage', () => {
+    test('cancelling an unsaved draft discards, persists in storage', async () => {
       const clock: SinonFakeTimers = sinon.useFakeTimers();
       const tickAndFlush = async (repetitions: number) => {
         for (let i = 1; i <= repetitions; i++) {
@@ -1230,7 +1243,7 @@
         ...new Event('click'),
         preventDefault: sinon.stub(),
       });
-      flush();
+      await flush();
       assert.isTrue(discardSpy.called);
       assert.isFalse(eraseStub.called);
     });
@@ -1261,19 +1274,21 @@
       assert.isTrue(discardStub.called);
     });
 
-    test('_handleFix fires create-fix event', done => {
+    test('_handleFix fires create-fix event', async () => {
+      const promise = mockPromise();
       element.addEventListener(
         'create-fix-comment',
         (e: CreateFixCommentEvent) => {
           assert.deepEqual(e.detail, element._getEventPayload());
-          done();
+          promise.resolve();
         }
       );
       element.isRobotComment = true;
       element.comments = [element.comment!];
-      flush();
+      await flush();
 
       tap(queryAndAssert(element, '.fix'));
+      await promise;
     });
 
     test('do not show Please Fix button if human reply exists', () => {
@@ -1419,19 +1434,21 @@
       queryAndAssert(element, '.robotActions gr-button');
     });
 
-    test('_handleShowFix fires open-fix-preview event', done => {
+    test('_handleShowFix fires open-fix-preview event', async () => {
+      const promise = mockPromise();
       element.addEventListener('open-fix-preview', e => {
         assert.deepEqual(e.detail, element._getEventPayload());
-        done();
+        promise.resolve();
       });
       element.comment = {
         ...createComment(),
         fix_suggestions: [{...createFixSuggestionInfo()}],
       };
       element.isRobotComment = true;
-      flush();
+      await flush();
 
       tap(queryAndAssert(element, '.show-fix'));
+      await promise;
     });
   });
 
@@ -1449,7 +1466,7 @@
       sinon.restore();
     });
 
-    test('show tip when no cached record', done => {
+    test('show tip when no cached record', async () => {
       element = draftFixture.instantiate() as GrComment;
       const respectfulGetStub = stubStorage('getRespectfulTipVisibility');
       const respectfulSetStub = stubStorage('setRespectfulTipVisibility');
@@ -1457,15 +1474,13 @@
       // fake random
       element.getRandomNum = () => 0;
       element.comment = {__editing: true, __draft: true};
-      flush(() => {
-        assert.isTrue(respectfulGetStub.called);
-        assert.isTrue(respectfulSetStub.called);
-        assert.isTrue(!!queryAndAssert(element, '.respectfulReviewTip'));
-        done();
-      });
+      await flush();
+      assert.isTrue(respectfulGetStub.called);
+      assert.isTrue(respectfulSetStub.called);
+      assert.isTrue(!!queryAndAssert(element, '.respectfulReviewTip'));
     });
 
-    test('add 14-day delays once dismissed', done => {
+    test('add 14-day delays once dismissed', async () => {
       element = draftFixture.instantiate() as GrComment;
       const respectfulGetStub = stubStorage('getRespectfulTipVisibility');
       const respectfulSetStub = stubStorage('setRespectfulTipVisibility');
@@ -1473,20 +1488,18 @@
       // fake random
       element.getRandomNum = () => 0;
       element.comment = {__editing: true, __draft: true};
-      flush(() => {
-        assert.isTrue(respectfulGetStub.called);
-        assert.isTrue(respectfulSetStub.called);
-        assert.isTrue(respectfulSetStub.lastCall.args[0] === undefined);
-        assert.isTrue(!!queryAndAssert(element, '.respectfulReviewTip'));
+      await flush();
+      assert.isTrue(respectfulGetStub.called);
+      assert.isTrue(respectfulSetStub.called);
+      assert.isTrue(respectfulSetStub.lastCall.args[0] === undefined);
+      assert.isTrue(!!queryAndAssert(element, '.respectfulReviewTip'));
 
-        tap(queryAndAssert(element, '.respectfulReviewTip .close'));
-        flush();
-        assert.isTrue(respectfulSetStub.lastCall.args[0] === 14);
-        done();
-      });
+      tap(queryAndAssert(element, '.respectfulReviewTip .close'));
+      flush();
+      assert.isTrue(respectfulSetStub.lastCall.args[0] === 14);
     });
 
-    test('do not show tip when fall out of probability', done => {
+    test('do not show tip when fall out of probability', async () => {
       element = draftFixture.instantiate() as GrComment;
       const respectfulGetStub = stubStorage('getRespectfulTipVisibility');
       const respectfulSetStub = stubStorage('setRespectfulTipVisibility');
@@ -1494,15 +1507,13 @@
       // fake random
       element.getRandomNum = () => 3;
       element.comment = {__editing: true, __draft: true};
-      flush(() => {
-        assert.isTrue(respectfulGetStub.called);
-        assert.isFalse(respectfulSetStub.called);
-        assert.isNotOk(query(element, '.respectfulReviewTip'));
-        done();
-      });
+      await flush();
+      assert.isTrue(respectfulGetStub.called);
+      assert.isFalse(respectfulSetStub.called);
+      assert.isNotOk(query(element, '.respectfulReviewTip'));
     });
 
-    test('show tip when editing changed to true', done => {
+    test('show tip when editing changed to true', async () => {
       element = draftFixture.instantiate() as GrComment;
       const respectfulGetStub = stubStorage('getRespectfulTipVisibility');
       const respectfulSetStub = stubStorage('setRespectfulTipVisibility');
@@ -1510,22 +1521,19 @@
       // fake random
       element.getRandomNum = () => 0;
       element.comment = {__editing: false};
-      flush(() => {
-        assert.isFalse(respectfulGetStub.called);
-        assert.isFalse(respectfulSetStub.called);
-        assert.isNotOk(query(element, '.respectfulReviewTip'));
+      await flush();
+      assert.isFalse(respectfulGetStub.called);
+      assert.isFalse(respectfulSetStub.called);
+      assert.isNotOk(query(element, '.respectfulReviewTip'));
 
-        element.editing = true;
-        flush(() => {
-          assert.isTrue(respectfulGetStub.called);
-          assert.isTrue(respectfulSetStub.called);
-          assert.isTrue(!!queryAndAssert(element, '.respectfulReviewTip'));
-          done();
-        });
-      });
+      element.editing = true;
+      await flush();
+      assert.isTrue(respectfulGetStub.called);
+      assert.isTrue(respectfulSetStub.called);
+      assert.isTrue(!!queryAndAssert(element, '.respectfulReviewTip'));
     });
 
-    test('no tip when cached record', done => {
+    test('no tip when cached record', async () => {
       element = draftFixture.instantiate() as GrComment;
       const respectfulGetStub = stubStorage('getRespectfulTipVisibility');
       const respectfulSetStub = stubStorage('setRespectfulTipVisibility');
@@ -1533,12 +1541,10 @@
       // fake random
       element.getRandomNum = () => 0;
       element.comment = {__editing: true, __draft: true};
-      flush(() => {
-        assert.isTrue(respectfulGetStub.called);
-        assert.isFalse(respectfulSetStub.called);
-        assert.isNotOk(query(element, '.respectfulReviewTip'));
-        done();
-      });
+      await flush();
+      assert.isTrue(respectfulGetStub.called);
+      assert.isFalse(respectfulSetStub.called);
+      assert.isNotOk(query(element, '.respectfulReviewTip'));
     });
   });
 });
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
index b85703a..2e95bd0 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
@@ -19,10 +19,10 @@
 import '../gr-icons/gr-icons';
 import {IronIconElement} from '@polymer/iron-icon';
 import {assertIsDefined, queryAndAssert} from '../../../utils/common-util';
-import {classMap} from 'lit-html/directives/class-map';
-import {ifDefined} from 'lit-html/directives/if-defined';
-import {css, customElement, html, property} from 'lit-element';
-import {GrLitElement} from '../../lit/gr-lit-element';
+import {classMap} from 'lit/directives/class-map';
+import {ifDefined} from 'lit/directives/if-defined';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 import {GrButton} from '../gr-button/gr-button';
 
 const COPY_TIMEOUT_MS = 1000;
@@ -33,7 +33,7 @@
   }
 }
 @customElement('gr-copy-clipboard')
-export class GrCopyClipboard extends GrLitElement {
+export class GrCopyClipboard extends LitElement {
   @property({type: String})
   text: string | undefined;
 
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.ts b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.ts
index 39ec9e4..a3800a8 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.ts
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.ts
@@ -75,8 +75,11 @@
   }
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = TooltipMixin(PolymerElement);
+
 @customElement('gr-date-formatter')
-export class GrDateFormatter extends TooltipMixin(PolymerElement) {
+export class GrDateFormatter extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts
index f39e5b8..27c6341 100644
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 import '../gr-button/gr-button';
+import '../../../styles/gr-font-styles';
 import '../../../styles/shared-styles';
 import {PolymerElement} from '@polymer/polymer/polymer-element';
 import {htmlTemplate} from './gr-dialog_html';
diff --git a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_html.ts b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_html.ts
index f8ddcfd..a5cf8f1 100644
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_html.ts
@@ -17,6 +17,9 @@
 import {html} from '@polymer/polymer/lib/utils/html-tag';
 
 export const htmlTemplate = html`
+  <style include="gr-font-styles">
+    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+  </style>
   <style include="shared-styles">
     :host {
       color: var(--primary-text-color);
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts
index 078e2ac..f4179f4 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts
@@ -67,8 +67,11 @@
   bold?: boolean;
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-dropdown')
-export class GrDropdown extends KeyboardShortcutMixin(PolymerElement) {
+export class GrDropdown extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts
index b2bd925..bd1046f 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts
@@ -48,8 +48,11 @@
   };
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-editable-label')
-export class GrEditableLabel extends KeyboardShortcutMixin(PolymerElement) {
+export class GrEditableLabel extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-file-status-chip/gr-file-status-chip.ts b/polygerrit-ui/app/elements/shared/gr-file-status-chip/gr-file-status-chip.ts
index ac14960..0f94b11 100644
--- a/polygerrit-ui/app/elements/shared/gr-file-status-chip/gr-file-status-chip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-file-status-chip/gr-file-status-chip.ts
@@ -18,8 +18,8 @@
 import {NormalizedFileInfo} from '../../change/gr-file-list/gr-file-list';
 import {hasOwnProperty} from '../../../utils/common-util';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 const FileStatus = {
   A: 'Added',
@@ -32,7 +32,7 @@
 };
 
 @customElement('gr-file-status-chip')
-export class GrFileStatusChip extends GrLitElement {
+export class GrFileStatusChip extends LitElement {
   @property({type: Object})
   file?: NormalizedFileInfo;
 
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
index 487f288..17621c3 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
@@ -16,17 +16,23 @@
  */
 import '../gr-linked-text/gr-linked-text';
 import {CommentLinks} from '../../../types/common';
-import {appContext} from '../../../services/app-context';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property} from 'lit-element';
+import {LitElement, css, html, TemplateResult} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 const CODE_MARKER_PATTERN = /^(`{1,3})([^`]+?)\1$/;
 
-export interface Block {
-  type: string;
-  text?: string;
-  blocks?: Block[];
-  items?: string[];
+export type Block = ListBlock | QuoteBlock | TextBlock;
+export interface ListBlock {
+  type: 'list';
+  items: string[];
+}
+export interface QuoteBlock {
+  type: 'quote';
+  blocks: Block[];
+}
+export interface TextBlock {
+  type: 'paragraph' | 'code' | 'pre';
+  text: string;
 }
 
 declare global {
@@ -35,7 +41,7 @@
   }
 }
 @customElement('gr-formatted-text')
-export class GrFormattedText extends GrLitElement {
+export class GrFormattedText extends LitElement {
   @property({type: String})
   content?: string;
 
@@ -45,8 +51,6 @@
   @property({type: Boolean, reflect: true})
   noTrailingMargin = false;
 
-  private readonly reporting = appContext.reportingService;
-
   static override get styles() {
     return [
       css`
@@ -99,8 +103,9 @@
   }
 
   override render() {
-    const nodes = this._computeNodes(this._computeBlocks(this.content));
-    return html`<div id="container">${nodes}</div>`;
+    if (!this.content) return;
+    const blocks = this._computeBlocks(this.content);
+    return html`${blocks.map(block => this.renderBlock(block))}`;
   }
 
   /**
@@ -123,9 +128,7 @@
    *
    * NOTE: Strings appearing in all block objects are NOT escaped.
    */
-  _computeBlocks(content?: string): Block[] {
-    if (!content) return [];
-
+  _computeBlocks(content: string): Block[] {
     const result: Block[] = [];
     const lines = content.replace(/[\s\n\r\t]+$/g, '').split('\n');
 
@@ -134,82 +137,87 @@
         continue;
       }
 
-      if (this._isCodeMarkLine(lines[i])) {
-        // handle multi-line code
-        let nextI = i + 1;
-        while (!this._isCodeMarkLine(lines[nextI]) && nextI < lines.length) {
-          nextI++;
-        }
-
-        if (this._isCodeMarkLine(lines[nextI])) {
+      if (this.isCodeMarkLine(lines[i])) {
+        const startOfCode = i + 1;
+        const endOfCode = this.getEndOfSection(
+          lines,
+          startOfCode,
+          line => !this.isCodeMarkLine(line)
+        );
+        // If the code extends to the end then there is no closing``` and the
+        // opening``` should not be counted as a multiline code block.
+        const lineAfterCode = lines[endOfCode];
+        if (lineAfterCode && this.isCodeMarkLine(lineAfterCode)) {
           result.push({
             type: 'code',
-            text: lines.slice(i + 1, nextI).join('\n'),
+            // Does not include either of the ``` lines
+            text: lines.slice(startOfCode, endOfCode).join('\n'),
           });
-          i = nextI;
+          i = endOfCode; // advances past the closing```
           continue;
         }
-
-        // otherwise treat it as regular line and continue
-        // check for other cases
       }
-
-      if (this._isSingleLineCode(lines[i])) {
+      if (this.isSingleLineCode(lines[i])) {
         // no guard check as _isSingleLineCode tested on the pattern
         const codeContent = lines[i].match(CODE_MARKER_PATTERN)![2];
         result.push({type: 'code', text: codeContent});
-      } else if (this._isList(lines[i])) {
-        let nextI = i + 1;
-        while (this._isList(lines[nextI])) {
-          nextI++;
-        }
-        result.push(this._makeList(lines.slice(i, nextI)));
-        i = nextI - 1;
-      } else if (this._isQuote(lines[i])) {
-        let nextI = i + 1;
-        while (this._isQuote(lines[nextI])) {
-          nextI++;
-        }
+      } else if (this.isList(lines[i])) {
+        const endOfList = this.getEndOfSection(lines, i + 1, line =>
+          this.isList(line)
+        );
+        result.push(this.makeList(lines.slice(i, endOfList)));
+        i = endOfList - 1;
+      } else if (this.isQuote(lines[i])) {
+        const endOfQuote = this.getEndOfSection(lines, i + 1, line =>
+          this.isQuote(line)
+        );
         const blockLines = lines
-          .slice(i, nextI)
+          .slice(i, endOfQuote)
           .map(l => l.replace(/^[ ]?>[ ]?/, ''));
         result.push({
           type: 'quote',
           blocks: this._computeBlocks(blockLines.join('\n')),
         });
-        i = nextI - 1;
-      } else if (this._isPreFormat(lines[i])) {
-        let nextI = i + 1;
+        i = endOfQuote - 1;
+      } else if (this.isPreFormat(lines[i])) {
         // include pre or all regular lines but stop at next new line
-        while (
-          this._isPreFormat(lines[nextI]) ||
-          (this._isRegularLine(lines[nextI]) &&
-            !this._isWhitespaceLine(lines[nextI]) &&
-            lines[nextI].length)
-        ) {
-          nextI++;
-        }
+        const predicate = (line: string) =>
+          this.isPreFormat(line) ||
+          (this.isRegularLine(line) &&
+            !this.isWhitespaceLine(line) &&
+            line.length > 0);
+        const endOfPre = this.getEndOfSection(lines, i + 1, predicate);
         result.push({
           type: 'pre',
-          text: lines.slice(i, nextI).join('\n'),
+          text: lines.slice(i, endOfPre).join('\n'),
         });
-        i = nextI - 1;
+        i = endOfPre - 1;
       } else {
-        let nextI = i + 1;
-        while (this._isRegularLine(lines[nextI])) {
-          nextI++;
-        }
+        const endOfRegularLines = this.getEndOfSection(lines, i + 1, line =>
+          this.isRegularLine(line)
+        );
         result.push({
           type: 'paragraph',
-          text: lines.slice(i, nextI).join('\n'),
+          text: lines.slice(i, endOfRegularLines).join('\n'),
         });
-        i = nextI - 1;
+        i = endOfRegularLines - 1;
       }
     }
 
     return result;
   }
 
+  private getEndOfSection(
+    lines: string[],
+    startIndex: number,
+    sectionPredicate: (line: string) => boolean
+  ) {
+    const index = lines
+      .slice(startIndex)
+      .findIndex(line => !sectionPredicate(line));
+    return index === -1 ? lines.length : index + startIndex;
+  }
+
   /**
    * Take a block of comment text that contains a list, generate appropriate
    * block objects and append them to the output list.
@@ -222,105 +230,78 @@
    *
    * @param lines The block containing the list.
    */
-  _makeList(lines: string[]) {
-    const items = [];
-    for (let i = 0; i < lines.length; i++) {
-      let line = lines[i];
-      line = line.substring(1).trim();
-      items.push(line);
-    }
+  private makeList(lines: string[]): Block {
+    const items = lines.map(line => line.substring(1).trim());
     return {type: 'list', items};
   }
 
-  _isRegularLine(line: string) {
-    // line can not be recognized by existing patterns
-    if (line === undefined) return false;
+  private isRegularLine(line: string): boolean {
     return (
-      !this._isQuote(line) &&
-      !this._isCodeMarkLine(line) &&
-      !this._isSingleLineCode(line) &&
-      !this._isList(line) &&
-      !this._isPreFormat(line)
+      !this.isQuote(line) &&
+      !this.isCodeMarkLine(line) &&
+      !this.isSingleLineCode(line) &&
+      !this.isList(line) &&
+      !this.isPreFormat(line)
     );
   }
 
-  _isQuote(line: string) {
-    return line && (line.startsWith('> ') || line.startsWith(' > '));
+  private isQuote(line: string): boolean {
+    return line.startsWith('> ') || line.startsWith(' > ');
   }
 
-  _isCodeMarkLine(line: string) {
-    return line && line.trim() === '```';
+  private isCodeMarkLine(line: string): boolean {
+    return line.trim() === '```';
   }
 
-  _isSingleLineCode(line: string) {
-    return line && CODE_MARKER_PATTERN.test(line);
+  private isSingleLineCode(line: string): boolean {
+    return CODE_MARKER_PATTERN.test(line);
   }
 
-  _isPreFormat(line: string) {
-    return line && /^[ \t]/.test(line) && !this._isWhitespaceLine(line);
+  private isPreFormat(line: string): boolean {
+    return /^[ \t]/.test(line) && !this.isWhitespaceLine(line);
   }
 
-  _isList(line: string) {
-    return line && /^[-*] /.test(line);
+  private isList(line: string): boolean {
+    return /^[-*] /.test(line);
   }
 
-  _isWhitespaceLine(line: string) {
-    return line && /^\s+$/.test(line);
+  private isWhitespaceLine(line: string): boolean {
+    return /^\s+$/.test(line);
   }
 
-  _makeLinkedText(content = '', isPre?: boolean) {
-    const text = document.createElement('gr-linked-text');
-    text.config = this.config;
-    text.content = content;
-    text.pre = true;
-    if (isPre) {
-      text.classList.add('pre');
+  private renderLinkedText(content: string, isPre?: boolean): TemplateResult {
+    return html`
+      <gr-linked-text
+        class="${isPre ? 'pre' : ''}"
+        .config=${this.config}
+        content=${content}
+        pre
+      ></gr-linked-text>
+    `;
+  }
+
+  private renderBlock(block: Block): TemplateResult {
+    switch (block.type) {
+      case 'paragraph':
+        return html`<p>${this.renderLinkedText(block.text)}</p>`;
+      case 'quote':
+        return html`
+          <blockquote>
+            ${block.blocks.map(subBlock => this.renderBlock(subBlock))}
+          </blockquote>
+        `;
+      case 'code':
+        return html`<code>${block.text}</code>`;
+      case 'pre':
+        return this.renderLinkedText(block.text, true);
+      case 'list':
+        return html`
+          <ul>
+            ${block.items.map(
+              item => html`<li>${this.renderLinkedText(item)}</li>`
+            )}
+          </ul>
+        `;
     }
-    return text;
-  }
-
-  /**
-   * Map an array of block objects to an array of DOM nodes.
-   */
-  _computeNodes(blocks: Block[]): HTMLElement[] {
-    return blocks.map(block => {
-      if (block.type === 'paragraph') {
-        const p = document.createElement('p');
-        p.appendChild(this._makeLinkedText(block.text));
-        return p;
-      }
-
-      if (block.type === 'quote') {
-        const bq = document.createElement('blockquote');
-        for (const node of this._computeNodes(block.blocks || [])) {
-          if (node) bq.appendChild(node);
-        }
-        return bq;
-      }
-
-      if (block.type === 'code') {
-        const code = document.createElement('code');
-        code.textContent = block.text || '';
-        return code;
-      }
-
-      if (block.type === 'pre') {
-        return this._makeLinkedText(block.text, true);
-      }
-
-      if (block.type === 'list') {
-        const ul = document.createElement('ul');
-        const items = block.items || [];
-        for (const item of items) {
-          const li = document.createElement('li');
-          li.appendChild(this._makeLinkedText(item));
-          ul.appendChild(li);
-        }
-        return ul;
-      }
-
-      this.reporting.error(new Error(`Unrecognized block type: ${block.type}`));
-      return document.createElement('span');
-    });
   }
 }
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
index cf12032..8cecf71 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
@@ -17,40 +17,41 @@
 
 import '../../../test/common-test-setup-karma';
 import './gr-formatted-text';
-import {GrFormattedText, Block} from './gr-formatted-text';
+import {
+  GrFormattedText,
+  Block,
+  ListBlock,
+  TextBlock,
+  QuoteBlock,
+} from './gr-formatted-text';
 
 const basicFixture = fixtureFromElement('gr-formatted-text');
 
 suite('gr-formatted-text tests', () => {
   let element: GrFormattedText;
 
-  function assertBlock(
-    result: Block[],
-    index: number,
-    type: string,
-    text?: string
-  ) {
-    assert.equal(result[index].type, type);
-    assert.equal(result[index].text, text);
+  function assertTextBlock(block: Block, type: string, text: string) {
+    assert.equal(block.type, type);
+    const textBlock = block as TextBlock;
+    assert.equal(textBlock.text, text);
   }
 
-  function assertListBlock(
-    result: Block[],
-    resultIndex: number,
-    itemIndex: number,
-    text: string
-  ) {
-    assert.equal(result[resultIndex].type, 'list');
-    const item = result[resultIndex].items?.[itemIndex];
-    assert.equal(item, text);
+  function assertListBlock(block: Block, items: string[]) {
+    assert.equal(block.type, 'list');
+    const listBlock = block as ListBlock;
+    assert.deepEqual(listBlock.items, items);
+  }
+
+  function assertQuoteBlock(block: Block): QuoteBlock {
+    assert.equal(block.type, 'quote');
+    return block as QuoteBlock;
   }
 
   setup(() => {
     element = basicFixture.instantiate();
   });
 
-  test('parse null undefined and empty', () => {
-    assert.lengthOf(element._computeBlocks(undefined), 0);
+  test('parse empty', () => {
     assert.lengthOf(element._computeBlocks(''), 0);
   });
 
@@ -58,53 +59,49 @@
     const comment = 'Para1';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
-    assertBlock(result, 0, 'paragraph', comment);
+    assertTextBlock(result[0], 'paragraph', comment);
   });
 
   test('parse multiline para', () => {
     const comment = 'Para 1\nStill para 1';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
-    assertBlock(result, 0, 'paragraph', comment);
+    assertTextBlock(result[0], 'paragraph', comment);
   });
 
   test('parse para break without special blocks', () => {
     const comment = 'Para 1\n\nPara 2\n\nPara 3';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
-    assertBlock(result, 0, 'paragraph', comment);
+    assertTextBlock(result[0], 'paragraph', comment);
   });
 
   test('parse quote', () => {
     const comment = '> Quote text';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
-    assert.equal(result[0].type, 'quote');
-    const blocks = result[0].blocks!;
-    assert.lengthOf(blocks, 1);
-    assertBlock(blocks, 0, 'paragraph', 'Quote text');
+    const quoteBlock = assertQuoteBlock(result[0]);
+    assert.lengthOf(quoteBlock.blocks, 1);
+    assertTextBlock(quoteBlock.blocks[0], 'paragraph', 'Quote text');
   });
 
   test('parse quote lead space', () => {
     const comment = ' > Quote text';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
-    assert.equal(result[0].type, 'quote');
-    const blocks = result[0].blocks!;
-    assert.lengthOf(blocks, 1);
-    assertBlock(blocks, 0, 'paragraph', 'Quote text');
+    const quoteBlock = assertQuoteBlock(result[0]);
+    assert.lengthOf(quoteBlock.blocks, 1);
+    assertTextBlock(quoteBlock.blocks[0], 'paragraph', 'Quote text');
   });
 
   test('parse multiline quote', () => {
     const comment = '> Quote line 1\n> Quote line 2\n > Quote line 3\n';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
-    assert.equal(result[0].type, 'quote');
-    const blocks = result[0].blocks!;
-    assert.lengthOf(blocks, 1);
-    assertBlock(
-      blocks,
-      0,
+    const quoteBlock = assertQuoteBlock(result[0]);
+    assert.lengthOf(quoteBlock.blocks, 1);
+    assertTextBlock(
+      quoteBlock.blocks[0],
       'paragraph',
       'Quote line 1\nQuote line 2\nQuote line 3'
     );
@@ -114,49 +111,42 @@
     const comment = '    Four space indent.';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
-    assertBlock(result, 0, 'pre', comment);
+    assertTextBlock(result[0], 'pre', comment);
   });
 
   test('parse one space pre', () => {
     const comment = ' One space indent.\n Another line.';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
-    assertBlock(result, 0, 'pre', comment);
+    assertTextBlock(result[0], 'pre', comment);
   });
 
   test('parse tab pre', () => {
     const comment = '\tOne tab indent.\n\tAnother line.\n  Yet another!';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
-    assertBlock(result, 0, 'pre', comment);
+    assertTextBlock(result[0], 'pre', comment);
   });
 
   test('parse star list', () => {
     const comment = '* Item 1\n* Item 2\n* Item 3';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
-    assertListBlock(result, 0, 0, 'Item 1');
-    assertListBlock(result, 0, 1, 'Item 2');
-    assertListBlock(result, 0, 2, 'Item 3');
+    assertListBlock(result[0], ['Item 1', 'Item 2', 'Item 3']);
   });
 
   test('parse dash list', () => {
     const comment = '- Item 1\n- Item 2\n- Item 3';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
-    assertListBlock(result, 0, 0, 'Item 1');
-    assertListBlock(result, 0, 1, 'Item 2');
-    assertListBlock(result, 0, 2, 'Item 3');
+    assertListBlock(result[0], ['Item 1', 'Item 2', 'Item 3']);
   });
 
   test('parse mixed list', () => {
     const comment = '- Item 1\n* Item 2\n- Item 3\n* Item 4';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
-    assertListBlock(result, 0, 0, 'Item 1');
-    assertListBlock(result, 0, 1, 'Item 2');
-    assertListBlock(result, 0, 2, 'Item 3');
-    assertListBlock(result, 0, 3, 'Item 4');
+    assertListBlock(result[0], ['Item 1', 'Item 2', 'Item 3', 'Item 4']);
   });
 
   test('parse mixed block types', () => {
@@ -176,64 +166,75 @@
       'Parting words.';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 7);
-    assertBlock(result, 0, 'paragraph', 'Paragraph\nacross\na\nfew\nlines.\n');
+    assertTextBlock(
+      result[0],
+      'paragraph',
+      'Paragraph\nacross\na\nfew\nlines.\n'
+    );
 
-    assert.equal(result[1].type, 'quote');
-    const secondBlocks = result[1].blocks!;
-    assert.lengthOf(secondBlocks, 1);
-    assertBlock(secondBlocks, 0, 'paragraph', 'Quote\nacross\nnot many lines.');
+    const quoteBlock = assertQuoteBlock(result[1]);
+    assert.lengthOf(quoteBlock.blocks, 1);
+    assertTextBlock(
+      quoteBlock.blocks[0],
+      'paragraph',
+      'Quote\nacross\nnot many lines.'
+    );
 
-    assertBlock(result, 2, 'paragraph', 'Another paragraph\n');
-    assertListBlock(result, 3, 0, 'Series');
-    assertListBlock(result, 3, 1, 'of');
-    assertListBlock(result, 3, 2, 'list');
-    assertListBlock(result, 3, 3, 'items');
-    assertBlock(result, 4, 'paragraph', 'Yet another paragraph\n');
-    assertBlock(result, 5, 'pre', '\tPreformatted text.');
-    assertBlock(result, 6, 'paragraph', 'Parting words.');
+    assertTextBlock(result[2], 'paragraph', 'Another paragraph\n');
+    assertListBlock(result[3], ['Series', 'of', 'list', 'items']);
+    assertTextBlock(result[4], 'paragraph', 'Yet another paragraph\n');
+    assertTextBlock(result[5], 'pre', '\tPreformatted text.');
+    assertTextBlock(result[6], 'paragraph', 'Parting words.');
   });
 
   test('bullet list 1', () => {
-    const comment = 'A\n\n* line 1\n* 2nd line';
+    const comment = 'A\n\n* line 1';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 2);
-    assertBlock(result, 0, 'paragraph', 'A\n');
-    assertListBlock(result, 1, 0, 'line 1');
-    assertListBlock(result, 1, 1, '2nd line');
+    assertTextBlock(result[0], 'paragraph', 'A\n');
+    assertListBlock(result[1], ['line 1']);
   });
 
   test('bullet list 2', () => {
-    const comment = 'A\n* line 1\n* 2nd line\n\nB';
+    const comment = 'A\n\n* line 1\n* 2nd line';
     const result = element._computeBlocks(comment);
-    assert.lengthOf(result, 3);
-    assertBlock(result, 0, 'paragraph', 'A');
-    assertListBlock(result, 1, 0, 'line 1');
-    assertListBlock(result, 1, 1, '2nd line');
-    assertBlock(result, 2, 'paragraph', 'B');
+    assert.lengthOf(result, 2);
+    assertTextBlock(result[0], 'paragraph', 'A\n');
+    assertListBlock(result[1], ['line 1', '2nd line']);
   });
 
   test('bullet list 3', () => {
-    const comment = '* line 1\n* 2nd line\n\nB';
+    const comment = 'A\n* line 1\n* 2nd line\n\nB';
     const result = element._computeBlocks(comment);
-    assert.lengthOf(result, 2);
-    assertListBlock(result, 0, 0, 'line 1');
-    assertListBlock(result, 0, 1, '2nd line');
-    assertBlock(result, 1, 'paragraph', 'B');
+    assert.lengthOf(result, 3);
+    assertTextBlock(result[0], 'paragraph', 'A');
+    assertListBlock(result[1], ['line 1', '2nd line']);
+    assertTextBlock(result[2], 'paragraph', 'B');
   });
 
   test('bullet list 4', () => {
+    const comment = '* line 1\n* 2nd line\n\nB';
+    const result = element._computeBlocks(comment);
+    assert.lengthOf(result, 2);
+    assertListBlock(result[0], ['line 1', '2nd line']);
+    assertTextBlock(result[1], 'paragraph', 'B');
+  });
+
+  test('bullet list 5', () => {
     const comment =
       'To see this bug, you have to:\n' +
       '* Be on IMAP or EAS (not on POP)\n' +
       '* Be very unlucky\n';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 2);
-    assertBlock(result, 0, 'paragraph', 'To see this bug, you have to:');
-    assertListBlock(result, 1, 0, 'Be on IMAP or EAS (not on POP)');
-    assertListBlock(result, 1, 1, 'Be very unlucky');
+    assertTextBlock(result[0], 'paragraph', 'To see this bug, you have to:');
+    assertListBlock(result[1], [
+      'Be on IMAP or EAS (not on POP)',
+      'Be very unlucky',
+    ]);
   });
 
-  test('bullet list 5', () => {
+  test('bullet list 6', () => {
     const comment =
       'To see this bug,\n' +
       'you have to:\n' +
@@ -241,37 +242,36 @@
       '* Be very unlucky\n';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 2);
-    assertBlock(result, 0, 'paragraph', 'To see this bug,\nyou have to:');
-    assertListBlock(result, 1, 0, 'Be on IMAP or EAS (not on POP)');
-    assertListBlock(result, 1, 1, 'Be very unlucky');
+    assertTextBlock(result[0], 'paragraph', 'To see this bug,\nyou have to:');
+    assertListBlock(result[1], [
+      'Be on IMAP or EAS (not on POP)',
+      'Be very unlucky',
+    ]);
   });
 
   test('dash list 1', () => {
     const comment = 'A\n- line 1\n- 2nd line';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 2);
-    assertBlock(result, 0, 'paragraph', 'A');
-    assertListBlock(result, 1, 0, 'line 1');
-    assertListBlock(result, 1, 1, '2nd line');
+    assertTextBlock(result[0], 'paragraph', 'A');
+    assertListBlock(result[1], ['line 1', '2nd line']);
   });
 
   test('dash list 2', () => {
     const comment = 'A\n- line 1\n- 2nd line\n\nB';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 3);
-    assertBlock(result, 0, 'paragraph', 'A');
-    assertListBlock(result, 1, 0, 'line 1');
-    assertListBlock(result, 1, 1, '2nd line');
-    assertBlock(result, 2, 'paragraph', 'B');
+    assertTextBlock(result[0], 'paragraph', 'A');
+    assertListBlock(result[1], ['line 1', '2nd line']);
+    assertTextBlock(result[2], 'paragraph', 'B');
   });
 
   test('dash list 3', () => {
     const comment = '- line 1\n- 2nd line\n\nB';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 2);
-    assertListBlock(result, 0, 0, 'line 1');
-    assertListBlock(result, 0, 1, '2nd line');
-    assertBlock(result, 1, 'paragraph', 'B');
+    assertListBlock(result[0], ['line 1', '2nd line']);
+    assertTextBlock(result[1], 'paragraph', 'B');
   });
 
   test('nested list will NOT be recognized', () => {
@@ -279,134 +279,143 @@
     const comment = '- line 1\n  - line with indentation\n- line 2';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 3);
-    assertListBlock(result, 0, 0, 'line 1');
-    assert.equal(result[1].type, 'pre');
-    assertListBlock(result, 2, 0, 'line 2');
+    assertListBlock(result[0], ['line 1']);
+    assertTextBlock(result[1], 'pre', '  - line with indentation');
+    assertListBlock(result[2], ['line 2']);
   });
 
   test('pre format 1', () => {
     const comment = 'A\n  This is pre\n  formatted';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 2);
-    assertBlock(result, 0, 'paragraph', 'A');
-    assertBlock(result, 1, 'pre', '  This is pre\n  formatted');
+    assertTextBlock(result[0], 'paragraph', 'A');
+    assertTextBlock(result[1], 'pre', '  This is pre\n  formatted');
   });
 
   test('pre format 2', () => {
     const comment = 'A\n  This is pre\n  formatted\n\nbut this is not';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 3);
-    assertBlock(result, 0, 'paragraph', 'A');
-    assertBlock(result, 1, 'pre', '  This is pre\n  formatted');
-    assertBlock(result, 2, 'paragraph', 'but this is not');
+    assertTextBlock(result[0], 'paragraph', 'A');
+    assertTextBlock(result[1], 'pre', '  This is pre\n  formatted');
+    assertTextBlock(result[2], 'paragraph', 'but this is not');
   });
 
   test('pre format 3', () => {
     const comment = 'A\n  Q\n    <R>\n  S\n\nB';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 3);
-    assertBlock(result, 0, 'paragraph', 'A');
-    assertBlock(result, 1, 'pre', '  Q\n    <R>\n  S');
-    assertBlock(result, 2, 'paragraph', 'B');
+    assertTextBlock(result[0], 'paragraph', 'A');
+    assertTextBlock(result[1], 'pre', '  Q\n    <R>\n  S');
+    assertTextBlock(result[2], 'paragraph', 'B');
   });
 
   test('pre format 4', () => {
     const comment = '  Q\n    <R>\n  S\n\nB';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 2);
-    assertBlock(result, 0, 'pre', '  Q\n    <R>\n  S');
-    assertBlock(result, 1, 'paragraph', 'B');
+    assertTextBlock(result[0], 'pre', '  Q\n    <R>\n  S');
+    assertTextBlock(result[1], 'paragraph', 'B');
   });
 
   test('pre format 5', () => {
     const comment = '  Q\n    <R>\n  S\n \nB';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 2);
-    assertBlock(result, 0, 'pre', '  Q\n    <R>\n  S');
-    assertBlock(result, 1, 'paragraph', ' \nB');
+    assertTextBlock(result[0], 'pre', '  Q\n    <R>\n  S');
+    assertTextBlock(result[1], 'paragraph', ' \nB');
   });
 
   test('quote 1', () => {
-    const comment = "> I'm happy\n > with quotes!\n\nSee above.";
+    const comment = "> I'm happy with quotes!!";
     const result = element._computeBlocks(comment);
-    assert.lengthOf(result, 2);
-    assert.equal(result[0].type, 'quote');
-    const blocks = result[0].blocks!;
-    assert.lengthOf(blocks, 1);
-    assertBlock(blocks, 0, 'paragraph', "I'm happy\nwith quotes!");
-    assertBlock(result, 1, 'paragraph', 'See above.');
+    assert.lengthOf(result, 1);
+    const quoteBlock = assertQuoteBlock(result[0]);
+    assert.lengthOf(quoteBlock.blocks, 1);
+    assertTextBlock(
+      quoteBlock.blocks[0],
+      'paragraph',
+      "I'm happy with quotes!!"
+    );
   });
 
   test('quote 2', () => {
+    const comment = "> I'm happy\n > with quotes!\n\nSee above.";
+    const result = element._computeBlocks(comment);
+    assert.lengthOf(result, 2);
+    const quoteBlock = assertQuoteBlock(result[0]);
+    assert.lengthOf(quoteBlock.blocks, 1);
+    assertTextBlock(
+      quoteBlock.blocks[0],
+      'paragraph',
+      "I'm happy\nwith quotes!"
+    );
+    assertTextBlock(result[1], 'paragraph', 'See above.');
+  });
+
+  test('quote 3', () => {
     const comment = 'See this said:\n > a quoted\n > string block\n\nOK?';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 3);
-    assertBlock(result, 0, 'paragraph', 'See this said:');
-    assert.equal(result[1].type, 'quote');
-    const secondBlocks = result[1].blocks!;
-    assert.lengthOf(secondBlocks, 1);
-    assertBlock(secondBlocks, 0, 'paragraph', 'a quoted\nstring block');
-    assertBlock(result, 2, 'paragraph', 'OK?');
+    assertTextBlock(result[0], 'paragraph', 'See this said:');
+    const quoteBlock = assertQuoteBlock(result[1]);
+    assert.lengthOf(quoteBlock.blocks, 1);
+    assertTextBlock(
+      quoteBlock.blocks[0],
+      'paragraph',
+      'a quoted\nstring block'
+    );
+    assertTextBlock(result[2], 'paragraph', 'OK?');
   });
 
   test('nested quotes', () => {
     const comment = ' > > prior\n > \n > next\n';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
-    assert.equal(result[0].type, 'quote');
-    const blocks = result[0].blocks!;
-    assert.lengthOf(blocks, 2);
-    assert.equal(blocks[0].type, 'quote');
-    const blocksBlocks = blocks[0].blocks!;
-    assert.lengthOf(blocksBlocks, 1);
-    assertBlock(blocksBlocks, 0, 'paragraph', 'prior');
-    assertBlock(blocks, 1, 'paragraph', 'next');
+    const outerQuoteBlock = assertQuoteBlock(result[0]);
+    assert.lengthOf(outerQuoteBlock.blocks, 2);
+    const nestedQuoteBlock = assertQuoteBlock(outerQuoteBlock.blocks[0]);
+    assert.lengthOf(nestedQuoteBlock.blocks, 1);
+    assertTextBlock(nestedQuoteBlock.blocks[0], 'paragraph', 'prior');
+    assertTextBlock(outerQuoteBlock.blocks[1], 'paragraph', 'next');
   });
 
   test('code 1', () => {
     const comment = '```\n// test code\n```';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
-    assert.equal(result[0].type, 'code');
-    assert.equal(result[0].text, '// test code');
+    assertTextBlock(result[0], 'code', '// test code');
   });
 
   test('code 2', () => {
     const comment = 'test code\n```// test code```';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 2);
-    assert.equal(result[0].type, 'paragraph');
-    assert.equal(result[0].text, 'test code');
-    assert.equal(result[1].type, 'code');
-    assert.equal(result[1].text, '// test code');
+    assertTextBlock(result[0], 'paragraph', 'test code');
+    assertTextBlock(result[1], 'code', '// test code');
   });
 
-  test('code 3', () => {
-    const comment = 'test code\n```// test code```';
-    const result = element._computeBlocks(comment);
-    assert.lengthOf(result, 2);
-    assert.equal(result[0].type, 'paragraph');
-    assert.equal(result[0].text, 'test code');
-    assert.equal(result[1].type, 'code');
-    assert.equal(result[1].text, '// test code');
-  });
-
-  test('not a code', () => {
+  test('not a code block', () => {
     const comment = 'test code\n```// test code';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
-    assert.equal(result[0].type, 'paragraph');
-    assert.equal(result[0].text, 'test code\n```// test code');
+    assertTextBlock(result[0], 'paragraph', 'test code\n```// test code');
   });
 
-  test('not a code 2', () => {
+  test('not a code block 2', () => {
     const comment = 'test code\n```\n// test code';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 2);
-    assert.equal(result[0].type, 'paragraph');
-    assert.equal(result[0].text, 'test code');
-    assert.equal(result[1].type, 'paragraph');
-    assert.equal(result[1].text, '```\n// test code');
+    assertTextBlock(result[0], 'paragraph', 'test code');
+    assertTextBlock(result[1], 'paragraph', '```\n// test code');
+  });
+
+  test('not a code block 3', () => {
+    const comment = 'test code\n```';
+    const result = element._computeBlocks(comment);
+    assert.lengthOf(result, 2);
+    assertTextBlock(result[0], 'paragraph', 'test code');
+    assertTextBlock(result[1], 'paragraph', '```');
   });
 
   test('mix all 1', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.ts b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.ts
index a788f05..a329736 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.ts
@@ -16,10 +16,11 @@
  */
 
 import '@polymer/iron-icon/iron-icon';
+import '../../../styles/gr-font-styles';
 import '../../../styles/shared-styles';
 import '../gr-avatar/gr-avatar';
 import '../gr-button/gr-button';
-import {hovercardBehaviorMixin} from '../gr-hovercard/gr-hovercard-behavior';
+import {HovercardBehaviorMixin} from '../gr-hovercard/gr-hovercard-behavior';
 import {PolymerElement} from '@polymer/polymer/polymer-element';
 import {htmlTemplate} from './gr-hovercard-account_html';
 import {appContext} from '../../../services/app-context';
@@ -45,8 +46,11 @@
 import {isInvolved, isRemovableReviewer} from '../../../utils/change-util';
 import {assertIsDefined} from '../../../utils/common-util';
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = HovercardBehaviorMixin(PolymerElement);
+
 @customElement('gr-hovercard-account')
-export class GrHovercardAccount extends hovercardBehaviorMixin(PolymerElement) {
+export class GrHovercardAccount extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_html.ts b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_html.ts
index dac5962..f1f6bf8 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_html.ts
@@ -18,6 +18,9 @@
 import {html} from '@polymer/polymer/lib/utils/html-tag';
 
 export const htmlTemplate = html`
+  <style include="gr-font-styles">
+    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+  </style>
   <style include="gr-hovercard-shared-style">
     .top,
     .attention,
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts
index d5e9152..2bb9de5 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts
@@ -81,7 +81,7 @@
  * @polymer
  * @mixinFunction
  */
-export const hovercardBehaviorMixin = <T extends Constructor<PolymerElement>>(
+export const HovercardBehaviorMixin = <T extends Constructor<PolymerElement>>(
   superClass: T
 ) => {
   /**
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.ts b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.ts
index 8857d36..acc5e15 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.ts
@@ -18,12 +18,15 @@
 import '../../../styles/shared-styles';
 import {PolymerElement} from '@polymer/polymer/polymer-element';
 import {htmlTemplate} from './gr-hovercard_html';
-import {hovercardBehaviorMixin} from './gr-hovercard-behavior';
+import {HovercardBehaviorMixin} from './gr-hovercard-behavior';
 import './gr-hovercard-shared-style';
 import {customElement} from '@polymer/decorators';
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = HovercardBehaviorMixin(PolymerElement);
+
 @customElement('gr-hovercard')
-export class GrHovercard extends hovercardBehaviorMixin(PolymerElement) {
+export class GrHovercard extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts
index a3d9e58..69573c7 100644
--- a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts
+++ b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts
@@ -44,6 +44,8 @@
       <!-- This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js -->
       <g id="chevron-right"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"></path></g>
       <!-- This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js -->
+      <g id="more-horiz"><path d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"></path></g>
+      <!-- This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js -->
       <g id="more-vert"><path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"></path></g>
       <!-- This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js -->
       <g id="deleteEdit"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"></path></g>
@@ -154,6 +156,8 @@
       <g id="file-present"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M15 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V7l-5-5zM6 20V4h8v4h4v12H6zm10-10v5c0 2.21-1.79 4-4 4s-4-1.79-4-4V8.5c0-1.47 1.26-2.64 2.76-2.49 1.3.13 2.24 1.32 2.24 2.63V15h-2V8.5c0-.28-.22-.5-.5-.5s-.5.22-.5.5V15c0 1.1.9 2 2 2s2-.9 2-2v-5h2z"/></g>
       <!-- This SVG is a copy from material.io https://fonts.google.com/icons?selected=Material%20Icons%3Aarrow_forward-->
       <g id="arrow-forward"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"/></g>
+      <!-- This SVG is a copy from material.io https://fonts.google.com/icons?selected=Material+Icons:feedback -->
+      <g id="feedback"><path d="M0 0h24v24H0z" fill="none"/><path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 12h-2v-2h2v2zm0-4h-2V6h2v4z"/></g>
     </defs>
   </svg>
 </iron-iconset-svg>`;
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.ts
similarity index 70%
rename from polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.js
rename to polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.ts
index e45cb0c..865aa20 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.ts
@@ -15,9 +15,9 @@
  * limitations under the License.
  */
 
-import '../../../test/common-test-setup-karma.js';
-import './gr-js-api-interface.js';
-import {getPluginNameFromUrl} from './gr-api-utils.js';
+import '../../../test/common-test-setup-karma';
+import './gr-js-api-interface';
+import {getPluginNameFromUrl} from './gr-api-utils';
 
 suite('gr-api-utils tests', () => {
   suite('test getPluginNameFromUrl', () => {
@@ -32,37 +32,36 @@
     test('with random invalid url', () => {
       assert.equal(getPluginNameFromUrl('http://example.com'), null);
       assert.equal(
-          getPluginNameFromUrl('http://example.com/static/a.js'),
-          null
+        getPluginNameFromUrl('http://example.com/static/a.js'),
+        null
       );
     });
 
     test('with valid urls', () => {
       assert.equal(
-          getPluginNameFromUrl('http://example.com/plugins/a.js'),
-          'a'
+        getPluginNameFromUrl('http://example.com/plugins/a.js'),
+        'a'
       );
       assert.equal(
-          getPluginNameFromUrl('http://example.com/plugins/a/static/t.js'),
-          'a'
+        getPluginNameFromUrl('http://example.com/plugins/a/static/t.js'),
+        'a'
       );
     });
 
     test('with gerrit-theme override', () => {
       assert.equal(
-          getPluginNameFromUrl('http://example.com/static/gerrit-theme.js'),
-          'gerrit-theme'
+        getPluginNameFromUrl('http://example.com/static/gerrit-theme.js'),
+        'gerrit-theme'
       );
     });
 
     test('with ASSETS_PATH', () => {
       window.ASSETS_PATH = 'http://cdn.com/2';
       assert.equal(
-          getPluginNameFromUrl(`${window.ASSETS_PATH}/plugins/a.js`),
-          'a'
+        getPluginNameFromUrl(`${window.ASSETS_PATH}/plugins/a.js`),
+        'a'
       );
       window.ASSETS_PATH = undefined;
     });
   });
 });
-
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.ts
index 466e8e4..a33b145 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.ts
@@ -90,7 +90,7 @@
    * Ensure GrChangeActionsInterface instance has access to gr-change-actions
    * element and retrieve if the interface was created before element.
    */
-  private ensureEl(): GrChangeActionsElement {
+  ensureEl(): GrChangeActionsElement {
     if (!this.el) {
       const sharedApiElement = appContext.jsApiService;
       this.setEl(
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.ts
index 3ee393c..85491ef 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.ts
@@ -31,6 +31,7 @@
 } from '../../../services/gr-event-interface/gr-event-interface';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation';
 import {Gerrit} from '../../../api/gerrit';
+import {fontStyles} from '../../../styles/gr-font-styles';
 import {formStyles} from '../../../styles/gr-form-styles';
 import {menuPageStyles} from '../../../styles/gr-menu-page-styles';
 import {subpageStyles} from '../../../styles/gr-subpage-styles';
@@ -121,6 +122,7 @@
   public readonly Auth = appContext.authService;
 
   public readonly styles = {
+    font: fontStyles,
     form: formStyles,
     menuPage: menuPageStyles,
     subPage: subpageStyles,
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts
index f7475bf..b039a7e 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts
@@ -104,7 +104,7 @@
    * which endpoints to dynamically add to the page.
    */
   registerModule(plugin: PluginApi, opts: Options) {
-    const endpoint = opts.endpoint!;
+    const endpoint = opts.endpoint;
     const dynamicEndpoint = opts.dynamicEndpoint;
     if (dynamicEndpoint) {
       if (!this._dynamicPlugins.has(dynamicEndpoint)) {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.js
deleted file mode 100644
index e3475ad..0000000
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.js
+++ /dev/null
@@ -1,166 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../../../test/common-test-setup-karma.js';
-import {resetPlugins} from '../../../test/test-utils.js';
-import './gr-js-api-interface.js';
-import {GrPluginEndpoints} from './gr-plugin-endpoints.js';
-import {_testOnly_initGerritPluginApi} from './gr-gerrit.js';
-
-const pluginApi = _testOnly_initGerritPluginApi();
-
-suite('gr-plugin-endpoints tests', () => {
-  let instance;
-  let pluginFoo;
-  let pluginBar;
-  let domHook;
-
-  setup(() => {
-    domHook = {};
-    instance = new GrPluginEndpoints();
-    pluginApi.install(p => { pluginFoo = p; }, '0.1',
-        'http://test.com/plugins/testplugin/static/foo.js');
-    instance.registerModule(
-        pluginFoo,
-        {
-          endpoint: 'a-place',
-          type: 'decorate',
-          moduleName: 'foo-module',
-          domHook,
-        }
-    );
-    pluginApi.install(p => { pluginBar = p; }, '0.1',
-        'http://test.com/plugins/testplugin/static/bar.js');
-    instance.registerModule(
-        pluginBar,
-        {
-          endpoint: 'a-place',
-          type: 'style',
-          moduleName: 'bar-module',
-          domHook,
-        }
-    );
-  });
-
-  teardown(() => {
-    resetPlugins();
-  });
-
-  test('getDetails all', () => {
-    assert.deepEqual(instance.getDetails('a-place'), [
-      {
-        moduleName: 'foo-module',
-        plugin: pluginFoo,
-        pluginUrl: pluginFoo._url,
-        type: 'decorate',
-        domHook,
-        slot: undefined,
-      },
-      {
-        moduleName: 'bar-module',
-        plugin: pluginBar,
-        pluginUrl: pluginBar._url,
-        type: 'style',
-        domHook,
-        slot: undefined,
-      },
-    ]);
-  });
-
-  test('getDetails by type', () => {
-    assert.deepEqual(instance.getDetails('a-place', {type: 'style'}), [
-      {
-        moduleName: 'bar-module',
-        plugin: pluginBar,
-        pluginUrl: pluginBar._url,
-        type: 'style',
-        domHook,
-        slot: undefined,
-      },
-    ]);
-  });
-
-  test('getDetails by module', () => {
-    assert.deepEqual(
-        instance.getDetails('a-place', {moduleName: 'foo-module'}),
-        [
-          {
-            moduleName: 'foo-module',
-            plugin: pluginFoo,
-            pluginUrl: pluginFoo._url,
-            type: 'decorate',
-            domHook,
-            slot: undefined,
-          },
-        ]);
-  });
-
-  test('getModules', () => {
-    assert.deepEqual(
-        instance.getModules('a-place'), ['foo-module', 'bar-module']);
-  });
-
-  test('getPlugins', () => {
-    assert.deepEqual(
-        instance.getPlugins('a-place'), [pluginFoo._url]);
-  });
-
-  test('onNewEndpoint', () => {
-    const newModuleStub = sinon.stub();
-    instance.setPluginsReady();
-    instance.onNewEndpoint('a-place', newModuleStub);
-    instance.registerModule(
-        pluginFoo,
-        {
-          endpoint: 'a-place',
-          type: 'replace',
-          moduleName: 'zaz-module',
-          domHook,
-        });
-    assert.deepEqual(newModuleStub.lastCall.args[0], {
-      moduleName: 'zaz-module',
-      plugin: pluginFoo,
-      pluginUrl: pluginFoo._url,
-      type: 'replace',
-      domHook,
-      slot: undefined,
-    });
-  });
-
-  test('reuse dom hooks', () => {
-    instance.registerModule(
-        pluginFoo, 'a-place', 'decorate', 'foo-module', domHook);
-    assert.deepEqual(instance.getDetails('a-place'), [
-      {
-        moduleName: 'foo-module',
-        plugin: pluginFoo,
-        pluginUrl: pluginFoo._url,
-        type: 'decorate',
-        domHook,
-        slot: undefined,
-      },
-      {
-        moduleName: 'bar-module',
-        plugin: pluginBar,
-        pluginUrl: pluginBar._url,
-        type: 'style',
-        domHook,
-        slot: undefined,
-      },
-    ]);
-  });
-});
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.ts
new file mode 100644
index 0000000..c7bdfb4
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.ts
@@ -0,0 +1,177 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import '../../../test/common-test-setup-karma';
+import {resetPlugins} from '../../../test/test-utils';
+import './gr-js-api-interface';
+import {GrPluginEndpoints} from './gr-plugin-endpoints';
+import {_testOnly_initGerritPluginApi} from './gr-gerrit';
+import {PluginApi} from '../../../api/plugin';
+import {HookApi, HookCallback, PluginElement} from '../../../api/hook';
+
+const pluginApi = _testOnly_initGerritPluginApi();
+
+export class MockHook<T extends PluginElement> implements HookApi<T> {
+  handleInstanceDetached(_: T) {}
+
+  handleInstanceAttached(_: T) {}
+
+  getLastAttached(): Promise<HTMLElement> {
+    throw new Error('unimplemented in mock');
+  }
+
+  getAllAttached() {
+    return [];
+  }
+
+  onAttached(_: HookCallback<T>) {
+    return this;
+  }
+
+  onDetached(_: HookCallback<T>) {
+    return this;
+  }
+
+  getModuleName() {
+    return 'MockHookApi-ModuleName';
+  }
+}
+
+suite('gr-plugin-endpoints tests', () => {
+  let instance: GrPluginEndpoints;
+  let decoratePlugin: PluginApi;
+  let stylePlugin: PluginApi;
+  let domHook: HookApi<PluginElement>;
+
+  setup(() => {
+    domHook = new MockHook<PluginElement>();
+    instance = new GrPluginEndpoints();
+    pluginApi.install(
+      plugin => (decoratePlugin = plugin),
+      '0.1',
+      'http://test.com/plugins/testplugin/static/decorate.js'
+    );
+    instance.registerModule(decoratePlugin, {
+      endpoint: 'my-endpoint',
+      type: 'decorate',
+      moduleName: 'decorate-module',
+      domHook,
+    });
+    pluginApi.install(
+      plugin => (stylePlugin = plugin),
+      '0.1',
+      'http://test.com/plugins/testplugin/static/style.js'
+    );
+    instance.registerModule(stylePlugin, {
+      endpoint: 'my-endpoint',
+      type: 'style',
+      moduleName: 'style-module',
+      domHook,
+    });
+  });
+
+  teardown(() => {
+    resetPlugins();
+  });
+
+  test('getDetails all', () => {
+    assert.deepEqual(instance.getDetails('my-endpoint'), [
+      {
+        moduleName: 'decorate-module',
+        plugin: decoratePlugin,
+        pluginUrl: decoratePlugin._url,
+        type: 'decorate',
+        domHook,
+        slot: undefined,
+      },
+      {
+        moduleName: 'style-module',
+        plugin: stylePlugin,
+        pluginUrl: stylePlugin._url,
+        type: 'style',
+        domHook,
+        slot: undefined,
+      },
+    ]);
+  });
+
+  test('getDetails by type', () => {
+    assert.deepEqual(
+      instance.getDetails('my-endpoint', {endpoint: 'a-place', type: 'style'}),
+      [
+        {
+          moduleName: 'style-module',
+          plugin: stylePlugin,
+          pluginUrl: stylePlugin._url,
+          type: 'style',
+          domHook,
+          slot: undefined,
+        },
+      ]
+    );
+  });
+
+  test('getDetails by module', () => {
+    assert.deepEqual(
+      instance.getDetails('my-endpoint', {
+        endpoint: 'my-endpoint',
+        moduleName: 'decorate-module',
+      }),
+      [
+        {
+          moduleName: 'decorate-module',
+          plugin: decoratePlugin,
+          pluginUrl: decoratePlugin._url,
+          type: 'decorate',
+          domHook,
+          slot: undefined,
+        },
+      ]
+    );
+  });
+
+  test('getModules', () => {
+    assert.deepEqual(instance.getModules('my-endpoint'), [
+      'decorate-module',
+      'style-module',
+    ]);
+  });
+
+  test('getPlugins URLs are unique', () => {
+    assert.equal(decoratePlugin._url, stylePlugin._url);
+    assert.deepEqual(instance.getPlugins('my-endpoint'), [decoratePlugin._url]);
+  });
+
+  test('onNewEndpoint', () => {
+    const newModuleStub = sinon.stub();
+    instance.setPluginsReady();
+    instance.onNewEndpoint('my-endpoint', newModuleStub);
+    instance.registerModule(decoratePlugin, {
+      endpoint: 'my-endpoint',
+      type: 'replace',
+      moduleName: 'replace-module',
+      domHook,
+    });
+    assert.deepEqual(newModuleStub.lastCall.args[0], {
+      moduleName: 'replace-module',
+      plugin: decoratePlugin,
+      pluginUrl: decoratePlugin._url,
+      type: 'replace',
+      domHook,
+      slot: undefined,
+    });
+  });
+});
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.ts
index 903fb2c..2b6db21 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.ts
@@ -68,11 +68,6 @@
     return this.restApi.getAccount();
   }
 
-  getAccountCapabilities(capabilities: string[]) {
-    this.reporting.trackApi(this.plugin, 'rest', 'getAccountCapabilities');
-    return this.restApi.getAccountCapabilities(capabilities);
-  }
-
   getRepos(filter: string, reposPerPage: number, offset?: number) {
     this.reporting.trackApi(this.plugin, 'rest', 'getRepos');
     return this.restApi.getRepos(filter, reposPerPage, offset);
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
index 31a709a..026ca4c 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
@@ -14,6 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import '../../../styles/gr-font-styles';
 import '../../../styles/gr-voting-styles';
 import '../../../styles/shared-styles';
 import '../gr-account-label/gr-account-label';
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_html.ts b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_html.ts
index 1588020..f31b57f 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_html.ts
@@ -17,6 +17,9 @@
 import {html} from '@polymer/polymer/lib/utils/html-tag';
 
 export const htmlTemplate = html`
+  <style include="gr-font-styles">
+    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+  </style>
   <style include="gr-voting-styles">
     /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
   </style>
diff --git a/polygerrit-ui/app/elements/shared/gr-label/gr-label.ts b/polygerrit-ui/app/elements/shared/gr-label/gr-label.ts
index c13ca6e..fd10145 100644
--- a/polygerrit-ui/app/elements/shared/gr-label/gr-label.ts
+++ b/polygerrit-ui/app/elements/shared/gr-label/gr-label.ts
@@ -32,8 +32,11 @@
   }
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = TooltipMixin(PolymerElement);
+
 @customElement('gr-label')
-export class GrLabel extends TooltipMixin(PolymerElement) {
+export class GrLabel extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts
index 4a2ba86..f5a0c4d 100644
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts
@@ -25,6 +25,9 @@
   }
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = TooltipMixin(PolymerElement);
+
 /**
  * The gr-limited-text element is for displaying text with a maximum length
  * (in number of characters) to display. If the length of the text exceeds the
@@ -32,7 +35,7 @@
  * and a tooltip containing the full text is enabled.
  */
 @customElement('gr-limited-text')
-export class GrLimitedText extends TooltipMixin(PolymerElement) {
+export class GrLimitedText extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.ts b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.ts
index b74d643..5bf5a8f 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.ts
@@ -20,8 +20,8 @@
 import '../gr-limited-text/gr-limited-text';
 import {fireEvent} from '../../../utils/event-util';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -30,7 +30,7 @@
 }
 
 @customElement('gr-linked-chip')
-export class GrLinkedChip extends GrLitElement {
+export class GrLinkedChip extends LitElement {
   @property({type: String})
   href = '';
 
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
index 5d72925..2812b47 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
@@ -14,10 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import '../../../styles/shared-styles';
+import {PolymerElement} from '@polymer/polymer/polymer-element';
+import {htmlTemplate} from './gr-linked-text_html';
 import {GrLinkTextParser, LinkTextParserConfig} from './link-text-parser';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property, query} from 'lit-element';
+import {customElement, property, observe} from '@polymer/decorators';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -25,10 +27,17 @@
   }
 }
 
+export interface GrLinkedText {
+  $: {
+    output: HTMLSpanElement;
+  };
+}
+
 @customElement('gr-linked-text')
-export class GrLinkedText extends GrLitElement {
-  @query('#output')
-  outputElement?: HTMLSpanElement;
+export class GrLinkedText extends PolymerElement {
+  static get template() {
+    return htmlTemplate;
+  }
 
   @property({type: Boolean})
   removeZeroWidthSpace?: boolean;
@@ -37,63 +46,61 @@
   @property({type: String})
   content: string | null = null;
 
-  @property({type: Boolean, reflect: true})
+  @property({type: Boolean, reflectToAttribute: true})
   pre = false;
 
-  @property({type: Boolean, reflect: true})
+  @property({type: Boolean, reflectToAttribute: true})
   disabled = false;
 
   @property({type: Object})
   config?: LinkTextParserConfig;
 
-  static override get styles() {
-    return [
-      css`
-        :host {
-          display: block;
-        }
-        :host([pre]) span {
-          white-space: var(--linked-text-white-space, pre-wrap);
-          word-wrap: var(--linked-text-word-wrap, break-word);
-        }
-        :host([disabled]) a {
-          color: inherit;
-          text-decoration: none;
-          pointer-events: none;
-        }
-        a {
-          color: var(--link-color);
-        }
-      `,
-    ];
-  }
-
-  override render() {
+  @observe('content')
+  _contentChanged(content: string | null) {
+    // In the case where the config may not be set (perhaps due to the
+    // request for it still being in flight), set the content anyway to
+    // prevent waiting on the config to display the text.
     if (!this.config) {
       return;
     }
-    return html`<span id="output">${this.content}</span>`;
+    this.$.output.textContent = content;
   }
 
-  override updated() {
-    if (!this.outputElement || !this.config) return;
-    this.outputElement.textContent = '';
+  /**
+   * Because either the source text or the linkification config has changed,
+   * the content should be re-parsed.
+   *
+   * @param content The raw, un-linkified source string to parse.
+   * @param config The server config specifying commentLink patterns
+   */
+  @observe('content', 'config')
+  _contentOrConfigChanged(
+    content: string | null,
+    config?: LinkTextParserConfig
+  ) {
+    if (!config) {
+      return;
+    }
+
     // TODO(TS): mapCommentlinks always has value, remove
     if (!GerritNav.mapCommentlinks) return;
-    const config = GerritNav.mapCommentlinks(this.config);
+    config = GerritNav.mapCommentlinks(config);
+    const output = this.$.output;
+    output.textContent = '';
     const parser = new GrLinkTextParser(
       config,
       (text: string | null, href: string | null, fragment?: DocumentFragment) =>
         this._handleParseResult(text, href, fragment),
       this.removeZeroWidthSpace
     );
-    parser.parse(this.content);
+    parser.parse(content);
+
     // Ensure that external links originating from HTML commentlink configs
     // open in a new tab. @see Issue 5567
     // Ensure links to the same host originating from commentlink configs
     // open in the same tab. When target is not set - default is _self
     // @see Issue 4616
-    this.outputElement.querySelectorAll('a').forEach(anchor => {
+    output.querySelectorAll('a').forEach(anchor => {
       if (anchor.hostname === window.location.hostname) {
         anchor.removeAttribute('target');
       } else {
@@ -117,8 +124,7 @@
     href: string | null,
     fragment?: DocumentFragment
   ) {
-    const output = this.outputElement;
-    if (!output) return;
+    const output = this.$.output;
     if (href) {
       const a = document.createElement('a');
       a.setAttribute('href', href);
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_html.ts b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_html.ts
new file mode 100644
index 0000000..0d44bc8
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_html.ts
@@ -0,0 +1,38 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {html} from '@polymer/polymer/lib/utils/html-tag';
+
+export const htmlTemplate = html`
+  <style>
+    :host {
+      display: block;
+    }
+    :host([pre]) span {
+      white-space: var(--linked-text-white-space, pre-wrap);
+      word-wrap: var(--linked-text-word-wrap, break-word);
+    }
+    :host([disabled]) a {
+      color: inherit;
+      text-decoration: none;
+      pointer-events: none;
+    }
+    a {
+      color: var(--link-color);
+    }
+  </style>
+  <span id="output"></span>
+`;
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.ts b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.ts
index c97c168..b2cdba1 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.ts
@@ -85,11 +85,10 @@
     window.CANONICAL_PATH = originalCanonicalPath;
   });
 
-  test('URL pattern was parsed and linked.', async () => {
+  test('URL pattern was parsed and linked.', () => {
     // Regular inline link.
     const url = 'https://bugs.chromium.org/p/gerrit/issues/detail?id=3650';
     element.content = url;
-    await flush();
     const linkEl = queryAndAssert(element, '#output')
       .childNodes[0] as HTMLAnchorElement;
     assert.equal(linkEl.target, '_blank');
@@ -98,10 +97,9 @@
     assert.equal(linkEl.textContent, url);
   });
 
-  test('Bug pattern was parsed and linked', async () => {
+  test('Bug pattern was parsed and linked', () => {
     // "Issue/Bug" pattern.
     element.content = 'Issue 3650';
-    await flush();
 
     let linkEl = queryAndAssert(element, '#output')
       .childNodes[0] as HTMLAnchorElement;
@@ -111,7 +109,6 @@
     assert.equal(linkEl.textContent, 'Issue 3650');
 
     element.content = 'Bug 3650';
-    await flush();
     linkEl = queryAndAssert(element, '#output')
       .childNodes[0] as HTMLAnchorElement;
     assert.equal(linkEl.target, '_blank');
@@ -120,10 +117,10 @@
     assert.equal(linkEl.textContent, 'Bug 3650');
   });
 
-  test('Pattern with same prefix as link was correctly parsed', async () => {
+  test('Pattern with same prefix as link was correctly parsed', () => {
     // Pattern starts with the same prefix (`http`) as the url.
     element.content = 'httpexample 3650';
-    await flush();
+
     assert.equal(queryAndAssert(element, '#output').childNodes.length, 1);
     const linkEl = queryAndAssert(element, '#output')
       .childNodes[0] as HTMLAnchorElement;
@@ -133,12 +130,12 @@
     assert.equal(linkEl.textContent, 'httpexample 3650');
   });
 
-  test('Change-Id pattern was parsed and linked', async () => {
+  test('Change-Id pattern was parsed and linked', () => {
     // "Change-Id:" pattern.
     const changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
     const prefix = 'Change-Id: ';
     element.content = prefix + changeID;
-    await flush();
+
     const textNode = queryAndAssert(element, '#output').childNodes[0];
     const linkEl = queryAndAssert(element, '#output')
       .childNodes[1] as HTMLAnchorElement;
@@ -150,14 +147,14 @@
     assert.equal(linkEl.textContent, changeID);
   });
 
-  test('Change-Id pattern was parsed and linked with base url', async () => {
+  test('Change-Id pattern was parsed and linked with base url', () => {
     window.CANONICAL_PATH = '/r';
 
     // "Change-Id:" pattern.
     const changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
     const prefix = 'Change-Id: ';
     element.content = prefix + changeID;
-    await flush();
+
     const textNode = queryAndAssert(element, '#output').childNodes[0];
     const linkEl = queryAndAssert(element, '#output')
       .childNodes[1] as HTMLAnchorElement;
@@ -169,9 +166,8 @@
     assert.equal(linkEl.textContent, changeID);
   });
 
-  test('Multiple matches', async () => {
+  test('Multiple matches', () => {
     element.content = 'Issue 3650\nIssue 3450';
-    await flush();
     const linkEl1 = queryAndAssert(element, '#output')
       .childNodes[0] as HTMLAnchorElement;
     const linkEl2 = queryAndAssert(element, '#output')
@@ -192,7 +188,7 @@
     assert.equal(linkEl2.textContent, 'Issue 3450');
   });
 
-  test('Change-Id pattern parsed before bug pattern', async () => {
+  test('Change-Id pattern parsed before bug pattern', () => {
     // "Change-Id:" pattern.
     const changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
     const prefix = 'Change-Id: ';
@@ -204,7 +200,7 @@
     const bugUrl = 'https://bugs.chromium.org/p/gerrit/issues/detail?id=3650';
 
     element.content = prefix + changeID + bug;
-    await flush();
+
     const textNode = queryAndAssert(element, '#output').childNodes[0];
     const changeLinkEl = queryAndAssert(element, '#output')
       .childNodes[1] as HTMLAnchorElement;
@@ -222,9 +218,8 @@
     assert.equal(bugLinkEl.textContent, 'Issue 3650');
   });
 
-  test('html field in link config', async () => {
+  test('html field in link config', () => {
     element.content = 'google:do a barrel roll';
-    await flush();
     const linkEl = queryAndAssert(element, '#output')
       .childNodes[0] as HTMLAnchorElement;
     assert.equal(
@@ -234,58 +229,52 @@
     assert.equal(linkEl.textContent, 'do a barrel roll');
   });
 
-  test('removing hash from links', async () => {
+  test('removing hash from links', () => {
     element.content = 'hash:foo';
-    await flush();
     const linkEl = queryAndAssert(element, '#output')
       .childNodes[0] as HTMLAnchorElement;
     assert.isTrue(linkEl.href.endsWith('/awesomesauce'));
     assert.equal(linkEl.textContent, 'foo');
   });
 
-  test('html with base url', async () => {
+  test('html with base url', () => {
     window.CANONICAL_PATH = '/r';
 
     element.content = 'test foo';
-    await flush();
     const linkEl = queryAndAssert(element, '#output')
       .childNodes[0] as HTMLAnchorElement;
     assert.isTrue(linkEl.href.endsWith('/r/awesomesauce'));
     assert.equal(linkEl.textContent, 'foo');
   });
 
-  test('a is not at start', async () => {
+  test('a is not at start', () => {
     window.CANONICAL_PATH = '/r';
 
     element.content = 'a test foo';
-    await flush();
     const linkEl = queryAndAssert(element, '#output')
       .childNodes[1] as HTMLAnchorElement;
     assert.isTrue(linkEl.href.endsWith('/r/awesomesauce'));
     assert.equal(linkEl.textContent, 'foo');
   });
 
-  test('hash html with base url', async () => {
+  test('hash html with base url', () => {
     window.CANONICAL_PATH = '/r';
 
     element.content = 'hash:foo';
-    await flush();
     const linkEl = queryAndAssert(element, '#output')
       .childNodes[0] as HTMLAnchorElement;
     assert.isTrue(linkEl.href.endsWith('/r/awesomesauce'));
     assert.equal(linkEl.textContent, 'foo');
   });
 
-  test('disabled config', async () => {
+  test('disabled config', () => {
     element.content = 'foo:baz';
-    await flush();
     assert.equal(queryAndAssert(element, '#output').innerHTML, 'foo:baz');
   });
 
-  test('R=email labels link correctly', async () => {
+  test('R=email labels link correctly', () => {
     element.removeZeroWidthSpace = true;
     element.content = 'R=\u200Btest@google.com';
-    await flush();
     assert.equal(
       queryAndAssert(element, '#output').textContent,
       'R=test@google.com'
@@ -296,10 +285,9 @@
     );
   });
 
-  test('CC=email labels link correctly', async () => {
+  test('CC=email labels link correctly', () => {
     element.removeZeroWidthSpace = true;
     element.content = 'CC=\u200Btest@google.com';
-    await flush();
     assert.equal(
       queryAndAssert(element, '#output').textContent,
       'CC=test@google.com'
@@ -310,42 +298,36 @@
     );
   });
 
-  test('only {http,https,mailto} protocols are linkified', async () => {
+  test('only {http,https,mailto} protocols are linkified', () => {
     element.content = 'xx mailto:test@google.com yy';
-    await flush();
     let links = queryAndAssert(element, '#output').querySelectorAll('a');
     assert.equal(links.length, 1);
     assert.equal(links[0].getAttribute('href'), 'mailto:test@google.com');
     assert.equal(links[0].innerHTML, 'mailto:test@google.com');
 
     element.content = 'xx http://google.com yy';
-    await flush();
     links = queryAndAssert(element, '#output').querySelectorAll('a');
     assert.equal(links.length, 1);
     assert.equal(links[0].getAttribute('href'), 'http://google.com');
     assert.equal(links[0].innerHTML, 'http://google.com');
 
     element.content = 'xx https://google.com yy';
-    await flush();
     links = queryAndAssert(element, '#output').querySelectorAll('a');
     assert.equal(links.length, 1);
     assert.equal(links[0].getAttribute('href'), 'https://google.com');
     assert.equal(links[0].innerHTML, 'https://google.com');
 
     element.content = 'xx ssh://google.com yy';
-    await flush();
     links = queryAndAssert(element, '#output').querySelectorAll('a');
     assert.equal(links.length, 0);
 
     element.content = 'xx ftp://google.com yy';
-    await flush();
     links = queryAndAssert(element, '#output').querySelectorAll('a');
     assert.equal(links.length, 0);
   });
 
-  test('links without leading whitespace are linkified', async () => {
+  test('links without leading whitespace are linkified', () => {
     element.content = 'xx abcmailto:test@google.com yy';
-    await flush();
     assert.equal(
       queryAndAssert(element, '#output').innerHTML.substr(0, 6),
       'xx abc'
@@ -356,7 +338,6 @@
     assert.equal(links[0].innerHTML, 'mailto:test@google.com');
 
     element.content = 'xx defhttp://google.com yy';
-    await flush();
     assert.equal(
       queryAndAssert(element, '#output').innerHTML.substr(0, 6),
       'xx def'
@@ -367,7 +348,6 @@
     assert.equal(links[0].innerHTML, 'http://google.com');
 
     element.content = 'xx qwehttps://google.com yy';
-    await flush();
     assert.equal(
       queryAndAssert(element, '#output').innerHTML.substr(0, 6),
       'xx qwe'
@@ -379,7 +359,6 @@
 
     // Non-latin character
     element.content = 'xx абвhttps://google.com yy';
-    await flush();
     assert.equal(
       queryAndAssert(element, '#output').innerHTML.substr(0, 6),
       'xx абв'
@@ -390,17 +369,15 @@
     assert.equal(links[0].innerHTML, 'https://google.com');
 
     element.content = 'xx ssh://google.com yy';
-    await flush();
     links = queryAndAssert(element, '#output').querySelectorAll('a');
     assert.equal(links.length, 0);
 
     element.content = 'xx ftp://google.com yy';
-    await flush();
     links = queryAndAssert(element, '#output').querySelectorAll('a');
     assert.equal(links.length, 0);
   });
 
-  test('overlapping links', async () => {
+  test('overlapping links', () => {
     element.config = {
       b1: {
         match: '(B:\\s*)(\\d+)',
@@ -412,8 +389,7 @@
       },
     };
     element.content = '- B: 123, 45';
-    await flush();
-    const links = element.shadowRoot!.querySelectorAll('a');
+    const links = element.root!.querySelectorAll('a');
 
     assert.equal(links.length, 2);
     assert.equal(
@@ -427,4 +403,12 @@
     assert.equal(links[1].href, 'ftp://foo/45');
     assert.equal(links[1].textContent, '45');
   });
+
+  test('_contentOrConfigChanged called with config', () => {
+    const contentStub = sinon.stub(element, '_contentChanged');
+    const contentConfigStub = sinon.stub(element, '_contentOrConfigChanged');
+    element.content = 'some text';
+    assert.isTrue(contentStub.called);
+    assert.isTrue(contentConfigStub.called);
+  });
 });
diff --git a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts
index b6cb8f9..1622365 100644
--- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts
+++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts
@@ -34,11 +34,14 @@
   }
 }
 
-@customElement('gr-overlay')
-export class GrOverlay extends IronOverlayMixin(
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = IronOverlayMixin(
   PolymerElement,
   IronOverlayBehavior as IronOverlayBehavior
-) {
+);
+
+@customElement('gr-overlay')
+export class GrOverlay extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.ts b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.ts
index 5b42318..7ed000b 100644
--- a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.ts
+++ b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.ts
@@ -18,8 +18,8 @@
 import {GrCopyClipboard} from '../gr-copy-clipboard/gr-copy-clipboard';
 import {queryAndAssert} from '../../../utils/common-util';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {GrLitElement} from '../../lit/gr-lit-element';
-import {css, customElement, html, property} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -28,7 +28,7 @@
 }
 
 @customElement('gr-shell-command')
-export class GrShellCommand extends GrLitElement {
+export class GrShellCommand extends LitElement {
   @property({type: String})
   command: string | undefined;
 
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
index 3d5cb3d..ce1eec3 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
@@ -85,8 +85,11 @@
   }
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = KeyboardShortcutMixin(PolymerElement);
+
 @customElement('gr-textarea')
-export class GrTextarea extends KeyboardShortcutMixin(PolymerElement) {
+export class GrTextarea extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts
index feed8a6..9d228ab 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts
@@ -26,11 +26,14 @@
   }
 }
 
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = TooltipMixin(PolymerElement);
+
 /**
  * Transclude anything inside and wrap them to support tooltip functionality.
  */
 @customElement('gr-tooltip-content')
-export class GrTooltipContent extends TooltipMixin(PolymerElement) {
+export class GrTooltipContent extends base {
   static get template() {
     return htmlTemplate;
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-vote-chip/gr-vote-chip.ts b/polygerrit-ui/app/elements/shared/gr-vote-chip/gr-vote-chip.ts
index 789637c..5b9ad5c 100644
--- a/polygerrit-ui/app/elements/shared/gr-vote-chip/gr-vote-chip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-vote-chip/gr-vote-chip.ts
@@ -14,12 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {css, customElement, html, property} from 'lit-element';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
 import {ApprovalInfo, LabelInfo} from '../../../api/rest-api';
 import {appContext} from '../../../services/app-context';
 import {KnownExperimentId} from '../../../services/flags/flags';
-import {getVotingRangeOrDefault, valueString} from '../../../utils/label-util';
-import {GrLitElement} from '../../lit/gr-lit-element';
+import {
+  classForLabelStatus,
+  getLabelStatus,
+  valueString,
+} from '../../../utils/label-util';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -28,44 +32,76 @@
 }
 
 @customElement('gr-vote-chip')
-export class GrVoteChip extends GrLitElement {
+export class GrVoteChip extends LitElement {
   @property({type: Object})
   vote?: ApprovalInfo;
 
   @property({type: Object})
   label?: LabelInfo;
 
+  /** Show vote as a stack of same votes. */
+  @property({type: Boolean})
+  more = false;
+
   private readonly flagsService = appContext.flagsService;
 
   static get styles() {
     return [
       css`
-        .chipVote {
-          display: flex;
-          justify-content: center;
-          margin-right: var(--spacing-s);
-          padding: 1px;
-          border-radius: var(--border-radius);
-          color: var(--vote-text-color);
-          border: 1px solid var(--border-color);
-          line-height: calc(var(--line-height-normal) - 4px);
-        }
-        .max {
+        .vote-chip.max {
           background-color: var(--vote-color-approved);
+          padding: 2px;
         }
-        .min {
+        .vote-chip.max.more {
+          padding: 1px;
+          border: 1px solid var(--vote-outline-recommended);
+        }
+        .vote-chip.min {
           background-color: var(--vote-color-rejected);
+          padding: 2px;
         }
-        .positive {
+        .vote-chip.min.more {
+          padding: 1px;
+          border: 1px solid var(--vote-outline-disliked);
+        }
+        .vote-chip.positive,
+        .chip-angle.max,
+        .chip-angle.positive {
           background-color: var(--vote-color-recommended);
           border: 1px solid var(--vote-outline-recommended);
           color: var(--chip-color);
         }
-        .negative {
+        .vote-chip.negative,
+        .chip-angle.min,
+        .chip-angle.negative {
           background-color: var(--vote-color-disliked);
           border: 1px solid var(--vote-outline-disliked);
           color: var(--chip-color);
         }
+        .vote-chip,
+        .chip-angle {
+          display: flex;
+          width: var(--gr-vote-chip-width, 18px);
+          height: var(--gr-vote-chip-height, 18px);
+          justify-content: center;
+          margin-right: var(--spacing-s);
+          padding: 1px;
+          border-radius: var(--border-radius);
+          line-height: var(--gr-vote-chip-width, 18px);
+        }
+        .vote-chip {
+          position: relative;
+          z-index: 2;
+        }
+        .chip-angle {
+          position: absolute;
+          top: 2px;
+          left: 2px;
+          z-index: 1;
+        }
+        .container {
+          position: relative;
+        }
       `,
     ];
   }
@@ -75,26 +111,20 @@
       return;
     if (!this.vote?.value) return;
     const className = this.computeClass(this.vote.value, this.label);
-    return html`<div class="chipVote ${className}">
-      ${valueString(this.vote.value)}
-    </div>`;
+    return html`<span class="container">
+      <div class="vote-chip ${className} ${this.more ? 'more' : ''}">
+        ${valueString(this.vote.value)}
+      </div>
+      ${this.more
+        ? html`<div class="chip-angle ${className}">
+            ${valueString(this.vote.value)}
+          </div>`
+        : ''}
+    </span>`;
   }
 
-  computeClass(vote: Number, label?: LabelInfo) {
-    const votingRange = getVotingRangeOrDefault(label);
-    if (vote > 0) {
-      if (vote === votingRange.max) {
-        return 'max';
-      } else {
-        return 'positive';
-      }
-    } else if (vote < 0) {
-      if (vote === votingRange.min) {
-        return 'min';
-      } else {
-        return 'negative';
-      }
-    }
-    return '';
+  computeClass(vote: number, label?: LabelInfo) {
+    const status = getLabelStatus(label, vote);
+    return classForLabelStatus(status);
   }
 }
diff --git a/polygerrit-ui/app/mixins/gr-change-table-mixin/gr-change-table-mixin_test.js b/polygerrit-ui/app/mixins/gr-change-table-mixin/gr-change-table-mixin_test.js
index 0d6b4ad..8bc223f 100644
--- a/polygerrit-ui/app/mixins/gr-change-table-mixin/gr-change-table-mixin_test.js
+++ b/polygerrit-ui/app/mixins/gr-change-table-mixin/gr-change-table-mixin_test.js
@@ -19,8 +19,10 @@
 import {ChangeTableMixin} from './gr-change-table-mixin.js';
 import {PolymerElement} from '@polymer/polymer/polymer-element.js';
 
-class GrChangeTableMixinTestElement extends
-  ChangeTableMixin(PolymerElement) {
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = ChangeTableMixin(PolymerElement);
+
+class GrChangeTableMixinTestElement extends base {
   static get is() { return 'gr-change-table-mixin-test-element'; }
 }
 
diff --git a/polygerrit-ui/app/mixins/gr-list-view-mixin/gr-list-view-mixin_test.js b/polygerrit-ui/app/mixins/gr-list-view-mixin/gr-list-view-mixin_test.js
index 407f29f..d2b429f 100644
--- a/polygerrit-ui/app/mixins/gr-list-view-mixin/gr-list-view-mixin_test.js
+++ b/polygerrit-ui/app/mixins/gr-list-view-mixin/gr-list-view-mixin_test.js
@@ -14,7 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 import '../../test/common-test-setup-karma.js';
 import {ListViewMixin} from './gr-list-view-mixin.js';
 import {PolymerElement} from '@polymer/polymer/polymer-element.js';
@@ -22,8 +21,10 @@
 const basicFixture = fixtureFromElement(
     'gr-list-view-mixin-test-element');
 
-class GrListViewMixinTestElement extends
-  ListViewMixin(PolymerElement) {
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = ListViewMixin(PolymerElement);
+
+class GrListViewMixinTestElement extends base {
   static get is() { return 'gr-list-view-mixin-test-element'; }
 }
 
diff --git a/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin_test.js b/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin_test.js
index 209c83af..69e6e86 100644
--- a/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin_test.js
+++ b/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin_test.js
@@ -21,7 +21,10 @@
 
 const basicFixture = fixtureFromElement('gr-tooltip-mixin-element');
 
-class GrTooltipMixinTestElement extends TooltipMixin(PolymerElement) {
+// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
+const base = TooltipMixin(PolymerElement);
+
+class GrTooltipMixinTestElement extends base {
   static get is() {
     return 'gr-tooltip-mixin-element';
   }
diff --git a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts b/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
index 3efa9d6..5c4c9ce 100644
--- a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
+++ b/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
@@ -150,6 +150,7 @@
   TOGGLE_CHANGE_REVIEWED = 'TOGGLE_CHANGE_REVIEWED',
   TOGGLE_CHANGE_STAR = 'TOGGLE_CHANGE_STAR',
   REFRESH_CHANGE_LIST = 'REFRESH_CHANGE_LIST',
+  OPEN_SUBMIT_DIALOG = 'OPEN_SUBMIT_DIALOG',
 
   OPEN_REPLY_DIALOG = 'OPEN_REPLY_DIALOG',
   OPEN_DOWNLOAD_DIALOG = 'OPEN_DOWNLOAD_DIALOG',
@@ -328,6 +329,11 @@
   ShortcutSection.ACTIONS,
   'Star/unstar change'
 );
+_describe(
+  Shortcut.OPEN_SUBMIT_DIALOG,
+  ShortcutSection.ACTIONS,
+  'Open submit dialog'
+);
 _describe(Shortcut.EDIT_TOPIC, ShortcutSection.ACTIONS, 'Add a change topic');
 _describe(
   Shortcut.DIFF_AGAINST_BASE,
diff --git a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin_test.js b/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin_test.js
index d5e7fe7..4536ecd 100644
--- a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin_test.js
+++ b/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin_test.js
@@ -22,6 +22,7 @@
 } from './keyboard-shortcut-mixin.js';
 import {html} from '@polymer/polymer/lib/utils/html-tag.js';
 import {PolymerElement} from '@polymer/polymer/polymer-element.js';
+import {mockPromise} from '../../test/test-utils.js';
 
 const basicFixture =
     fixtureFromElement('keyboard-shortcut-mixin-test-element');
@@ -271,69 +272,81 @@
     });
   });
 
-  test('doesn’t block kb shortcuts for non-allowed els', done => {
+  test('doesn’t block kb shortcuts for non-allowed els', async () => {
     const divEl = document.createElement('div');
     element.appendChild(divEl);
+    const promise = mockPromise();
     element._handleKey = e => {
       assert.isFalse(element.shouldSuppressKeyboardShortcut(e));
-      done();
+      promise.resolve();
     };
     MockInteractions.keyDownOn(divEl, 75, null, 'k');
+    await promise;
   });
 
-  test('blocks kb shortcuts for input els', done => {
+  test('blocks kb shortcuts for input els', async () => {
     const inputEl = document.createElement('input');
     element.appendChild(inputEl);
+    const promise = mockPromise();
     element._handleKey = e => {
       assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
-      done();
+      promise.resolve();
     };
     MockInteractions.keyDownOn(inputEl, 75, null, 'k');
+    await promise;
   });
 
-  test('doesn’t block kb shortcuts for checkboxes', done => {
+  test('doesn’t block kb shortcuts for checkboxes', async () => {
     const inputEl = document.createElement('input');
     inputEl.setAttribute('type', 'checkbox');
     element.appendChild(inputEl);
+    const promise = mockPromise();
     element._handleKey = e => {
       assert.isFalse(element.shouldSuppressKeyboardShortcut(e));
-      done();
+      promise.resolve();
     };
     MockInteractions.keyDownOn(inputEl, 75, null, 'k');
+    await promise;
   });
 
-  test('blocks kb shortcuts for textarea els', done => {
+  test('blocks kb shortcuts for textarea els', async () => {
     const textareaEl = document.createElement('textarea');
     element.appendChild(textareaEl);
+    const promise = mockPromise();
     element._handleKey = e => {
       assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
-      done();
+      promise.resolve();
     };
     MockInteractions.keyDownOn(textareaEl, 75, null, 'k');
+    await promise;
   });
 
-  test('blocks kb shortcuts for anything in a gr-overlay', done => {
+  test('blocks kb shortcuts for anything in a gr-overlay', async () => {
     const divEl = document.createElement('div');
     const element =
         overlay.querySelector('keyboard-shortcut-mixin-test-element');
     element.appendChild(divEl);
+    const promise = mockPromise();
     element._handleKey = e => {
       assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
-      done();
+      promise.resolve();
     };
     MockInteractions.keyDownOn(divEl, 75, null, 'k');
+    await promise;
   });
 
-  test('blocks enter shortcut on an anchor', done => {
+  test('blocks enter shortcut on an anchor', async () => {
     const anchorEl = document.createElement('a');
     const element =
         overlay.querySelector('keyboard-shortcut-mixin-test-element');
     element.appendChild(anchorEl);
+    const promise = mockPromise();
     element._handleKey = e => {
       assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
-      done();
+      promise.resolve();
     };
     MockInteractions.keyDownOn(anchorEl, 13, null, 'enter');
+    await promise;
   });
 
   test('modifierPressed returns accurate values', () => {
diff --git a/polygerrit-ui/app/node_modules_licenses/licenses.ts b/polygerrit-ui/app/node_modules_licenses/licenses.ts
index bcdab0e..ede84ff 100644
--- a/polygerrit-ui/app/node_modules_licenses/licenses.ts
+++ b/polygerrit-ui/app/node_modules_licenses/licenses.ts
@@ -45,6 +45,12 @@
 /** List of licenses texts. Add the licenses here if there is no text file with license
  * in package. For details - see comments for {@link LicenseInfo} and {@link PackageInfo} */
 class SharedLicenses {
+  public static Lit: LicenseInfo = {
+    name: "Lit",
+    type: LicenseTypes.Bsd3,
+    sharedLicenseFile: "lit.txt",
+  };
+
   public static Polymer2014: LicenseInfo = {
     name: "Polymer-2014",
     type: LicenseTypes.Bsd3,
@@ -97,6 +103,10 @@
 
 const packages: PackageInfo[] = [
   {
+    name: "@lit/reactive-element",
+    license: SharedLicenses.Lit,
+  },
+  {
     name: "@polymer/decorators",
     license: SharedLicenses.Polymer2017,
   },
@@ -304,6 +314,14 @@
     }
   },
   {
+    name: "@types/trusted-types",
+    license: {
+      name: 'DefinitelyTyped',
+      type: LicenseTypes.Mit,
+      packageLicenseFile: "LICENSE"
+    }
+  },
+  {
     name: "@webcomponents/shadycss",
     license: SharedLicenses.Polymer2017
   },
@@ -373,20 +391,16 @@
       "src/operators", "src/testing", "src/webSocket"],
   },
   {
+    name: "lit",
+    license: SharedLicenses.Lit,
+  },
+  {
     name: "lit-element",
-    license: {
-      name: "lit-element",
-      type: LicenseTypes.Bsd3,
-      packageLicenseFile: "LICENSE"
-    },
+    license: SharedLicenses.Lit,
   },
   {
     name: "lit-html",
-    license: {
-      name: "lit-html",
-      type: LicenseTypes.Bsd3,
-      packageLicenseFile: "LICENSE"
-    },
+    license: SharedLicenses.Lit,
   },
   {
     name: "tslib",
diff --git a/polygerrit-ui/app/node_modules_licenses/licenses/lit.txt b/polygerrit-ui/app/node_modules_licenses/licenses/lit.txt
new file mode 100644
index 0000000..c8ed226
--- /dev/null
+++ b/polygerrit-ui/app/node_modules_licenses/licenses/lit.txt
@@ -0,0 +1,28 @@
+BSD 3-Clause License
+
+Copyright (c) 2017 Google LLC. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/polygerrit-ui/app/node_modules_licenses/tsconfig.json b/polygerrit-ui/app/node_modules_licenses/tsconfig.json
index c562a0c..f0a540b 100644
--- a/polygerrit-ui/app/node_modules_licenses/tsconfig.json
+++ b/polygerrit-ui/app/node_modules_licenses/tsconfig.json
@@ -1,7 +1,7 @@
 {
   "compilerOptions": {
-    "target": "es6",
-    "module": "commonjs",
+    "target": "es2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
+    "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
     "allowSyntheticDefaultImports": true,
     "esModuleInterop": true,
     "strict": true,
diff --git a/polygerrit-ui/app/package.json b/polygerrit-ui/app/package.json
index a2e9c32..4be6241 100644
--- a/polygerrit-ui/app/package.json
+++ b/polygerrit-ui/app/package.json
@@ -38,7 +38,7 @@
     "ba-linkify": "^1.0.1",
     "codemirror-minified": "^5.62.2",
     "immer": "^9.0.5",
-    "lit-element": "^2.5.1",
+    "lit": "2.0.0-rc.3",
     "page": "^1.11.6",
     "polymer-bridges": "file:../../polymer-bridges/",
     "polymer-resin": "^2.0.1",
diff --git a/polygerrit-ui/app/polymer.json b/polygerrit-ui/app/polymer.json
index 02ffd26..4348ba8 100644
--- a/polygerrit-ui/app/polymer.json
+++ b/polygerrit-ui/app/polymer.json
@@ -9,10 +9,13 @@
   ],
   "lint": {
     "rules": ["polymer-3"],
-    "ignoreWarnings": ["deprecated-dom-call"],
+    "ignoreWarnings": [
+      "deprecated-dom-call",
+      "multiple-global-declarations"
+    ],
     "filesToIgnore": [
-        "**/gr-plugin-rest-api.js",
-        "**/.cache/**/gr-plugin-rest-api.js"
+      "**/gr-plugin-rest-api.js",
+      "**/.cache/**/gr-plugin-rest-api.js"
     ]
   }
 }
diff --git a/polygerrit-ui/app/samples/repo-command.js b/polygerrit-ui/app/samples/repo-command.js
index acecd7d..6abb120 100644
--- a/polygerrit-ui/app/samples/repo-command.js
+++ b/polygerrit-ui/app/samples/repo-command.js
@@ -32,7 +32,7 @@
         margin-bottom: var(--spacing-xxl);
       }
       </style>
-      <h3>Plugin Bork</h3>
+      <h3 class="heading-3">Plugin Bork</h3>
       <gr-button on-click="_handleCommandTap">Bork</gr-button>
     `;
   }
diff --git a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.js b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.js
index 66681ee..989bafb 100644
--- a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.js
+++ b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.js
@@ -35,17 +35,15 @@
     provider = new GrEmailSuggestionsProvider(appContext.restApiService);
   });
 
-  test('getSuggestions', done => {
+  test('getSuggestions', async () => {
     const getSuggestedAccountsStub =
         stubRestApi('getSuggestedAccounts').returns(
             Promise.resolve([account1, account2]));
 
-    provider.getSuggestions('Some input').then(res => {
-      assert.deepEqual(res, [account1, account2]);
-      assert.isTrue(getSuggestedAccountsStub.calledOnce);
-      assert.equal(getSuggestedAccountsStub.lastCall.args[0], 'Some input');
-      done();
-    });
+    const res = await provider.getSuggestions('Some input');
+    assert.deepEqual(res, [account1, account2]);
+    assert.isTrue(getSuggestedAccountsStub.calledOnce);
+    assert.equal(getSuggestedAccountsStub.lastCall.args[0], 'Some input');
   });
 
   test('makeSuggestionItem', () => {
diff --git a/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.js b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.js
index 1a14abf..67f9433 100644
--- a/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.js
+++ b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.js
@@ -36,7 +36,7 @@
     provider = new GrGroupSuggestionsProvider(appContext.restApiService);
   });
 
-  test('getSuggestions', done => {
+  test('getSuggestions', async () => {
     const getSuggestedAccountsStub =
         stubRestApi('getSuggestedGroups')
             .returns(Promise.resolve({
@@ -44,12 +44,10 @@
               'Other name': {id: 3, url: 'abcd'},
             }));
 
-    provider.getSuggestions('Some input').then(res => {
-      assert.deepEqual(res, [group1, group2]);
-      assert.isTrue(getSuggestedAccountsStub.calledOnce);
-      assert.equal(getSuggestedAccountsStub.lastCall.args[0], 'Some input');
-      done();
-    });
+    const res = await provider.getSuggestions('Some input');
+    assert.deepEqual(res, [group1, group2]);
+    assert.isTrue(getSuggestedAccountsStub.calledOnce);
+    assert.equal(getSuggestedAccountsStub.lastCall.args[0], 'Some input');
   });
 
   test('makeSuggestionItem', () => {
diff --git a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.js b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.js
index d3cad45..762d36c 100644
--- a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.js
+++ b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.js
@@ -54,7 +54,7 @@
   let redundantSuggestion3;
   let change;
 
-  setup(done => {
+  setup(async () => {
     owner = makeAccount();
     existingReviewer1 = makeAccount();
     existingReviewer2 = makeAccount();
@@ -78,15 +78,15 @@
       },
     };
 
-    return flush(done);
+    await flush();
   });
 
   suite('allowAnyUser set to false', () => {
-    setup(done => {
+    setup(async () => {
       provider = GrReviewerSuggestionsProvider.create(
           appContext.restApiService, change._number,
           SUGGESTIONS_PROVIDERS_USERS_TYPES.REVIEWER);
-      provider.init().then(done);
+      await provider.init();
     });
     suite('stubbed values for _getReviewerSuggestions', () => {
       let getChangeSuggestedReviewersStub;
@@ -165,17 +165,15 @@
         });
       });
 
-      test('getSuggestions', done => {
-        provider.getSuggestions()
-            .then(reviewers => {
-              // Default is no filtering.
-              assert.equal(reviewers.length, 6);
-              assert.deepEqual(reviewers,
-                  [redundantSuggestion1, redundantSuggestion2,
-                    redundantSuggestion3, suggestion1,
-                    suggestion2, suggestion3]);
-            })
-            .then(done);
+      test('getSuggestions', async () => {
+        const reviewers = await provider.getSuggestions();
+
+        // Default is no filtering.
+        assert.equal(reviewers.length, 6);
+        assert.deepEqual(reviewers,
+            [redundantSuggestion1, redundantSuggestion2,
+              redundantSuggestion3, suggestion1,
+              suggestion2, suggestion3]);
       });
 
       test('getSuggestions short circuits when logged out', () => {
@@ -190,41 +188,37 @@
       });
     });
 
-    test('getChangeSuggestedReviewers is used', done => {
+    test('getChangeSuggestedReviewers is used', async () => {
       const suggestReviewerStub = stubRestApi('getChangeSuggestedReviewers')
           .returns(Promise.resolve([]));
       const suggestAccountStub = stubRestApi('getSuggestedAccounts')
           .returns(Promise.resolve([]));
 
-      provider.getSuggestions('').then(() => {
-        assert.isTrue(suggestReviewerStub.calledOnce);
-        assert.isTrue(suggestReviewerStub.calledWith(42, ''));
-        assert.isFalse(suggestAccountStub.called);
-        done();
-      });
+      await provider.getSuggestions('');
+      assert.isTrue(suggestReviewerStub.calledOnce);
+      assert.isTrue(suggestReviewerStub.calledWith(42, ''));
+      assert.isFalse(suggestAccountStub.called);
     });
   });
 
   suite('allowAnyUser set to true', () => {
-    setup(done => {
+    setup(async () => {
       provider = GrReviewerSuggestionsProvider.create(
           appContext.restApiService, change._number,
           SUGGESTIONS_PROVIDERS_USERS_TYPES.ANY);
-      provider.init().then(done);
+      await provider.init();
     });
 
-    test('getSuggestedAccounts is used', done => {
+    test('getSuggestedAccounts is used', async () => {
       const suggestReviewerStub = stubRestApi('getChangeSuggestedReviewers')
           .returns(Promise.resolve([]));
       const suggestAccountStub = stubRestApi('getSuggestedAccounts')
           .returns(Promise.resolve([]));
 
-      provider.getSuggestions('').then(() => {
-        assert.isFalse(suggestReviewerStub.called);
-        assert.isTrue(suggestAccountStub.calledOnce);
-        assert.isTrue(suggestAccountStub.calledWith('cansee:42 '));
-        done();
-      });
+      await provider.getSuggestions('');
+      assert.isFalse(suggestReviewerStub.called);
+      assert.isTrue(suggestAccountStub.calledOnce);
+      assert.isTrue(suggestAccountStub.calledWith('cansee:42 '));
     });
   });
 });
diff --git a/polygerrit-ui/app/services/checks/checks-model.ts b/polygerrit-ui/app/services/checks/checks-model.ts
index f55631b..f5d6e35 100644
--- a/polygerrit-ui/app/services/checks/checks-model.ts
+++ b/polygerrit-ui/app/services/checks/checks-model.ts
@@ -30,6 +30,7 @@
 import {PatchSetNumber} from '../../types/common';
 import {AttemptDetail, createAttemptMap} from './checks-util';
 import {assertIsDefined} from '../../utils/common-util';
+import {deepEqualStringDict, equalArray} from '../../utils/compare-util';
 
 /**
  * The checks model maintains the state of checks for two patchsets: the latest
@@ -85,6 +86,13 @@
 interface ChecksProviderState {
   pluginName: string;
   loading: boolean;
+  /**
+   * Allows to distinguish whether loading:true is the *first* time of loading
+   * something for this provider. Or just a subsequent background update.
+   * Note that this is initially true even before loading is being set to true,
+   * so you may want to check loading && firstTimeLoad.
+   */
+  firstTimeLoad: boolean;
   /** Presence of errorMessage implicitly means that the provider is in ERROR state. */
   errorMessage?: string;
   /** Presence of loginCallback implicitly means that the provider is in NOT_LOGGED_IN state. */
@@ -160,6 +168,15 @@
   distinctUntilChanged()
 );
 
+export const someProvidersAreLoadingFirstTime$ = checksLatest$.pipe(
+  map(state =>
+    Object.values(state).some(
+      provider => provider.loading && provider.firstTimeLoad
+    )
+  ),
+  distinctUntilChanged()
+);
+
 export const someProvidersAreLoadingLatest$ = checksLatest$.pipe(
   map(state =>
     Object.values(state).some(providerState => providerState.loading)
@@ -184,6 +201,23 @@
   distinctUntilChanged()
 );
 
+export interface ErrorMessages {
+  /* Maps plugin name to error message. */
+  [name: string]: string;
+}
+
+export const errorMessagesLatest$ = checksLatest$.pipe(
+  map(state => {
+    const errorMessages: ErrorMessages = {};
+    for (const providerState of Object.values(state)) {
+      if (providerState.errorMessage === undefined) continue;
+      errorMessages[providerState.pluginName] = providerState.errorMessage;
+    }
+    return errorMessages;
+  }),
+  distinctUntilChanged(deepEqualStringDict)
+);
+
 export const loginCallbackLatest$ = checksLatest$.pipe(
   map(
     state =>
@@ -194,6 +228,19 @@
   distinctUntilChanged()
 );
 
+export const topLevelActionsLatest$ = checksLatest$.pipe(
+  map(state =>
+    Object.values(state).reduce(
+      (allActions: Action[], providerState: ChecksProviderState) => [
+        ...allActions,
+        ...providerState.actions,
+      ],
+      []
+    )
+  ),
+  distinctUntilChanged<Action[]>(equalArray)
+);
+
 export const topLevelActionsSelected$ = checksSelected$.pipe(
   map(state =>
     Object.values(state).reduce(
@@ -203,19 +250,21 @@
       ],
       []
     )
-  )
+  ),
+  distinctUntilChanged<Action[]>(equalArray)
 );
 
 export const topLevelLinksSelected$ = checksSelected$.pipe(
   map(state =>
     Object.values(state).reduce(
-      (allActions: Link[], providerState: ChecksProviderState) => [
-        ...allActions,
+      (allLinks: Link[], providerState: ChecksProviderState) => [
+        ...allLinks,
         ...providerState.links,
       ],
       []
     )
-  )
+  ),
+  distinctUntilChanged<Link[]>(equalArray)
 );
 
 export const allRunsLatestPatchset$ = checksLatest$.pipe(
@@ -227,7 +276,8 @@
       ],
       []
     )
-  )
+  ),
+  distinctUntilChanged<CheckRun[]>(equalArray)
 );
 
 export const allRunsSelectedPatchset$ = checksSelected$.pipe(
@@ -239,7 +289,8 @@
       ],
       []
     )
-  )
+  ),
+  distinctUntilChanged<CheckRun[]>(equalArray)
 );
 
 export const allRunsLatestPatchsetLatestAttempt$ = allRunsLatestPatchset$.pipe(
@@ -287,6 +338,7 @@
   pluginState[pluginName] = {
     pluginName,
     loading: false,
+    firstTimeLoad: true,
     runs: [],
     actions: [],
     links: [],
@@ -301,7 +353,7 @@
 export const fakeRun0: CheckRun = {
   pluginName: 'f0',
   internalRunId: 'f0',
-  checkName: 'FAKE Error Finder',
+  checkName: 'FAKE Error Finder Finder Finder',
   labelName: 'Presubmit',
   isSingleAttempt: true,
   isLatestAttempt: true,
@@ -493,7 +545,7 @@
 export const fakeRun4_1: CheckRun = {
   pluginName: 'f4',
   internalRunId: 'f4',
-  checkName: 'FAKE Elimination',
+  checkName: 'FAKE Elimination Long Long Long Long Long',
   status: RunStatus.COMPLETED,
   attempt: 1,
   isSingleAttempt: false,
@@ -504,7 +556,7 @@
 export const fakeRun4_2: CheckRun = {
   pluginName: 'f4',
   internalRunId: 'f4',
-  checkName: 'FAKE Elimination',
+  checkName: 'FAKE Elimination Long Long Long Long Long',
   status: RunStatus.COMPLETED,
   attempt: 2,
   isSingleAttempt: false,
@@ -522,7 +574,7 @@
 export const fakeRun4_3: CheckRun = {
   pluginName: 'f4',
   internalRunId: 'f4',
-  checkName: 'FAKE Elimination',
+  checkName: 'FAKE Elimination Long Long Long Long Long',
   status: RunStatus.COMPLETED,
   attempt: 3,
   isSingleAttempt: false,
@@ -540,13 +592,13 @@
 export const fakeRun4_4: CheckRun = {
   pluginName: 'f4',
   internalRunId: 'f4',
-  checkName: 'FAKE Elimination',
+  checkName: 'FAKE Elimination Long Long Long Long Long',
   checkDescription: 'Shows you the possible eliminations.',
   checkLink: 'https://www.google.com',
-  status: RunStatus.RUNNING,
+  status: RunStatus.COMPLETED,
   statusDescription: 'Everything was eliminated already.',
   statusLink: 'https://www.google.com',
-  attempt: 4,
+  attempt: 40,
   scheduledTimestamp: new Date('2021-04-02T03:14:15'),
   startedTimestamp: new Date('2021-04-02T04:24:25'),
   finishedTimestamp: new Date('2021-04-02T04:25:44'),
@@ -568,8 +620,56 @@
       ],
     },
   ],
+  actions: [
+    {
+      name: 'Re-Run',
+      tooltip: 'small',
+      primary: true,
+      callback: () => Promise.resolve({message: 'fake "re-run" triggered'}),
+    },
+  ],
 };
 
+export function fakeRun4CreateAttempts(from: number, to: number): CheckRun[] {
+  const runs: CheckRun[] = [];
+  for (let i = from; i < to; i++) {
+    runs.push(fakeRun4CreateAttempt(i));
+  }
+  return runs;
+}
+
+export function fakeRun4CreateAttempt(attempt: number): CheckRun {
+  return {
+    pluginName: 'f4',
+    internalRunId: 'f4',
+    checkName: 'FAKE Elimination Long Long Long Long Long',
+    status: RunStatus.COMPLETED,
+    attempt,
+    isSingleAttempt: false,
+    isLatestAttempt: false,
+    attemptDetails: [],
+    results:
+      attempt % 2 === 0
+        ? [
+            {
+              internalResultId: 'f43r0',
+              category: Category.ERROR,
+              summary:
+                'Without eliminating all the TODOs your change will break!',
+            },
+          ]
+        : [],
+  };
+}
+
+export const fakeRun4Att = [
+  fakeRun4_1,
+  fakeRun4_2,
+  fakeRun4_3,
+  ...fakeRun4CreateAttempts(5, 40),
+  fakeRun4_4,
+];
+
 export const fakeActions: Action[] = [
   {
     name: 'Fake Action 1',
@@ -587,6 +687,7 @@
   },
   {
     name: 'Fake Action 3',
+    summary: true,
     primary: false,
     tooltip: 'Tooltip for Fake Action 3',
     callback: () => Promise.resolve({message: 'fake action 3 triggered'}),
@@ -674,6 +775,7 @@
   pluginState[pluginName] = {
     ...pluginState[pluginName],
     loading: false,
+    firstTimeLoad: false,
     errorMessage,
     loginCallback: undefined,
     runs: [],
@@ -692,6 +794,7 @@
   pluginState[pluginName] = {
     ...pluginState[pluginName],
     loading: false,
+    firstTimeLoad: false,
     errorMessage: undefined,
     loginCallback,
     runs: [],
@@ -718,6 +821,7 @@
   pluginState[pluginName] = {
     ...pluginState[pluginName],
     loading: false,
+    firstTimeLoad: false,
     errorMessage: undefined,
     loginCallback: undefined,
     runs: runs.map(run => {
diff --git a/polygerrit-ui/app/services/checks/checks-model_test.ts b/polygerrit-ui/app/services/checks/checks-model_test.ts
index f05facb..dbd3f86 100644
--- a/polygerrit-ui/app/services/checks/checks-model_test.ts
+++ b/polygerrit-ui/app/services/checks/checks-model_test.ts
@@ -20,6 +20,7 @@
   _testOnly_getState,
   _testOnly_resetState,
   ChecksPatchset,
+  updateStateSetLoading,
   updateStateSetProvider,
   updateStateSetResults,
   updateStateUpdateResult,
@@ -45,34 +46,55 @@
   },
 ];
 
+function current() {
+  return _testOnly_getState().pluginStateLatest[PLUGIN_NAME];
+}
+
 suite('checks-model tests', () => {
   test('updateStateSetProvider', () => {
     _testOnly_resetState();
     updateStateSetProvider(PLUGIN_NAME, ChecksPatchset.LATEST);
-    const state = _testOnly_getState().pluginStateLatest[PLUGIN_NAME];
-    assert.deepEqual(state, {
+    assert.deepEqual(current(), {
       pluginName: PLUGIN_NAME,
       loading: false,
+      firstTimeLoad: true,
       runs: [],
       actions: [],
       links: [],
     });
   });
 
+  test('loading and first time load', () => {
+    _testOnly_resetState();
+    updateStateSetProvider(PLUGIN_NAME, ChecksPatchset.LATEST);
+    assert.isFalse(current().loading);
+    assert.isTrue(current().firstTimeLoad);
+    updateStateSetLoading(PLUGIN_NAME, ChecksPatchset.LATEST);
+    assert.isTrue(current().loading);
+    assert.isTrue(current().firstTimeLoad);
+    updateStateSetResults(PLUGIN_NAME, RUNS, [], [], ChecksPatchset.LATEST);
+    assert.isFalse(current().loading);
+    assert.isFalse(current().firstTimeLoad);
+    updateStateSetLoading(PLUGIN_NAME, ChecksPatchset.LATEST);
+    assert.isTrue(current().loading);
+    assert.isFalse(current().firstTimeLoad);
+    updateStateSetResults(PLUGIN_NAME, RUNS, [], [], ChecksPatchset.LATEST);
+    assert.isFalse(current().loading);
+    assert.isFalse(current().firstTimeLoad);
+  });
+
   test('updateStateSetResults', () => {
     _testOnly_resetState();
     updateStateSetResults(PLUGIN_NAME, RUNS, [], [], ChecksPatchset.LATEST);
-    const state = _testOnly_getState().pluginStateLatest[PLUGIN_NAME];
-    assert.lengthOf(state.runs, 1);
-    assert.lengthOf(state.runs[0].results!, 1);
+    assert.lengthOf(current().runs, 1);
+    assert.lengthOf(current().runs[0].results!, 1);
   });
 
   test('updateStateUpdateResult', () => {
     _testOnly_resetState();
     updateStateSetResults(PLUGIN_NAME, RUNS, [], [], ChecksPatchset.LATEST);
-    let state = _testOnly_getState().pluginStateLatest[PLUGIN_NAME];
     assert.equal(
-      state.runs[0].results![0].summary,
+      current().runs[0].results![0].summary,
       RUNS[0]!.results![0].summary
     );
     const result = RUNS[0].results![0];
@@ -83,9 +105,8 @@
       updatedResult,
       ChecksPatchset.LATEST
     );
-    state = _testOnly_getState().pluginStateLatest[PLUGIN_NAME];
-    assert.lengthOf(state.runs, 1);
-    assert.lengthOf(state.runs[0].results!, 1);
-    assert.equal(state.runs[0].results![0].summary, 'new');
+    assert.lengthOf(current().runs, 1);
+    assert.lengthOf(current().runs[0].results!, 1);
+    assert.equal(current().runs[0].results![0].summary, 'new');
   });
 });
diff --git a/polygerrit-ui/app/services/checks/checks-service.ts b/polygerrit-ui/app/services/checks/checks-service.ts
index 164074b..5ebc13c 100644
--- a/polygerrit-ui/app/services/checks/checks-service.ts
+++ b/polygerrit-ui/app/services/checks/checks-service.ts
@@ -14,16 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 import {
   catchError,
   filter,
   switchMap,
+  takeUntil,
   takeWhile,
   throttleTime,
   withLatestFrom,
 } from 'rxjs/operators';
 import {
+  Action,
   ChangeData,
   CheckResult,
   CheckRun,
@@ -54,13 +55,14 @@
   Subject,
   timer,
 } from 'rxjs';
-import {ChangeInfo, PatchSetNumber} from '../../types/common';
+import {ChangeInfo, NumericChangeId, PatchSetNumber} from '../../types/common';
 import {getCurrentRevision} from '../../utils/change-util';
 import {getShaByPatchNum} from '../../utils/patch-set-util';
 import {assertIsDefined} from '../../utils/common-util';
 import {ReportingService} from '../gr-reporting/gr-reporting';
 import {routerPatchNum$} from '../router/router-model';
 import {Execution} from '../../constants/reporting';
+import {fireAlert, fireEvent} from '../../utils/event-util';
 
 export class ChecksService {
   private readonly providers: {[name: string]: ChecksProvider} = {};
@@ -69,11 +71,14 @@
 
   private checkToPluginMap = new Map<string, string>();
 
+  private changeNum?: NumericChangeId;
+
   private latestPatchNum?: PatchSetNumber;
 
   private readonly documentVisibilityChange$ = new BehaviorSubject(undefined);
 
   constructor(readonly reporting: ReportingService) {
+    changeNum$.subscribe(x => (this.changeNum = x));
     checkToPluginMap$.subscribe(map => {
       this.checkToPluginMap = map;
     });
@@ -120,11 +125,50 @@
     updateStateUpdateResult(pluginName, run, result, ChecksPatchset.SELECTED);
   }
 
+  triggerAction(action?: Action, run?: CheckRun) {
+    if (!action?.callback) return;
+    if (!this.changeNum) return;
+    const patchSet = run?.patchset ?? this.latestPatchNum;
+    if (!patchSet) return;
+    const promise = action.callback(
+      this.changeNum,
+      patchSet,
+      run?.attempt,
+      run?.externalId,
+      run?.checkName,
+      action.name
+    );
+    // If plugins return undefined or not a promise, then show no toast.
+    if (!promise?.then) return;
+
+    fireAlert(document, `Triggering action '${action.name}' ...`);
+    from(promise)
+      // If the action takes longer than 5 seconds, then most likely the
+      // user is either not interested or the result not relevant anymore.
+      .pipe(takeUntil(timer(5000)))
+      .subscribe(result => {
+        if (result.errorMessage || result.message) {
+          fireAlert(document, `${result.message ?? result.errorMessage}`);
+        } else {
+          fireEvent(document, 'hide-alert');
+        }
+        if (result.shouldReload) {
+          this.reloadForCheck(run?.checkName);
+        }
+      });
+  }
+
   register(
     pluginName: string,
     provider: ChecksProvider,
     config: ChecksApiConfig
   ) {
+    if (this.providers[pluginName]) {
+      console.warn(
+        `Plugin '${pluginName}' was trying to register twice as a Checks UI provider. Ignored.`
+      );
+      return;
+    }
     this.providers[pluginName] = provider;
     this.reloadSubjects[pluginName] = new BehaviorSubject<void>(undefined);
     updateStateSetProvider(pluginName, ChecksPatchset.LATEST);
diff --git a/polygerrit-ui/app/services/checks/checks-util.ts b/polygerrit-ui/app/services/checks/checks-util.ts
index 28c73d3..37ebe9d 100644
--- a/polygerrit-ui/app/services/checks/checks-util.ts
+++ b/polygerrit-ui/app/services/checks/checks-util.ts
@@ -157,7 +157,6 @@
 export enum PRIMARY_STATUS_ACTIONS {
   RERUN = 'rerun',
   RUN = 'run',
-  CANCEL = 'cancel',
 }
 
 export function toCanonicalAction(action: Action, status: RunStatus) {
@@ -165,20 +164,17 @@
   if (status === RunStatus.COMPLETED && (name === 'run' || name === 're-run')) {
     name = PRIMARY_STATUS_ACTIONS.RERUN;
   }
-  if (status === RunStatus.RUNNING && name === 'stop') {
-    name = PRIMARY_STATUS_ACTIONS.CANCEL;
-  }
   return {...action, name};
 }
 
-export function primaryActionName(status: RunStatus) {
+function primaryActionName(status: RunStatus) {
   switch (status) {
     case RunStatus.COMPLETED:
       return PRIMARY_STATUS_ACTIONS.RERUN;
     case RunStatus.RUNNABLE:
       return PRIMARY_STATUS_ACTIONS.RUN;
     case RunStatus.RUNNING:
-      return PRIMARY_STATUS_ACTIONS.CANCEL;
+      return undefined;
     default:
       assertNever(status, `Unsupported status: ${status}`);
   }
@@ -278,21 +274,6 @@
   }
 }
 
-export function fireActionTriggered(
-  target: EventTarget,
-  action?: Action,
-  run?: CheckRun
-) {
-  if (!action) return;
-  target.dispatchEvent(
-    new CustomEvent('action-triggered', {
-      detail: {action, run},
-      composed: true,
-      bubbles: true,
-    })
-  );
-}
-
 export interface AttemptDetail {
   attempt: number | undefined;
   icon: string;
diff --git a/polygerrit-ui/app/services/comments/comments-model.ts b/polygerrit-ui/app/services/comments/comments-model.ts
index f7e316a..850acbc 100644
--- a/polygerrit-ui/app/services/comments/comments-model.ts
+++ b/polygerrit-ui/app/services/comments/comments-model.ts
@@ -91,12 +91,16 @@
   distinctUntilChanged()
 );
 
+function publishState(state: CommentState) {
+  privateState$.next(state);
+}
+
 export function updateStateComments(comments?: {
   [path: string]: CommentInfo[];
 }) {
   const nextState = {...privateState$.getValue()};
   nextState.comments = addPath(comments) || {};
-  privateState$.next(nextState);
+  publishState(nextState);
 }
 
 export function updateStateRobotComments(robotComments?: {
@@ -104,13 +108,13 @@
 }) {
   const nextState = {...privateState$.getValue()};
   nextState.robotComments = addPath(robotComments) || {};
-  privateState$.next(nextState);
+  publishState(nextState);
 }
 
 export function updateStateDrafts(drafts?: {[path: string]: DraftInfo[]}) {
   const nextState = {...privateState$.getValue()};
   nextState.drafts = addPath(drafts) || {};
-  privateState$.next(nextState);
+  publishState(nextState);
 }
 
 export function updateStatePortedComments(
@@ -118,19 +122,19 @@
 ) {
   const nextState = {...privateState$.getValue()};
   nextState.portedComments = portedComments || {};
-  privateState$.next(nextState);
+  publishState(nextState);
 }
 
 export function updateStatePortedDrafts(portedDrafts?: PathToCommentsInfoMap) {
   const nextState = {...privateState$.getValue()};
   nextState.portedDrafts = portedDrafts || {};
-  privateState$.next(nextState);
+  publishState(nextState);
 }
 
 export function updateStateAddDiscardedDraft(draft: DraftInfo) {
   const nextState = {...privateState$.getValue()};
   nextState.discardedDrafts = [...nextState.discardedDrafts, draft];
-  privateState$.next(nextState);
+  publishState(nextState);
 }
 
 export function updateStateUndoDiscardedDraft(draftID?: string) {
@@ -142,7 +146,7 @@
   }
   drafts.splice(index, 1);
   nextState.discardedDrafts = drafts;
-  privateState$.next(nextState);
+  publishState(nextState);
 }
 
 export function updateStateAddDraft(draft: DraftInfo) {
@@ -162,7 +166,25 @@
   } else {
     drafts[draft.path].push(draft);
   }
-  privateState$.next(nextState);
+  publishState(nextState);
+}
+
+export function updateStateUpdateDraft(draft: DraftInfo) {
+  const nextState = {...privateState$.getValue()};
+  if (!draft.path) throw new Error('draft path undefined');
+  nextState.drafts = {...nextState.drafts};
+  const drafts = nextState.drafts;
+  if (!drafts[draft.path])
+    throw new Error('draft: trying to edit non-existent draft');
+  drafts[draft.path] = [...drafts[draft.path]];
+  const index = drafts[draft.path].findIndex(
+    d =>
+      (d.__draftID && d.__draftID === draft.__draftID) ||
+      (d.id && d.id === draft.id)
+  );
+  if (index === -1) return;
+  drafts[draft.path][index] = draft;
+  publishState(nextState);
 }
 
 export function updateStateDeleteDraft(draft: DraftInfo) {
@@ -179,6 +201,6 @@
   const discardedDraft = drafts[draft.path][index];
   drafts[draft.path] = [...drafts[draft.path]];
   drafts[draft.path].splice(index, 1);
-  privateState$.next(nextState);
+  publishState(nextState);
   updateStateAddDiscardedDraft(discardedDraft);
 }
diff --git a/polygerrit-ui/app/services/comments/comments-service.ts b/polygerrit-ui/app/services/comments/comments-service.ts
index 9aa297a..16ee2f7 100644
--- a/polygerrit-ui/app/services/comments/comments-service.ts
+++ b/polygerrit-ui/app/services/comments/comments-service.ts
@@ -23,6 +23,7 @@
 import {
   updateStateAddDraft,
   updateStateDeleteDraft,
+  updateStateUpdateDraft,
   updateStateComments,
   updateStateRobotComments,
   updateStateDrafts,
@@ -96,6 +97,14 @@
     updateStateAddDraft(draft);
   }
 
+  cancelDraft(draft: DraftInfo) {
+    updateStateUpdateDraft(draft);
+  }
+
+  editDraft(draft: DraftInfo) {
+    updateStateUpdateDraft(draft);
+  }
+
   deleteDraft(draft: DraftInfo) {
     updateStateDeleteDraft(draft);
   }
diff --git a/polygerrit-ui/app/services/flags/flags.ts b/polygerrit-ui/app/services/flags/flags.ts
index ef5fde2..21f3aa4 100644
--- a/polygerrit-ui/app/services/flags/flags.ts
+++ b/polygerrit-ui/app/services/flags/flags.ts
@@ -25,7 +25,6 @@
  */
 export enum KnownExperimentId {
   NEW_IMAGE_DIFF_UI = 'UiFeature__new_image_diff_ui',
-  TOKEN_HIGHLIGHTING = 'UiFeature__token_highlighting',
   CHECKS_DEVELOPER = 'UiFeature__checks_developer',
   NEW_REPLY_DIALOG = 'UiFeature__new_reply_dialog',
   SUBMIT_REQUIREMENTS_UI = 'UiFeature__submit_requirements_ui',
diff --git a/polygerrit-ui/app/services/gr-auth/gr-auth_impl.ts b/polygerrit-ui/app/services/gr-auth/gr-auth_impl.ts
index 024fa2a..c254284 100644
--- a/polygerrit-ui/app/services/gr-auth/gr-auth_impl.ts
+++ b/polygerrit-ui/app/services/gr-auth/gr-auth_impl.ts
@@ -108,7 +108,7 @@
         // See https://stackoverflow.com/questions/45816743/how-to-solve-this-caution-request-is-not-finished-yet-in-chrome
         try {
           res.clone().text();
-        } catch (error) {
+        } catch {
           // Ignore error
         }
 
@@ -306,7 +306,7 @@
         // See https://stackoverflow.com/questions/45816743/how-to-solve-this-caution-request-is-not-finished-yet-in-chrome
         try {
           response.clone().text();
-        } catch (error) {
+        } catch {
           // Ignore error
         }
       }
diff --git a/polygerrit-ui/app/services/gr-auth/gr-auth_test.js b/polygerrit-ui/app/services/gr-auth/gr-auth_test.js
index ac93a7d..debba6d 100644
--- a/polygerrit-ui/app/services/gr-auth/gr-auth_test.js
+++ b/polygerrit-ui/app/services/gr-auth/gr-auth_test.js
@@ -34,40 +34,32 @@
       fakeFetch = sinon.stub(window, 'fetch');
     });
 
-    test('auth-check returns 403', done => {
+    test('auth-check returns 403', async () => {
       fakeFetch.returns(Promise.resolve({status: 403}));
-      auth.authCheck().then(authed => {
-        assert.isFalse(authed);
-        assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
-        done();
-      });
+      const authed = await auth.authCheck();
+      assert.isFalse(authed);
+      assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
     });
 
-    test('auth-check returns 204', done => {
+    test('auth-check returns 204', async () => {
       fakeFetch.returns(Promise.resolve({status: 204}));
-      auth.authCheck().then(authed => {
-        assert.isTrue(authed);
-        assert.equal(auth.status, Auth.STATUS.AUTHED);
-        done();
-      });
+      const authed = await auth.authCheck();
+      assert.isTrue(authed);
+      assert.equal(auth.status, Auth.STATUS.AUTHED);
     });
 
-    test('auth-check returns 502', done => {
+    test('auth-check returns 502', async () => {
       fakeFetch.returns(Promise.resolve({status: 502}));
-      auth.authCheck().then(authed => {
-        assert.isFalse(authed);
-        assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
-        done();
-      });
+      const authed = await auth.authCheck();
+      assert.isFalse(authed);
+      assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
     });
 
-    test('auth-check failed', done => {
+    test('auth-check failed', async () => {
       fakeFetch.returns(Promise.reject(new Error('random error')));
-      auth.authCheck().then(authed => {
-        assert.isFalse(authed);
-        assert.equal(auth.status, Auth.STATUS.ERROR);
-        done();
-      });
+      const authed = await auth.authCheck();
+      assert.isFalse(authed);
+      assert.equal(auth.status, Auth.STATUS.ERROR);
     });
   });
 
@@ -80,129 +72,105 @@
       fakeFetch = sinon.stub(window, 'fetch');
     });
 
-    test('cache auth-check result', done => {
+    test('cache auth-check result', async () => {
       fakeFetch.returns(Promise.resolve({status: 403}));
-      auth.authCheck().then(authed => {
-        assert.isFalse(authed);
-        assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
-        fakeFetch.returns(Promise.resolve({status: 204}));
-        auth.authCheck().then(authed2 => {
-          assert.isFalse(authed);
-          assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
-          done();
-        });
-      });
+      const authed = await auth.authCheck();
+      assert.isFalse(authed);
+      assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
+      fakeFetch.returns(Promise.resolve({status: 204}));
+      const authed2 = await auth.authCheck();
+      assert.isFalse(authed2);
+      assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
     });
 
-    test('clearCache should refetch auth-check result', done => {
+    test('clearCache should refetch auth-check result', async () => {
       fakeFetch.returns(Promise.resolve({status: 403}));
-      auth.authCheck().then(authed => {
-        assert.isFalse(authed);
-        assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
-        fakeFetch.returns(Promise.resolve({status: 204}));
-        auth.clearCache();
-        auth.authCheck().then(authed2 => {
-          assert.isTrue(authed2);
-          assert.equal(auth.status, Auth.STATUS.AUTHED);
-          done();
-        });
-      });
+      const authed = await auth.authCheck();
+      assert.isFalse(authed);
+      assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
+      fakeFetch.returns(Promise.resolve({status: 204}));
+      auth.clearCache();
+      const authed2 = await auth.authCheck();
+      assert.isTrue(authed2);
+      assert.equal(auth.status, Auth.STATUS.AUTHED);
     });
 
-    test('cache expired on auth-check after certain time', done => {
+    test('cache expired on auth-check after certain time', async () => {
       fakeFetch.returns(Promise.resolve({status: 403}));
-      auth.authCheck().then(authed => {
-        assert.isFalse(authed);
-        assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
-        clock.tick(1000 * 10000);
-        fakeFetch.returns(Promise.resolve({status: 204}));
-        auth.authCheck().then(authed2 => {
-          assert.isTrue(authed2);
-          assert.equal(auth.status, Auth.STATUS.AUTHED);
-          done();
-        });
-      });
+      const authed = await auth.authCheck();
+      assert.isFalse(authed);
+      assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
+      clock.tick(1000 * 10000);
+      fakeFetch.returns(Promise.resolve({status: 204}));
+      const authed2 = await auth.authCheck();
+      assert.isTrue(authed2);
+      assert.equal(auth.status, Auth.STATUS.AUTHED);
     });
 
-    test('no cache if auth-check failed', done => {
+    test('no cache if auth-check failed', async () => {
       fakeFetch.returns(Promise.reject(new Error('random error')));
-      auth.authCheck().then(authed => {
-        assert.isFalse(authed);
-        assert.equal(auth.status, Auth.STATUS.ERROR);
-        assert.equal(fakeFetch.callCount, 1);
-        auth.authCheck().then(() => {
-          assert.equal(fakeFetch.callCount, 2);
-          done();
-        });
-      });
+      const authed = await auth.authCheck();
+      assert.isFalse(authed);
+      assert.equal(auth.status, Auth.STATUS.ERROR);
+      assert.equal(fakeFetch.callCount, 1);
+      await auth.authCheck();
+      assert.equal(fakeFetch.callCount, 2);
     });
 
-    test('fire event when switch from authed to unauthed', done => {
+    test('fire event when switch from authed to unauthed', async () => {
       fakeFetch.returns(Promise.resolve({status: 204}));
-      auth.authCheck().then(authed => {
-        assert.isTrue(authed);
-        assert.equal(auth.status, Auth.STATUS.AUTHED);
-        clock.tick(1000 * 10000);
-        fakeFetch.returns(Promise.resolve({status: 403}));
-        const emitStub = sinon.stub(appContext.eventEmitter, 'emit');
-        auth.authCheck().then(authed2 => {
-          assert.isFalse(authed2);
-          assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
-          assert.isTrue(emitStub.called);
-          done();
-        });
-      });
+      const authed = await auth.authCheck();
+      assert.isTrue(authed);
+      assert.equal(auth.status, Auth.STATUS.AUTHED);
+      clock.tick(1000 * 10000);
+      fakeFetch.returns(Promise.resolve({status: 403}));
+      const emitStub = sinon.stub(appContext.eventEmitter, 'emit');
+      const authed2 = await auth.authCheck();
+      assert.isFalse(authed2);
+      assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
+      assert.isTrue(emitStub.called);
     });
 
-    test('fire event when switch from authed to error', done => {
+    test('fire event when switch from authed to error', async () => {
       fakeFetch.returns(Promise.resolve({status: 204}));
-      auth.authCheck().then(authed => {
-        assert.isTrue(authed);
-        assert.equal(auth.status, Auth.STATUS.AUTHED);
-        clock.tick(1000 * 10000);
-        fakeFetch.returns(Promise.reject(new Error('random error')));
-        const emitStub = sinon.stub(appContext.eventEmitter, 'emit');
-        auth.authCheck().then(authed2 => {
-          assert.isFalse(authed2);
-          assert.isTrue(emitStub.called);
-          assert.equal(auth.status, Auth.STATUS.ERROR);
-          done();
-        });
-      });
+      const authed = await auth.authCheck();
+      assert.isTrue(authed);
+      assert.equal(auth.status, Auth.STATUS.AUTHED);
+      clock.tick(1000 * 10000);
+      fakeFetch.returns(Promise.reject(new Error('random error')));
+      const emitStub = sinon.stub(appContext.eventEmitter, 'emit');
+      const authed2 = await auth.authCheck();
+      assert.isFalse(authed2);
+      assert.isTrue(emitStub.called);
+      assert.equal(auth.status, Auth.STATUS.ERROR);
     });
 
-    test('no event from non-authed to other status', done => {
+    test('no event from non-authed to other status', async () => {
       fakeFetch.returns(Promise.resolve({status: 403}));
-      auth.authCheck().then(authed => {
-        assert.isFalse(authed);
-        assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
-        clock.tick(1000 * 10000);
-        fakeFetch.returns(Promise.resolve({status: 204}));
-        const emitStub = sinon.stub(appContext.eventEmitter, 'emit');
-        auth.authCheck().then(authed2 => {
-          assert.isTrue(authed2);
-          assert.isFalse(emitStub.called);
-          assert.equal(auth.status, Auth.STATUS.AUTHED);
-          done();
-        });
-      });
+      const authed = await auth.authCheck();
+      assert.isFalse(authed);
+      assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
+      clock.tick(1000 * 10000);
+      fakeFetch.returns(Promise.resolve({status: 204}));
+      const emitStub = sinon.stub(appContext.eventEmitter, 'emit');
+      const authed2 = await auth.authCheck();
+      assert.isTrue(authed2);
+      assert.isFalse(emitStub.called);
+      assert.equal(auth.status, Auth.STATUS.AUTHED);
     });
 
-    test('no event from non-authed to other status', done => {
+    test('no event from non-authed to other status', async () => {
       fakeFetch.returns(Promise.resolve({status: 403}));
-      auth.authCheck().then(authed => {
-        assert.isFalse(authed);
-        assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
-        clock.tick(1000 * 10000);
-        fakeFetch.returns(Promise.reject(new Error('random error')));
-        const emitStub = sinon.stub(appContext.eventEmitter, 'emit');
-        auth.authCheck().then(authed2 => {
-          assert.isFalse(authed2);
-          assert.isFalse(emitStub.called);
-          assert.equal(auth.status, Auth.STATUS.ERROR);
-          done();
-        });
-      });
+      const authed = await auth.authCheck();
+      assert.isFalse(authed);
+      assert.equal(auth.status, Auth.STATUS.NOT_AUTHED);
+      clock.tick(1000 * 10000);
+      fakeFetch.returns(Promise.reject(new Error('random error')));
+      const emitStub = sinon.stub(appContext.eventEmitter, 'emit');
+      const authed2 = await auth.authCheck();
+      assert.isFalse(authed2);
+      assert.isFalse(emitStub.called);
+      assert.equal(auth.status, Auth.STATUS.ERROR);
     });
   });
 
@@ -211,26 +179,22 @@
       sinon.stub(window, 'fetch').returns(Promise.resolve({ok: true}));
     });
 
-    test('GET', done => {
-      auth.fetch('/url', {bar: 'bar'}).then(() => {
-        const [url, options] = fetch.lastCall.args;
-        assert.equal(url, '/url');
-        assert.equal(options.credentials, 'same-origin');
-        done();
-      });
+    test('GET', async () => {
+      await auth.fetch('/url', {bar: 'bar'});
+      const [url, options] = fetch.lastCall.args;
+      assert.equal(url, '/url');
+      assert.equal(options.credentials, 'same-origin');
     });
 
-    test('POST', done => {
+    test('POST', async () => {
       sinon.stub(auth, '_getCookie')
           .withArgs('XSRF_TOKEN')
           .returns('foobar');
-      auth.fetch('/url', {method: 'POST'}).then(() => {
-        const [url, options] = fetch.lastCall.args;
-        assert.equal(url, '/url');
-        assert.equal(options.credentials, 'same-origin');
-        assert.equal(options.headers.get('X-Gerrit-Auth'), 'foobar');
-        done();
-      });
+      await auth.fetch('/url', {method: 'POST'});
+      const [url, options] = fetch.lastCall.args;
+      assert.equal(url, '/url');
+      assert.equal(options.credentials, 'same-origin');
+      assert.equal(options.headers.get('X-Gerrit-Auth'), 'foobar');
     });
   });
 
@@ -254,45 +218,36 @@
       auth.setup(getToken);
     });
 
-    test('base url support', done => {
+    test('base url support', async () => {
       const baseUrl = 'http://foo';
       stubBaseUrl(baseUrl);
-      auth.fetch(baseUrl + '/url', {bar: 'bar'}).then(() => {
-        const [url] = fetch.lastCall.args;
-        assert.equal(url, 'http://foo/a/url?access_token=zbaz');
-        done();
-      });
+      await auth.fetch(baseUrl + '/url', {bar: 'bar'});
+      const [url] = fetch.lastCall.args;
+      assert.equal(url, 'http://foo/a/url?access_token=zbaz');
     });
 
-    test('fetch not signed in', done => {
+    test('fetch not signed in', async () => {
       getToken.returns(Promise.resolve());
-      auth.fetch('/url', {bar: 'bar'}).then(() => {
-        const [url, options] = fetch.lastCall.args;
-        assert.equal(url, '/url');
-        assert.equal(options.bar, 'bar');
-        assert.equal(Object.keys(options.headers).length, 0);
-        done();
-      });
+      await auth.fetch('/url', {bar: 'bar'});
+      const [url, options] = fetch.lastCall.args;
+      assert.equal(url, '/url');
+      assert.equal(options.bar, 'bar');
+      assert.equal(Object.keys(options.headers).length, 0);
     });
 
-    test('fetch signed in', done => {
-      auth.fetch('/url', {bar: 'bar'}).then(() => {
-        const [url, options] = fetch.lastCall.args;
-        assert.equal(url, '/a/url?access_token=zbaz');
-        assert.equal(options.bar, 'bar');
-        done();
-      });
+    test('fetch signed in', async () => {
+      await auth.fetch('/url', {bar: 'bar'});
+      const [url, options] = fetch.lastCall.args;
+      assert.equal(url, '/a/url?access_token=zbaz');
+      assert.equal(options.bar, 'bar');
     });
 
-    test('getToken calls are cached', done => {
-      Promise.all([
-        auth.fetch('/url-one'), auth.fetch('/url-two')]).then(() => {
-        assert.equal(getToken.callCount, 1);
-        done();
-      });
+    test('getToken calls are cached', async () => {
+      await Promise.all([auth.fetch('/url-one'), auth.fetch('/url-two')]);
+      assert.equal(getToken.callCount, 1);
     });
 
-    test('getToken refreshes token', done => {
+    test('getToken refreshes token', async () => {
       sinon.stub(auth, '_isTokenValid');
       auth._isTokenValid
           .onFirstCall().returns(true)
@@ -300,27 +255,21 @@
           .returns(false)
           .onThirdCall()
           .returns(true);
-      auth.fetch('/url-one')
-          .then(() => {
-            getToken.returns(Promise.resolve(makeToken('bzzbb')));
-            return auth.fetch('/url-two');
-          })
-          .then(() => {
-            const [[firstUrl], [secondUrl]] = fetch.args;
-            assert.equal(firstUrl, '/a/url-one?access_token=zbaz');
-            assert.equal(secondUrl, '/a/url-two?access_token=bzzbb');
-            done();
-          });
+      await auth.fetch('/url-one');
+      getToken.returns(Promise.resolve(makeToken('bzzbb')));
+      await auth.fetch('/url-two');
+
+      const [[firstUrl], [secondUrl]] = fetch.args;
+      assert.equal(firstUrl, '/a/url-one?access_token=zbaz');
+      assert.equal(secondUrl, '/a/url-two?access_token=bzzbb');
     });
 
-    test('signed in token error falls back to anonymous', done => {
+    test('signed in token error falls back to anonymous', async () => {
       getToken.returns(Promise.resolve('rubbish'));
-      auth.fetch('/url', {bar: 'bar'}).then(() => {
-        const [url, options] = fetch.lastCall.args;
-        assert.equal(url, '/url');
-        assert.equal(options.bar, 'bar');
-        done();
-      });
+      await auth.fetch('/url', {bar: 'bar'});
+      const [url, options] = fetch.lastCall.args;
+      assert.equal(url, '/url');
+      assert.equal(options.bar, 'bar');
     });
 
     test('_isTokenValid', () => {
@@ -337,37 +286,33 @@
       }));
     });
 
-    test('HTTP PUT with content type', done => {
+    test('HTTP PUT with content type', async () => {
       const originalOptions = {
         method: 'PUT',
         headers: new Headers({'Content-Type': 'mail/pigeon'}),
       };
-      auth.fetch('/url', originalOptions).then(() => {
-        assert.isTrue(getToken.called);
-        const [url, options] = fetch.lastCall.args;
-        assert.include(url, '$ct=mail%2Fpigeon');
-        assert.include(url, '$m=PUT');
-        assert.include(url, 'access_token=zbaz');
-        assert.equal(options.method, 'POST');
-        assert.equal(options.headers.get('Content-Type'), 'text/plain');
-        done();
-      });
+      await auth.fetch('/url', originalOptions);
+      assert.isTrue(getToken.called);
+      const [url, options] = fetch.lastCall.args;
+      assert.include(url, '$ct=mail%2Fpigeon');
+      assert.include(url, '$m=PUT');
+      assert.include(url, 'access_token=zbaz');
+      assert.equal(options.method, 'POST');
+      assert.equal(options.headers.get('Content-Type'), 'text/plain');
     });
 
-    test('HTTP PUT without content type', done => {
+    test('HTTP PUT without content type', async () => {
       const originalOptions = {
         method: 'PUT',
       };
-      auth.fetch('/url', originalOptions).then(() => {
-        assert.isTrue(getToken.called);
-        const [url, options] = fetch.lastCall.args;
-        assert.include(url, '$ct=text%2Fplain');
-        assert.include(url, '$m=PUT');
-        assert.include(url, 'access_token=zbaz');
-        assert.equal(options.method, 'POST');
-        assert.equal(options.headers.get('Content-Type'), 'text/plain');
-        done();
-      });
+      await auth.fetch('/url', originalOptions);
+      assert.isTrue(getToken.called);
+      const [url, options] = fetch.lastCall.args;
+      assert.include(url, '$ct=text%2Fplain');
+      assert.include(url, '$m=PUT');
+      assert.include(url, 'access_token=zbaz');
+      assert.equal(options.method, 'POST');
+      assert.equal(options.headers.get('Content-Type'), 'text/plain');
     });
   });
 });
diff --git a/polygerrit-ui/app/services/gr-event-interface/gr-event-interface_test.js b/polygerrit-ui/app/services/gr-event-interface/gr-event-interface_test.js
index 6ce5eea..54a0f72e 100644
--- a/polygerrit-ui/app/services/gr-event-interface/gr-event-interface_test.js
+++ b/polygerrit-ui/app/services/gr-event-interface/gr-event-interface_test.js
@@ -16,6 +16,7 @@
  */
 
 import '../../test/common-test-setup-karma.js';
+import {mockPromise} from '../../test/test-utils.js';
 import {EventEmitter} from './gr-event-interface_impl.js';
 
 suite('gr-event-interface tests', () => {
@@ -29,37 +30,42 @@
       gerrit.removeAllListeners();
     });
 
-    test('communicate between plugin and Gerrit', done => {
+    test('communicate between plugin and Gerrit', async () => {
       const eventName = 'test-plugin-event';
       let p;
+      const promise = mockPromise();
       gerrit.on(eventName, e => {
         assert.equal(e.value, 'test');
         assert.equal(e.plugin, p);
-        done();
+        promise.resolve();
       });
       gerrit.install(plugin => {
         p = plugin;
         gerrit.emit(eventName, {value: 'test', plugin});
       }, '0.1',
       'http://test.com/plugins/testplugin/static/test.js');
+      await promise;
     });
 
-    test('listen on events from core', done => {
+    test('listen on events from core', async () => {
       const eventName = 'test-plugin-event';
+      const promise = mockPromise();
       gerrit.on(eventName, e => {
         assert.equal(e.value, 'test');
-        done();
+        promise.resolve();
       });
 
       gerrit.emit(eventName, {value: 'test'});
+      await promise;
     });
 
-    test('communicate across plugins', done => {
+    test('communicate across plugins', async () => {
       const eventName = 'test-plugin-event';
+      const promise = mockPromise();
       gerrit.install(plugin => {
         gerrit.on(eventName, e => {
           assert.equal(e.plugin.getPluginName(), 'testB');
-          done();
+          promise.resolve();
         });
       }, '0.1',
       'http://test.com/plugins/testA/static/testA.js');
@@ -68,6 +74,7 @@
         gerrit.emit(eventName, {plugin});
       }, '0.1',
       'http://test.com/plugins/testB/static/testB.js');
+      await promise;
     });
   });
 
diff --git a/polygerrit-ui/app/styles/dashboard-header-styles.ts b/polygerrit-ui/app/styles/dashboard-header-styles.ts
index 2bbca63..643a76a 100644
--- a/polygerrit-ui/app/styles/dashboard-header-styles.ts
+++ b/polygerrit-ui/app/styles/dashboard-header-styles.ts
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import {css} from 'lit-element';
+import {css} from 'lit';
 
 // Mark the file as a module. Otherwise typescript assumes this is a script
 // and $_documentContainer is a global variable.
diff --git a/polygerrit-ui/app/styles/gr-font-styles.ts b/polygerrit-ui/app/styles/gr-font-styles.ts
new file mode 100644
index 0000000..422a7c5
--- /dev/null
+++ b/polygerrit-ui/app/styles/gr-font-styles.ts
@@ -0,0 +1,61 @@
+/**
+ * @license
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {css} from 'lit';
+
+export const fontStyles = css`
+  .font-normal {
+    font-size: var(--font-size-normal);
+    font-weight: var(--font-weight-normal);
+    line-height: var(--line-height-normal);
+  }
+  .font-small {
+    font-size: var(--font-size-small);
+    font-weight: var(--font-weight-normal);
+    line-height: var(--line-height-small);
+  }
+  .heading-1 {
+    font-family: var(--header-font-family);
+    font-size: var(--font-size-h1);
+    font-weight: var(--font-weight-h1);
+    line-height: var(--line-height-h1);
+  }
+  .heading-2 {
+    font-family: var(--header-font-family);
+    font-size: var(--font-size-h2);
+    font-weight: var(--font-weight-h2);
+    line-height: var(--line-height-h2);
+  }
+  .heading-3 {
+    font-family: var(--header-font-family);
+    font-size: var(--font-size-h3);
+    font-weight: var(--font-weight-h3);
+    line-height: var(--line-height-h3);
+  }
+  strong {
+    font-weight: var(--font-weight-bold);
+  }
+`;
+
+const $_documentContainer = document.createElement('template');
+$_documentContainer.innerHTML = `<dom-module id="gr-font-styles">
+  <template>
+    <style>
+    ${fontStyles.cssText}
+    </style>
+  </template>
+</dom-module>`;
+document.head.appendChild($_documentContainer.content);
diff --git a/polygerrit-ui/app/styles/gr-form-styles.ts b/polygerrit-ui/app/styles/gr-form-styles.ts
index b9aecff9..34a6936 100644
--- a/polygerrit-ui/app/styles/gr-form-styles.ts
+++ b/polygerrit-ui/app/styles/gr-form-styles.ts
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {css} from 'lit-element';
+import {css} from 'lit';
 
 export const formStyles = css`
   .gr-form-styles input {
diff --git a/polygerrit-ui/app/styles/gr-page-nav-styles.ts b/polygerrit-ui/app/styles/gr-page-nav-styles.ts
index 1b3830a..f928848 100644
--- a/polygerrit-ui/app/styles/gr-page-nav-styles.ts
+++ b/polygerrit-ui/app/styles/gr-page-nav-styles.ts
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import {css} from 'lit-element';
+import {css} from 'lit';
 
 // Mark the file as a module. Otherwise typescript assumes this is a script
 // and $_documentContainer is a global variable.
diff --git a/polygerrit-ui/app/styles/gr-spinner-styles.ts b/polygerrit-ui/app/styles/gr-spinner-styles.ts
index 6fb1ae6..0f8341f 100644
--- a/polygerrit-ui/app/styles/gr-spinner-styles.ts
+++ b/polygerrit-ui/app/styles/gr-spinner-styles.ts
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {css} from 'lit-element';
+import {css} from 'lit';
 
 // Mark the file as a module. Otherwise typescript assumes this is a script
 // and $_documentContainer is a global variable.
diff --git a/polygerrit-ui/app/styles/gr-subpage-styles.ts b/polygerrit-ui/app/styles/gr-subpage-styles.ts
index fa3e55f..e426a7d 100644
--- a/polygerrit-ui/app/styles/gr-subpage-styles.ts
+++ b/polygerrit-ui/app/styles/gr-subpage-styles.ts
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {css} from 'lit-element';
+import {css} from 'lit';
 
 export const subpageStyles = css`
   .main {
diff --git a/polygerrit-ui/app/styles/gr-table-styles.ts b/polygerrit-ui/app/styles/gr-table-styles.ts
index 72e36e1..6871499 100644
--- a/polygerrit-ui/app/styles/gr-table-styles.ts
+++ b/polygerrit-ui/app/styles/gr-table-styles.ts
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {css} from 'lit-element';
+import {css} from 'lit';
 
 export const tableStyles = css`
   .genericList {
diff --git a/polygerrit-ui/app/styles/shared-styles.ts b/polygerrit-ui/app/styles/shared-styles.ts
index a8274cc..b58d5b9 100644
--- a/polygerrit-ui/app/styles/shared-styles.ts
+++ b/polygerrit-ui/app/styles/shared-styles.ts
@@ -14,15 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-import {css} from 'lit-element';
-
-// Mark the file as a module. Otherwise typescript assumes this is a script
-// and $_documentContainer is a global variable.
-// See: https://www.typescriptlang.org/docs/handbook/modules.html
-export {};
-
-const $_documentContainer = document.createElement('template');
+import {css} from 'lit';
 
 export const sharedStyles = css`
   /* CSS reset */
@@ -176,36 +168,6 @@
     border-spacing: 0;
   }
 
-  /* Fonts */
-
-  .font-normal {
-    font-size: var(--font-size-normal);
-    font-weight: var(--font-weight-normal);
-    line-height: var(--line-height-normal);
-  }
-  .font-small {
-    font-size: var(--font-size-small);
-    font-weight: var(--font-weight-normal);
-    line-height: var(--line-height-small);
-  }
-  .heading-1 {
-    font-family: var(--header-font-family);
-    font-size: var(--font-size-h1);
-    font-weight: var(--font-weight-h1);
-    line-height: var(--line-height-h1);
-  }
-  .heading-2 {
-    font-family: var(--header-font-family);
-    font-size: var(--font-size-h2);
-    font-weight: var(--font-weight-h2);
-    line-height: var(--line-height-h2);
-  }
-  .heading-3 {
-    font-family: var(--header-font-family);
-    font-size: var(--font-size-h3);
-    font-weight: var(--font-weight-h3);
-    line-height: var(--line-height-h3);
-  }
   iron-icon {
     color: var(--deemphasized-text-color);
     vertical-align: top;
@@ -261,9 +223,6 @@
     /** This is needed for firefox */
     --iron-autogrow-textarea_-_white-space: pre-wrap;
   }
-  strong {
-    font-weight: var(--font-weight-bold);
-  }
 
   .assistive-tech-only {
     user-select: none;
@@ -303,6 +262,7 @@
   /** END: loading spiner */
 `;
 
+const $_documentContainer = document.createElement('template');
 $_documentContainer.innerHTML = `<dom-module id="shared-styles">
   <template>
     <style>
@@ -310,5 +270,4 @@
     </style>
   </template>
 </dom-module>`;
-
 document.head.appendChild($_documentContainer.content);
diff --git a/polygerrit-ui/app/test/test-utils.ts b/polygerrit-ui/app/test/test-utils.ts
index 03a9525..156217c 100644
--- a/polygerrit-ui/app/test/test-utils.ts
+++ b/polygerrit-ui/app/test/test-utils.ts
@@ -28,6 +28,7 @@
 import {AuthService} from '../services/gr-auth/gr-auth';
 import {ReportingService} from '../services/gr-reporting/gr-reporting';
 import {CommentsService} from '../services/comments/comments-service';
+export {query, queryAll, queryAndAssert} from '../utils/common-util';
 
 export interface MockPromise extends Promise<unknown> {
   resolve: (value?: unknown) => void;
@@ -47,33 +48,6 @@
   return getComputedStyle(el).display === 'none';
 }
 
-export function queryAll<E extends Element = Element>(
-  el: Element | undefined,
-  selector: string
-): NodeListOf<E> {
-  if (!el) assert.fail('element not defined');
-  const root = el.shadowRoot ?? el;
-  return root.querySelectorAll<E>(selector);
-}
-
-export function query<E extends Element = Element>(
-  el: Element | undefined,
-  selector: string
-): E | undefined {
-  if (!el) return undefined;
-  const root = el.shadowRoot ?? el;
-  return root.querySelector<E>(selector) ?? undefined;
-}
-
-export function queryAndAssert<E extends Element = Element>(
-  el: Element | undefined,
-  selector: string
-): E {
-  const found = query<E>(el, selector);
-  if (!found) assert.fail(`selector '${selector}' did not match anything'`);
-  return found;
-}
-
 export function isVisible(el: Element) {
   assert.ok(el);
   return getComputedStyle(el).getPropertyValue('display') !== 'none';
@@ -218,6 +192,27 @@
   el.parentNode?.removeChild(el);
 }
 
+export function waitUntil(
+  predicate: () => boolean,
+  maxMillis = 100
+): Promise<void> {
+  const start = Date.now();
+  let sleep = 1;
+  return new Promise((resolve, reject) => {
+    const waiter = () => {
+      if (predicate()) {
+        return resolve();
+      }
+      if (Date.now() - start >= maxMillis) {
+        return reject(new Error('Took to long to waitUntil'));
+      }
+      setTimeout(waiter, sleep);
+      sleep *= 2;
+    };
+    waiter();
+  });
+}
+
 /**
  * Promisify an event callback to simplify async...await tests.
  *
diff --git a/polygerrit-ui/app/tsconfig.json b/polygerrit-ui/app/tsconfig.json
index a533a0f..79dae41 100644
--- a/polygerrit-ui/app/tsconfig.json
+++ b/polygerrit-ui/app/tsconfig.json
@@ -1,7 +1,7 @@
 {
   "compilerOptions": {
     /* Basic Options */
-    "target": "es2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
+    "target": "es2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
     "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
     "allowJs": true, /* Allow javascript files to be compiled. */
     "checkJs": false, /* Report errors in .js files. */
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index 696ab63..98c60d2 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -37,6 +37,7 @@
 import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
 import {
   AccountId,
+  AccountDetailInfo,
   AccountInfo,
   AccountsConfigInfo,
   ActionInfo,
@@ -125,6 +126,7 @@
 
 export {
   AccountId,
+  AccountDetailInfo,
   AccountInfo,
   AccountsConfigInfo,
   ActionInfo,
@@ -285,14 +287,6 @@
 }
 
 /**
- * The AccountDetailInfo entity contains detailed information about an account.
- * https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#account-detail-info
- */
-export interface AccountDetailInfo extends AccountInfo {
-  registered_on: Timestamp;
-}
-
-/**
  * The AccountExternalIdInfo entity contains information for an external id of
  * an account.
  * https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#account-external-id-info
diff --git a/polygerrit-ui/app/utils/admin-nav-util_test.js b/polygerrit-ui/app/utils/admin-nav-util_test.js
index ae2e7a9..2a13904 100644
--- a/polygerrit-ui/app/utils/admin-nav-util_test.js
+++ b/polygerrit-ui/app/utils/admin-nav-util_test.js
@@ -27,71 +27,69 @@
     menuLinkStub = sinon.stub().returns([]);
   });
 
-  const testAdminLinks = (account, options, expected, done) => {
-    getAdminLinks(account,
+  const testAdminLinks = async (account, options, expected) => {
+    const res = await getAdminLinks(account,
         capabilityStub,
         menuLinkStub,
-        options)
-        .then(res => {
-          assert.equal(expected.totalLength, res.links.length);
-          assert.equal(res.links[0].name, 'Repositories');
-          // Repos
-          if (expected.groupListShown) {
-            assert.equal(res.links[1].name, 'Groups');
-          }
+        options);
 
-          if (expected.pluginListShown) {
-            assert.equal(res.links[2].name, 'Plugins');
-            assert.isNotOk(res.links[2].subsection);
-          }
+    assert.equal(expected.totalLength, res.links.length);
+    assert.equal(res.links[0].name, 'Repositories');
+    // Repos
+    if (expected.groupListShown) {
+      assert.equal(res.links[1].name, 'Groups');
+    }
 
-          if (expected.projectPageShown) {
-            assert.isOk(res.links[0].subsection);
-            assert.equal(res.links[0].subsection.children.length, 6);
-          } else {
-            assert.isNotOk(res.links[0].subsection);
-          }
-          // Groups
-          if (expected.groupPageShown) {
-            assert.isOk(res.links[1].subsection);
-            assert.equal(res.links[1].subsection.children.length,
-                expected.groupSubpageLength);
-          } else if ( expected.totalLength > 1) {
-            assert.isNotOk(res.links[1].subsection);
-          }
+    if (expected.pluginListShown) {
+      assert.equal(res.links[2].name, 'Plugins');
+      assert.isNotOk(res.links[2].subsection);
+    }
 
-          if (expected.pluginGeneratedLinks) {
-            for (const link of expected.pluginGeneratedLinks) {
-              const linkMatch = res.links
-                  .find(l => (l.url === link.url && l.name === link.text));
-              assert.isTrue(!!linkMatch);
+    if (expected.projectPageShown) {
+      assert.isOk(res.links[0].subsection);
+      assert.equal(res.links[0].subsection.children.length, 6);
+    } else {
+      assert.isNotOk(res.links[0].subsection);
+    }
+    // Groups
+    if (expected.groupPageShown) {
+      assert.isOk(res.links[1].subsection);
+      assert.equal(res.links[1].subsection.children.length,
+          expected.groupSubpageLength);
+    } else if ( expected.totalLength > 1) {
+      assert.isNotOk(res.links[1].subsection);
+    }
 
-              // External links should open in new tab.
-              if (link.url[0] !== '/') {
-                assert.equal(linkMatch.target, '_blank');
-              } else {
-                assert.isNotOk(linkMatch.target);
-              }
-            }
-          }
+    if (expected.pluginGeneratedLinks) {
+      for (const link of expected.pluginGeneratedLinks) {
+        const linkMatch = res.links
+            .find(l => (l.url === link.url && l.name === link.text));
+        assert.isTrue(!!linkMatch);
 
-          // Current section
-          if (expected.projectPageShown || expected.groupPageShown) {
-            assert.isOk(res.expandedSection);
-            assert.isOk(res.expandedSection.children);
-          } else {
-            assert.isNotOk(res.expandedSection);
-          }
-          if (expected.projectPageShown) {
-            assert.equal(res.expandedSection.name, 'my-repo');
-            assert.equal(res.expandedSection.children.length, 6);
-          } else if (expected.groupPageShown) {
-            assert.equal(res.expandedSection.name, 'my-group');
-            assert.equal(res.expandedSection.children.length,
-                expected.groupSubpageLength);
-          }
-          done();
-        });
+        // External links should open in new tab.
+        if (link.url[0] !== '/') {
+          assert.equal(linkMatch.target, '_blank');
+        } else {
+          assert.isNotOk(linkMatch.target);
+        }
+      }
+    }
+
+    // Current section
+    if (expected.projectPageShown || expected.groupPageShown) {
+      assert.isOk(res.expandedSection);
+      assert.isOk(res.expandedSection.children);
+    } else {
+      assert.isNotOk(res.expandedSection);
+    }
+    if (expected.projectPageShown) {
+      assert.equal(res.expandedSection.name, 'my-repo');
+      assert.equal(res.expandedSection.children.length, 6);
+    } else if (expected.groupPageShown) {
+      assert.equal(res.expandedSection.name, 'my-group');
+      assert.equal(res.expandedSection.children.length,
+          expected.groupSubpageLength);
+    }
   };
 
   suite('logged out', () => {
@@ -106,25 +104,25 @@
       };
     });
 
-    test('without a specific repo or group', done => {
+    test('without a specific repo or group', async () => {
       let options;
       expected = Object.assign(expected, {
         totalLength: 1,
         projectPageShown: false,
       });
-      testAdminLinks(account, options, expected, done);
+      await testAdminLinks(account, options, expected);
     });
 
-    test('with a repo', done => {
+    test('with a repo', async () => {
       const options = {repoName: 'my-repo'};
       expected = Object.assign(expected, {
         totalLength: 1,
         projectPageShown: true,
       });
-      testAdminLinks(account, options, expected, done);
+      await testAdminLinks(account, options, expected);
     });
 
-    test('with plugin generated links', done => {
+    test('with plugin generated links', async () => {
       let options;
       const generatedLinks = [
         {text: 'internal link text', url: '/internal/link/url'},
@@ -136,7 +134,7 @@
         projectPageShown: false,
         pluginGeneratedLinks: generatedLinks,
       });
-      testAdminLinks(account, options, expected, done);
+      await testAdminLinks(account, options, expected);
     });
   });
 
@@ -154,17 +152,17 @@
       capabilityStub.returns(Promise.resolve({}));
     });
 
-    test('without a specific project or group', done => {
+    test('without a specific project or group', async () => {
       let options;
       expected = Object.assign(expected, {
         projectPageShown: false,
         groupListShown: true,
         groupPageShown: false,
       });
-      testAdminLinks(account, options, expected, done);
+      await testAdminLinks(account, options, expected);
     });
 
-    test('with a repo', done => {
+    test('with a repo', async () => {
       const account = {
         name: 'test-user',
       };
@@ -174,7 +172,7 @@
         groupListShown: true,
         groupPageShown: false,
       });
-      testAdminLinks(account, options, expected, done);
+      await testAdminLinks(account, options, expected);
     });
   });
 
@@ -193,25 +191,25 @@
       };
     });
 
-    test('without a specific repo or group', done => {
+    test('without a specific repo or group', async () => {
       let options;
       expected = Object.assign(expected, {
         projectPageShown: false,
         groupPageShown: false,
       });
-      testAdminLinks(account, options, expected, done);
+      await testAdminLinks(account, options, expected);
     });
 
-    test('with a repo', done => {
+    test('with a repo', async () => {
       const options = {repoName: 'my-repo'};
       expected = Object.assign(expected, {
         projectPageShown: true,
         groupPageShown: false,
       });
-      testAdminLinks(account, options, expected, done);
+      await testAdminLinks(account, options, expected);
     });
 
-    test('admin with internal group', done => {
+    test('admin with internal group', async () => {
       const options = {
         groupId: 'a15262',
         groupName: 'my-group',
@@ -224,10 +222,10 @@
         groupPageShown: true,
         groupSubpageLength: 2,
       });
-      testAdminLinks(account, options, expected, done);
+      await testAdminLinks(account, options, expected);
     });
 
-    test('group owner with internal group', done => {
+    test('group owner with internal group', async () => {
       const options = {
         groupId: 'a15262',
         groupName: 'my-group',
@@ -240,10 +238,10 @@
         groupPageShown: true,
         groupSubpageLength: 2,
       });
-      testAdminLinks(account, options, expected, done);
+      await testAdminLinks(account, options, expected);
     });
 
-    test('non owner or admin with internal group', done => {
+    test('non owner or admin with internal group', async () => {
       const options = {
         groupId: 'a15262',
         groupName: 'my-group',
@@ -256,10 +254,10 @@
         groupPageShown: true,
         groupSubpageLength: 1,
       });
-      testAdminLinks(account, options, expected, done);
+      await testAdminLinks(account, options, expected);
     });
 
-    test('admin with external group', done => {
+    test('admin with external group', async () => {
       const options = {
         groupId: 'a15262',
         groupName: 'my-group',
@@ -272,7 +270,7 @@
         groupPageShown: true,
         groupSubpageLength: 0,
       });
-      testAdminLinks(account, options, expected, done);
+      await testAdminLinks(account, options, expected);
     });
   });
 
@@ -287,7 +285,7 @@
       expected = {};
     });
 
-    test('with plugin with capabilities', done => {
+    test('with plugin with capabilities', async () => {
       let options;
       const generatedLinks = [
         {text: 'without capability', url: '/without'},
@@ -300,7 +298,7 @@
         totalLength: 4,
         pluginGeneratedLinks: generatedLinks,
       });
-      testAdminLinks(account, options, expected, done);
+      await testAdminLinks(account, options, expected);
     });
   });
 
@@ -315,7 +313,7 @@
       expected = {};
     });
 
-    test('with plugin with capabilities', done => {
+    test('with plugin with capabilities', async () => {
       let options;
       const generatedLinks = [
         {text: 'without capability', url: '/without'},
@@ -328,7 +326,7 @@
         totalLength: 3,
         pluginGeneratedLinks: [generatedLinks[0]],
       });
-      testAdminLinks(account, options, expected, done);
+      await testAdminLinks(account, options, expected);
     });
   });
 });
diff --git a/polygerrit-ui/app/utils/change-metadata-util.ts b/polygerrit-ui/app/utils/change-metadata-util.ts
index a4865dc..9692ab31 100644
--- a/polygerrit-ui/app/utils/change-metadata-util.ts
+++ b/polygerrit-ui/app/utils/change-metadata-util.ts
@@ -15,9 +15,7 @@
  * limitations under the License.
  */
 
-import {SubmitRequirementResultInfo} from '../api/rest-api';
 import {ParsedChangeInfo} from '../types/types';
-import {unique} from './common-util';
 
 export enum Metadata {
   OWNER = 'Owner',
@@ -83,19 +81,3 @@
   }
   return true;
 }
-
-export function extractAssociatedLabels(
-  requirement: SubmitRequirementResultInfo
-): string[] {
-  const pattern = new RegExp('label[0-9]*:([\\w-]+)', 'g');
-  const labels = [];
-  let match;
-  while (
-    (match = pattern.exec(
-      requirement.submittability_expression_result.expression
-    )) !== null
-  ) {
-    labels.push(match[1]);
-  }
-  return labels.filter(unique);
-}
diff --git a/polygerrit-ui/app/utils/change-metadata-util_test.ts b/polygerrit-ui/app/utils/change-metadata-util_test.ts
deleted file mode 100644
index 888114d..0000000
--- a/polygerrit-ui/app/utils/change-metadata-util_test.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../test/common-test-setup-karma';
-import {
-  createSubmitRequirementExpressionInfo,
-  createSubmitRequirementResultInfo,
-} from '../test/test-data-generators';
-import {extractAssociatedLabels} from './change-metadata-util';
-
-suite('change-metadata-util', () => {
-  suite('extractAssociatedLabels()', () => {
-    function createSubmitRequirementExpressionInfoWith(expression: string) {
-      return {
-        ...createSubmitRequirementResultInfo(),
-        submittability_expression_result: {
-          ...createSubmitRequirementExpressionInfo(),
-          expression,
-        },
-      };
-    }
-
-    test('1 label', () => {
-      const submitRequirement = createSubmitRequirementExpressionInfoWith(
-        'label:Verified=MAX -label:Verified=MIN'
-      );
-      const labels = extractAssociatedLabels(submitRequirement);
-      assert.deepEqual(labels, ['Verified']);
-    });
-    test('label with number', () => {
-      const submitRequirement = createSubmitRequirementExpressionInfoWith(
-        'label2:verified=MAX'
-      );
-      const labels = extractAssociatedLabels(submitRequirement);
-      assert.deepEqual(labels, ['verified']);
-    });
-    test('2 labels', () => {
-      const submitRequirement = createSubmitRequirementExpressionInfoWith(
-        'label:Verified=MAX -label:Code-Review=MIN'
-      );
-      const labels = extractAssociatedLabels(submitRequirement);
-      assert.deepEqual(labels, ['Verified', 'Code-Review']);
-    });
-  });
-});
diff --git a/polygerrit-ui/app/utils/comment-util.ts b/polygerrit-ui/app/utils/comment-util.ts
index 48d6947..5b08fab 100644
--- a/polygerrit-ui/app/utils/comment-util.ts
+++ b/polygerrit-ui/app/utils/comment-util.ts
@@ -129,6 +129,7 @@
     const newThread: CommentThread = {
       comments: [comment],
       patchNum: comment.patch_set,
+      diffSide: Side.LEFT,
       commentSide: comment.side ?? CommentSide.REVISION,
       mergeParentNum: comment.parent,
       path: comment.path,
diff --git a/polygerrit-ui/app/utils/common-util.ts b/polygerrit-ui/app/utils/common-util.ts
index 36c3657..5370cf9 100644
--- a/polygerrit-ui/app/utils/common-util.ts
+++ b/polygerrit-ui/app/utils/common-util.ts
@@ -90,7 +90,16 @@
   }
 }
 
-function query<E extends Element = Element>(
+export function queryAll<E extends Element = Element>(
+  el: Element,
+  selector: string
+): NodeListOf<E> {
+  if (!el) throw new Error('element not defined');
+  const root = el.shadowRoot ?? el;
+  return root.querySelectorAll<E>(selector);
+}
+
+export function query<E extends Element = Element>(
   el: Element | undefined,
   selector: string
 ): E | undefined {
diff --git a/polygerrit-ui/app/utils/compare-util.ts b/polygerrit-ui/app/utils/compare-util.ts
new file mode 100644
index 0000000..dd20915
--- /dev/null
+++ b/polygerrit-ui/app/utils/compare-util.ts
@@ -0,0 +1,39 @@
+/**
+ * @license
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export function deepEqualStringDict(
+  a: {[name: string]: string},
+  b: {[name: string]: string}
+): boolean {
+  const aKeys = Object.keys(a);
+  const bKeys = Object.keys(b);
+  if (aKeys.length !== bKeys.length) return false;
+  for (const key of aKeys) {
+    if (a[key] !== b[key]) return false;
+  }
+  return true;
+}
+
+export function equalArray(a?: unknown[], b?: unknown[]): boolean {
+  if (a === b) return true;
+  if (a === undefined) return b === undefined;
+  if (b === undefined) return a === undefined;
+  if (a.length !== b.length) return false;
+  for (let i = 0; i < a.length; i++) {
+    if (a[i] !== b[i]) return false;
+  }
+  return true;
+}
diff --git a/polygerrit-ui/app/utils/compare-util_test.ts b/polygerrit-ui/app/utils/compare-util_test.ts
new file mode 100644
index 0000000..7cd71bf
--- /dev/null
+++ b/polygerrit-ui/app/utils/compare-util_test.ts
@@ -0,0 +1,43 @@
+/**
+ * @license
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import '../test/common-test-setup-karma';
+import {deepEqualStringDict, equalArray} from './compare-util';
+
+suite('compare-utils tests', () => {
+  test('deepEqual', () => {
+    assert.isTrue(deepEqualStringDict({}, {}));
+    assert.isTrue(deepEqualStringDict({x: 'y'}, {x: 'y'}));
+    assert.isTrue(deepEqualStringDict({x: 'y', p: 'q'}, {p: 'q', x: 'y'}));
+
+    assert.isFalse(deepEqualStringDict({}, {x: 'y'}));
+    assert.isFalse(deepEqualStringDict({x: 'y'}, {x: 'z'}));
+    assert.isFalse(deepEqualStringDict({x: 'y'}, {z: 'y'}));
+  });
+
+  test('equalArray', () => {
+    assert.isTrue(equalArray(undefined, undefined));
+    assert.isTrue(equalArray([], []));
+    assert.isTrue(equalArray([1], [1]));
+    assert.isTrue(equalArray(['a', 'b'], ['a', 'b']));
+
+    assert.isFalse(equalArray(undefined, []));
+    assert.isFalse(equalArray([], undefined));
+    assert.isFalse(equalArray([], [1]));
+    assert.isFalse(equalArray([1], [2]));
+    assert.isFalse(equalArray([1, 2], [1]));
+  });
+});
diff --git a/polygerrit-ui/app/utils/label-util.ts b/polygerrit-ui/app/utils/label-util.ts
index 92bbdac..16e6803 100644
--- a/polygerrit-ui/app/utils/label-util.ts
+++ b/polygerrit-ui/app/utils/label-util.ts
@@ -15,6 +15,10 @@
  * limitations under the License.
  */
 import {
+  SubmitRequirementResultInfo,
+  SubmitRequirementStatus,
+} from '../api/rest-api';
+import {
   AccountInfo,
   ApprovalInfo,
   DetailedLabelInfo,
@@ -23,6 +27,7 @@
   LabelNameToInfoMap,
   VotingRangeInfo,
 } from '../types/common';
+import {assertNever, unique} from './common-util';
 
 // Name of the standard Code-Review label.
 export const CODE_REVIEW = 'Code-Review';
@@ -64,8 +69,11 @@
   return max > -min ? max : min;
 }
 
-export function getLabelStatus(label?: DetailedLabelInfo): LabelStatus {
-  const value = getRepresentativeValue(label);
+export function getLabelStatus(
+  label?: DetailedLabelInfo,
+  vote?: number
+): LabelStatus {
+  const value = vote ?? getRepresentativeValue(label);
   const range = getVotingRangeOrDefault(label);
   if (value < 0) {
     return value === range.min ? LabelStatus.REJECTED : LabelStatus.DISLIKED;
@@ -76,6 +84,23 @@
   return LabelStatus.NEUTRAL;
 }
 
+export function classForLabelStatus(status: LabelStatus) {
+  switch (status) {
+    case LabelStatus.APPROVED:
+      return 'max';
+    case LabelStatus.RECOMMENDED:
+      return 'positive';
+    case LabelStatus.DISLIKED:
+      return 'negative';
+    case LabelStatus.REJECTED:
+      return 'min';
+    case LabelStatus.NEUTRAL:
+      return 'neutral';
+    default:
+      assertNever(status, `Unsupported status: ${status}`);
+  }
+}
+
 export function valueString(value?: number) {
   if (!value) return ' 0';
   let s = `${value}`;
@@ -114,3 +139,34 @@
   }
   return;
 }
+
+export function extractAssociatedLabels(
+  requirement: SubmitRequirementResultInfo
+): string[] {
+  const pattern = new RegExp('label[0-9]*:([\\w-]+)', 'g');
+  const labels = [];
+  let match;
+  while (
+    (match = pattern.exec(
+      requirement.submittability_expression_result.expression
+    )) !== null
+  ) {
+    labels.push(match[1]);
+  }
+  return labels.filter(unique);
+}
+
+export function iconForStatus(status: SubmitRequirementStatus) {
+  switch (status) {
+    case SubmitRequirementStatus.SATISFIED:
+      return 'check';
+    case SubmitRequirementStatus.UNSATISFIED:
+      return 'close';
+    case SubmitRequirementStatus.OVERRIDDEN:
+      return 'warning';
+    case SubmitRequirementStatus.NOT_APPLICABLE:
+      return 'info';
+    default:
+      assertNever(status, `Unsupported status: ${status}`);
+  }
+}
diff --git a/polygerrit-ui/app/utils/label-util_test.ts b/polygerrit-ui/app/utils/label-util_test.ts
index 56941ba..196c5e9 100644
--- a/polygerrit-ui/app/utils/label-util_test.ts
+++ b/polygerrit-ui/app/utils/label-util_test.ts
@@ -17,6 +17,7 @@
 
 import '../test/common-test-setup-karma';
 import {
+  extractAssociatedLabels,
   getApprovalInfo,
   getLabelStatus,
   getMaxAccounts,
@@ -32,6 +33,10 @@
   ApprovalInfo,
   DetailedLabelInfo,
 } from '../types/common';
+import {
+  createSubmitRequirementExpressionInfo,
+  createSubmitRequirementResultInfo,
+} from '../test/test-data-generators';
 
 const VALUES_0 = {
   '0': 'neutral',
@@ -186,4 +191,38 @@
     labelInfo = {all: [{value: 0}, {value: -2}]};
     assert.equal(getRepresentativeValue(labelInfo), -2);
   });
+
+  suite('extractAssociatedLabels()', () => {
+    function createSubmitRequirementExpressionInfoWith(expression: string) {
+      return {
+        ...createSubmitRequirementResultInfo(),
+        submittability_expression_result: {
+          ...createSubmitRequirementExpressionInfo(),
+          expression,
+        },
+      };
+    }
+
+    test('1 label', () => {
+      const submitRequirement = createSubmitRequirementExpressionInfoWith(
+        'label:Verified=MAX -label:Verified=MIN'
+      );
+      const labels = extractAssociatedLabels(submitRequirement);
+      assert.deepEqual(labels, ['Verified']);
+    });
+    test('label with number', () => {
+      const submitRequirement = createSubmitRequirementExpressionInfoWith(
+        'label2:verified=MAX'
+      );
+      const labels = extractAssociatedLabels(submitRequirement);
+      assert.deepEqual(labels, ['verified']);
+    });
+    test('2 labels', () => {
+      const submitRequirement = createSubmitRequirementExpressionInfoWith(
+        'label:Verified=MAX -label:Code-Review=MIN'
+      );
+      const labels = extractAssociatedLabels(submitRequirement);
+      assert.deepEqual(labels, ['Verified', 'Code-Review']);
+    });
+  });
 });
diff --git a/polygerrit-ui/app/yarn.lock b/polygerrit-ui/app/yarn.lock
index ca04edf..395ef64 100644
--- a/polygerrit-ui/app/yarn.lock
+++ b/polygerrit-ui/app/yarn.lock
@@ -2,6 +2,11 @@
 # yarn lockfile v1
 
 
+"@lit/reactive-element@^1.0.0-rc.2":
+  version "1.0.0-rc.3"
+  resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.0.0-rc.3.tgz#5032f493fbf39781b187a7e2dd5d256537c8760c"
+  integrity sha512-Rs2px1keOQUNJUo5B+WExl5v244ZNCiN/iMVNO9evFdJjAdWCIupR/p14zRPkNHsciRBELLTcOZ379cI9O6PDg==
+
 "@mapbox/node-pre-gyp@^1.0.0":
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz#2a0b32fcb416fb3f2250fd24cb2a81421a4f5950"
@@ -420,6 +425,11 @@
   resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.6.tgz#d8e6c2f830e2650dc06fe74464472ff64b54a302"
   integrity sha512-61IfTac0s9jvNtBCpyo86QeaN8qqpMGHdK0uGKCCIy2dt5/Yk84VduHIdWAcmkC5QvdkPL0p5eWYgUZtHKKUVg==
 
+"@types/trusted-types@^1.0.1":
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-1.0.6.tgz#569b8a08121d3203398290d602d84d73c8dcf5da"
+  integrity sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==
+
 "@webcomponents/shadycss@^1.10.2", "@webcomponents/shadycss@^1.9.1":
   version "1.11.0"
   resolved "https://registry.yarnpkg.com/@webcomponents/shadycss/-/shadycss-1.11.0.tgz#73e289996c002d8be694cd3be0e83c46ad25e7e0"
@@ -642,17 +652,29 @@
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
   integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
 
-lit-element@^2.5.1:
-  version "2.5.1"
-  resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-2.5.1.tgz#3fa74b121a6cd22902409ae3859b7847d01aa6b6"
-  integrity sha512-ogu7PiJTA33bEK0xGu1dmaX5vhcRjBXCFexPja0e7P7jqLhTpNKYRPmE+GmiCaRVAbiQKGkUgkh/i6+bh++dPQ==
+lit-element@^3.0.0-rc.2:
+  version "3.0.0-rc.3"
+  resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-3.0.0-rc.3.tgz#cece8f092d28eb6f9c6b23e4138ff5d7260897ef"
+  integrity sha512-NDe7yjW18gfYQb1GIEQr1T8sB1GUAb1HB62pdAEw+SK6lUW7OFPKQqCOlRhZ6qJXsw9KxMnyYIprLZT4FZdYdQ==
   dependencies:
-    lit-html "^1.1.1"
+    "@lit/reactive-element" "^1.0.0-rc.2"
+    lit-html "^2.0.0-rc.4"
 
-lit-html@^1.1.1:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-1.4.1.tgz#0c6f3ee4ad4eb610a49831787f0478ad8e9ae5e0"
-  integrity sha512-B9btcSgPYb1q4oSOb/PrOT6Z/H+r6xuNzfH4lFli/AWhYwdtrgQkQWBbIc6mdnf6E2IL3gDXdkkqNktpU0OZQA==
+lit-html@^2.0.0-rc.4:
+  version "2.0.0-rc.4"
+  resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.0.0-rc.4.tgz#1015fa8f1f7c8c5b79999ed0bc11c3b79ff1aab5"
+  integrity sha512-WSLGu3vxq7y8q/oOd9I3zxyBELNLLiDk6gAYoKK4PGctI5fbh6lhnO/jVBdy0PV/vTc+cLJCA/occzx3YoNPeg==
+  dependencies:
+    "@types/trusted-types" "^1.0.1"
+
+lit@2.0.0-rc.3:
+  version "2.0.0-rc.3"
+  resolved "https://registry.yarnpkg.com/lit/-/lit-2.0.0-rc.3.tgz#8b6a85268aba287c11125dfe57e88e0bc09beaff"
+  integrity sha512-UZDLWuspl7saA+WvS0e+TE3NdGGE05hOIwUPTWiibs34c5QupcEzpjB/aElt79V9bELQVNbUUwa0Ow7D1Wuszw==
+  dependencies:
+    "@lit/reactive-element" "^1.0.0-rc.2"
+    lit-element "^3.0.0-rc.2"
+    lit-html "^2.0.0-rc.4"
 
 lru-cache@^6.0.0:
   version "6.0.0"
diff --git a/polygerrit-ui/karma.conf.js b/polygerrit-ui/karma.conf.js
index fe3fa0c..00ebc63 100644
--- a/polygerrit-ui/karma.conf.js
+++ b/polygerrit-ui/karma.conf.js
@@ -67,11 +67,23 @@
   // It can be just a file name, without a path:
   // --test-files async-foreach-behavior_test.js
   // If you specify --test-files without pattern, it gets true value
-  // In this case we ill run all tests (usefull for package.json "debugtest"
+  // In this case we will run all tests (usefull for package.json "debugtest"
   // script)
-  const testFilesPattern = (typeof config.testFiles == 'string') ?
-      testFilesLocationPattern + config.testFiles :
-      testFilesLocationPattern + '*_test.js';
+  // We will convert a .ts argument to .js and fill in .js if no extension is
+  // given.
+  let filePattern;
+  if (typeof config.testFiles === "string") {
+    if (config.testFiles.endsWith('.ts')) {
+      filePattern = config.testFiles.substr(0, config.testFiles.lastIndexOf(".")) + ".js";
+    } else if (config.testFiles.endsWith('.js')) {
+      filePattern = config.testFiles;
+    } else {
+      filePattern = config.testFiles + '.js';
+    }
+  } else {
+    filePattern = '*_test.js';
+  }
+  const testFilesPattern = testFilesLocationPattern + filePattern;
   // Special patch for grep parameters (see details in the grep-patch-karam.js)
   const additionalFiles = runUnderBazel ? [] : ['polygerrit-ui/grep-patch-karma.js'];
   config.set({
diff --git a/polygerrit-ui/server.go b/polygerrit-ui/server.go
index ddfaeb4..2a433fb 100644
--- a/polygerrit-ui/server.go
+++ b/polygerrit-ui/server.go
@@ -184,10 +184,10 @@
 		//   'page/page.mjs' -> '/node_modules/page.mjs'
 		//   '@polymer/iron-icon' -> '/node_modules/@polymer/iron-icon.js'
 		//   './element/file' -> './element/file.js'
-		moduleImportRegexp = regexp.MustCompile(`(?m)^(import.*|export.* from )['"](.*?)(\.(m?)js)?['"];$`)
+		moduleImportRegexp = regexp.MustCompile(`(import[^'";]*|export[^'";]*from ?)['"]([^;\s]*?)(\.(m?)js)?['"];`)
 		data = moduleImportRegexp.ReplaceAll(data, []byte("$1'$2.${4}js';"))
 
-		moduleImportRegexp = regexp.MustCompile(`(?m)^(import.*|export.* from )['"]([^/.].*)['"];$`)
+		moduleImportRegexp = regexp.MustCompile(`(import[^'";]*|export[^'";]*from ?)['"]([^/.;\s][^;\s]*)['"];`)
 		data = moduleImportRegexp.ReplaceAll(data, []byte("$1'/node_modules/$2';"))
 
 		// The es module version of rxjs can be found in the _esm2015/ directory.
@@ -198,9 +198,12 @@
 		moduleImportRegexp = regexp.MustCompile("(?m)^((import|export).*'/node_modules/)tslib.js';$")
 		data = moduleImportRegexp.ReplaceAll(data, []byte("${1}tslib/tslib.es6.js';"))
 
-		// 'lit-element' imports and exports have to be resolved to 'lit-element/lit-element.js'.
-		moduleImportRegexp = regexp.MustCompile("(?m)^((import|export).*'/node_modules/)lit-(element|html).js';$")
-		data = moduleImportRegexp.ReplaceAll(data, []byte("${1}lit-${3}/lit-${3}.js';"))
+		// 'lit.js' has to be resolved as 'lit/index.js'.
+		moduleImportRegexp = regexp.MustCompile("(?m)^((import|export).*'/node_modules/)lit.js';$")
+		data = moduleImportRegexp.ReplaceAll(data, []byte("${1}lit/index.js';"))
+		// Some lit imports 'a.js' have to be resolved as 'a/a.js'.
+		moduleImportRegexp = regexp.MustCompile(`((import|export)[^'";]*'/node_modules/(@lit/)?)(lit-element|lit-html|reactive-element).js';`)
+		data = moduleImportRegexp.ReplaceAll(data, []byte("${1}${4}/${4}.js';"))
 
 		// 'immer' imports and exports have to be resolved to 'immer/dist/immer.esm.js'.
 		moduleImportRegexp = regexp.MustCompile("(?m)^((import|export).*'/node_modules/)immer.js';$")
diff --git a/proto/cache.proto b/proto/cache.proto
index aa04555..16e5e95 100644
--- a/proto/cache.proto
+++ b/proto/cache.proto
@@ -268,13 +268,14 @@
 // com.google.gerrit.server.account.externalids.AllExternalIds.
 // Next ID: 2
 message AllExternalIdsProto {
-  // Next ID: 6
+  // Next ID: 7
   message ExternalIdProto {
     string key = 1;
     int32 accountId = 2;
     string email = 3;
     string password = 4;
     bytes blobId = 5;
+    bool isCaseInsensitive = 6;
   }
   repeated ExternalIdProto external_id = 1;
 }
@@ -483,7 +484,7 @@
 }
 
 // Serialized form of com.google.gerrit.entities.SubmitRequirementResult.
-// Next ID: 6
+// Next ID: 7
 message SubmitRequirementResultProto {
   SubmitRequirementProto submit_requirement = 1;
   SubmitRequirementExpressionResultProto applicability_expression_result = 2;
@@ -492,6 +493,9 @@
 
   // Patchset commit ID at which the submit requirements are evaluated.
   bytes commit = 5;
+
+  // Whether this result was created from a legacy submit record.
+  bool legacy = 6;
 }
 
 // Serialized form of com.google.gerrit.entities.SubmitRequirementExpressionResult.
diff --git a/tools/node_tools/launchpad.patch b/tools/node_tools/launchpad.patch
new file mode 100644
index 0000000..565494b
--- /dev/null
+++ b/tools/node_tools/launchpad.patch
@@ -0,0 +1,240 @@
+From d430b5d912bebe87529b887f408ee55c82a0e003 Mon Sep 17 00:00:00 2001
+From: Michele Romano <33063403+Mik317@users.noreply.github.com>
+Date: Fri, 26 Jun 2020 20:16:47 +0200
+Subject: [PATCH 1/7] Update version.js
+
+---
+ lib/local/version.js | 15 ++++++++++++---
+ 1 file changed, 12 insertions(+), 3 deletions(-)
+
+diff --git a/tools/node_tools/node_modules/launchpad/lib/local/version.js b/tools/node_tools/node_modules/launchpad/lib/g/local/version.js
+index 0110a74..2c02bef 100644
+--- a/tools/node_tools/node_modules/launchpad/lib/local/version.js
++++ b/tools/node_tools/node_modules/launchpad/lib/local/version.js
+@@ -6,6 +6,15 @@ var plist = require('plist');
+ var utils = require('./utils');
+ var debug = require('debug')('launchpad:local:version');
+ 
++var validPath = function (filename){
++  var filter = /[`!@#$%^&*()_+\-=\[\]{};':"\\|,<>\/?~]/;
++  if (filter.test(filename)){
++    console.log('\nInvalid characters inside the path to the browser\n');
++    return
++  }
++  return filename;
++}
++
+ module.exports = function(browser) {
+   if (!browser || !browser.path) {
+     return Q(null);
+@@ -18,7 +27,7 @@ module.exports = function(browser) {
+ 
+     debug('Retrieving version for windows executable', command);
+     // Can't use Q.nfcall here unfortunately because of non 0 exit code
+-    exec(command, function(error, stdout) {
++    exec(command.split(' ')[0], command.split(' ').slice(1), function(error, stdout) {
+       var regex = /ProductVersion:\s*(.*)/;
+       // ShowVer.exe returns a non zero status code even if it works
+       if (typeof stdout === 'string' && regex.test(stdout)) {
+@@ -47,8 +56,8 @@ module.exports = function(browser) {
+   }
+ 
+   // Try executing <browser> --version (everything else)
+-  return Q.nfcall(exec, browser.path + ' --version').then(function(stdout) {
+-    debug('Ran ' + browser.path + ' --version', stdout);
++  return Q.nfcall(exec, validPath(browser.path) + ' --version').then(function(stdout) {
++    debug('Ran ' + validPath(browser.path) + ' --version', stdout);
+     var version = utils.getStdout(stdout);
+     if (version) {
+       browser.version = version;
+
+From 09ce4fab2fd53cab893ceaa3b4d7f997af9b41d8 Mon Sep 17 00:00:00 2001
+From: Michele Romano <33063403+Mik317@users.noreply.github.com>
+Date: Fri, 26 Jun 2020 20:18:35 +0200
+Subject: [PATCH 2/7] Update instance.js
+
+---
+ lib/local/instance.js | 11 +++++++++--
+ 1 file changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/tools/node_tools/node_modules/launchpad/lib/local/instance.js b/tools/node_tools/node_modules/launchpad/lib/g/local/instance.js
+index 484a866..b49990f 100644
+--- a/tools/node_tools/node_modules/launchpad/lib/local/instance.js
++++ b/tools/node_tools/node_modules/launchpad/lib/local/instance.js
+@@ -5,8 +5,15 @@ var EventEmitter = require('events').EventEmitter;
+ var debug = require('debug')('launchpad:local:instance');
+ var rimraf = require('rimraf');
+ 
++var safe = function (str) {
++   // Avoid quotes makes impossible escape the `multi command` scenario
++   return str.replace(/['"]+/g, '');
++}
++
+ var getProcessId = function (name, callback) {
+ 
++  name = safe(name);
++
+   var commands = {
+     darwin: "ps -clx | grep '" + name + "$' | awk '{print $2}' | head -1",
+     linux: "ps -ax | grep '" + name + "$' | awk '{print $2}' | head -1",
+@@ -90,11 +97,11 @@ Instance.prototype.stop = function (callback) {
+     } catch (error) {}
+   } else {
+     if (this.options.command.indexOf('open') === 0) {
+-      command = 'osascript -e \'tell application "' + self.options.process + '" to quit\'';
++      command = 'osascript -e \'tell application "' + safe(self.options.process) + '" to quit\'';
+       debug('Executing shutdown AppleScript', command);
+       exec(command);
+     } else if (process.platform === 'win32') {
+-      command = 'taskkill /IM ' + (this.options.imageName || path.basename(this.cmd));
++      command = 'taskkill /IM "' + safe(this.options.imageName || path.basename(this.cmd)) + '"';
+       debug('Executing shutdown taskkil', command);
+       exec(command).once('exit', function(data) {
+         self.emit('stop', data);
+
+From d3993fce090ed6ef378c1f0594eff18d125dad1e Mon Sep 17 00:00:00 2001
+From: Michele Romano <33063403+Mik317@users.noreply.github.com>
+Date: Fri, 26 Jun 2020 20:19:17 +0200
+Subject: [PATCH 3/7] Update version.js
+
+---
+ lib/local/version.js | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/tools/node_tools/node_modules/launchpad/lib/local/version.js b/tools/node_tools/node_modules/launchpad/lib/g/local/version.js
+index 2c02bef..5eac082 100644
+--- a/tools/node_tools/node_modules/launchpad/lib/local/version.js
++++ b/tools/node_tools/node_modules/launchpad/lib/local/version.js
+@@ -6,6 +6,7 @@ var plist = require('plist');
+ var utils = require('./utils');
+ var debug = require('debug')('launchpad:local:version');
+ 
++// Validate paths supplied by the user in order to avoid "arbitrary command execution"
+ var validPath = function (filename){
+   var filter = /[`!@#$%^&*()_+\-=\[\]{};':"\\|,<>\/?~]/;
+   if (filter.test(filename)){
+
+From abf3dbcc79e6b338338594ab2dbef834550e8f65 Mon Sep 17 00:00:00 2001
+From: Michele Romano <33063403+Mik317@users.noreply.github.com>
+Date: Mon, 29 Jun 2020 13:32:50 +0200
+Subject: [PATCH 4/7] Update instance.js
+
+---
+ lib/local/instance.js | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/tools/node_tools/node_modules/launchpad/lib/local/instance.js b/tools/node_tools/node_modules/launchpad/lib/g/local/instance.js
+index b49990f..9375d1f 100644
+--- a/tools/node_tools/node_modules/launchpad/lib/local/instance.js
++++ b/tools/node_tools/node_modules/launchpad/lib/local/instance.js
+@@ -1,6 +1,7 @@
+ var path = require('path');
+ var spawn = require("child_process").spawn;
+ var exec = require("child_process").exec;
++var execFile = require("child_process").execFile;
+ var EventEmitter = require('events').EventEmitter;
+ var debug = require('debug')('launchpad:local:instance');
+ var rimraf = require('rimraf');
+@@ -99,11 +100,14 @@ Instance.prototype.stop = function (callback) {
+     if (this.options.command.indexOf('open') === 0) {
+       command = 'osascript -e \'tell application "' + safe(self.options.process) + '" to quit\'';
+       debug('Executing shutdown AppleScript', command);
+-      exec(command);
++      command = command.split(' ');
++      execFile(command[0], command.slice(1));
+     } else if (process.platform === 'win32') {
+-      command = 'taskkill /IM "' + safe(this.options.imageName || path.basename(this.cmd)) + '"';
++      //Adding `"` wasn't safe/functional on Win systems
++      command = 'taskkill /IM ' + (this.options.imageName || path.basename(this.cmd); 
+       debug('Executing shutdown taskkil', command);
+-      exec(command).once('exit', function(data) {
++      command = command.split(' ');
++      execFile(command[0], command.slice(1)).once('exit', function(data) {
+         self.emit('stop', data);
+       });
+     } else {
+
+From 68518b274c9351f799d41ce85f23499ca4a785e9 Mon Sep 17 00:00:00 2001
+From: Michele Romano <33063403+Mik317@users.noreply.github.com>
+Date: Tue, 30 Jun 2020 00:01:31 +0200
+Subject: [PATCH 5/7] Update instance.js
+
+---
+ lib/local/instance.js | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/tools/node_tools/node_modules/launchpad/lib/local/instance.js b/tools/node_tools/node_modules/launchpad/lib/g/local/instance.js
+index 9375d1f..f157dd4 100644
+--- a/tools/node_tools/node_modules/launchpad/lib/local/instance.js
++++ b/tools/node_tools/node_modules/launchpad/lib/local/instance.js
+@@ -104,7 +104,7 @@ Instance.prototype.stop = function (callback) {
+       execFile(command[0], command.slice(1));
+     } else if (process.platform === 'win32') {
+       //Adding `"` wasn't safe/functional on Win systems
+-      command = 'taskkill /IM ' + (this.options.imageName || path.basename(this.cmd); 
++      command = 'taskkill /IM ' + (this.options.imageName || path.basename(this.cmd)); 
+       debug('Executing shutdown taskkil', command);
+       command = command.split(' ');
+       execFile(command[0], command.slice(1)).once('exit', function(data) {
+
+From e711d07d40d39162ea4bdb1ed344c58f92bfa10b Mon Sep 17 00:00:00 2001
+From: Michele Romano <33063403+Mik317@users.noreply.github.com>
+Date: Fri, 3 Jul 2020 12:30:31 +0200
+Subject: [PATCH 6/7] Update version.js
+
+---
+ lib/local/version.js | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/tools/node_tools/node_modules/launchpad/lib/local/version.js b/tools/node_tools/node_modules/launchpad/lib/g/local/version.js
+index 5eac082..d1403a0 100644
+--- a/tools/node_tools/node_modules/launchpad/lib/local/version.js
++++ b/tools/node_tools/node_modules/launchpad/lib/local/version.js
+@@ -1,5 +1,6 @@
+ var fs = require('fs');
+ var exec = require('child_process').exec;
++var execFile = require('child_process').execFile;
+ var Q = require('q');
+ var path = require('path');
+ var plist = require('plist');
+@@ -8,7 +9,7 @@ var debug = require('debug')('launchpad:local:version');
+ 
+ // Validate paths supplied by the user in order to avoid "arbitrary command execution"
+ var validPath = function (filename){
+-  var filter = /[`!@#$%^&*()_+\-=\[\]{};':"\\|,<>\/?~]/;
++  var filter = /[`!@#$%^&*()_+\-=\[\]{};':"|,<>?~]/;
+   if (filter.test(filename)){
+     console.log('\nInvalid characters inside the path to the browser\n');
+     return
+@@ -28,7 +29,7 @@ module.exports = function(browser) {
+ 
+     debug('Retrieving version for windows executable', command);
+     // Can't use Q.nfcall here unfortunately because of non 0 exit code
+-    exec(command.split(' ')[0], command.split(' ').slice(1), function(error, stdout) {
++    execFile(command.split(' ')[0], command.split(' ').slice(1), function(error, stdout) {
+       var regex = /ProductVersion:\s*(.*)/;
+       // ShowVer.exe returns a non zero status code even if it works
+       if (typeof stdout === 'string' && regex.test(stdout)) {
+
+From a3ff1804f0aacfb4fa20dad1312427b81280bb3e Mon Sep 17 00:00:00 2001
+From: Michele Romano <33063403+Mik317@users.noreply.github.com>
+Date: Fri, 3 Jul 2020 12:31:31 +0200
+Subject: [PATCH 7/7] Update version.js
+
+---
+ lib/local/version.js | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/tools/node_tools/node_modules/launchpad/lib/local/version.js b/tools/node_tools/node_modules/launchpad/lib/g/local/version.js
+index d1403a0..d937be4 100644
+--- a/tools/node_tools/node_modules/launchpad/lib/local/version.js
++++ b/tools/node_tools/node_modules/launchpad/lib/local/version.js
+@@ -9,7 +9,7 @@ var debug = require('debug')('launchpad:local:version');
+ 
+ // Validate paths supplied by the user in order to avoid "arbitrary command execution"
+ var validPath = function (filename){
+-  var filter = /[`!@#$%^&*()_+\-=\[\]{};':"|,<>?~]/;
++  var filter = /[`!@#$%^&*()_+\-=\[\]{};'"|,<>?~]/;
+   if (filter.test(filename)){
+     console.log('\nInvalid characters inside the path to the browser\n');
+     return
diff --git a/tools/node_tools/node_modules_licenses/tsconfig.json b/tools/node_tools/node_modules_licenses/tsconfig.json
index cb7bb60..4a57571 100644
--- a/tools/node_tools/node_modules_licenses/tsconfig.json
+++ b/tools/node_tools/node_modules_licenses/tsconfig.json
@@ -8,8 +8,8 @@
         ]
       }
     ],
-    "target": "es6",
-    "module": "commonjs",
+    "target": "es2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
+    "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
     "allowSyntheticDefaultImports": true,
     "esModuleInterop": true,
     "strict": true,
diff --git a/tools/node_tools/package.json b/tools/node_tools/package.json
index ea36e9f..7ee64df 100644
--- a/tools/node_tools/package.json
+++ b/tools/node_tools/package.json
@@ -19,6 +19,12 @@
     "typescript": "4.3.2"
   },
   "devDependencies": {},
+  "scripts": {
+    "postinstall": "(git apply --reverse --ignore-whitespace launchpad.patch || true) && git apply --ignore-whitespace launchpad.patch"
+  },
   "license": "Apache-2.0",
-  "private": true
+  "private": true,
+  "resolutions": {
+    "lodash": "4.17.21"
+  }
 }
diff --git a/tools/node_tools/polygerrit_app_preprocessor/tsconfig.json b/tools/node_tools/polygerrit_app_preprocessor/tsconfig.json
index 34ffb2f..d3c7d1d 100644
--- a/tools/node_tools/polygerrit_app_preprocessor/tsconfig.json
+++ b/tools/node_tools/polygerrit_app_preprocessor/tsconfig.json
@@ -1,7 +1,7 @@
 {
   "compilerOptions": {
-    "target": "es6",
-    "module": "commonjs",
+    "target": "es2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
+    "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
     "allowSyntheticDefaultImports": true,
     "esModuleInterop": true,
     "strict": true,
diff --git a/tools/node_tools/utils/tsconfig.json b/tools/node_tools/utils/tsconfig.json
index 56ab91b..d6bffa0 100644
--- a/tools/node_tools/utils/tsconfig.json
+++ b/tools/node_tools/utils/tsconfig.json
@@ -1,7 +1,7 @@
 {
   "compilerOptions": {
-    "target": "es6",
-    "module": "commonjs",
+    "target": "es2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
+    "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
     "allowSyntheticDefaultImports": true,
     "esModuleInterop": true,
     "strict": true,
diff --git a/tools/node_tools/yarn.lock b/tools/node_tools/yarn.lock
index d9a2c93..6525c41 100644
--- a/tools/node_tools/yarn.lock
+++ b/tools/node_tools/yarn.lock
@@ -2,255 +2,262 @@
 # yarn lockfile v1
 
 
-"@babel/code-frame@^7.5.5", "@babel/code-frame@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
-  integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.14.5", "@babel/code-frame@^7.5.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb"
+  integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==
   dependencies:
-    "@babel/highlight" "^7.8.3"
+    "@babel/highlight" "^7.14.5"
+
+"@babel/compat-data@^7.14.7", "@babel/compat-data@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.15.0.tgz#2dbaf8b85334796cafbb0f5793a90a2fc010b176"
+  integrity sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==
 
 "@babel/core@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.8.3.tgz#30b0ebb4dd1585de6923a0b4d179e0b9f5d82941"
-  integrity sha512-4XFkf8AwyrEG7Ziu3L2L0Cv+WyY47Tcsp70JFmpftbAA1K7YL/sgE9jh9HyNj08Y/U50ItUchpN0w6HxAoX1rA==
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.15.0.tgz#749e57c68778b73ad8082775561f67f5196aafa8"
+  integrity sha512-tXtmTminrze5HEUPn/a0JtOzzfp0nk+UEXQ/tqIJo3WDGypl/2OFQEMll/zSFU8f/lfmfLXvTaORHF3cfXIQMw==
   dependencies:
-    "@babel/code-frame" "^7.8.3"
-    "@babel/generator" "^7.8.3"
-    "@babel/helpers" "^7.8.3"
-    "@babel/parser" "^7.8.3"
-    "@babel/template" "^7.8.3"
-    "@babel/traverse" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/code-frame" "^7.14.5"
+    "@babel/generator" "^7.15.0"
+    "@babel/helper-compilation-targets" "^7.15.0"
+    "@babel/helper-module-transforms" "^7.15.0"
+    "@babel/helpers" "^7.14.8"
+    "@babel/parser" "^7.15.0"
+    "@babel/template" "^7.14.5"
+    "@babel/traverse" "^7.15.0"
+    "@babel/types" "^7.15.0"
     convert-source-map "^1.7.0"
     debug "^4.1.0"
-    gensync "^1.0.0-beta.1"
-    json5 "^2.1.0"
-    lodash "^4.17.13"
-    resolve "^1.3.2"
-    semver "^5.4.1"
+    gensync "^1.0.0-beta.2"
+    json5 "^2.1.2"
+    semver "^6.3.0"
     source-map "^0.5.0"
 
-"@babel/generator@^7.0.0-beta.42", "@babel/generator@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.3.tgz#0e22c005b0a94c1c74eafe19ef78ce53a4d45c03"
-  integrity sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug==
+"@babel/generator@^7.0.0-beta.42", "@babel/generator@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.15.0.tgz#a7d0c172e0d814974bad5aa77ace543b97917f15"
+  integrity sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==
   dependencies:
-    "@babel/types" "^7.8.3"
+    "@babel/types" "^7.15.0"
     jsesc "^2.5.1"
-    lodash "^4.17.13"
     source-map "^0.5.0"
 
-"@babel/helper-annotate-as-pure@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee"
-  integrity sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==
+"@babel/helper-annotate-as-pure@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz#7bf478ec3b71726d56a8ca5775b046fc29879e61"
+  integrity sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==
   dependencies:
-    "@babel/types" "^7.8.3"
+    "@babel/types" "^7.14.5"
 
-"@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503"
-  integrity sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==
+"@babel/helper-builder-binary-assignment-operator-visitor@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.14.5.tgz#b939b43f8c37765443a19ae74ad8b15978e0a191"
+  integrity sha512-YTA/Twn0vBXDVGJuAX6PwW7x5zQei1luDDo2Pl6q1qZ7hVNl0RZrhHCQG/ArGpR29Vl7ETiB8eJyrvpuRp300w==
   dependencies:
-    "@babel/helper-explode-assignable-expression" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/helper-explode-assignable-expression" "^7.14.5"
+    "@babel/types" "^7.14.5"
 
-"@babel/helper-call-delegate@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.8.3.tgz#de82619898aa605d409c42be6ffb8d7204579692"
-  integrity sha512-6Q05px0Eb+N4/GTyKPPvnkig7Lylw+QzihMpws9iiZQv7ZImf84ZsZpQH7QoWN4n4tm81SnSzPgHw2qtO0Zf3A==
+"@babel/helper-compilation-targets@^7.14.5", "@babel/helper-compilation-targets@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.0.tgz#973df8cbd025515f3ff25db0c05efc704fa79818"
+  integrity sha512-h+/9t0ncd4jfZ8wsdAsoIxSa61qhBYlycXiHWqJaQBCXAhDCMbPRSMTGnZIkkmt1u4ag+UQmuqcILwqKzZ4N2A==
   dependencies:
-    "@babel/helper-hoist-variables" "^7.8.3"
-    "@babel/traverse" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/compat-data" "^7.15.0"
+    "@babel/helper-validator-option" "^7.14.5"
+    browserslist "^4.16.6"
+    semver "^6.3.0"
 
-"@babel/helper-create-regexp-features-plugin@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.3.tgz#c774268c95ec07ee92476a3862b75cc2839beb79"
-  integrity sha512-Gcsm1OHCUr9o9TcJln57xhWHtdXbA2pgQ58S0Lxlks0WMGNXuki4+GLfX0p+L2ZkINUGZvfkz8rzoqJQSthI+Q==
+"@babel/helper-create-regexp-features-plugin@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz#c7d5ac5e9cf621c26057722fb7a8a4c5889358c4"
+  integrity sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A==
   dependencies:
-    "@babel/helper-regex" "^7.8.3"
-    regexpu-core "^4.6.0"
+    "@babel/helper-annotate-as-pure" "^7.14.5"
+    regexpu-core "^4.7.1"
 
-"@babel/helper-define-map@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz#a0655cad5451c3760b726eba875f1cd8faa02c15"
-  integrity sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==
+"@babel/helper-explode-assignable-expression@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.14.5.tgz#8aa72e708205c7bb643e45c73b4386cdf2a1f645"
+  integrity sha512-Htb24gnGJdIGT4vnRKMdoXiOIlqOLmdiUYpAQ0mYfgVT/GDm8GOYhgi4GL+hMKrkiPRohO4ts34ELFsGAPQLDQ==
   dependencies:
-    "@babel/helper-function-name" "^7.8.3"
-    "@babel/types" "^7.8.3"
-    lodash "^4.17.13"
+    "@babel/types" "^7.14.5"
 
-"@babel/helper-explode-assignable-expression@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982"
-  integrity sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==
+"@babel/helper-function-name@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz#89e2c474972f15d8e233b52ee8c480e2cfcd50c4"
+  integrity sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==
   dependencies:
-    "@babel/traverse" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/helper-get-function-arity" "^7.14.5"
+    "@babel/template" "^7.14.5"
+    "@babel/types" "^7.14.5"
 
-"@babel/helper-function-name@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca"
-  integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==
+"@babel/helper-get-function-arity@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz#25fbfa579b0937eee1f3b805ece4ce398c431815"
+  integrity sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==
   dependencies:
-    "@babel/helper-get-function-arity" "^7.8.3"
-    "@babel/template" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/types" "^7.14.5"
 
-"@babel/helper-get-function-arity@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5"
-  integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==
+"@babel/helper-hoist-variables@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz#e0dd27c33a78e577d7c8884916a3e7ef1f7c7f8d"
+  integrity sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==
   dependencies:
-    "@babel/types" "^7.8.3"
+    "@babel/types" "^7.14.5"
 
-"@babel/helper-hoist-variables@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134"
-  integrity sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==
+"@babel/helper-member-expression-to-functions@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.0.tgz#0ddaf5299c8179f27f37327936553e9bba60990b"
+  integrity sha512-Jq8H8U2kYiafuj2xMTPQwkTBnEEdGKpT35lJEQsRRjnG0LW3neucsaMWLgKcwu3OHKNeYugfw+Z20BXBSEs2Lg==
   dependencies:
-    "@babel/types" "^7.8.3"
+    "@babel/types" "^7.15.0"
 
-"@babel/helper-member-expression-to-functions@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c"
-  integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==
+"@babel/helper-module-imports@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz#6d1a44df6a38c957aa7c312da076429f11b422f3"
+  integrity sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==
   dependencies:
-    "@babel/types" "^7.8.3"
+    "@babel/types" "^7.14.5"
 
-"@babel/helper-module-imports@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498"
-  integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==
+"@babel/helper-module-transforms@^7.14.5", "@babel/helper-module-transforms@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.15.0.tgz#679275581ea056373eddbe360e1419ef23783b08"
+  integrity sha512-RkGiW5Rer7fpXv9m1B3iHIFDZdItnO2/BLfWVW/9q7+KqQSDY5kUfQEbzdXM1MVhJGcugKV7kRrNVzNxmk7NBg==
   dependencies:
-    "@babel/types" "^7.8.3"
+    "@babel/helper-module-imports" "^7.14.5"
+    "@babel/helper-replace-supers" "^7.15.0"
+    "@babel/helper-simple-access" "^7.14.8"
+    "@babel/helper-split-export-declaration" "^7.14.5"
+    "@babel/helper-validator-identifier" "^7.14.9"
+    "@babel/template" "^7.14.5"
+    "@babel/traverse" "^7.15.0"
+    "@babel/types" "^7.15.0"
 
-"@babel/helper-module-transforms@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.8.3.tgz#d305e35d02bee720fbc2c3c3623aa0c316c01590"
-  integrity sha512-C7NG6B7vfBa/pwCOshpMbOYUmrYQDfCpVL/JCRu0ek8B5p8kue1+BCXpg2vOYs7w5ACB9GTOBYQ5U6NwrMg+3Q==
+"@babel/helper-optimise-call-expression@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz#f27395a8619e0665b3f0364cddb41c25d71b499c"
+  integrity sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==
   dependencies:
-    "@babel/helper-module-imports" "^7.8.3"
-    "@babel/helper-simple-access" "^7.8.3"
-    "@babel/helper-split-export-declaration" "^7.8.3"
-    "@babel/template" "^7.8.3"
-    "@babel/types" "^7.8.3"
-    lodash "^4.17.13"
+    "@babel/types" "^7.14.5"
 
-"@babel/helper-optimise-call-expression@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9"
-  integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==
+"@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.8.0":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9"
+  integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==
+
+"@babel/helper-remap-async-to-generator@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.14.5.tgz#51439c913612958f54a987a4ffc9ee587a2045d6"
+  integrity sha512-rLQKdQU+HYlxBwQIj8dk4/0ENOUEhA/Z0l4hN8BexpvmSMN9oA9EagjnhnDpNsRdWCfjwa4mn/HyBXO9yhQP6A==
   dependencies:
-    "@babel/types" "^7.8.3"
+    "@babel/helper-annotate-as-pure" "^7.14.5"
+    "@babel/helper-wrap-function" "^7.14.5"
+    "@babel/types" "^7.14.5"
 
-"@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670"
-  integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==
-
-"@babel/helper-regex@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965"
-  integrity sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==
+"@babel/helper-replace-supers@^7.14.5", "@babel/helper-replace-supers@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.15.0.tgz#ace07708f5bf746bf2e6ba99572cce79b5d4e7f4"
+  integrity sha512-6O+eWrhx+HEra/uJnifCwhwMd6Bp5+ZfZeJwbqUTuqkhIT6YcRhiZCOOFChRypOIe0cV46kFrRBlm+t5vHCEaA==
   dependencies:
-    lodash "^4.17.13"
+    "@babel/helper-member-expression-to-functions" "^7.15.0"
+    "@babel/helper-optimise-call-expression" "^7.14.5"
+    "@babel/traverse" "^7.15.0"
+    "@babel/types" "^7.15.0"
 
-"@babel/helper-remap-async-to-generator@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86"
-  integrity sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==
+"@babel/helper-simple-access@^7.14.8":
+  version "7.14.8"
+  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz#82e1fec0644a7e775c74d305f212c39f8fe73924"
+  integrity sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.8.3"
-    "@babel/helper-wrap-function" "^7.8.3"
-    "@babel/template" "^7.8.3"
-    "@babel/traverse" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/types" "^7.14.8"
 
-"@babel/helper-replace-supers@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.3.tgz#91192d25f6abbcd41da8a989d4492574fb1530bc"
-  integrity sha512-xOUssL6ho41U81etpLoT2RTdvdus4VfHamCuAm4AHxGr+0it5fnwoVdwUJ7GFEqCsQYzJUhcbsN9wB9apcYKFA==
+"@babel/helper-skip-transparent-expression-wrappers@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.14.5.tgz#96f486ac050ca9f44b009fbe5b7d394cab3a0ee4"
+  integrity sha512-dmqZB7mrb94PZSAOYtr+ZN5qt5owZIAgqtoTuqiFbHFtxgEcmQlRJVI+bO++fciBunXtB6MK7HrzrfcAzIz2NQ==
   dependencies:
-    "@babel/helper-member-expression-to-functions" "^7.8.3"
-    "@babel/helper-optimise-call-expression" "^7.8.3"
-    "@babel/traverse" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/types" "^7.14.5"
 
-"@babel/helper-simple-access@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae"
-  integrity sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==
+"@babel/helper-split-export-declaration@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz#22b23a54ef51c2b7605d851930c1976dd0bc693a"
+  integrity sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==
   dependencies:
-    "@babel/template" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/types" "^7.14.5"
 
-"@babel/helper-split-export-declaration@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9"
-  integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==
-  dependencies:
-    "@babel/types" "^7.8.3"
+"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9":
+  version "7.14.9"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48"
+  integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==
 
-"@babel/helper-wrap-function@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610"
-  integrity sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==
-  dependencies:
-    "@babel/helper-function-name" "^7.8.3"
-    "@babel/template" "^7.8.3"
-    "@babel/traverse" "^7.8.3"
-    "@babel/types" "^7.8.3"
+"@babel/helper-validator-option@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3"
+  integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==
 
-"@babel/helpers@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.8.3.tgz#382fbb0382ce7c4ce905945ab9641d688336ce85"
-  integrity sha512-LmU3q9Pah/XyZU89QvBgGt+BCsTPoQa+73RxAQh8fb8qkDyIfeQnmgs+hvzhTCKTzqOyk7JTkS3MS1S8Mq5yrQ==
+"@babel/helper-wrap-function@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.14.5.tgz#5919d115bf0fe328b8a5d63bcb610f51601f2bff"
+  integrity sha512-YEdjTCq+LNuNS1WfxsDCNpgXkJaIyqco6DAelTUjT4f2KIWC1nBcaCaSdHTBqQVLnTBexBcVcFhLSU1KnYuePQ==
   dependencies:
-    "@babel/template" "^7.8.3"
-    "@babel/traverse" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/helper-function-name" "^7.14.5"
+    "@babel/template" "^7.14.5"
+    "@babel/traverse" "^7.14.5"
+    "@babel/types" "^7.14.5"
 
-"@babel/highlight@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797"
-  integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==
+"@babel/helpers@^7.14.8":
+  version "7.15.3"
+  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.15.3.tgz#c96838b752b95dcd525b4e741ed40bb1dc2a1357"
+  integrity sha512-HwJiz52XaS96lX+28Tnbu31VeFSQJGOeKHJeaEPQlTl7PnlhFElWPj8tUXtqFIzeN86XxXoBr+WFAyK2PPVz6g==
   dependencies:
+    "@babel/template" "^7.14.5"
+    "@babel/traverse" "^7.15.0"
+    "@babel/types" "^7.15.0"
+
+"@babel/highlight@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9"
+  integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==
+  dependencies:
+    "@babel/helper-validator-identifier" "^7.14.5"
     chalk "^2.0.0"
-    esutils "^2.0.2"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.3.tgz#790874091d2001c9be6ec426c2eed47bc7679081"
-  integrity sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==
+"@babel/parser@^7.14.5", "@babel/parser@^7.15.0":
+  version "7.15.3"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.3.tgz#3416d9bea748052cfcb63dbcc27368105b1ed862"
+  integrity sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==
 
 "@babel/plugin-external-helpers@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-external-helpers/-/plugin-external-helpers-7.8.3.tgz#5a94164d9af393b2820a3cdc407e28ebf237de4b"
-  integrity sha512-mx0WXDDiIl5DwzMtzWGRSPugXi9BxROS05GQrhLNbEamhBiicgn994ibwkyiBH+6png7bm/yA7AUsvHyCXi4Vw==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-external-helpers/-/plugin-external-helpers-7.14.5.tgz#920baa1569a8df5d5710abc342c7b1ac8968ed76"
+  integrity sha512-q/B/hLX+nDGk73Xn529d7Ar4ih17J8pNBbsXafq8oXij0XfFEA/bks+u+6q5q04zO5o/qivjzui6BqzPfYShEg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
 "@babel/plugin-proposal-async-generator-functions@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f"
-  integrity sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==
+  version "7.14.9"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.9.tgz#7028dc4fa21dc199bbacf98b39bab1267d0eaf9a"
+  integrity sha512-d1lnh+ZnKrFKwtTYdw320+sQWCTwgkB9fmUhNXRADA4akR6wLjaruSGnIEUjpt9HCOwTr4ynFTKu19b7rFRpmw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
-    "@babel/helper-remap-async-to-generator" "^7.8.3"
-    "@babel/plugin-syntax-async-generators" "^7.8.0"
+    "@babel/helper-plugin-utils" "^7.14.5"
+    "@babel/helper-remap-async-to-generator" "^7.14.5"
+    "@babel/plugin-syntax-async-generators" "^7.8.4"
 
 "@babel/plugin-proposal-object-rest-spread@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.8.3.tgz#eb5ae366118ddca67bed583b53d7554cad9951bb"
-  integrity sha512-8qvuPwU/xxUCt78HocNlv0mXXo0wdh9VT1R04WU8HGOfaOob26pF+9P5/lYjN/q7DHOX1bvX60hnhOvuQUJdbA==
+  version "7.14.7"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.7.tgz#5920a2b3df7f7901df0205974c0641b13fd9d363"
+  integrity sha512-082hsZz+sVabfmDWo1Oct1u1AgbKbUAyVgmX4otIc7bdsRgHBXwTwb3DpDmD4Eyyx6DNiuz5UAATT655k+kL5g==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
-    "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
+    "@babel/compat-data" "^7.14.7"
+    "@babel/helper-compilation-targets" "^7.14.5"
+    "@babel/helper-plugin-utils" "^7.14.5"
+    "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+    "@babel/plugin-transform-parameters" "^7.14.5"
 
-"@babel/plugin-syntax-async-generators@^7.0.0", "@babel/plugin-syntax-async-generators@^7.8.0":
+"@babel/plugin-syntax-async-generators@^7.0.0", "@babel/plugin-syntax-async-generators@^7.8.4":
   version "7.8.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
   integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==
@@ -265,13 +272,13 @@
     "@babel/helper-plugin-utils" "^7.8.0"
 
 "@babel/plugin-syntax-import-meta@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.8.3.tgz#230afff79d3ccc215b5944b438e4e266daf3d84d"
-  integrity sha512-vYiGd4wQ9gx0Lngb7+bPCwQXGK/PR6FeTIJ+TIOlq+OfOKG/kCAOO2+IBac3oMM9qV7/fU76hfcqxUaLKZf1hQ==
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51"
+  integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.8.0":
+"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3":
   version "7.8.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
   integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==
@@ -279,234 +286,244 @@
     "@babel/helper-plugin-utils" "^7.8.0"
 
 "@babel/plugin-transform-arrow-functions@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6"
-  integrity sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.14.5.tgz#f7187d9588a768dd080bf4c9ffe117ea62f7862a"
+  integrity sha512-KOnO0l4+tD5IfOdi4x8C1XmEIRWUjNRV8wc6K2vz/3e8yAOoZZvsRXRRIF/yo/MAOFb4QjtAw9xSxMXbSMRy8A==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
 "@babel/plugin-transform-async-to-generator@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086"
-  integrity sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.14.5.tgz#72c789084d8f2094acb945633943ef8443d39e67"
+  integrity sha512-szkbzQ0mNk0rpu76fzDdqSyPu0MuvpXgC+6rz5rpMb5OIRxdmHfQxrktL8CYolL2d8luMCZTR0DpIMIdL27IjA==
   dependencies:
-    "@babel/helper-module-imports" "^7.8.3"
-    "@babel/helper-plugin-utils" "^7.8.3"
-    "@babel/helper-remap-async-to-generator" "^7.8.3"
+    "@babel/helper-module-imports" "^7.14.5"
+    "@babel/helper-plugin-utils" "^7.14.5"
+    "@babel/helper-remap-async-to-generator" "^7.14.5"
 
 "@babel/plugin-transform-block-scoped-functions@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3"
-  integrity sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.14.5.tgz#e48641d999d4bc157a67ef336aeb54bc44fd3ad4"
+  integrity sha512-dtqWqdWZ5NqBX3KzsVCWfQI3A53Ft5pWFCT2eCVUftWZgjc5DpDponbIF1+c+7cSGk2wN0YK7HGL/ezfRbpKBQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
 "@babel/plugin-transform-block-scoping@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a"
-  integrity sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==
+  version "7.15.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.15.3.tgz#94c81a6e2fc230bcce6ef537ac96a1e4d2b3afaf"
+  integrity sha512-nBAzfZwZb4DkaGtOes1Up1nOAp9TDRRFw4XBzBBSG9QK7KVFmYzgj9o9sbPv7TX5ofL4Auq4wZnxCoPnI/lz2Q==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
-    lodash "^4.17.13"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
 "@babel/plugin-transform-classes@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.3.tgz#46fd7a9d2bb9ea89ce88720477979fe0d71b21b8"
-  integrity sha512-SjT0cwFJ+7Rbr1vQsvphAHwUHvSUPmMjMU/0P59G8U2HLFqSa082JO7zkbDNWs9kH/IUqpHI6xWNesGf8haF1w==
+  version "7.14.9"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.9.tgz#2a391ffb1e5292710b00f2e2c210e1435e7d449f"
+  integrity sha512-NfZpTcxU3foGWbl4wxmZ35mTsYJy8oQocbeIMoDAGGFarAmSQlL+LWMkDx/tj6pNotpbX3rltIA4dprgAPOq5A==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.8.3"
-    "@babel/helper-define-map" "^7.8.3"
-    "@babel/helper-function-name" "^7.8.3"
-    "@babel/helper-optimise-call-expression" "^7.8.3"
-    "@babel/helper-plugin-utils" "^7.8.3"
-    "@babel/helper-replace-supers" "^7.8.3"
-    "@babel/helper-split-export-declaration" "^7.8.3"
+    "@babel/helper-annotate-as-pure" "^7.14.5"
+    "@babel/helper-function-name" "^7.14.5"
+    "@babel/helper-optimise-call-expression" "^7.14.5"
+    "@babel/helper-plugin-utils" "^7.14.5"
+    "@babel/helper-replace-supers" "^7.14.5"
+    "@babel/helper-split-export-declaration" "^7.14.5"
     globals "^11.1.0"
 
 "@babel/plugin-transform-computed-properties@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b"
-  integrity sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.14.5.tgz#1b9d78987420d11223d41195461cc43b974b204f"
+  integrity sha512-pWM+E4283UxaVzLb8UBXv4EIxMovU4zxT1OPnpHJcmnvyY9QbPPTKZfEj31EUvG3/EQRbYAGaYEUZ4yWOBC2xg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
 "@babel/plugin-transform-destructuring@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.3.tgz#20ddfbd9e4676906b1056ee60af88590cc7aaa0b"
-  integrity sha512-H4X646nCkiEcHZUZaRkhE2XVsoz0J/1x3VVujnn96pSoGCtKPA99ZZA+va+gK+92Zycd6OBKCD8tDb/731bhgQ==
+  version "7.14.7"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz#0ad58ed37e23e22084d109f185260835e5557576"
+  integrity sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
 "@babel/plugin-transform-duplicate-keys@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1"
-  integrity sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.14.5.tgz#365a4844881bdf1501e3a9f0270e7f0f91177954"
+  integrity sha512-iJjbI53huKbPDAsJ8EmVmvCKeeq21bAze4fu9GBQtSLqfvzj2oRuHVx4ZkDwEhg1htQ+5OBZh/Ab0XDf5iBZ7A==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
 "@babel/plugin-transform-exponentiation-operator@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7"
-  integrity sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.14.5.tgz#5154b8dd6a3dfe6d90923d61724bd3deeb90b493"
+  integrity sha512-jFazJhMBc9D27o9jDnIE5ZErI0R0m7PbKXVq77FFvqFbzvTMuv8jaAwLZ5PviOLSFttqKIW0/wxNSDbjLk0tYA==
   dependencies:
-    "@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3"
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-builder-binary-assignment-operator-visitor" "^7.14.5"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
 "@babel/plugin-transform-for-of@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.3.tgz#15f17bce2fc95c7d59a24b299e83e81cedc22e18"
-  integrity sha512-ZjXznLNTxhpf4Q5q3x1NsngzGA38t9naWH8Gt+0qYZEJAcvPI9waSStSh56u19Ofjr7QmD0wUsQ8hw8s/p1VnA==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.14.5.tgz#dae384613de8f77c196a8869cbf602a44f7fc0eb"
+  integrity sha512-CfmqxSUZzBl0rSjpoQSFoR9UEj3HzbGuGNL21/iFTmjb5gFggJp3ph0xR1YBhexmLoKRHzgxuFvty2xdSt6gTA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
 "@babel/plugin-transform-function-name@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b"
-  integrity sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.14.5.tgz#e81c65ecb900746d7f31802f6bed1f52d915d6f2"
+  integrity sha512-vbO6kv0fIzZ1GpmGQuvbwwm+O4Cbm2NrPzwlup9+/3fdkuzo1YqOZcXw26+YUJB84Ja7j9yURWposEHLYwxUfQ==
   dependencies:
-    "@babel/helper-function-name" "^7.8.3"
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-function-name" "^7.14.5"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
 "@babel/plugin-transform-instanceof@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-instanceof/-/plugin-transform-instanceof-7.8.3.tgz#a44d7d71590da36be7429573300618aefd784c3d"
-  integrity sha512-c/jB6Ebe2u17hxo+rce6PDgbkuHyfcJOleqgHYttnvMrCsxVwUnYsMq7GhxXekzUQsv9IImhv6YICKihpen+Ag==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-instanceof/-/plugin-transform-instanceof-7.14.5.tgz#8568277fbcfd7a3e4f3e6c8b7aa8ce4f60cba6e7"
+  integrity sha512-3CIpRzBLk5tEwIzjjD86KR8oMYrp1fl9q7kbdJa6O6Lcmkcee9DXfeO6zRXis//5gWRf63o5oDlNBh0VAlmtgw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
 "@babel/plugin-transform-literals@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1"
-  integrity sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.14.5.tgz#41d06c7ff5d4d09e3cf4587bd3ecf3930c730f78"
+  integrity sha512-ql33+epql2F49bi8aHXxvLURHkxJbSmMKl9J5yHqg4PLtdE6Uc48CH1GS6TQvZ86eoB/ApZXwm7jlA+B3kra7A==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
 "@babel/plugin-transform-modules-amd@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.8.3.tgz#65606d44616b50225e76f5578f33c568a0b876a5"
-  integrity sha512-MadJiU3rLKclzT5kBH4yxdry96odTUwuqrZM+GllFI/VhxfPz+k9MshJM+MwhfkCdxxclSbSBbUGciBngR+kEQ==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.5.tgz#4fd9ce7e3411cb8b83848480b7041d83004858f7"
+  integrity sha512-3lpOU8Vxmp3roC4vzFpSdEpGUWSMsHFreTWOMMLzel2gNGfHE5UWIh/LN6ghHs2xurUp4jRFYMUIZhuFbody1g==
   dependencies:
-    "@babel/helper-module-transforms" "^7.8.3"
-    "@babel/helper-plugin-utils" "^7.8.3"
-    babel-plugin-dynamic-import-node "^2.3.0"
+    "@babel/helper-module-transforms" "^7.14.5"
+    "@babel/helper-plugin-utils" "^7.14.5"
+    babel-plugin-dynamic-import-node "^2.3.3"
 
 "@babel/plugin-transform-object-super@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725"
-  integrity sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.14.5.tgz#d0b5faeac9e98597a161a9cf78c527ed934cdc45"
+  integrity sha512-MKfOBWzK0pZIrav9z/hkRqIk/2bTv9qvxHzPQc12RcVkMOzpIKnFCNYJip00ssKWYkd8Sf5g0Wr7pqJ+cmtuFg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
-    "@babel/helper-replace-supers" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.14.5"
+    "@babel/helper-replace-supers" "^7.14.5"
 
-"@babel/plugin-transform-parameters@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.3.tgz#7890576a13b17325d8b7d44cb37f21dc3bbdda59"
-  integrity sha512-/pqngtGb54JwMBZ6S/D3XYylQDFtGjWrnoCF4gXZOUpFV/ujbxnoNGNvDGu6doFWRPBveE72qTx/RRU44j5I/Q==
+"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.14.5.tgz#49662e86a1f3ddccac6363a7dfb1ff0a158afeb3"
+  integrity sha512-Tl7LWdr6HUxTmzQtzuU14SqbgrSKmaR77M0OKyq4njZLQTPfOvzblNKyNkGwOfEFCEx7KeYHQHDI0P3F02IVkA==
   dependencies:
-    "@babel/helper-call-delegate" "^7.8.3"
-    "@babel/helper-get-function-arity" "^7.8.3"
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
 "@babel/plugin-transform-regenerator@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.3.tgz#b31031e8059c07495bf23614c97f3d9698bc6ec8"
-  integrity sha512-qt/kcur/FxrQrzFR432FGZznkVAjiyFtCOANjkAKwCbt465L6ZCiUQh2oMYGU3Wo8LRFJxNDFwWn106S5wVUNA==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.14.5.tgz#9676fd5707ed28f522727c5b3c0aa8544440b04f"
+  integrity sha512-NVIY1W3ITDP5xQl50NgTKlZ0GrotKtLna08/uGY6ErQt6VEQZXla86x/CTddm5gZdcr+5GSsvMeTmWA5Ii6pkg==
   dependencies:
-    regenerator-transform "^0.14.0"
+    regenerator-transform "^0.14.2"
 
 "@babel/plugin-transform-shorthand-properties@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8"
-  integrity sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz#97f13855f1409338d8cadcbaca670ad79e091a58"
+  integrity sha512-xLucks6T1VmGsTB+GWK5Pl9Jl5+nRXD1uoFdA5TSO6xtiNjtXTjKkmPdFXVLGlK5A2/or/wQMKfmQ2Y0XJfn5g==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
 "@babel/plugin-transform-spread@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8"
-  integrity sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==
+  version "7.14.6"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.6.tgz#6bd40e57fe7de94aa904851963b5616652f73144"
+  integrity sha512-Zr0x0YroFJku7n7+/HH3A2eIrGMjbmAIbJSVv0IZ+t3U2WUQUA64S/oeied2e+MaGSjmt4alzBCsK9E8gh+fag==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.14.5"
+    "@babel/helper-skip-transparent-expression-wrappers" "^7.14.5"
 
 "@babel/plugin-transform-sticky-regex@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100"
-  integrity sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.14.5.tgz#5b617542675e8b7761294381f3c28c633f40aeb9"
+  integrity sha512-Z7F7GyvEMzIIbwnziAZmnSNpdijdr4dWt+FJNBnBLz5mwDFkqIXU9wmBcWWad3QeJF5hMTkRe4dAq2sUZiG+8A==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
-    "@babel/helper-regex" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
 "@babel/plugin-transform-template-literals@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80"
-  integrity sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.14.5.tgz#a5f2bc233937d8453885dc736bdd8d9ffabf3d93"
+  integrity sha512-22btZeURqiepOfuy/VkFr+zStqlujWaarpMErvay7goJS6BWwdd6BY9zQyDLDa4x2S3VugxFb162IZ4m/S/+Gg==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.8.3"
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
 "@babel/plugin-transform-typeof-symbol@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.3.tgz#5cffb216fb25c8c64ba6bf5f76ce49d3ab079f4d"
-  integrity sha512-3TrkKd4LPqm4jHs6nPtSDI/SV9Cm5PRJkHLUgTcqRQQTMAZ44ZaAdDZJtvWFSaRcvT0a1rTmJ5ZA5tDKjleF3g==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.14.5.tgz#39af2739e989a2bd291bf6b53f16981423d457d4"
+  integrity sha512-lXzLD30ffCWseTbMQzrvDWqljvZlHkXU+CnseMhkMNqU1sASnCsz3tSzAaH3vCUXb9PHeUb90ZT1BdFTm1xxJw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
 "@babel/plugin-transform-unicode-regex@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad"
-  integrity sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.14.5.tgz#4cd09b6c8425dd81255c7ceb3fb1836e7414382e"
+  integrity sha512-UygduJpC5kHeCiRw/xDVzC+wj8VaYSoKl5JNVmbP7MadpNinAm3SvZCxZ42H37KZBKztz46YC73i9yV34d0Tzw==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.8.3"
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-create-regexp-features-plugin" "^7.14.5"
+    "@babel/helper-plugin-utils" "^7.14.5"
 
-"@babel/template@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8"
-  integrity sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==
+"@babel/runtime@^7.8.4":
+  version "7.15.3"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.3.tgz#2e1c2880ca118e5b2f9988322bd8a7656a32502b"
+  integrity sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==
   dependencies:
-    "@babel/code-frame" "^7.8.3"
-    "@babel/parser" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    regenerator-runtime "^0.13.4"
 
-"@babel/traverse@^7.0.0", "@babel/traverse@^7.0.0-beta.42", "@babel/traverse@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.3.tgz#a826215b011c9b4f73f3a893afbc05151358bf9a"
-  integrity sha512-we+a2lti+eEImHmEXp7bM9cTxGzxPmBiVJlLVD+FuuQMeeO7RaDbutbgeheDkw+Xe3mCfJHnGOWLswT74m2IPg==
+"@babel/template@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4"
+  integrity sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==
   dependencies:
-    "@babel/code-frame" "^7.8.3"
-    "@babel/generator" "^7.8.3"
-    "@babel/helper-function-name" "^7.8.3"
-    "@babel/helper-split-export-declaration" "^7.8.3"
-    "@babel/parser" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/code-frame" "^7.14.5"
+    "@babel/parser" "^7.14.5"
+    "@babel/types" "^7.14.5"
+
+"@babel/traverse@^7.0.0", "@babel/traverse@^7.0.0-beta.42", "@babel/traverse@^7.14.5", "@babel/traverse@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.15.0.tgz#4cca838fd1b2a03283c1f38e141f639d60b3fc98"
+  integrity sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==
+  dependencies:
+    "@babel/code-frame" "^7.14.5"
+    "@babel/generator" "^7.15.0"
+    "@babel/helper-function-name" "^7.14.5"
+    "@babel/helper-hoist-variables" "^7.14.5"
+    "@babel/helper-split-export-declaration" "^7.14.5"
+    "@babel/parser" "^7.15.0"
+    "@babel/types" "^7.15.0"
     debug "^4.1.0"
     globals "^11.1.0"
-    lodash "^4.17.13"
 
-"@babel/types@^7.0.0-beta.42", "@babel/types@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c"
-  integrity sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==
+"@babel/types@^7.0.0-beta.42", "@babel/types@^7.14.5", "@babel/types@^7.14.8", "@babel/types@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.0.tgz#61af11f2286c4e9c69ca8deb5f4375a73c72dcbd"
+  integrity sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==
   dependencies:
-    esutils "^2.0.2"
-    lodash "^4.17.13"
+    "@babel/helper-validator-identifier" "^7.14.9"
     to-fast-properties "^2.0.0"
 
 "@bazel/rollup@^3.5.0":
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/@bazel/rollup/-/rollup-3.5.0.tgz#3de2db08cbc62c3cffbbabaa4517ec250cf6419a"
-  integrity sha512-sFPqbzSbIn6h66uuZdXgK5oitSmEGtnDPfL3TwTS4ZWy75SpYvk9X1TFGlvkralEkVnFfdH15sq80/1t+YgQow==
+  version "3.8.0"
+  resolved "https://registry.yarnpkg.com/@bazel/rollup/-/rollup-3.8.0.tgz#850f56176d73e3b7d99a43c7e33df21ecc6ac161"
+  integrity sha512-u63ubqYtfQhOu8Km3uYdhKa6qiLSlOKYsWwMP1xGkkXzu1hOiUznN1N7q8gCF1BV2DMy1D5IYkv+Xg4a+LEiBA==
 
 "@bazel/typescript@^3.5.0":
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-3.5.0.tgz#605493f4f0a5297df8a7fcccb86a1a80ea2090bb"
-  integrity sha512-BtGFp4nYFkQTmnONCzomk7dkmOwaINBL3piq+lykBlcc6UxLe9iCAnZpOyPypB1ReN3k3SRNAa53x6oGScQxMg==
+  version "3.8.0"
+  resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-3.8.0.tgz#725d51a1c25e314a1d8cddb8b880ac05ba97acd4"
+  integrity sha512-4C1pLe4V7aidWqcPsWNqXFS7uHAB1nH5SUKG5uWoVv4JT9XhkNSvzzQIycMwXs2tZeCylX4KYNeNvfKrmkyFlw==
   dependencies:
     protobufjs "6.8.8"
     semver "5.6.0"
     source-map-support "0.5.9"
     tsutils "2.27.2"
 
+"@dabh/diagnostics@^2.0.2":
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.2.tgz#290d08f7b381b8f94607dc8f471a12c675f9db31"
+  integrity sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==
+  dependencies:
+    colorspace "1.1.x"
+    enabled "2.0.x"
+    kuler "^2.0.0"
+
 "@mrmlnc/readdir-enhanced@^2.2.1":
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
@@ -521,50 +538,85 @@
   integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
 
 "@octokit/auth-token@^2.4.0":
+  version "2.4.5"
+  resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.5.tgz#568ccfb8cb46f36441fac094ce34f7a875b197f3"
+  integrity sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==
+  dependencies:
+    "@octokit/types" "^6.0.3"
+
+"@octokit/endpoint@^6.0.1":
+  version "6.0.12"
+  resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658"
+  integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==
+  dependencies:
+    "@octokit/types" "^6.0.3"
+    is-plain-object "^5.0.0"
+    universal-user-agent "^6.0.0"
+
+"@octokit/openapi-types@^10.0.0":
+  version "10.0.0"
+  resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-10.0.0.tgz#db4335de99509021f501fc4e026e6ff495fe1e62"
+  integrity sha512-k1iO2zKuEjjRS1EJb4FwSLk+iF6EGp+ZV0OMRViQoWhQ1fZTk9hg1xccZII5uyYoiqcbC73MRBmT45y1vp2PPg==
+
+"@octokit/plugin-paginate-rest@^1.1.1":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-1.1.2.tgz#004170acf8c2be535aba26727867d692f7b488fc"
+  integrity sha512-jbsSoi5Q1pj63sC16XIUboklNw+8tL9VOnJsWycWYR78TKss5PVpIPb1TUUcMQ+bBh7cY579cVAWmf5qG+dw+Q==
+  dependencies:
+    "@octokit/types" "^2.0.1"
+
+"@octokit/plugin-request-log@^1.0.0":
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85"
+  integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==
+
+"@octokit/plugin-rest-endpoint-methods@2.4.0":
   version "2.4.0"
-  resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.0.tgz#b64178975218b99e4dfe948253f0673cbbb59d9f"
-  integrity sha512-eoOVMjILna7FVQf96iWc3+ZtE/ZT6y8ob8ZzcqKY1ibSQCnu4O/B7pJvzMx5cyZ/RjAff6DAdEb0O0Cjcxidkg==
+  resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-2.4.0.tgz#3288ecf5481f68c494dd0602fc15407a59faf61e"
+  integrity sha512-EZi/AWhtkdfAYi01obpX0DF7U6b1VRr30QNQ5xSFPITMdLSfhcBqjamE3F+sKcxPbD7eZuMHu3Qkk2V+JGxBDQ==
   dependencies:
-    "@octokit/types" "^2.0.0"
+    "@octokit/types" "^2.0.1"
+    deprecation "^2.3.1"
 
-"@octokit/endpoint@^5.5.0":
-  version "5.5.1"
-  resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-5.5.1.tgz#2eea81e110ca754ff2de11c79154ccab4ae16b3f"
-  integrity sha512-nBFhRUb5YzVTCX/iAK1MgQ4uWo89Gu0TH00qQHoYRCsE12dWcG1OiLd7v2EIo2+tpUKPMOQ62QFy9hy9Vg2ULg==
+"@octokit/request-error@^1.0.2":
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-1.2.1.tgz#ede0714c773f32347576c25649dc013ae6b31801"
+  integrity sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==
   dependencies:
     "@octokit/types" "^2.0.0"
-    is-plain-object "^3.0.0"
-    universal-user-agent "^4.0.0"
+    deprecation "^2.0.0"
+    once "^1.4.0"
 
-"@octokit/request-error@^1.0.1", "@octokit/request-error@^1.0.2":
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-1.2.0.tgz#a64d2a9d7a13555570cd79722de4a4d76371baaa"
-  integrity sha512-DNBhROBYjjV/I9n7A8kVkmQNkqFAMem90dSxqvPq57e2hBr7mNTX98y3R2zDpqMQHVRpBDjsvsfIGgBzy+4PAg==
+"@octokit/request-error@^2.1.0":
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677"
+  integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==
   dependencies:
-    "@octokit/types" "^2.0.0"
+    "@octokit/types" "^6.0.3"
     deprecation "^2.0.0"
     once "^1.4.0"
 
 "@octokit/request@^5.2.0":
-  version "5.3.1"
-  resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.3.1.tgz#3a1ace45e6f88b1be4749c5da963b3a3b4a2f120"
-  integrity sha512-5/X0AL1ZgoU32fAepTfEoggFinO3rxsMLtzhlUX+RctLrusn/CApJuGFCd0v7GMFhF+8UiCsTTfsu7Fh1HnEJg==
+  version "5.6.1"
+  resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.1.tgz#f97aff075c37ab1d427c49082fefeef0dba2d8ce"
+  integrity sha512-Ls2cfs1OfXaOKzkcxnqw5MR6drMA/zWX/LIS/p8Yjdz7QKTPQLMsB3R+OvoxE6XnXeXEE2X7xe4G4l4X0gRiKQ==
   dependencies:
-    "@octokit/endpoint" "^5.5.0"
-    "@octokit/request-error" "^1.0.1"
-    "@octokit/types" "^2.0.0"
-    deprecation "^2.0.0"
-    is-plain-object "^3.0.0"
-    node-fetch "^2.3.0"
-    once "^1.4.0"
-    universal-user-agent "^4.0.0"
+    "@octokit/endpoint" "^6.0.1"
+    "@octokit/request-error" "^2.1.0"
+    "@octokit/types" "^6.16.1"
+    is-plain-object "^5.0.0"
+    node-fetch "^2.6.1"
+    universal-user-agent "^6.0.0"
 
 "@octokit/rest@^16.2.0":
-  version "16.38.3"
-  resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.38.3.tgz#d5d200f88962392f71e048e12833ea36f4e0d192"
-  integrity sha512-Ui5W4Gzv0YHe9P3KDZAuU/BkRrT88PCuuATfWBMBf4fux4nB8th8LlyVAVnHKba1s/q4umci+sNHzoFYFujPEg==
+  version "16.43.2"
+  resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.43.2.tgz#c53426f1e1d1044dee967023e3279c50993dd91b"
+  integrity sha512-ngDBevLbBTFfrHZeiS7SAMAZ6ssuVmXuya+F/7RaVvlysgGa1JKJkKWY+jV6TCJYcW0OALfJ7nTIGXcBXzycfQ==
   dependencies:
     "@octokit/auth-token" "^2.4.0"
+    "@octokit/plugin-paginate-rest" "^1.1.1"
+    "@octokit/plugin-request-log" "^1.0.0"
+    "@octokit/plugin-rest-endpoint-methods" "2.4.0"
     "@octokit/request" "^5.2.0"
     "@octokit/request-error" "^1.0.2"
     atob-lite "^2.0.0"
@@ -578,13 +630,20 @@
     once "^1.4.0"
     universal-user-agent "^4.0.0"
 
-"@octokit/types@^2.0.0":
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/@octokit/types/-/types-2.1.1.tgz#77e80d1b663c5f1f829e5377b728fa3c4fe5a97d"
-  integrity sha512-89LOYH+d/vsbDX785NOfLxTW88GjNd0lWRz1DVPVsZgg9Yett5O+3MOvwo7iHgvUwbFz0mf/yPIjBkUbs4kxoQ==
+"@octokit/types@^2.0.0", "@octokit/types@^2.0.1":
+  version "2.16.2"
+  resolved "https://registry.yarnpkg.com/@octokit/types/-/types-2.16.2.tgz#4c5f8da3c6fecf3da1811aef678fda03edac35d2"
+  integrity sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==
   dependencies:
     "@types/node" ">= 8"
 
+"@octokit/types@^6.0.3", "@octokit/types@^6.16.1":
+  version "6.26.0"
+  resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.26.0.tgz#b8af298485d064ad9424cb41520541c1bf820346"
+  integrity sha512-RDxZBAFMtqs1ZPnbUu1e7ohPNfoNhTiep4fErY7tZs995BeHu369Vsh5woMIaFbllRWEZBfvTCS4hvDnMPiHrA==
+  dependencies:
+    "@octokit/openapi-types" "^10.0.0"
+
 "@polymer/esm-amd-loader@^1.0.0":
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/@polymer/esm-amd-loader/-/esm-amd-loader-1.0.4.tgz#4e77f2f59b29b01e0ad02aa83d33716cddc5f9f9"
@@ -653,24 +712,36 @@
   resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
   integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
 
+"@sindresorhus/is@^4.0.0":
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.1.tgz#d26729db850fa327b7cacc5522252194404226f5"
+  integrity sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==
+
+"@szmarczak/http-timer@^4.0.5":
+  version "4.0.6"
+  resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807"
+  integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==
+  dependencies:
+    defer-to-connect "^2.0.0"
+
 "@types/babel-generator@^6.25.1":
-  version "6.25.3"
-  resolved "https://registry.yarnpkg.com/@types/babel-generator/-/babel-generator-6.25.3.tgz#8f06caa12d0595a0538560abe771966d77d29286"
-  integrity sha512-pGgnuxVddKcYIc+VJkRDop7gxLhqclNKBdlsm/5Vp8d+37pQkkDK7fef8d9YYImRzw9xcojEPc18pUYnbxmjqA==
+  version "6.25.4"
+  resolved "https://registry.yarnpkg.com/@types/babel-generator/-/babel-generator-6.25.4.tgz#74eacdaa4822c4c6923e68c541144a04415ad8a1"
+  integrity sha512-Rnsen+ckop5mbl9d43bempS7i9wdTN1vytiTlmQla/YiNm6kH8kEVABVSXmp1UbnpkUV44nUCPeDQoa+Mu7ALA==
   dependencies:
     "@types/babel-types" "*"
 
 "@types/babel-traverse@^6.25.2", "@types/babel-traverse@^6.25.3":
-  version "6.25.5"
-  resolved "https://registry.yarnpkg.com/@types/babel-traverse/-/babel-traverse-6.25.5.tgz#6d293cf7523e48b524faa7b86dc3c488191484e5"
-  integrity sha512-WrMbwmu+MWf8FiUMbmVOGkc7bHPzndUafn1CivMaBHthBBoo0VNIcYk1KV71UovYguhsNOwf3UF5oRmkkGOU3w==
+  version "6.25.7"
+  resolved "https://registry.yarnpkg.com/@types/babel-traverse/-/babel-traverse-6.25.7.tgz#bc75fce23d8394534562a36a32dec94a54d11835"
+  integrity sha512-BeQiEGLnVzypzBdsexEpZAHUx+WucOMXW6srEWDkl4SegBlaCy+iBvRO+4vz6EZ+BNQg22G4MCdDdvZxf+jW5A==
   dependencies:
     "@types/babel-types" "*"
 
 "@types/babel-types@*":
-  version "7.0.7"
-  resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-7.0.7.tgz#667eb1640e8039436028055737d2b9986ee336e3"
-  integrity sha512-dBtBbrc+qTHy1WdfHYjBwRln4+LWqASWakLHsWHR2NWHIFkv4W3O070IGoGLEBrJBvct3r0L1BUPuvURi7kYUQ==
+  version "7.0.11"
+  resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-7.0.11.tgz#263b113fa396fac4373188d73225297fb86f19a9"
+  integrity sha512-pkPtJUUY+Vwv6B1inAz55rQvivClHJxc9aVEPPmaq2cbyeMLCiDpbKpcKyX4LAwpNGi+SHBv0tHv6+0gXv0P2A==
 
 "@types/babel-types@^6.25.1":
   version "6.25.2"
@@ -678,25 +749,35 @@
   integrity sha512-+3bMuktcY4a70a0KZc8aPJlEOArPuAKQYHU5ErjkOqGJdx8xuEEVK6nWogqigBOJ8nKPxRpyCUDTCPmZ3bUxGA==
 
 "@types/babylon@^6.16.2":
-  version "6.16.5"
-  resolved "https://registry.yarnpkg.com/@types/babylon/-/babylon-6.16.5.tgz#1c5641db69eb8cdf378edd25b4be7754beeb48b4"
-  integrity sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==
+  version "6.16.6"
+  resolved "https://registry.yarnpkg.com/@types/babylon/-/babylon-6.16.6.tgz#a1e7e01567b26a5ebad321a74d10299189d8d932"
+  integrity sha512-G4yqdVlhr6YhzLXFKy5F7HtRBU8Y23+iWy7UKthMq/OSQnL1hbsoeXESQ2LY8zEDlknipDG3nRGhUC9tkwvy/w==
   dependencies:
     "@types/babel-types" "*"
 
 "@types/bluebird@*":
-  version "3.5.29"
-  resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.29.tgz#7cd933c902c4fc83046517a1bef973886d00bdb6"
-  integrity sha512-kmVtnxTuUuhCET669irqQmPAez4KFnFVKvpleVRyfC3g+SHD1hIkFZcWLim9BVcwUBLO59o8VZE4yGCmTif8Yw==
+  version "3.5.36"
+  resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.36.tgz#00d9301d4dc35c2f6465a8aec634bb533674c652"
+  integrity sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q==
 
 "@types/body-parser@*":
-  version "1.17.1"
-  resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.1.tgz#18fcf61768fb5c30ccc508c21d6fd2e8b3bf7897"
-  integrity sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==
+  version "1.19.1"
+  resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.1.tgz#0c0174c42a7d017b818303d4b5d969cb0b75929c"
+  integrity sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==
   dependencies:
     "@types/connect" "*"
     "@types/node" "*"
 
+"@types/cacheable-request@^6.0.1":
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9"
+  integrity sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==
+  dependencies:
+    "@types/http-cache-semantics" "*"
+    "@types/keyv" "*"
+    "@types/node" "*"
+    "@types/responselike" "*"
+
 "@types/chai-subset@^1.3.0":
   version "1.3.3"
   resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94"
@@ -705,9 +786,9 @@
     "@types/chai" "*"
 
 "@types/chai@*":
-  version "4.2.7"
-  resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.7.tgz#1c8c25cbf6e59ffa7d6b9652c78e547d9a41692d"
-  integrity sha512-luq8meHGYwvky0O7u0eQZdA7B4Wd9owUCqvbw2m3XCrCU8mplYOujMBbvyS547AxJkC+pGnd0Cm15eNxEUNU8g==
+  version "4.2.21"
+  resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.21.tgz#9f35a5643129df132cf3b5c1ec64046ea1af0650"
+  integrity sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg==
 
 "@types/chalk@^0.4.30":
   version "0.4.31"
@@ -722,22 +803,18 @@
     chalk "*"
 
 "@types/clean-css@*":
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/@types/clean-css/-/clean-css-4.2.1.tgz#cb0134241ec5e6ede1b5344bc829668fd9871a8d"
-  integrity sha512-A1HQhQ0hkvqqByJMgg+Wiv9p9XdoYEzuwm11SVo1mX2/4PSdhjcrUlilJQoqLscIheC51t1D5g+EFWCXZ2VTQQ==
+  version "4.2.5"
+  resolved "https://registry.yarnpkg.com/@types/clean-css/-/clean-css-4.2.5.tgz#69ce62cc13557c90ca40460133f672dc52ceaf89"
+  integrity sha512-NEzjkGGpbs9S9fgC4abuBvTpVwE3i+Acu9BBod3PUyjDVZcNsGx61b8r2PphR61QGPnn0JHVs5ey6/I4eTrkxw==
   dependencies:
     "@types/node" "*"
+    source-map "^0.6.0"
 
 "@types/clone@^0.1.29", "@types/clone@^0.1.30":
   version "0.1.30"
   resolved "https://registry.yarnpkg.com/@types/clone/-/clone-0.1.30.tgz#e7365648c1b42136a59c7d5040637b3b5c83b614"
   integrity sha1-5zZWSMG0ITalnH1QQGN7O1yDthQ=
 
-"@types/color-name@^1.1.1":
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
-  integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
-
 "@types/compression@^0.0.33":
   version "0.0.33"
   resolved "https://registry.yarnpkg.com/@types/compression/-/compression-0.0.33.tgz#95dc733a2339aa846381d7f1377792d2553dc27d"
@@ -746,21 +823,21 @@
     "@types/express" "*"
 
 "@types/connect@*":
-  version "3.4.33"
-  resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546"
-  integrity sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==
+  version "3.4.35"
+  resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"
+  integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==
   dependencies:
     "@types/node" "*"
 
 "@types/content-type@^1.1.0":
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/@types/content-type/-/content-type-1.1.3.tgz#3688bd77fc12f935548eef102a4e34c512b03a07"
-  integrity sha512-pv8VcFrZ3fN93L4rTNIbbUzdkzjEyVMp5mPVjsFfOYTDOZMZiZ8P1dhu+kEv3faYyKzZgLlSvnyQNFg+p/v5ug==
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/@types/content-type/-/content-type-1.1.5.tgz#aa02dca40864749a9e2bf0161a6216da57e3ede5"
+  integrity sha512-dgMN+syt1xb7Hk8LU6AODOfPlvz5z1CbXpPuJE5ZrX9STfBOIXF09pEB8N7a97WT9dbngt3ksDCm6GW6yMrxfQ==
 
 "@types/cssbeautify@^0.3.1":
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/@types/cssbeautify/-/cssbeautify-0.3.1.tgz#8e0bee8f7decb952250da0caebe05e30591c17ef"
-  integrity sha1-jgvuj33suVIlDaDK6+BeMFkcF+8=
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/@types/cssbeautify/-/cssbeautify-0.3.2.tgz#8a76207cd980d3e7b29b4b6dea1f4ed861285615"
+  integrity sha512-b3PXlFAcS4gvGr2pDz0NoZEBo3MMQe8Ozy6+Mvm3XIEcHS4oQstvCnnCofBZD/0tQgxSzkYbW+cD3yD4yaKTxQ==
 
 "@types/del@^3.0.0":
   version "3.0.1"
@@ -780,9 +857,9 @@
   integrity sha512-6dhZJLbA7aOwkYB2GDGdIqJ20wmHnkDzaxV9PJXe7O02I2dSFTERzRB6JrX6cWKaS+VqhhY7cQUMCbO5kloFUw==
 
 "@types/estree@*":
-  version "0.0.42"
-  resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.42.tgz#8d0c1f480339efedb3e46070e22dd63e0430dd11"
-  integrity sha512-K1DPVvnBCPxzD+G51/cxVIoc2X8uUVl1zpJeE6iKcgHMj4+tbat5Xu4TjV7v2QSDbIeAfLi2hIk+u2+s0MlpUQ==
+  version "0.0.50"
+  resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83"
+  integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==
 
 "@types/events@*":
   version "3.0.0"
@@ -794,21 +871,23 @@
   resolved "https://registry.yarnpkg.com/@types/expect/-/expect-1.20.4.tgz#8288e51737bf7e3ab5d7c77bfa695883745264e5"
   integrity sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==
 
-"@types/express-serve-static-core@*":
-  version "4.17.2"
-  resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz#f6f41fa35d42e79dbf6610eccbb2637e6008a0cf"
-  integrity sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==
+"@types/express-serve-static-core@^4.17.18":
+  version "4.17.24"
+  resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz#ea41f93bf7e0d59cd5a76665068ed6aab6815c07"
+  integrity sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==
   dependencies:
     "@types/node" "*"
+    "@types/qs" "*"
     "@types/range-parser" "*"
 
 "@types/express@*", "@types/express@^4.0.30", "@types/express@^4.0.36":
-  version "4.17.2"
-  resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.2.tgz#a0fb7a23d8855bac31bc01d5a58cadd9b2173e6c"
-  integrity sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==
+  version "4.17.13"
+  resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034"
+  integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==
   dependencies:
     "@types/body-parser" "*"
-    "@types/express-serve-static-core" "*"
+    "@types/express-serve-static-core" "^4.17.18"
+    "@types/qs" "*"
     "@types/serve-static" "*"
 
 "@types/fast-levenshtein@0.0.1":
@@ -831,24 +910,23 @@
     form-data "*"
 
 "@types/freeport@^1.0.19":
-  version "1.0.21"
-  resolved "https://registry.yarnpkg.com/@types/freeport/-/freeport-1.0.21.tgz#73f6543ed67d3ca3fff97b985591598b7092066f"
-  integrity sha1-c/ZUPtZ9PKP/+XuYVZFZi3CSBm8=
+  version "1.0.22"
+  resolved "https://registry.yarnpkg.com/@types/freeport/-/freeport-1.0.22.tgz#dbe627a20cb30c17c8aaaba09332e1d14cc2281f"
+  integrity sha512-UGg4s5PDPXZXkkrHarU1l6WDbULxN3g7xUEtdbNf9HQhU/JnCj1G1/xZHZmQjC0uWqN1LlB0R0xOlk3k5svgTQ==
 
 "@types/glob-stream@*":
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/@types/glob-stream/-/glob-stream-6.1.0.tgz#7ede8a33e59140534f8d8adfb8ac9edfb31897bc"
-  integrity sha512-RHv6ZQjcTncXo3thYZrsbAVwoy4vSKosSWhuhuQxLOTv74OJuFQxXkmUuZCr3q9uNBEVCvIzmZL/FeRNbHZGUg==
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/@types/glob-stream/-/glob-stream-6.1.1.tgz#c792d8d1514278ff03cad5689aba4c4ab4fbc805"
+  integrity sha512-AGOUTsTdbPkRS0qDeyeS+6KypmfVpbT5j23SN8UPG63qjKXNKjXn6V9wZUr8Fin0m9l8oGYaPK8b2WUMF8xI1A==
   dependencies:
     "@types/glob" "*"
     "@types/node" "*"
 
-"@types/glob@*":
-  version "7.1.1"
-  resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
-  integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
+"@types/glob@*", "@types/glob@^7.1.1":
+  version "7.1.4"
+  resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.4.tgz#ea59e21d2ee5c517914cb4bc8e4153b99e566672"
+  integrity sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA==
   dependencies:
-    "@types/events" "*"
     "@types/minimatch" "*"
     "@types/node" "*"
 
@@ -876,10 +954,15 @@
     "@types/relateurl" "*"
     "@types/uglify-js" "*"
 
+"@types/http-cache-semantics@*":
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812"
+  integrity sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==
+
 "@types/inquirer@*":
-  version "6.5.0"
-  resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-6.5.0.tgz#b83b0bf30b88b8be7246d40e51d32fe9d10e09be"
-  integrity sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==
+  version "7.3.3"
+  resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-7.3.3.tgz#92e6676efb67fa6925c69a2ee638f67a822952ac"
+  integrity sha512-HhxyLejTHMfohAuhRun4csWigAMjXTmRyiJTU1Y/I1xmggikFMkOUoMQRlFm+zQcPEGHSs3io/0FAmNZf8EymQ==
   dependencies:
     "@types/through" "*"
     rxjs "^6.4.0"
@@ -897,10 +980,17 @@
   resolved "https://registry.yarnpkg.com/@types/is-windows/-/is-windows-0.2.0.tgz#6f24ee48731d31168ea510610d6dd15e5fc9c6ff"
   integrity sha1-byTuSHMdMRaOpRBhDW3RXl/Jxv8=
 
+"@types/keyv@*":
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.2.tgz#5d97bb65526c20b6e0845f6b0d2ade4f28604ee5"
+  integrity sha512-/FvAK2p4jQOaJ6CGDHJTqZcUtbZe820qIeTg7o0Shg7drB4JHeL+V/dhSaly7NXx6u8eSee+r7coT+yuJEvDLg==
+  dependencies:
+    "@types/node" "*"
+
 "@types/launchpad@^0.6.0":
-  version "0.6.0"
-  resolved "https://registry.yarnpkg.com/@types/launchpad/-/launchpad-0.6.0.tgz#37296109b7f277f6e6c5fd7e0c0706bc918fbb51"
-  integrity sha1-NylhCbfyd/bmxf1+DAcGvJGPu1E=
+  version "0.6.1"
+  resolved "https://registry.yarnpkg.com/@types/launchpad/-/launchpad-0.6.1.tgz#9a5f285128598f5e0cb4a5db3e458e1b7cf07f35"
+  integrity sha512-kQ1a7PwzJelwwOIw1SABmW5OsbCRPvdjps0J84MahGsEKzN89StrPyrWCMWfwpONR3ZqSxDeblxS+8WznIBEGw==
 
 "@types/long@^4.0.0":
   version "4.0.1"
@@ -914,15 +1004,20 @@
   dependencies:
     "@types/node" "*"
 
-"@types/mime@*", "@types/mime@^2.0.0":
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
-  integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==
+"@types/mime@^1":
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
+  integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
 
-"@types/minimatch@*", "@types/minimatch@^3.0.1":
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
-  integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
+"@types/mime@^2.0.0":
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a"
+  integrity sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==
+
+"@types/minimatch@*", "@types/minimatch@^3.0.1", "@types/minimatch@^3.0.3":
+  version "3.0.5"
+  resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40"
+  integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==
 
 "@types/mz@0.0.29":
   version "0.0.29"
@@ -940,29 +1035,29 @@
     "@types/node" "*"
 
 "@types/node@*", "@types/node@>= 8":
-  version "13.5.0"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-13.5.0.tgz#4e498dbf355795a611a87ae5ef811a8660d42662"
-  integrity sha512-Onhn+z72D2O2Pb2ql2xukJ55rglumsVo1H6Fmyi8mlU9SvKdBk/pUSUAiBY/d9bAOF7VVWajX3sths/+g6ZiAQ==
+  version "16.7.10"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.10.tgz#7aa732cc47341c12a16b7d562f519c2383b6d4fc"
+  integrity sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==
 
 "@types/node@6.0.*":
   version "6.0.118"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.118.tgz#8014a9b1dee0b72b4d7cd142563f1af21241c3a2"
   integrity sha512-N33cKXGSqhOYaPiT4xUGsYlPPDwFtQM/6QxJxuMXA/7BcySW+lkn2yigWP7vfs4daiL/7NJNU6DMCqg5N4B+xQ==
 
-"@types/node@^10.1.0":
-  version "10.17.42"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.42.tgz#90dd71b26fe4f4e2929df6b07e72ef2e9648a173"
-  integrity sha512-HElxYF7C/MSkuvlaHB2c+82zhXiuO49Cq056Dol8AQuTph7oJtduo2n6J8rFa+YhJyNgQ/Lm20ZaxqD0vxU0+Q==
-
-"@types/node@^10.17.12":
-  version "10.17.24"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.24.tgz#c57511e3a19c4b5e9692bb2995c40a3a52167944"
-  integrity sha512-5SCfvCxV74kzR3uWgTYiGxrd69TbT1I6+cMx1A5kEly/IVveJBimtAMlXiEyVFn5DvUFewQWxOOiJhlxeQwxgA==
+"@types/node@^10.1.0", "@types/node@^10.17.12":
+  version "10.17.60"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b"
+  integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==
 
 "@types/node@^4.0.30":
-  version "4.9.4"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-4.9.4.tgz#75ef91733afaa856b01e12da6ecf48aa9d5e221f"
-  integrity sha512-nKoiCZ87x6+fs26bNHjy07AQt6f46nFEitGH0P9JmWbY6tEyum6LLfLf7SIsKFh4DnBWsyUM2gYhaQAt+aA0Sw==
+  version "4.9.5"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-4.9.5.tgz#a3785db96b07a4b56466cc99fd624838746f2e25"
+  integrity sha512-+8fpgbXsbATKRF2ayAlYhPl2E9MPdLjrnK/79ZEpyPJ+k7dZwJm9YM8FK+l4rqL//xHk7PgQhGwz6aA2ckxbCQ==
+
+"@types/normalize-package-data@^2.4.0":
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
+  integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==
 
 "@types/opn@^3.0.28":
   version "3.0.28"
@@ -979,17 +1074,17 @@
     "@types/parse5-sax-parser" "*"
 
 "@types/parse5-sax-parser@*":
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/@types/parse5-sax-parser/-/parse5-sax-parser-5.0.1.tgz#f1e26e82bb09e48cb0c16ff6d1e88aea1e538fd5"
-  integrity sha512-wBEwg10aACLggnb44CwzAA27M1Jrc/8TR16zA61/rKO5XZoi7JSfLjdpXbshsm7wOlM6hpfvwygh40rzM2RsQQ==
+  version "5.0.2"
+  resolved "https://registry.yarnpkg.com/@types/parse5-sax-parser/-/parse5-sax-parser-5.0.2.tgz#4cdca0f8bc0ce71b17e27b96e7ca9b5f79e861ff"
+  integrity sha512-EQtGoduLbdMmS4N27g6wcXdCCJ70dWYemfogWuumYg+JmzRqwYvTRAbGOYFortSHtS/qRzRCFwcP3ixy62RsdA==
   dependencies:
     "@types/node" "*"
     "@types/parse5" "*"
 
 "@types/parse5@*":
-  version "5.0.2"
-  resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.2.tgz#a877a4658f8238c8266faef300ae41c84d72ec8a"
-  integrity sha512-BOl+6KDs4ItndUWUFchy3aEqGdHhw0BC4Uu+qoDonN/f0rbUnJbm71Ulj8Tt9jLFRaAxPLKvdS1bBLfx1qXR9g==
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.1.tgz#f8ae4fbcd2b9ba4ff934698e28778961f9cb22ca"
+  integrity sha512-ARATsLdrGPUnaBvxLhUlnltcMgn7pQG312S8ccdYlnyijabrX9RN/KN/iGj9Am96CoW8e/K9628BA7Bv4XHdrA==
 
 "@types/parse5@^0.0.31":
   version "0.0.31"
@@ -1006,9 +1101,9 @@
     "@types/node" "*"
 
 "@types/parse5@^4.0.0":
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-4.0.0.tgz#26dd73df171a69be517395d294c7af2ae0cd2579"
-  integrity sha512-OaBwNFk6dO8gbdfWut41VYiD5Fmj3Yi24cr/oGCXFXCjT2fteSQx2l3kx/phuQvBte/F54ajN2uDQF5MRwupGw==
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-4.0.1.tgz#ec53c3f948f284be08454f622ba83765efdd06d7"
+  integrity sha512-CgJIkoNLclXUUg5cEo/SybyhxgVuKGqGcw8BPBpkKWX7wGcNfDlO2Ot+5O9u5E6k3NDg6RmJzm5w5N2prDIE8Q==
   dependencies:
     "@types/node" "*"
 
@@ -1018,21 +1113,26 @@
   integrity sha512-hfnXRGugz+McgX2jxyy5qz9sB21LRzlGn24zlwN2KEgoPtEvjzNRrLtUkOOebPDPZl3Rq7ywKxYvylVcEZDnEw==
 
 "@types/pem@^1.8.1":
-  version "1.9.5"
-  resolved "https://registry.yarnpkg.com/@types/pem/-/pem-1.9.5.tgz#cd5548b5e0acb4b41a9e21067e9fcd8c57089c99"
-  integrity sha512-C0txxEw8B7DCoD85Ko7SEvzUogNd5VDJ5/YBG8XUcacsOGqxr5Oo4g3OUAfdEDUbhXanwUoVh/ZkMFw77FGPQQ==
+  version "1.9.6"
+  resolved "https://registry.yarnpkg.com/@types/pem/-/pem-1.9.6.tgz#c3686832e935947fdd9d848dec3b8fe830068de7"
+  integrity sha512-IC67SxacM9fxEi/w7hf98dTun83OwUMeLMo1NS2gE0wdM9MHeg73iH/Pp9nB02OUCQ7Zb2UuKE/IpFCmQw9jxw==
   dependencies:
     "@types/node" "*"
 
+"@types/qs@*":
+  version "6.9.7"
+  resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
+  integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
+
 "@types/range-parser@*":
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
-  integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
+  integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
 
 "@types/relateurl@*":
-  version "0.2.28"
-  resolved "https://registry.yarnpkg.com/@types/relateurl/-/relateurl-0.2.28.tgz#6bda7db8653fa62643f5ee69e9f69c11a392e3a6"
-  integrity sha1-a9p9uGU/piZD9e5p6facEaOS46Y=
+  version "0.2.29"
+  resolved "https://registry.yarnpkg.com/@types/relateurl/-/relateurl-0.2.29.tgz#68ccecec3d4ffdafb9c577fe764f912afc050fe6"
+  integrity sha512-QSvevZ+IRww2ldtfv1QskYsqVVVwCKQf1XbwtcyyoRvLIQzfyPhj/C+3+PKzSDRdiyejaiLgnq//XTkleorpLg==
 
 "@types/request@2.0.3":
   version "2.0.3"
@@ -1070,6 +1170,13 @@
   dependencies:
     "@types/node" "*"
 
+"@types/responselike@*", "@types/responselike@^1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29"
+  integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==
+  dependencies:
+    "@types/node" "*"
+
 "@types/rimraf@^0.0.28":
   version "0.0.28"
   resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-0.0.28.tgz#5562519bc7963caca8abf7f128cae3b594d41d06"
@@ -1159,9 +1266,9 @@
     "@types/rx-core-binding" "*"
 
 "@types/rx@*":
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/@types/rx/-/rx-4.1.1.tgz#598fc94a56baed975f194574e0f572fd8e627a48"
-  integrity sha1-WY/JSla67ZdfGUV04PVy/Y5iekg=
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/@types/rx/-/rx-4.1.2.tgz#a4061b3d72b03cf11a38d69e2022a17334c54dc0"
+  integrity sha512-1r8ZaT26Nigq7o4UBGl+aXB2UMFUIdLPP/8bLIP0x3d0pZL46ybKKjhWKaJQWIkLl5QCLD0nK3qTOO1QkwdFaA==
   dependencies:
     "@types/rx-core" "*"
     "@types/rx-core-binding" "*"
@@ -1182,17 +1289,17 @@
   integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==
 
 "@types/serve-static@*", "@types/serve-static@^1.7.31":
-  version "1.13.3"
-  resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1"
-  integrity sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==
+  version "1.13.10"
+  resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9"
+  integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==
   dependencies:
-    "@types/express-serve-static-core" "*"
-    "@types/mime" "*"
+    "@types/mime" "^1"
+    "@types/node" "*"
 
 "@types/spdy@^3.4.1":
-  version "3.4.4"
-  resolved "https://registry.yarnpkg.com/@types/spdy/-/spdy-3.4.4.tgz#3282fd4ad8c4603aa49f7017dd520a08a345b2bc"
-  integrity sha512-N9LBlbVRRYq6HgYpPkqQc3a9HJ/iEtVZToW6xlTtJiMhmRJ7jJdV7TaZQJw/Ve/1ePUsQiCTDc4JMuzzag94GA==
+  version "3.4.5"
+  resolved "https://registry.yarnpkg.com/@types/spdy/-/spdy-3.4.5.tgz#194dc132312ddcd31e8053789ae83a7bb32a8aaf"
+  integrity sha512-/33fIRK/aqkKNxg9BSjpzt1ucmvPremgeDywm9z2C2mOlIh5Ljjvgc3UhQHqwXsSLDLHPT9jlsnrjKQ1XiVJzA==
   dependencies:
     "@types/node" "*"
 
@@ -1211,28 +1318,26 @@
     "@types/node" "*"
 
 "@types/ua-parser-js@^0.7.31":
-  version "0.7.33"
-  resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.33.tgz#4a92089511574e12928a7cb6b99a01831acd1dd7"
-  integrity sha512-ngUKcHnytUodUCL7C6EZ+lVXUjTMQb+9p/e1JjV5tN9TVzS98lHozWEFRPY1QcCdwFeMsmVWfZ3DPPT/udCyIw==
+  version "0.7.36"
+  resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz#9bd0b47f26b5a3151be21ba4ce9f5fa457c5f190"
+  integrity sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==
 
 "@types/uglify-js@*":
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.4.tgz#96beae23df6f561862a830b4288a49e86baac082"
-  integrity sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ==
+  version "3.13.1"
+  resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.13.1.tgz#5e889e9e81e94245c75b6450600e1c5ea2878aea"
+  integrity sha512-O3MmRAk6ZuAKa9CHgg0Pr0+lUOqoMLpc9AS4R8ano2auvsg7IE8syF3Xh/NPr26TWklxYcqoEEFdzLLs1fV9PQ==
   dependencies:
     source-map "^0.6.1"
 
 "@types/update-notifier@^1.0.0":
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/@types/update-notifier/-/update-notifier-1.0.3.tgz#3c7ee1921af6f16149cdcaef356baf57d7a0b806"
-  integrity sha512-BLStNhP2DFF7funARwTcoD6tetRte8NK3Sc59mn7GNALCN975jOlKX3dGvsFxXr/HwQMxxCuRn9IWB3WQ7odHQ==
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/@types/update-notifier/-/update-notifier-1.0.4.tgz#ce73d597bd399d5df4544fe136a79c2b9fe41958"
+  integrity sha512-smyU9GTDitojg87woCcLNCdPnUfNx4LHRBWf+aWmHsAgE1kaCDhhcu84W+dFymAKL1yKDsq2JFWKkR2K6WjJfw==
 
 "@types/uuid@^3.4.3":
-  version "3.4.6"
-  resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.6.tgz#d2c4c48eb85a757bf2927f75f939942d521e3016"
-  integrity sha512-cCdlC/1kGEZdEglzOieLDYBxHsvEOIg7kp/2FYyVR9Pxakq+Qf/inL3RKQ+PA8gOlI/NnL+fXmQH12nwcGzsHw==
-  dependencies:
-    "@types/node" "*"
+  version "3.4.10"
+  resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.10.tgz#637d3c8431f112edf6728ac9bdfadfe029540f48"
+  integrity sha512-BgeaZuElf7DEYZhWYDTc/XcLZXdVgFkVSTa13BqKvbnmUrxr3TJFKofUxCtDO9UQOdhnV+HPOESdHiHKZOJV1A==
 
 "@types/vinyl-fs@0.0.28":
   version "0.0.28"
@@ -1244,18 +1349,18 @@
     "@types/vinyl" "*"
 
 "@types/vinyl-fs@^2.4.8":
-  version "2.4.11"
-  resolved "https://registry.yarnpkg.com/@types/vinyl-fs/-/vinyl-fs-2.4.11.tgz#b98119b8bb2494141eaf649b09fbfeb311161206"
-  integrity sha512-2OzQSfIr9CqqWMGqmcERE6Hnd2KY3eBVtFaulVo3sJghplUcaeMdL9ZjEiljcQQeHjheWY9RlNmumjIAvsBNaA==
+  version "2.4.12"
+  resolved "https://registry.yarnpkg.com/@types/vinyl-fs/-/vinyl-fs-2.4.12.tgz#7b4673d9b4d5a874c8652d10f0f0265479014c8e"
+  integrity sha512-LgBpYIWuuGsihnlF+OOWWz4ovwCYlT03gd3DuLwex50cYZLmX3yrW+sFF9ndtmh7zcZpS6Ri47PrIu+fV+sbXw==
   dependencies:
     "@types/glob-stream" "*"
     "@types/node" "*"
     "@types/vinyl" "*"
 
 "@types/vinyl@*", "@types/vinyl@^2.0.0":
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/@types/vinyl/-/vinyl-2.0.4.tgz#9a7a8071c8d14d3a95d41ebe7135babe4ad5995a"
-  integrity sha512-2o6a2ixaVI2EbwBPg1QYLGQoHK56p/8X/sGfKbFC8N6sY9lfjsMf/GprtkQkSya0D4uRiutRZ2BWj7k3JvLsAQ==
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/@types/vinyl/-/vinyl-2.0.5.tgz#52d3b850a4ed494aaad51e96708834c500c8d5cd"
+  integrity sha512-1m6uReH8R/RuLVQGvTT/4LlWq67jZEUxp+FBHt0hYv2BT7TUwFbKI0wa7JZVEU/XtlcnX1QcTuZ36es4rGj7jg==
   dependencies:
     "@types/expect" "^1.20.4"
     "@types/node" "*"
@@ -1285,6 +1390,14 @@
   resolved "https://registry.yarnpkg.com/@webcomponents/webcomponentsjs/-/webcomponentsjs-1.3.3.tgz#5bb82a0d3210c836bd4623e13a4a93145cb9dc27"
   integrity sha512-eLH04VBMpuZGzBIhOnUjECcQPEPcmfhWEijW9u1B5I+2PPYdWf3vWUExdDxu4Y3GljRSTCOlWnGtS9tpzmXMyQ==
 
+JSONStream@^1.2.1, JSONStream@^1.3.5:
+  version "1.3.5"
+  resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
+  integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==
+  dependencies:
+    jsonparse "^1.2.0"
+    through ">=2.2.7 <3"
+
 accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
   version "1.3.7"
   resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
@@ -1311,25 +1424,32 @@
   integrity sha1-ReN/s56No/JbruP/U2niu18iAXo=
 
 acorn@^5.5.0:
-  version "5.7.3"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279"
-  integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==
+  version "5.7.4"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
+  integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==
 
 acorn@^7.1.0:
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c"
-  integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==
+  version "7.4.1"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
+  integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
 
 adm-zip@~0.4.3:
-  version "0.4.13"
-  resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.13.tgz#597e2f8cc3672151e1307d3e95cddbc75672314a"
-  integrity sha512-fERNJX8sOXfel6qCBCMPvZLzENBEhZTzKqg6vrOW5pvoEaQuJhRU4ndTAh6lHOxn1I6jnz2NHra56ZODM751uw==
+  version "0.4.16"
+  resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365"
+  integrity sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==
 
 after@0.8.2:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
   integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
 
+agent-base@6:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
+  integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
+  dependencies:
+    debug "4"
+
 agent-base@^4.3.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
@@ -1337,10 +1457,10 @@
   dependencies:
     es6-promisify "^5.0.0"
 
-ajv@^6.5.5:
-  version "6.11.0"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.11.0.tgz#c3607cbc8ae392d8a5a536f25b21f8e5f3f87fe9"
-  integrity sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==
+ajv@^6.12.3:
+  version "6.12.6"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+  integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
   dependencies:
     fast-deep-equal "^3.1.1"
     fast-json-stable-stringify "^2.0.0"
@@ -1373,10 +1493,12 @@
   resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
   integrity sha1-06ioOzGapneTZisT52HHkRQiMG4=
 
-ansi-escapes@^3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
-  integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
+ansi-escapes@^4.2.1:
+  version "4.3.2"
+  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
+  integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
+  dependencies:
+    type-fest "^0.21.3"
 
 ansi-regex@^2.0.0:
   version "2.1.1"
@@ -1388,10 +1510,10 @@
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
   integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
 
-ansi-regex@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
-  integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
+ansi-regex@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
+  integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
 
 ansi-styles@^2.2.1:
   version "2.2.1"
@@ -1406,11 +1528,10 @@
     color-convert "^1.9.0"
 
 ansi-styles@^4.1.0:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
-  integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+  integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
   dependencies:
-    "@types/color-name" "^1.1.1"
     color-convert "^2.0.1"
 
 ansi-styles@~1.0.0:
@@ -1501,7 +1622,7 @@
   dependencies:
     typical "^2.6.1"
 
-array-back@^3.0.1:
+array-back@^3.0.1, array-back@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0"
   integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==
@@ -1511,6 +1632,11 @@
   resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031"
   integrity sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=
 
+array-differ@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b"
+  integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==
+
 array-find-index@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
@@ -1521,13 +1647,18 @@
   resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
   integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
 
-array-union@^1.0.1:
+array-union@^1.0.1, array-union@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
   integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=
   dependencies:
     array-uniq "^1.0.1"
 
+array-union@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
+  integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
+
 array-uniq@^1.0.1:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
@@ -1553,6 +1684,11 @@
   resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
   integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
 
+arrify@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
+  integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
+
 asn1@~0.2.3:
   version "0.2.4"
   resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
@@ -1575,18 +1711,23 @@
   resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
   integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
 
-async-limiter@~1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
-  integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
+async@0.9.x:
+  version "0.9.2"
+  resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
+  integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=
 
-async@^2.0.0, async@^2.0.1, async@^2.1.2, async@^2.4.1, async@^2.6.0, async@^2.6.1, async@^2.6.2, async@^2.6.3:
+async@^2.0.0, async@^2.0.1, async@^2.1.2, async@^2.4.1, async@^2.6.0, async@^2.6.2, async@^2.6.3:
   version "2.6.3"
   resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
   integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
   dependencies:
     lodash "^4.17.14"
 
+async@^3.1.0:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/async/-/async-3.2.1.tgz#d3274ec66d107a47476a4c49136aacdb00665fc8"
+  integrity sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==
+
 async@~0.2.9:
   version "0.2.10"
   resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
@@ -1613,9 +1754,16 @@
   integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
 
 aws4@^1.8.0:
-  version "1.9.1"
-  resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
-  integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==
+  version "1.11.0"
+  resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
+  integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
+
+axios@^0.21.1:
+  version "0.21.1"
+  resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
+  integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
+  dependencies:
+    follow-redirects "^1.10.0"
 
 babel-code-frame@^6.26.0:
   version "6.26.0"
@@ -1682,10 +1830,10 @@
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-dynamic-import-node@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f"
-  integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==
+babel-plugin-dynamic-import-node@^2.3.3:
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
+  integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==
   dependencies:
     object.assign "^4.1.0"
 
@@ -1902,24 +2050,24 @@
   integrity sha1-MasayLEpNjRj41s+u2n038+6eUc=
 
 balanced-match@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
-  integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+  integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
 
-base64-arraybuffer@0.1.5:
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
-  integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg=
+base64-arraybuffer@0.1.4:
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812"
+  integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=
 
 base64-js@1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1"
   integrity sha1-o5mS1yNYSBGYK+XikLtqU9hnAPE=
 
-base64-js@^1.0.2:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
-  integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
+base64-js@^1.3.1:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
+  integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
 
 base64id@2.0.0:
   version "2.0.0"
@@ -1947,16 +2095,9 @@
     tweetnacl "^0.14.3"
 
 before-after-hook@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635"
-  integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==
-
-better-assert@~1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522"
-  integrity sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=
-  dependencies:
-    callsite "1.0.0"
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e"
+  integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==
 
 binary-extensions@^1.0.0:
   version "1.13.1"
@@ -1964,9 +2105,9 @@
   integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
 
 binaryextensions@^2.1.2:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.2.0.tgz#e7c6ba82d4f5f5758c26078fe8eea28881233311"
-  integrity sha512-bHhs98rj/7i/RZpCSJ3uk55pLXOItjIrh2sRQZSM6OoktScX+LxJzvlU+FELp9j3TdcddTmmYArLSGptCTwjuw==
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.3.0.tgz#1d269cbf7e6243ea886aa41453c3651ccbe13c22"
+  integrity sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==
 
 bindings@^1.5.0:
   version "1.5.0"
@@ -1976,27 +2117,21 @@
     file-uri-to-path "1.0.0"
 
 bl@^1.0.0:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
-  integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7"
+  integrity sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==
   dependencies:
     readable-stream "^2.3.5"
     safe-buffer "^5.1.1"
 
-bl@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.0.tgz#e1a574cdf528e4053019bb800b041c0ac88da493"
-  integrity sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==
+bl@^4.0.3:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
+  integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
   dependencies:
-    readable-stream "^2.3.5"
-    safe-buffer "^5.1.1"
-
-bl@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88"
-  integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A==
-  dependencies:
-    readable-stream "^3.0.1"
+    buffer "^5.5.0"
+    inherits "^2.0.4"
+    readable-stream "^3.4.0"
 
 blob@0.0.5:
   version "0.0.5"
@@ -2020,25 +2155,28 @@
     type-is "~1.6.17"
 
 bower-config@^1.4.0, bower-config@^1.4.1:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/bower-config/-/bower-config-1.4.1.tgz#85fd9df367c2b8dbbd0caa4c5f2bad40cd84c2cc"
-  integrity sha1-hf2d82fCuNu9DKpMXyutQM2Ewsw=
+  version "1.4.3"
+  resolved "https://registry.yarnpkg.com/bower-config/-/bower-config-1.4.3.tgz#3454fecdc5f08e7aa9cc6d556e492be0669689ae"
+  integrity sha512-MVyyUk3d1S7d2cl6YISViwJBc2VXCkxF5AUFykvN0PQj5FsUiMNSgAYTso18oRFfyZ6XEtjrgg9MAaufHbOwNw==
   dependencies:
     graceful-fs "^4.1.3"
+    minimist "^0.2.1"
     mout "^1.0.0"
-    optimist "^0.6.1"
     osenv "^0.1.3"
     untildify "^2.1.0"
+    wordwrap "^0.0.3"
 
 bower-json@^0.8.1:
-  version "0.8.1"
-  resolved "https://registry.yarnpkg.com/bower-json/-/bower-json-0.8.1.tgz#96c14723241ae6466a9c52e16caa32623a883843"
-  integrity sha1-lsFHIyQa5kZqnFLhbKoyYjqIOEM=
+  version "0.8.4"
+  resolved "https://registry.yarnpkg.com/bower-json/-/bower-json-0.8.4.tgz#9c3b375870dcd9581350c1f403f6383dbf6a18b1"
+  integrity sha512-mMKghvq9ivbuzSsY5nrOLnDtZIJMUCpysqbGaGW3mj88JAcuSi8ZAzIt34vNZjohy0aR9VXLwgPTZGnBX2Vpjg==
   dependencies:
-    deep-extend "^0.4.0"
-    ext-name "^3.0.0"
+    deep-extend "^0.5.1"
+    ends-with "^0.2.0"
+    ext-list "^2.0.0"
     graceful-fs "^4.1.3"
     intersect "^1.0.1"
+    sort-keys-length "^1.0.0"
 
 bower-logger@^0.2.2:
   version "0.2.2"
@@ -2046,9 +2184,9 @@
   integrity sha1-Ob4H6Xmy/I4DqUY0IF7ZQiNz04E=
 
 bower@^1.8.8:
-  version "1.8.8"
-  resolved "https://registry.yarnpkg.com/bower/-/bower-1.8.8.tgz#82544be34a33aeae7efb8bdf9905247b2cffa985"
-  integrity sha512-1SrJnXnkP9soITHptSO+ahx3QKp3cVzn8poI6ujqc5SeOkg5iqM1pK9H+DSc2OQ8SnO0jC/NG4Ur/UIwy7574A==
+  version "1.8.12"
+  resolved "https://registry.yarnpkg.com/bower/-/bower-1.8.12.tgz#44cfca2a5e04b8d9a066621e24c8b179d8ac321e"
+  integrity sha512-u1xy9SrwwoPlgjuHNjhV+YUPVdqyBj2ALBxuzeIUKXaPI2i2xypGgxqXkuHcITGdi5yBj5JuXgyMvgiWiS1S3Q==
 
 boxen@^0.6.0:
   version "0.6.0"
@@ -2126,10 +2264,21 @@
   dependencies:
     pako "~0.2.0"
 
+browserslist@^4.16.6:
+  version "4.16.8"
+  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.8.tgz#cb868b0b554f137ba6e33de0ecff2eda403c4fb0"
+  integrity sha512-sc2m9ohR/49sWEbPj14ZSSZqp+kbi16aLao42Hmn3Z8FpjuMaq2xCA2l4zl9ITfyzvnvyE0hcg62YkIGKxgaNQ==
+  dependencies:
+    caniuse-lite "^1.0.30001251"
+    colorette "^1.3.0"
+    electron-to-chromium "^1.3.811"
+    escalade "^3.1.1"
+    node-releases "^1.1.75"
+
 browserstack@^1.2.0:
-  version "1.5.3"
-  resolved "https://registry.yarnpkg.com/browserstack/-/browserstack-1.5.3.tgz#93ab48799a12ef99dbd074dd595410ddb196a7ac"
-  integrity sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==
+  version "1.6.1"
+  resolved "https://registry.yarnpkg.com/browserstack/-/browserstack-1.6.1.tgz#e051f9733ec3b507659f395c7a4765a1b1e358b3"
+  integrity sha512-GxtFjpIaKdbAyzHfFDKixKO8IBT7wR3NjbzrGc78nNs/Ciys9wU3/nBtsqsWv5nDSrdI5tz0peKuzCPuNXNUiw==
   dependencies:
     https-proxy-agent "^2.2.1"
 
@@ -2162,22 +2311,22 @@
   integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
 
 buffer-from@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
-  integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
+  integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
 
-buffer@^5.1.0:
-  version "5.4.3"
-  resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.3.tgz#3fbc9c69eb713d323e3fc1a895eee0710c072115"
-  integrity sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==
+buffer@^5.1.0, buffer@^5.5.0:
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
+  integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
   dependencies:
-    base64-js "^1.0.2"
-    ieee754 "^1.1.4"
+    base64-js "^1.3.1"
+    ieee754 "^1.1.13"
 
 builtin-modules@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484"
-  integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887"
+  integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==
 
 busboy@^0.2.11:
   version "0.2.14"
@@ -2212,16 +2361,37 @@
     union-value "^1.0.0"
     unset-value "^1.0.0"
 
+cacheable-lookup@^5.0.3:
+  version "5.0.4"
+  resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005"
+  integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==
+
+cacheable-request@^7.0.1:
+  version "7.0.2"
+  resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27"
+  integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==
+  dependencies:
+    clone-response "^1.0.2"
+    get-stream "^5.1.0"
+    http-cache-semantics "^4.0.0"
+    keyv "^4.0.0"
+    lowercase-keys "^2.0.0"
+    normalize-url "^6.0.1"
+    responselike "^2.0.0"
+
+call-bind@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
+  integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
+  dependencies:
+    function-bind "^1.1.1"
+    get-intrinsic "^1.0.2"
+
 call-me-maybe@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
   integrity sha1-JtII6onje1y95gJQoV8DHBak1ms=
 
-callsite@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
-  integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA=
-
 camel-case@3.0.x:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73"
@@ -2255,6 +2425,11 @@
   dependencies:
     "@types/node" "^4.0.30"
 
+caniuse-lite@^1.0.30001251:
+  version "1.0.30001252"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001252.tgz#cb16e4e3dafe948fc4a9bb3307aea054b912019a"
+  integrity sha512-I56jhWDGMtdILQORdusxBOH+Nl/KgQSdDmpJezYddnAkVOmnoU8zwjTV9xAjMIYxr0iPreEAVylCGcmHCjfaOw==
+
 capture-stack-trace@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d"
@@ -2265,10 +2440,10 @@
   resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
   integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
 
-chalk@*:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
-  integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
+chalk@*, chalk@^4.1.0:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
+  integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
   dependencies:
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
@@ -2307,7 +2482,7 @@
   resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
   integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
 
-charenc@~0.0.1:
+charenc@0.0.2:
   version "0.0.2"
   resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
   integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
@@ -2329,9 +2504,9 @@
     fsevents "^1.0.0"
 
 chownr@^1.0.1:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142"
-  integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
+  integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
 
 ci-info@^1.5.0:
   version "1.6.0"
@@ -2349,9 +2524,9 @@
     static-extend "^0.1.1"
 
 clean-css@4.2.x:
-  version "4.2.2"
-  resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.2.tgz#8519abda724b3e759bc79d196369906925d81a3f"
-  integrity sha512-yKycArwReQXbOD/3pmsPmt6p7oUBww8MisDabL2pCUWkbVONvCJoBdCjgY4ZVQmKX5juz/JB9oDcP6XzGUpjwQ==
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78"
+  integrity sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==
   dependencies:
     source-map "~0.6.0"
 
@@ -2372,30 +2547,51 @@
   dependencies:
     restore-cursor "^1.0.1"
 
-cli-cursor@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
-  integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=
+cli-cursor@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
+  integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
   dependencies:
-    restore-cursor "^2.0.0"
+    restore-cursor "^3.1.0"
 
 cli-table@^0.3.1:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23"
-  integrity sha1-9TsFJmqLGguTSz0IIebi3FkUriM=
+  version "0.3.6"
+  resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.6.tgz#e9d6aa859c7fe636981fd3787378c2a20bce92fc"
+  integrity sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ==
   dependencies:
     colors "1.0.3"
 
 cli-width@^2.0.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
-  integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
+  integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==
+
+cli-width@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
+  integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
 
 clone-buffer@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
   integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg=
 
+clone-deep@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
+  integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
+  dependencies:
+    is-plain-object "^2.0.4"
+    kind-of "^6.0.2"
+    shallow-clone "^3.0.0"
+
+clone-response@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
+  integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
+  dependencies:
+    mimic-response "^1.0.0"
+
 clone-stats@^0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1"
@@ -2463,9 +2659,9 @@
   integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
 
 color-string@^1.5.2:
-  version "1.5.3"
-  resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc"
-  integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.6.0.tgz#c3915f61fe267672cb7e1e064c9d692219f6c312"
+  integrity sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==
   dependencies:
     color-name "^1.0.0"
     simple-swizzle "^0.2.2"
@@ -2478,10 +2674,10 @@
     color-convert "^1.9.1"
     color-string "^1.5.2"
 
-colornames@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/colornames/-/colornames-1.1.1.tgz#f8889030685c7c4ff9e2a559f5077eb76a816f96"
-  integrity sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=
+colorette@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.3.0.tgz#ff45d2f0edb244069d3b772adeb04fed38d0a0af"
+  integrity sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w==
 
 colors@1.0.3:
   version "1.0.3"
@@ -2519,11 +2715,11 @@
     typical "^2.6.0"
 
 command-line-args@^5.0.2:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.1.1.tgz#88e793e5bb3ceb30754a86863f0401ac92fd369a"
-  integrity sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg==
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.0.tgz#087b02748272169741f1fd7c785b295df079b9be"
+  integrity sha512-4zqtU1hYsSJzcJBOcNZIbW5Fbk9BkjCp1pZVhQKoRaWL5J7N4XphDLwo8aWwdQpTugxwu+jf9u2ZhkXiqp5Z6A==
   dependencies:
-    array-back "^3.0.1"
+    array-back "^3.1.0"
     find-replace "^3.0.0"
     lodash.camelcase "^4.3.0"
     typical "^4.0.0"
@@ -2561,7 +2757,7 @@
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
   integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
 
-commander@^2.19.0, commander@^2.20.0:
+commander@^2.20.0, commander@^2.20.3:
   version "2.20.3"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
   integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@@ -2586,7 +2782,7 @@
   resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
   integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=
 
-component-emitter@^1.2.1:
+component-emitter@^1.2.1, component-emitter@~1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
   integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
@@ -2657,11 +2853,11 @@
     xdg-basedir "^2.0.0"
 
 configstore@^3.0.0:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f"
-  integrity sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==
+  version "3.1.5"
+  resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.5.tgz#e9af331fadc14dabd544d3e7e76dc446a09a530f"
+  integrity sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==
   dependencies:
-    dot-prop "^4.1.0"
+    dot-prop "^4.2.1"
     graceful-fs "^4.1.2"
     make-dir "^1.0.0"
     unique-string "^1.0.0"
@@ -2681,9 +2877,9 @@
   integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
 
 convert-source-map@^1.1.1, convert-source-map@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
-  integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==
+  version "1.8.0"
+  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
+  integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==
   dependencies:
     safe-buffer "~5.1.1"
 
@@ -2692,31 +2888,36 @@
   resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
   integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
 
-cookie@0.3.1:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
-  integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
-
 cookie@0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
   integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
 
+cookie@~0.4.1:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
+  integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
+
 copy-descriptor@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
   integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
 
 core-js@^2.4.0, core-js@^2.4.1:
-  version "2.6.11"
-  resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c"
-  integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==
+  version "2.6.12"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
+  integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
 
-core-util-is@1.0.2, core-util-is@~1.0.0:
+core-util-is@1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
   integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
 
+core-util-is@~1.0.0:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
+  integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
+
 cors@^2.8.4:
   version "2.8.5"
   resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
@@ -2776,7 +2977,16 @@
     shebang-command "^1.2.0"
     which "^1.2.9"
 
-crypt@~0.0.1:
+cross-spawn@^7.0.0, cross-spawn@^7.0.3:
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
+  integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
+  dependencies:
+    path-key "^3.1.0"
+    shebang-command "^2.0.0"
+    which "^2.0.1"
+
+crypt@0.0.2:
   version "0.0.2"
   resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
   integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=
@@ -2814,7 +3024,7 @@
   dependencies:
     array-find-index "^1.0.1"
 
-dargs@^6.0.0:
+dargs@^6.0.0, dargs@^6.1.0:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/dargs/-/dargs-6.1.0.tgz#1f3b9b56393ecf8caa7cbfd6c31496ffcfb9b272"
   integrity sha512-5dVBvpBLBnPwSsYXqfybFyehMmC/EenKEcf23AhCTgTf48JFBbmJKqoZBsERDnjL0FyiVTYWdFsRfTLHxLyKdQ==
@@ -2838,17 +3048,17 @@
   dependencies:
     ms "2.0.0"
 
-debug@^3.0.0, debug@^3.1.0:
-  version "3.2.6"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
-  integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
+debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
+  version "4.3.2"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
+  integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
   dependencies:
-    ms "^2.1.1"
+    ms "2.1.2"
 
-debug@^4.1.0, debug@^4.1.1, debug@~4.1.0:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
-  integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
+debug@^3.1.0:
+  version "3.2.7"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
+  integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
   dependencies:
     ms "^2.1.1"
 
@@ -2859,6 +3069,13 @@
   dependencies:
     ms "2.0.0"
 
+debug@~4.1.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
+  integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
+  dependencies:
+    ms "^2.1.1"
+
 decamelize@^1.1.2:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@@ -2876,17 +3093,34 @@
   dependencies:
     mimic-response "^1.0.0"
 
-deep-extend@^0.4.0, deep-extend@~0.4.1:
-  version "0.4.2"
-  resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
-  integrity sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=
+decompress-response@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
+  integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
+  dependencies:
+    mimic-response "^3.1.0"
+
+deep-extend@^0.5.1:
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.1.tgz#b894a9dd90d3023fbf1c55a394fb858eb2066f1f"
+  integrity sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==
 
 deep-extend@^0.6.0, deep-extend@~0.6.0:
   version "0.6.0"
   resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
   integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
 
-define-properties@^1.1.2:
+deep-extend@~0.4.1:
+  version "0.4.2"
+  resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
+  integrity sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=
+
+defer-to-connect@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587"
+  integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==
+
+define-properties@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
   integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
@@ -2937,7 +3171,7 @@
   resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
   integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
 
-deprecation@^2.0.0:
+deprecation@^2.0.0, deprecation@^2.3.1:
   version "2.3.1"
   resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
   integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
@@ -2972,18 +3206,9 @@
     repeating "^2.0.0"
 
 detect-node@^2.0.3:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
-  integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
-
-diagnostics@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/diagnostics/-/diagnostics-1.1.1.tgz#cab6ac33df70c9d9a727490ae43ac995a769b22a"
-  integrity sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==
-  dependencies:
-    colorspace "1.1.x"
-    enabled "1.0.x"
-    kuler "1.0.x"
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
+  integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
 
 dicer@0.2.5:
   version "0.2.5"
@@ -3003,6 +3228,11 @@
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
   integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
 
+diff@^4.0.1:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
+  integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
+
 dir-glob@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034"
@@ -3011,6 +3241,13 @@
     arrify "^1.0.1"
     path-type "^3.0.0"
 
+dir-glob@^2.2.2:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4"
+  integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==
+  dependencies:
+    path-type "^3.0.0"
+
 doctrine@^2.0.2:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
@@ -3052,13 +3289,22 @@
   dependencies:
     is-obj "^1.0.0"
 
-dot-prop@^4.1.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57"
-  integrity sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==
+dot-prop@^4.2.1:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.1.tgz#45884194a71fc2cda71cbb4bceb3a4dd2f433ba4"
+  integrity sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==
   dependencies:
     is-obj "^1.0.0"
 
+download-stats@^0.3.4:
+  version "0.3.4"
+  resolved "https://registry.yarnpkg.com/download-stats/-/download-stats-0.3.4.tgz#67ea0c32f14acd9f639da704eef509684ba2dae7"
+  integrity sha512-ic2BigbyUWx7/CBbsfGjf71zUNZB4edBGC3oRliSzsoNmvyVx3Ycfp1w3vp2Y78Ee0eIIkjIEO5KzW0zThDGaA==
+  dependencies:
+    JSONStream "^1.2.1"
+    lazy-cache "^2.0.1"
+    moment "^2.15.1"
+
 duplexer2@^0.1.2, duplexer2@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
@@ -3090,9 +3336,9 @@
     safer-buffer "^2.1.0"
 
 editions@^2.2.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/editions/-/editions-2.3.0.tgz#47f2d5309340bce93ab5eb6ad755b9e90ff825e4"
-  integrity sha512-jeXYwHPKbitU1l14dWlsl5Nm+b1Hsm7VX73BsrQ4RVwEcAQQIPFHTZAbVtuIGxZBrpdT2FXd8lbtrNBrzZxIsA==
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/editions/-/editions-2.3.1.tgz#3bc9962f1978e801312fbd0aebfed63b49bfe698"
+  integrity sha512-ptGvkwTvGdGfC0hfhKg0MT+TRLRKGtUiWGBInxOm5pz7ssADezahjCUaYuZ8Dr+C05FW0AECIIPt4WBxVINEhA==
   dependencies:
     errlop "^2.0.0"
     semver "^6.3.0"
@@ -3102,22 +3348,37 @@
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
   integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
 
-ejs@^2.5.9:
+ejs@^2.5.9, ejs@^2.6.1:
   version "2.7.4"
   resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba"
   integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==
 
+ejs@^3.1.5:
+  version "3.1.6"
+  resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a"
+  integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==
+  dependencies:
+    jake "^10.6.1"
+
+electron-to-chromium@^1.3.811:
+  version "1.3.826"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.826.tgz#dbe356b1546b39d83bcd47e675a9c5f61dadaed2"
+  integrity sha512-bpLc4QU4B8PYmdO4MSu2ZBTMD8lAaEXRS43C09lB31BvYwuk9UxgBRXbY5OJBw7VuMGcg2MZG5FyTaP9u4PQnw==
+
 emitter-component@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/emitter-component/-/emitter-component-1.1.1.tgz#065e2dbed6959bf470679edabeaf7981d1003ab6"
   integrity sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY=
 
-enabled@1.0.x:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/enabled/-/enabled-1.0.2.tgz#965f6513d2c2d1c5f4652b64a2e3396467fc2f93"
-  integrity sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=
-  dependencies:
-    env-variable "0.0.x"
+emoji-regex@^8.0.0:
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+  integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
+enabled@2.0.x:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2"
+  integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==
 
 encodeurl@~1.0.2:
   version "1.0.2"
@@ -3136,55 +3397,50 @@
   resolved "https://registry.yarnpkg.com/ends-with/-/ends-with-0.2.0.tgz#2f9da98d57a50cfda4571ce4339000500f4e6b8a"
   integrity sha1-L52pjVelDP2kVxzkM5AAUA9Oa4o=
 
-engine.io-client@~3.4.0:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.0.tgz#82a642b42862a9b3f7a188f41776b2deab643700"
-  integrity sha512-a4J5QO2k99CM2a0b12IznnyQndoEvtA4UAldhGzKqnHf42I3Qs2W5SPnDvatZRcMaNZs4IevVicBPayxYt6FwA==
+engine.io-client@~3.5.0:
+  version "3.5.2"
+  resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.5.2.tgz#0ef473621294004e9ceebe73cef0af9e36f2f5fa"
+  integrity sha512-QEqIp+gJ/kMHeUun7f5Vv3bteRHppHH/FMBQX/esFj/fuYfjyUKWGMo3VCvIP/V8bE9KcjHmRZrhIz2Z9oNsDA==
   dependencies:
-    component-emitter "1.2.1"
+    component-emitter "~1.3.0"
     component-inherit "0.0.3"
-    debug "~4.1.0"
+    debug "~3.1.0"
     engine.io-parser "~2.2.0"
     has-cors "1.1.0"
     indexof "0.0.1"
-    parseqs "0.0.5"
-    parseuri "0.0.5"
-    ws "~6.1.0"
-    xmlhttprequest-ssl "~1.5.4"
+    parseqs "0.0.6"
+    parseuri "0.0.6"
+    ws "~7.4.2"
+    xmlhttprequest-ssl "~1.6.2"
     yeast "0.1.2"
 
 engine.io-parser@~2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.2.0.tgz#312c4894f57d52a02b420868da7b5c1c84af80ed"
-  integrity sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.2.1.tgz#57ce5611d9370ee94f99641b589f94c97e4f5da7"
+  integrity sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==
   dependencies:
     after "0.8.2"
     arraybuffer.slice "~0.0.7"
-    base64-arraybuffer "0.1.5"
+    base64-arraybuffer "0.1.4"
     blob "0.0.5"
     has-binary2 "~1.0.2"
 
-engine.io@~3.4.0:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.4.0.tgz#3a962cc4535928c252759a00f98519cb46c53ff3"
-  integrity sha512-XCyYVWzcHnK5cMz7G4VTu2W7zJS7SM1QkcelghyIk/FmobWBtXE7fwhBusEKvCSqc3bMh8fNFMlUkCKTFRxH2w==
+engine.io@~3.5.0:
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.5.0.tgz#9d6b985c8a39b1fe87cd91eb014de0552259821b"
+  integrity sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==
   dependencies:
     accepts "~1.3.4"
     base64id "2.0.0"
-    cookie "0.3.1"
+    cookie "~0.4.1"
     debug "~4.1.0"
     engine.io-parser "~2.2.0"
-    ws "^7.1.2"
-
-env-variable@0.0.x:
-  version "0.0.5"
-  resolved "https://registry.yarnpkg.com/env-variable/-/env-variable-0.0.5.tgz#913dd830bef11e96a039c038d4130604eba37f88"
-  integrity sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==
+    ws "~7.4.2"
 
 errlop@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/errlop/-/errlop-2.0.0.tgz#52b97d35da1b0795e2647b5d2d3a46d17776f55a"
-  integrity sha512-z00WIrQhtOMUnjdTG0O4f6hMG64EVccVDBy2WwgjcF8S4UB1exGYuc2OFwmdQmsJwLQVEIHWHPCz/omXXgAZHw==
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/errlop/-/errlop-2.2.0.tgz#1ff383f8f917ae328bebb802d6ca69666a42d21b"
+  integrity sha512-e64Qj9+4aZzjzzFpZC7p5kmm/ccCrbLhAJplhsDXQFs87XTsXwOpH4s1Io2s90Tau/8r2j9f4l/thhDevRjzxw==
 
 error-ex@^1.2.0, error-ex@^1.3.1:
   version "1.3.2"
@@ -3213,9 +3469,14 @@
     es6-promise "^4.0.3"
 
 es6-promisify@^6.0.0:
-  version "6.0.2"
-  resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-6.0.2.tgz#525c23725b8510f5f1f2feb5a1fbad93a93e29b4"
-  integrity sha512-eO6vFm0JvqGzjWIQA6QVKjxpmELfhWbDUWHm1rPfIbn55mhKPiAa5xpLmQWJrNa629ZIeQ8ZvMAi13kvrjK6Mg==
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-6.1.1.tgz#46837651b7b06bf6fff893d03f29393668d01621"
+  integrity sha512-HBL8I3mIki5C1Cc9QjKUenHtnG0A5/xA8Q/AllRcfiwl2CZFXGK7ddBiCoRwAix4i2KxcQfjtIVcrVbB3vbmwg==
+
+escalade@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
+  integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
 
 escape-html@^1.0.3, escape-html@~1.0.3:
   version "1.0.3"
@@ -3251,9 +3512,9 @@
   integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
 
 eventemitter3@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb"
-  integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==
+  version "4.0.7"
+  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
+  integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
 
 execa@^0.7.0:
   version "0.7.0"
@@ -3281,6 +3542,21 @@
     signal-exit "^3.0.0"
     strip-eof "^1.0.0"
 
+execa@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
+  integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==
+  dependencies:
+    cross-spawn "^7.0.0"
+    get-stream "^5.0.0"
+    human-signals "^1.1.1"
+    is-stream "^2.0.0"
+    merge-stream "^2.0.0"
+    npm-run-path "^4.0.0"
+    onetime "^5.1.0"
+    signal-exit "^3.0.2"
+    strip-final-newline "^2.0.0"
+
 exit-hook@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
@@ -3370,16 +3646,6 @@
   dependencies:
     mime-db "^1.28.0"
 
-ext-name@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/ext-name/-/ext-name-3.0.0.tgz#07e4418737cb1f513c32c6ea48d8b8c8e0471abb"
-  integrity sha1-B+RBhzfLH1E8MsbqSNi4yOBHGrs=
-  dependencies:
-    ends-with "^0.2.0"
-    ext-list "^2.0.0"
-    meow "^3.1.0"
-    sort-keys-length "^1.0.0"
-
 extend-shallow@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
@@ -3450,11 +3716,11 @@
   integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
 
 fast-deep-equal@^3.1.1:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4"
-  integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
+  integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
 
-fast-glob@^2.0.2:
+fast-glob@^2.0.2, fast-glob@^2.2.6:
   version "2.2.7"
   resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d"
   integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==
@@ -3477,9 +3743,9 @@
   integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
 
 fast-safe-stringify@^2.0.4:
-  version "2.0.7"
-  resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743"
-  integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==
+  version "2.0.8"
+  resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.8.tgz#dc2af48c46cf712b683e849b2bbd446b32de936f"
+  integrity sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag==
 
 fd-slicer@~1.1.0:
   version "1.1.0"
@@ -3500,6 +3766,11 @@
   resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd"
   integrity sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==
 
+fecha@^4.2.0:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.1.tgz#0a83ad8f86ef62a091e22bb5a039cd03d23eecce"
+  integrity sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==
+
 figures@^1.3.5:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
@@ -3508,10 +3779,10 @@
     escape-string-regexp "^1.0.5"
     object-assign "^4.1.0"
 
-figures@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
-  integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=
+figures@^3.0.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
+  integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
   dependencies:
     escape-string-regexp "^1.0.5"
 
@@ -3520,6 +3791,13 @@
   resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
   integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
 
+filelist@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b"
+  integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==
+  dependencies:
+    minimatch "^3.0.4"
+
 filename-regex@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
@@ -3633,12 +3911,15 @@
   dependencies:
     readable-stream "^2.0.2"
 
-follow-redirects@^1.0.0:
-  version "1.10.0"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.10.0.tgz#01f5263aee921c6a54fb91667f08f4155ce169eb"
-  integrity sha512-4eyLK6s6lH32nOvLLwlIOnr9zrL8Sm+OvW4pVTJNoXeGzYIkHVf+pADQi+OJ0E67hiuSLezPVPyBcIZO50TmmQ==
-  dependencies:
-    debug "^3.0.0"
+fn.name@1.x.x:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
+  integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
+
+follow-redirects@^1.0.0, follow-redirects@^1.10.0:
+  version "1.14.2"
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.2.tgz#cecb825047c00f5e66b142f90fed4f515dec789b"
+  integrity sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==
 
 for-in@^1.0.1, for-in@^1.0.2:
   version "1.0.2"
@@ -3663,9 +3944,9 @@
   integrity sha1-24Sfznf2cIpfjzhq5TOgkHtUrnA=
 
 form-data@*:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682"
-  integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
+  integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
   dependencies:
     asynckit "^0.4.0"
     combined-stream "^1.0.8"
@@ -3687,10 +3968,10 @@
   dependencies:
     samsam "1.x"
 
-forwarded@~0.1.2:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
-  integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
+forwarded@0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
+  integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
 
 fragment-cache@^0.2.1:
   version "0.2.1"
@@ -3725,27 +4006,36 @@
   integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
 
 fsevents@^1.0.0:
-  version "1.2.11"
-  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.11.tgz#67bf57f4758f02ede88fb2a1712fef4d15358be3"
-  integrity sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==
+  version "1.2.13"
+  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38"
+  integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==
   dependencies:
     bindings "^1.5.0"
     nan "^2.12.1"
 
-fsevents@~2.1.2:
-  version "2.1.3"
-  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
-  integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
+fsevents@~2.3.2:
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
+  integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
 
 function-bind@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
   integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
 
-gensync@^1.0.0-beta.1:
-  version "1.0.0-beta.1"
-  resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269"
-  integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==
+gensync@^1.0.0-beta.2:
+  version "1.0.0-beta.2"
+  resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
+  integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
+
+get-intrinsic@^1.0.2:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
+  integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
+  dependencies:
+    function-bind "^1.1.1"
+    has "^1.0.3"
+    has-symbols "^1.0.1"
 
 get-stdin@^4.0.1:
   version "4.0.1"
@@ -3764,6 +4054,13 @@
   dependencies:
     pump "^3.0.0"
 
+get-stream@^5.0.0, get-stream@^5.1.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
+  integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
+  dependencies:
+    pump "^3.0.0"
+
 get-value@^2.0.3, get-value@^2.0.6:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
@@ -3776,6 +4073,14 @@
   dependencies:
     assert-plus "^1.0.0"
 
+gh-got@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/gh-got/-/gh-got-5.0.0.tgz#ee95be37106fd8748a96f8d1db4baea89e1bfa8a"
+  integrity sha1-7pW+NxBv2HSKlvjR20uuqJ4b+oo=
+  dependencies:
+    got "^6.2.0"
+    is-plain-obj "^1.1.0"
+
 gh-got@^6.0.0:
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/gh-got/-/gh-got-6.0.0.tgz#d74353004c6ec466647520a10bd46f7299d268d0"
@@ -3784,6 +4089,13 @@
     got "^7.0.0"
     is-plain-obj "^1.1.0"
 
+github-username@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/github-username/-/github-username-3.0.0.tgz#0a772219b3130743429f2456d0bdd3db55dce7b1"
+  integrity sha1-CnciGbMTB0NCnyRW0L3T21Xc57E=
+  dependencies:
+    gh-got "^5.0.0"
+
 github-username@^4.0.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/github-username/-/github-username-4.1.0.tgz#cbe280041883206da4212ae9e4b5f169c30bf417"
@@ -3856,9 +4168,9 @@
     path-is-absolute "^1.0.0"
 
 glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
-  version "7.1.6"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
-  integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
+  version "7.1.7"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
+  integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
   dependencies:
     fs.realpath "^1.0.0"
     inflight "^1.0.4"
@@ -3958,6 +4270,37 @@
     pify "^3.0.0"
     slash "^1.0.0"
 
+globby@^9.2.0:
+  version "9.2.0"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d"
+  integrity sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==
+  dependencies:
+    "@types/glob" "^7.1.1"
+    array-union "^1.0.2"
+    dir-glob "^2.2.2"
+    fast-glob "^2.2.6"
+    glob "^7.1.3"
+    ignore "^4.0.3"
+    pify "^4.0.1"
+    slash "^2.0.0"
+
+got@^11.8.2:
+  version "11.8.2"
+  resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599"
+  integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==
+  dependencies:
+    "@sindresorhus/is" "^4.0.0"
+    "@szmarczak/http-timer" "^4.0.5"
+    "@types/cacheable-request" "^6.0.1"
+    "@types/responselike" "^1.0.0"
+    cacheable-lookup "^5.0.3"
+    cacheable-request "^7.0.1"
+    decompress-response "^6.0.0"
+    http2-wrapper "^1.0.0-beta.5.2"
+    lowercase-keys "^2.0.0"
+    p-cancelable "^2.0.0"
+    responselike "^2.0.0"
+
 got@^5.0.0:
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35"
@@ -3979,7 +4322,7 @@
     unzip-response "^1.0.2"
     url-parse-lax "^1.0.0"
 
-got@^6.7.1:
+got@^6.2.0, got@^6.7.1:
   version "6.7.1"
   resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
   integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=
@@ -4017,17 +4360,24 @@
     url-to-options "^1.0.1"
 
 graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.2.0:
-  version "4.2.3"
-  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
-  integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
+  version "4.2.8"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
+  integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
 
-grouped-queue@^0.3.0, grouped-queue@^0.3.3:
+grouped-queue@^0.3.0:
   version "0.3.3"
   resolved "https://registry.yarnpkg.com/grouped-queue/-/grouped-queue-0.3.3.tgz#c167d2a5319c5a0e0964ef6a25b7c2df8996c85c"
   integrity sha1-wWfSpTGcWg4JZO9qJbfC34mWyFw=
   dependencies:
     lodash "^4.17.2"
 
+grouped-queue@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/grouped-queue/-/grouped-queue-1.1.0.tgz#63e3f9ca90af952269d1d40879e41221eacc74cb"
+  integrity sha512-rZOFKfCqLhsu5VqjBjEWiwrYqJR07KxIkH4mLZlNlGDfntbb4FbMyGFP14TlvRPrU9S3Hnn/sgxbC5ZeN0no3Q==
+  dependencies:
+    lodash "^4.17.15"
+
 gulp-if@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/gulp-if/-/gulp-if-2.0.2.tgz#a497b7e7573005041caa2bc8b7dda3c80444d629"
@@ -4056,9 +4406,9 @@
     vinyl "^1.0.0"
 
 gunzip-maybe@^1.3.1:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/gunzip-maybe/-/gunzip-maybe-1.4.1.tgz#39c72ed89d1b49ba708e18776500488902a52027"
-  integrity sha512-qtutIKMthNJJgeHQS7kZ9FqDq59/Wn0G2HYCRNjpup7yKfVI6/eqwpmroyZGFoCYaG+sW6psNVb4zoLADHpp2g==
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz#b913564ae3be0eda6f3de36464837a9cd94b98ac"
+  integrity sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==
   dependencies:
     browserify-zlib "^0.1.4"
     is-deflate "^1.0.0"
@@ -4077,12 +4427,12 @@
   resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
   integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
 
-har-validator@~5.1.0:
-  version "5.1.3"
-  resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
-  integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
+har-validator@~5.1.0, har-validator@~5.1.3:
+  version "5.1.5"
+  resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
+  integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==
   dependencies:
-    ajv "^6.5.5"
+    ajv "^6.12.3"
     har-schema "^2.0.0"
 
 has-ansi@^2.0.0:
@@ -4124,10 +4474,10 @@
   resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455"
   integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==
 
-has-symbols@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
-  integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
+has-symbols@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
+  integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
 
 has-to-string-tag-x@^1.2.0:
   version "1.4.1"
@@ -4167,6 +4517,13 @@
     is-number "^3.0.0"
     kind-of "^4.0.0"
 
+has@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+  integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+  dependencies:
+    function-bind "^1.1.1"
+
 he@1.2.x:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
@@ -4180,9 +4537,9 @@
     parse-passwd "^1.0.0"
 
 hosted-git-info@^2.1.4:
-  version "2.8.5"
-  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c"
-  integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==
+  version "2.8.9"
+  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
+  integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
 
 hpack.js@^2.1.6:
   version "2.1.6"
@@ -4207,6 +4564,11 @@
     relateurl "0.2.x"
     uglify-js "3.4.x"
 
+http-cache-semantics@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
+  integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
+
 http-deceiver@^1.2.7:
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
@@ -4255,9 +4617,9 @@
     micromatch "^2.3.11"
 
 http-proxy@^1.16.2:
-  version "1.18.0"
-  resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.0.tgz#dbe55f63e75a347db7f3d99974f2692a314a6a3a"
-  integrity sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==
+  version "1.18.1"
+  resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
+  integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
   dependencies:
     eventemitter3 "^4.0.0"
     follow-redirects "^1.0.0"
@@ -4272,6 +4634,14 @@
     jsprim "^1.2.2"
     sshpk "^1.7.0"
 
+http2-wrapper@^1.0.0-beta.5.2:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d"
+  integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==
+  dependencies:
+    quick-lru "^5.1.1"
+    resolve-alpn "^1.0.0"
+
 https-proxy-agent@^2.2.1:
   version "2.2.4"
   resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
@@ -4280,13 +4650,18 @@
     agent-base "^4.3.0"
     debug "^3.1.0"
 
-https-proxy-agent@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz#b8c286433e87602311b01c8ea34413d856a4af81"
-  integrity sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==
+https-proxy-agent@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
+  integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
   dependencies:
-    agent-base "^4.3.0"
-    debug "^3.1.0"
+    agent-base "6"
+    debug "4"
+
+human-signals@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
+  integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
 
 iconv-lite@0.4.24, iconv-lite@^0.4.24:
   version "0.4.24"
@@ -4295,16 +4670,21 @@
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
-ieee754@^1.1.4:
-  version "1.1.13"
-  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
-  integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
+ieee754@^1.1.13:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
+  integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
 
 ignore@^3.3.5:
   version "3.3.10"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
   integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==
 
+ignore@^4.0.3:
+  version "4.0.6"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
+  integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
+
 import-lazy@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
@@ -4340,7 +4720,7 @@
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -4351,9 +4731,9 @@
   integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
 
 ini@^1.3.4, ini@~1.3.0:
-  version "1.3.5"
-  resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
-  integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
+  version "1.3.8"
+  resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
+  integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
 
 inquirer@^1.0.2:
   version "1.2.3"
@@ -4375,29 +4755,29 @@
     strip-ansi "^3.0.0"
     through "^2.3.6"
 
-inquirer@^6.0.0:
-  version "6.5.2"
-  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca"
-  integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==
+inquirer@^7.1.0:
+  version "7.3.3"
+  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003"
+  integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==
   dependencies:
-    ansi-escapes "^3.2.0"
-    chalk "^2.4.2"
-    cli-cursor "^2.1.0"
-    cli-width "^2.0.0"
+    ansi-escapes "^4.2.1"
+    chalk "^4.1.0"
+    cli-cursor "^3.1.0"
+    cli-width "^3.0.0"
     external-editor "^3.0.3"
-    figures "^2.0.0"
-    lodash "^4.17.12"
-    mute-stream "0.0.7"
-    run-async "^2.2.0"
-    rxjs "^6.4.0"
-    string-width "^2.1.0"
-    strip-ansi "^5.1.0"
+    figures "^3.0.0"
+    lodash "^4.17.19"
+    mute-stream "0.0.8"
+    run-async "^2.4.0"
+    rxjs "^6.6.0"
+    string-width "^4.1.0"
+    strip-ansi "^6.0.0"
     through "^2.3.6"
 
 interpret@^1.0.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296"
-  integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
+  integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
 
 intersect@^1.0.1:
   version "1.0.1"
@@ -4411,10 +4791,10 @@
   dependencies:
     loose-envify "^1.0.0"
 
-ipaddr.js@1.9.0:
-  version "1.9.0"
-  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
-  integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==
+ipaddr.js@1.9.1:
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
+  integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
 
 is-accessor-descriptor@^0.1.6:
   version "0.1.6"
@@ -4447,7 +4827,7 @@
   dependencies:
     binary-extensions "^1.0.0"
 
-is-buffer@^1.1.5, is-buffer@~1.1.1:
+is-buffer@^1.1.5, is-buffer@~1.1.6:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
   integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
@@ -4459,6 +4839,13 @@
   dependencies:
     ci-info "^1.5.0"
 
+is-core-module@^2.2.0:
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.6.0.tgz#d7553b2526fe59b92ba3e40c8df757ec8a709e19"
+  integrity sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==
+  dependencies:
+    has "^1.0.3"
+
 is-data-descriptor@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@@ -4531,11 +4918,9 @@
   integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
 
 is-finite@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
-  integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=
-  dependencies:
-    number-is-nan "^1.0.0"
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3"
+  integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==
 
 is-fullwidth-code-point@^1.0.0:
   version "1.0.0"
@@ -4549,6 +4934,11 @@
   resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
   integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
 
+is-fullwidth-code-point@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+  integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
 is-glob@^2.0.0, is-glob@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
@@ -4618,9 +5008,9 @@
   integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
 
 is-object@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470"
-  integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA=
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf"
+  integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==
 
 is-path-cwd@^1.0.0:
   version "1.0.0"
@@ -4653,12 +5043,10 @@
   dependencies:
     isobject "^3.0.1"
 
-is-plain-object@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.0.tgz#47bfc5da1b5d50d64110806c199359482e75a928"
-  integrity sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==
-  dependencies:
-    isobject "^4.0.0"
+is-plain-object@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
+  integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
 
 is-posix-bracket@^0.1.0:
   version "0.1.1"
@@ -4666,20 +5054,15 @@
   integrity sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=
 
 is-potential-custom-element-name@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397"
-  integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c=
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
+  integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
 
 is-primitive@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
   integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU=
 
-is-promise@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
-  integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=
-
 is-redirect@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
@@ -4702,12 +5085,17 @@
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
   integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
 
+is-stream@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
+  integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
+
 is-typedarray@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
   integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
 
-is-utf8@^0.2.0:
+is-utf8@^0.2.0, is-utf8@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
   integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
@@ -4749,6 +5137,11 @@
   dependencies:
     buffer-alloc "^1.2.0"
 
+isbinaryfile@^4.0.0:
+  version "4.0.8"
+  resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.8.tgz#5d34b94865bd4946633ecc78a026fc76c5b11fcf"
+  integrity sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==
+
 isexe@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -4766,17 +5159,12 @@
   resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
   integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
 
-isobject@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0"
-  integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==
-
 isstream@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
   integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
 
-istextorbinary@^2.2.1:
+istextorbinary@^2.2.1, istextorbinary@^2.5.1:
   version "2.6.0"
   resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-2.6.0.tgz#60776315fb0fa3999add276c02c69557b9ca28ab"
   integrity sha512-+XRlFseT8B3L9KyjxxLjfXSLMuErKDsd8DBNrsaxoViABMEZlOSCstwmw0qpoFX3+U6yWU1yhLudAe6/lETGGA==
@@ -4793,6 +5181,16 @@
     has-to-string-tag-x "^1.2.0"
     is-object "^1.0.1"
 
+jake@^10.6.1:
+  version "10.8.2"
+  resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b"
+  integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==
+  dependencies:
+    async "0.9.x"
+    chalk "^2.4.2"
+    filelist "^1.0.1"
+    minimatch "^3.0.4"
+
 jest-worker@^24.9.0:
   version "24.9.0"
   resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5"
@@ -4831,11 +5229,21 @@
   resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
   integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
 
+json-buffer@3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
+  integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
+
 json-parse-better-errors@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
   integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
 
+json-parse-even-better-errors@^2.3.0:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
+  integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
+
 json-schema-traverse@^0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@@ -4856,17 +5264,22 @@
   resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
   integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
 
-json5@^2.1.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.1.tgz#81b6cb04e9ba496f1c7005d07b4368a2638f90b6"
-  integrity sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==
+json5@^2.1.2:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
+  integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
   dependencies:
-    minimist "^1.2.0"
+    minimist "^1.2.5"
+
+jsonparse@^1.2.0:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
+  integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=
 
 jsonschema@^1.1.0, jsonschema@^1.1.1:
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.2.5.tgz#bab69d97fa28946aec0a56a9cc266d23fe80ae61"
-  integrity sha512-kVTF+08x25PQ0CjuVc0gRM9EUPb0Fe9Ln/utFOgcdxEIOHuU7ooBk/UPTd7t1M91pP35m0MU1T8M5P7vP1bRRw==
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.0.tgz#1afa34c4bc22190d8e42271ec17ac8b3404f87b2"
+  integrity sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw==
 
 jsprim@^1.2.2:
   version "1.4.1"
@@ -4878,6 +5291,13 @@
     json-schema "0.2.3"
     verror "1.10.0"
 
+keyv@^4.0.0:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254"
+  integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==
+  dependencies:
+    json-buffer "3.0.1"
+
 kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
@@ -4902,12 +5322,10 @@
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
   integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
 
-kuler@1.0.x:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/kuler/-/kuler-1.0.1.tgz#ef7c784f36c9fb6e16dd3150d152677b2b0228a6"
-  integrity sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==
-  dependencies:
-    colornames "^1.1.1"
+kuler@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3"
+  integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==
 
 latest-version@^2.0.0:
   version "2.0.0"
@@ -4937,6 +5355,13 @@
     rimraf "^3.0.0"
     underscore "^1.8.3"
 
+lazy-cache@^2.0.1:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264"
+  integrity sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=
+  dependencies:
+    set-getter "^0.1.0"
+
 lazy-req@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/lazy-req/-/lazy-req-1.1.0.tgz#bdaebead30f8d824039ce0ce149d4daa07ba1fac"
@@ -4949,6 +5374,11 @@
   dependencies:
     readable-stream "^2.0.5"
 
+lines-and-columns@^1.1.6:
+  version "1.1.6"
+  resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
+  integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
+
 load-json-file@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
@@ -5018,6 +5448,16 @@
   resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
   integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
 
+lodash.mapvalues@^4.6.0:
+  version "4.6.0"
+  resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c"
+  integrity sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=
+
+lodash.merge@^4.6.2:
+  version "4.6.2"
+  resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
+  integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+
 lodash.padend@^4.6.1:
   version "4.6.1"
   resolved "https://registry.yarnpkg.com/lodash.padend/-/lodash.padend-4.6.1.tgz#53ccba047d06e158d311f45da625f4e49e6f166e"
@@ -5058,15 +5498,10 @@
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
   integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
 
-lodash@^3.0.0, lodash@^3.10.1:
-  version "3.10.1"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
-  integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=
-
-lodash@^4.0.0, lodash@^4.11.1, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.3.0:
-  version "4.17.15"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
-  integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
+lodash@4.17.21, lodash@^3.0.0, lodash@^3.10.1, lodash@^4.0.0, lodash@^4.11.1, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.3.0:
+  version "4.17.21"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+  integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
 
 log-symbols@^1.0.0, log-symbols@^1.0.1:
   version "1.0.2"
@@ -5093,14 +5528,14 @@
     ms "^2.1.1"
     triple-beam "^1.2.0"
 
-logform@^2.1.1:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/logform/-/logform-2.1.2.tgz#957155ebeb67a13164069825ce67ddb5bb2dd360"
-  integrity sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==
+logform@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/logform/-/logform-2.2.0.tgz#40f036d19161fc76b68ab50fdc7fe495544492f2"
+  integrity sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==
   dependencies:
     colors "^1.2.1"
     fast-safe-stringify "^2.0.4"
-    fecha "^2.3.3"
+    fecha "^4.2.0"
     ms "^2.1.1"
     triple-beam "^1.3.0"
 
@@ -5139,6 +5574,11 @@
   resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
   integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
 
+lowercase-keys@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
+  integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
+
 lru-cache@^4.0.1, lru-cache@^4.0.2:
   version "4.1.5"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
@@ -5147,10 +5587,17 @@
     pseudomap "^1.0.2"
     yallist "^2.1.2"
 
+lru-cache@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+  integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+  dependencies:
+    yallist "^4.0.0"
+
 macos-release@^2.2.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f"
-  integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA==
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.5.0.tgz#067c2c88b5f3fb3c56a375b2ec93826220fa1ff2"
+  integrity sha512-EIgv+QZ9r+814gjJj0Bt5vSLJLzswGmSUbUpbi9AIr/fsN2IWFBl2NucV9PAiek+U1STK468tEkxmVYUtuAN3g==
 
 magic-string@^0.22.4:
   version "0.22.5"
@@ -5166,6 +5613,13 @@
   dependencies:
     pify "^3.0.0"
 
+make-dir@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
+  integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
+  dependencies:
+    semver "^6.0.0"
+
 map-cache@^0.2.2:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
@@ -5196,13 +5650,13 @@
   integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==
 
 md5@^2.2.1:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9"
-  integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f"
+  integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==
   dependencies:
-    charenc "~0.0.1"
-    crypt "~0.0.1"
-    is-buffer "~1.1.1"
+    charenc "0.0.2"
+    crypt "0.0.2"
+    is-buffer "~1.1.6"
 
 media-typer@0.3.0:
   version "0.3.0"
@@ -5226,16 +5680,50 @@
     through2 "^2.0.0"
     vinyl "^2.0.1"
 
-mem-fs@^1.1.0:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/mem-fs/-/mem-fs-1.1.3.tgz#b8ae8d2e3fcb6f5d3f9165c12d4551a065d989cc"
-  integrity sha1-uK6NLj/Lb10/kWXBLUVRoGXZicw=
+mem-fs-editor@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/mem-fs-editor/-/mem-fs-editor-6.0.0.tgz#d63607cf0a52fe6963fc376c6a7aa52db3edabab"
+  integrity sha512-e0WfJAMm8Gv1mP5fEq/Blzy6Lt1VbLg7gNnZmZak7nhrBTibs+c6nQ4SKs/ZyJYHS1mFgDJeopsLAv7Ow0FMFg==
   dependencies:
-    through2 "^2.0.0"
-    vinyl "^1.1.0"
-    vinyl-file "^2.0.0"
+    commondir "^1.0.1"
+    deep-extend "^0.6.0"
+    ejs "^2.6.1"
+    glob "^7.1.4"
+    globby "^9.2.0"
+    isbinaryfile "^4.0.0"
+    mkdirp "^0.5.0"
+    multimatch "^4.0.0"
+    rimraf "^2.6.3"
+    through2 "^3.0.1"
+    vinyl "^2.2.0"
 
-meow@^3.1.0, meow@^3.7.0:
+mem-fs-editor@^7.0.1:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/mem-fs-editor/-/mem-fs-editor-7.1.0.tgz#2a16f143228df87bf918874556723a7ee73bfe88"
+  integrity sha512-BH6QEqCXSqGeX48V7zu+e3cMwHU7x640NB8Zk8VNvVZniz+p4FK60pMx/3yfkzo6miI6G3a8pH6z7FeuIzqrzA==
+  dependencies:
+    commondir "^1.0.1"
+    deep-extend "^0.6.0"
+    ejs "^3.1.5"
+    glob "^7.1.4"
+    globby "^9.2.0"
+    isbinaryfile "^4.0.0"
+    mkdirp "^1.0.0"
+    multimatch "^4.0.0"
+    rimraf "^3.0.0"
+    through2 "^3.0.2"
+    vinyl "^2.2.1"
+
+mem-fs@^1.1.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/mem-fs/-/mem-fs-1.2.0.tgz#5f29b2d02a5875cd14cd836c388385892d556cde"
+  integrity sha512-b8g0jWKdl8pM0LqAPdK9i8ERL7nYrzmJfRhxMiWH2uYdfYnb7uXnmwVb0ZGe7xyEl4lj+nLIU3yf4zPUT+XsVQ==
+  dependencies:
+    through2 "^3.0.0"
+    vinyl "^2.0.1"
+    vinyl-file "^3.0.0"
+
+meow@^3.7.0:
   version "3.7.0"
   resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
   integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=
@@ -5269,9 +5757,9 @@
   integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
 
 merge2@^1.2.3:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81"
-  integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
+  integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
 
 methods@~1.1.2:
   version "1.1.2"
@@ -5316,17 +5804,17 @@
     snapdragon "^0.8.1"
     to-regex "^3.0.2"
 
-mime-db@1.43.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.28.0:
-  version "1.43.0"
-  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
-  integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==
+mime-db@1.49.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.28.0:
+  version "1.49.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed"
+  integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==
 
 mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24:
-  version "2.1.26"
-  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
-  integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==
+  version "2.1.32"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5"
+  integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==
   dependencies:
-    mime-db "1.43.0"
+    mime-db "1.49.0"
 
 mime@1.4.1:
   version "1.4.1"
@@ -5339,20 +5827,25 @@
   integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
 
 mime@^2.3.1:
-  version "2.4.4"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5"
-  integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==
+  version "2.5.2"
+  resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe"
+  integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==
 
-mimic-fn@^1.0.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
-  integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
+mimic-fn@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
+  integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
 
 mimic-response@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
   integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
 
+mimic-response@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
+  integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
+
 minimalistic-assert@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
@@ -5372,20 +5865,15 @@
   dependencies:
     brace-expansion "^1.1.7"
 
-minimist@0.0.8:
-  version "0.0.8"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
-  integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
+minimist@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.1.tgz#827ba4e7593464e7c221e8c5bed930904ee2c455"
+  integrity sha512-GY8fANSrTMfBVfInqJAY41QkOM+upUTytK1jZ0c8+3HdHrJxBJ3rF5i9moClXTE8uUSnUo8cAsCoxDXvSY4DHg==
 
-minimist@^1.1.3, minimist@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
-  integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
-
-minimist@~0.0.1:
-  version "0.0.10"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
-  integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
+minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5:
+  version "1.2.5"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
+  integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
 
 mixin-deep@^1.2.0:
   version "1.3.2"
@@ -5395,12 +5883,22 @@
     for-in "^1.0.2"
     is-extendable "^1.0.1"
 
-mkdirp@^0.5.0, mkdirp@^0.5.1:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
-  integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
+mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.4:
+  version "0.5.5"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
+  integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
   dependencies:
-    minimist "0.0.8"
+    minimist "^1.2.5"
+
+mkdirp@^1.0.0, mkdirp@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
+  integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
+
+moment@^2.15.1, moment@^2.24.0:
+  version "2.29.1"
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
+  integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
 
 mout@^1.0.0:
   version "1.2.2"
@@ -5417,20 +5915,25 @@
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
   integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
 
-ms@^2.1.1:
+ms@2.1.2:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
   integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
 
+ms@^2.1.1:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+  integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
 multer@^1.3.0:
-  version "1.4.2"
-  resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.2.tgz#2f1f4d12dbaeeba74cb37e623f234bf4d3d2057a"
-  integrity sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==
+  version "1.4.3"
+  resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.3.tgz#4db352d6992e028ac0eacf7be45c6efd0264297b"
+  integrity sha512-np0YLKncuZoTzufbkM6wEKp68EhWJXcU6fq6QqrSwkckd2LlMgd1UqhUJLj6NS/5sZ8dE8LYDWslsltJznnXlg==
   dependencies:
     append-field "^1.0.0"
     busboy "^0.2.11"
     concat-stream "^1.5.2"
-    mkdirp "^0.5.1"
+    mkdirp "^0.5.4"
     object-assign "^4.1.1"
     on-finished "^2.3.0"
     type-is "^1.6.4"
@@ -5446,6 +5949,17 @@
     arrify "^1.0.0"
     minimatch "^3.0.0"
 
+multimatch@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-4.0.0.tgz#8c3c0f6e3e8449ada0af3dd29efb491a375191b3"
+  integrity sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==
+  dependencies:
+    "@types/minimatch" "^3.0.3"
+    array-differ "^3.0.0"
+    array-union "^2.1.0"
+    arrify "^2.0.1"
+    minimatch "^3.0.4"
+
 multipipe@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-1.0.2.tgz#cc13efd833c9cda99f224f868461b8e1a3fd939d"
@@ -5459,10 +5973,10 @@
   resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db"
   integrity sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=
 
-mute-stream@0.0.7:
-  version "0.0.7"
-  resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
-  integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
+mute-stream@0.0.8:
+  version "0.0.8"
+  resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
+  integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
 
 mz@^2.4.0, mz@^2.6.0:
   version "2.7.0"
@@ -5474,9 +5988,9 @@
     thenify-all "^1.0.0"
 
 nan@^2.12.1:
-  version "2.14.0"
-  resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
-  integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
+  version "2.15.0"
+  resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
+  integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
 
 nanomatch@^1.2.9:
   version "1.2.13"
@@ -5517,10 +6031,15 @@
   dependencies:
     lower-case "^1.1.1"
 
-node-fetch@^2.3.0:
-  version "2.6.0"
-  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
-  integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
+node-fetch@^2.6.0, node-fetch@^2.6.1:
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
+  integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
+
+node-releases@^1.1.75:
+  version "1.1.75"
+  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.75.tgz#6dd8c876b9897a1b8e5a02de26afa79bb54ebbfe"
+  integrity sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==
 
 node-status-codes@^1.0.0:
   version "1.0.0"
@@ -5535,7 +6054,7 @@
     chalk "~0.4.0"
     underscore "~1.6.0"
 
-normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
+normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.5.0:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
   integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
@@ -5557,6 +6076,23 @@
   resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
   integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
 
+normalize-url@^6.0.1:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
+  integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
+
+npm-api@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/npm-api/-/npm-api-1.0.1.tgz#3def9b51afedca57db14ca0c970d92442d21c9c5"
+  integrity sha512-4sITrrzEbPcr0aNV28QyOmgn6C9yKiF8k92jn4buYAK8wmA5xo1qL3II5/gT1r7wxbXBflSduZ2K3FbtOrtGkA==
+  dependencies:
+    JSONStream "^1.3.5"
+    clone-deep "^4.0.1"
+    download-stats "^0.3.4"
+    moment "^2.24.0"
+    node-fetch "^2.6.0"
+    paged-request "^2.0.1"
+
 npm-run-path@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -5564,6 +6100,13 @@
   dependencies:
     path-key "^2.0.0"
 
+npm-run-path@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
+  integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
+  dependencies:
+    path-key "^3.0.0"
+
 number-is-nan@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
@@ -5579,11 +6122,6 @@
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
   integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
 
-object-component@0.0.3:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291"
-  integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=
-
 object-copy@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
@@ -5593,7 +6131,7 @@
     define-property "^0.2.5"
     kind-of "^3.0.3"
 
-object-keys@^1.0.11, object-keys@^1.0.12:
+object-keys@^1.0.12, object-keys@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
   integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
@@ -5606,14 +6144,14 @@
     isobject "^3.0.0"
 
 object.assign@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
-  integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
+  integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
   dependencies:
-    define-properties "^1.1.2"
-    function-bind "^1.1.1"
-    has-symbols "^1.0.0"
-    object-keys "^1.0.11"
+    call-bind "^1.0.0"
+    define-properties "^1.1.3"
+    has-symbols "^1.0.1"
+    object-keys "^1.1.1"
 
 object.omit@^2.0.0:
   version "2.0.1"
@@ -5659,22 +6197,24 @@
   dependencies:
     wrappy "1"
 
-one-time@0.0.4:
-  version "0.0.4"
-  resolved "https://registry.yarnpkg.com/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e"
-  integrity sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=
+one-time@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45"
+  integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==
+  dependencies:
+    fn.name "1.x.x"
 
 onetime@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
   integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=
 
-onetime@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
-  integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=
+onetime@^5.1.0:
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
+  integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
   dependencies:
-    mimic-fn "^1.0.0"
+    mimic-fn "^2.1.0"
 
 opn@^3.0.2:
   version "3.0.3"
@@ -5683,14 +6223,6 @@
   dependencies:
     object-assign "^4.0.1"
 
-optimist@^0.6.1:
-  version "0.6.1"
-  resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
-  integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY=
-  dependencies:
-    minimist "~0.0.1"
-    wordwrap "~0.0.2"
-
 ordered-read-streams@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b"
@@ -5735,15 +6267,20 @@
   resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa"
   integrity sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==
 
+p-cancelable@^2.0.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf"
+  integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==
+
 p-finally@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
   integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
 
 p-limit@^2.0.0:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e"
-  integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
+  integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
   dependencies:
     p-try "^2.0.0"
 
@@ -5791,6 +6328,13 @@
     registry-url "^3.0.3"
     semver "^5.1.0"
 
+paged-request@^2.0.1:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/paged-request/-/paged-request-2.0.2.tgz#4d621a08b8d6bee4440a0a92112354eeece5b5b0"
+  integrity sha512-NWrGqneZImDdcMU/7vMcAOo1bIi5h/pmpJqe7/jdsy85BA/s5MSaU/KlpxwW/IVPmIwBcq2uKPrBWWhEWhtxag==
+  dependencies:
+    axios "^0.21.1"
+
 pako@~0.2.0:
   version "0.2.9"
   resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
@@ -5828,6 +6372,16 @@
     error-ex "^1.3.1"
     json-parse-better-errors "^1.0.1"
 
+parse-json@^5.0.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
+  integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
+  dependencies:
+    "@babel/code-frame" "^7.0.0"
+    error-ex "^1.3.1"
+    json-parse-even-better-errors "^2.3.0"
+    lines-and-columns "^1.1.6"
+
 parse-passwd@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
@@ -5863,19 +6417,15 @@
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"
   integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==
 
-parseqs@0.0.5:
-  version "0.0.5"
-  resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
-  integrity sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=
-  dependencies:
-    better-assert "~1.0.0"
+parseqs@0.0.6:
+  version "0.0.6"
+  resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5"
+  integrity sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==
 
-parseuri@0.0.5:
-  version "0.0.5"
-  resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a"
-  integrity sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=
-  dependencies:
-    better-assert "~1.0.0"
+parseuri@0.0.6:
+  version "0.0.6"
+  resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.6.tgz#e1496e829e3ac2ff47f39a4dd044b32823c4a25a"
+  integrity sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==
 
 parseurl@~1.3.3:
   version "1.3.3"
@@ -5919,10 +6469,15 @@
   resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
   integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
 
+path-key@^3.0.0, path-key@^3.1.0:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
+  integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
 path-parse@^1.0.6:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
-  integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
+  integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
 
 path-to-regexp@0.1.7:
   version "0.1.7"
@@ -6312,15 +6867,10 @@
   resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9"
   integrity sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=
 
-pretty-bytes@^5.1.0:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2"
-  integrity sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg==
-
-private@^0.1.6:
-  version "0.1.8"
-  resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
-  integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==
+pretty-bytes@^5.1.0, pretty-bytes@^5.2.0:
+  version "5.6.0"
+  resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
+  integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==
 
 process-nextick-args@^2.0.0, process-nextick-args@~2.0.0:
   version "2.0.1"
@@ -6352,22 +6902,22 @@
     long "^4.0.0"
 
 proxy-addr@~2.0.5:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34"
-  integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==
+  version "2.0.7"
+  resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
+  integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
   dependencies:
-    forwarded "~0.1.2"
-    ipaddr.js "1.9.0"
+    forwarded "0.2.0"
+    ipaddr.js "1.9.1"
 
 pseudomap@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
   integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
 
-psl@^1.1.24:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c"
-  integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==
+psl@^1.1.24, psl@^1.1.28:
+  version "1.8.0"
+  resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
+  integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
 
 pump@^1.0.0:
   version "1.0.3"
@@ -6407,7 +6957,7 @@
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
   integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
 
-punycode@^2.1.0:
+punycode@^2.1.0, punycode@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
   integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
@@ -6427,6 +6977,11 @@
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
   integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
 
+quick-lru@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
+  integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
+
 randomatic@^3.0.0:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed"
@@ -6436,6 +6991,13 @@
     kind-of "^6.0.0"
     math-random "^1.0.1"
 
+randombytes@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
+  integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
+  dependencies:
+    safe-buffer "^5.1.0"
+
 range-parser@~1.2.0, range-parser@~1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
@@ -6469,7 +7031,7 @@
     pinkie-promise "^2.0.0"
     readable-stream "^2.0.0"
 
-read-chunk@^3.0.0:
+read-chunk@^3.0.0, read-chunk@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-3.2.0.tgz#2984afe78ca9bfbbdb74b19387bf9e86289c16ca"
   integrity sha512-CEjy9LCzhmD7nUpJ1oVOE6s/hBkejlcJEgLQHVnQznOSilOPb+kpKktlLfFDK3/WP43+F80xkUTM2VOkYoSYvQ==
@@ -6493,6 +7055,14 @@
     find-up "^3.0.0"
     read-pkg "^3.0.0"
 
+read-pkg-up@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-5.0.0.tgz#b6a6741cb144ed3610554f40162aa07a6db621b8"
+  integrity sha512-XBQjqOBtTzyol2CpsQOw8LHV0XbDZVG7xMMjmXAJomlVY03WOBRmYgDJETlvcg0H63AJvPRwT7GFi5rvOzUOKg==
+  dependencies:
+    find-up "^3.0.0"
+    read-pkg "^5.0.0"
+
 read-pkg@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
@@ -6511,6 +7081,16 @@
     normalize-package-data "^2.3.2"
     path-type "^3.0.0"
 
+read-pkg@^5.0.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc"
+  integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==
+  dependencies:
+    "@types/normalize-package-data" "^2.4.0"
+    normalize-package-data "^2.5.0"
+    parse-json "^5.0.0"
+    type-fest "^0.6.0"
+
 readable-stream@1.1.x:
   version "1.1.14"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
@@ -6521,10 +7101,10 @@
     isarray "0.0.1"
     string_decoder "~0.10.x"
 
-"readable-stream@2 || 3", readable-stream@^3.0.1, readable-stream@^3.1.1, readable-stream@^3.4.0:
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.5.0.tgz#465d70e6d1087f6162d079cd0b5db7fbebfd1606"
-  integrity sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA==
+"readable-stream@2 || 3", readable-stream@^3.1.1, readable-stream@^3.4.0:
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
+  integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
   dependencies:
     inherits "^2.0.3"
     string_decoder "^1.1.1"
@@ -6540,7 +7120,7 @@
     isarray "0.0.1"
     string_decoder "~0.10.x"
 
-readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
+readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@^2.3.7, readable-stream@~2.3.6:
   version "2.3.7"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
   integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
@@ -6582,29 +7162,34 @@
   resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-1.0.1.tgz#258c78efd153ddf93cb561237f61184f3696e327"
   integrity sha1-JYx479FT3fk8tWEjf2EYTzaW4yc=
 
-regenerate-unicode-properties@^8.1.0:
-  version "8.1.0"
-  resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e"
-  integrity sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==
+regenerate-unicode-properties@^8.2.0:
+  version "8.2.0"
+  resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
+  integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==
   dependencies:
     regenerate "^1.4.0"
 
 regenerate@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
-  integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
+  integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
 
 regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1:
   version "0.11.1"
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
   integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
 
-regenerator-transform@^0.14.0:
-  version "0.14.1"
-  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.1.tgz#3b2fce4e1ab7732c08f665dfdb314749c7ddd2fb"
-  integrity sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==
+regenerator-runtime@^0.13.4:
+  version "0.13.9"
+  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
+  integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
+
+regenerator-transform@^0.14.2:
+  version "0.14.5"
+  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4"
+  integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==
   dependencies:
-    private "^0.1.6"
+    "@babel/runtime" "^7.8.4"
 
 regex-cache@^0.4.2:
   version "0.4.4"
@@ -6621,17 +7206,17 @@
     extend-shallow "^3.0.2"
     safe-regex "^1.1.0"
 
-regexpu-core@^4.6.0:
-  version "4.6.0"
-  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.6.0.tgz#2037c18b327cfce8a6fea2a4ec441f2432afb8b6"
-  integrity sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==
+regexpu-core@^4.7.1:
+  version "4.7.1"
+  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6"
+  integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==
   dependencies:
     regenerate "^1.4.0"
-    regenerate-unicode-properties "^8.1.0"
-    regjsgen "^0.5.0"
-    regjsparser "^0.6.0"
+    regenerate-unicode-properties "^8.2.0"
+    regjsgen "^0.5.1"
+    regjsparser "^0.6.4"
     unicode-match-property-ecmascript "^1.0.4"
-    unicode-match-property-value-ecmascript "^1.1.0"
+    unicode-match-property-value-ecmascript "^1.2.0"
 
 registry-auth-token@^3.0.1:
   version "3.4.0"
@@ -6648,15 +7233,15 @@
   dependencies:
     rc "^1.0.1"
 
-regjsgen@^0.5.0:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c"
-  integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==
+regjsgen@^0.5.1:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733"
+  integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==
 
-regjsparser@^0.6.0:
-  version "0.6.2"
-  resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.2.tgz#fd62c753991467d9d1ffe0a9f67f27a529024b96"
-  integrity sha512-E9ghzUtoLwDekPT0DYCp+c4h+bvuUpe6rRHCTYn6eGoqj1LgKXxT6I0Il4WbjhQkOghzi/V+y03bPKvbllL93Q==
+regjsparser@^0.6.4:
+  version "0.6.9"
+  resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.9.tgz#b489eef7c9a2ce43727627011429cf833a7183e6"
+  integrity sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ==
   dependencies:
     jsesc "~0.5.0"
 
@@ -6671,9 +7256,9 @@
   integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8=
 
 repeat-element@^1.1.2:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
-  integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9"
+  integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==
 
 repeat-string@^1.5.2, repeat-string@^1.6.1:
   version "1.6.1"
@@ -6693,11 +7278,11 @@
   integrity sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=
 
 replace-ext@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb"
-  integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a"
+  integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==
 
-request@2.88.0, request@^2.72.0, request@^2.85.0:
+request@2.88.0:
   version "2.88.0"
   resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
   integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
@@ -6723,6 +7308,32 @@
     tunnel-agent "^0.6.0"
     uuid "^3.3.2"
 
+request@^2.72.0, request@^2.85.0:
+  version "2.88.2"
+  resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
+  integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
+  dependencies:
+    aws-sign2 "~0.7.0"
+    aws4 "^1.8.0"
+    caseless "~0.12.0"
+    combined-stream "~1.0.6"
+    extend "~3.0.2"
+    forever-agent "~0.6.1"
+    form-data "~2.3.2"
+    har-validator "~5.1.3"
+    http-signature "~1.2.0"
+    is-typedarray "~1.0.0"
+    isstream "~0.1.2"
+    json-stringify-safe "~5.0.1"
+    mime-types "~2.1.19"
+    oauth-sign "~0.9.0"
+    performance-now "^2.1.0"
+    qs "~6.5.2"
+    safe-buffer "^5.1.2"
+    tough-cookie "~2.5.0"
+    tunnel-agent "^0.6.0"
+    uuid "^3.3.2"
+
 requirejs@^2.3.4:
   version "2.3.6"
   resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.6.tgz#e5093d9601c2829251258c0b9445d4d19fa9e7c9"
@@ -6733,6 +7344,11 @@
   resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
   integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
 
+resolve-alpn@^1.0.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9"
+  integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==
+
 resolve-dir@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e"
@@ -6754,13 +7370,21 @@
   resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
   integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
 
-resolve@^1.1.6, resolve@^1.10.0, resolve@^1.11.1, resolve@^1.3.2, resolve@^1.5.0:
-  version "1.15.0"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.0.tgz#1b7ca96073ebb52e741ffd799f6b39ea462c67f5"
-  integrity sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==
+resolve@^1.1.6, resolve@^1.10.0, resolve@^1.11.1, resolve@^1.5.0:
+  version "1.20.0"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
+  integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
   dependencies:
+    is-core-module "^2.2.0"
     path-parse "^1.0.6"
 
+responselike@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723"
+  integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==
+  dependencies:
+    lowercase-keys "^2.0.0"
+
 restore-cursor@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
@@ -6769,12 +7393,12 @@
     exit-hook "^1.0.0"
     onetime "^1.0.0"
 
-restore-cursor@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
-  integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368=
+restore-cursor@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
+  integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
   dependencies:
-    onetime "^2.0.0"
+    onetime "^5.1.0"
     signal-exit "^3.0.2"
 
 ret@~0.1.10:
@@ -6782,7 +7406,7 @@
   resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
   integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
 
-rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2:
+rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
   version "2.7.1"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
   integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
@@ -6790,9 +7414,9 @@
     glob "^7.1.3"
 
 rimraf@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.0.tgz#614176d4b3010b75e5c390eb0ee96f6dc0cebb9b"
-  integrity sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
+  integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
   dependencies:
     glob "^7.1.3"
 
@@ -6815,14 +7439,14 @@
     rollup-pluginutils "^2.8.1"
 
 rollup-plugin-terser@^5.1.3:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-5.2.0.tgz#ba758adf769347b7f1eaf9ef35978d2e207dccc7"
-  integrity sha512-jQI+nYhtDBc9HFRBz8iGttQg7li9klmzR62RG2W2nN6hJ/FI2K2ItYQ7kJ7/zn+vs+BP1AEccmVRjRN989I+Nw==
+  version "5.3.1"
+  resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz#8c650062c22a8426c64268548957463bf981b413"
+  integrity sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w==
   dependencies:
     "@babel/code-frame" "^7.5.5"
     jest-worker "^24.9.0"
     rollup-pluginutils "^2.8.2"
-    serialize-javascript "^2.1.2"
+    serialize-javascript "^4.0.0"
     terser "^4.6.2"
 
 rollup-pluginutils@^2.8.1, rollup-pluginutils@^2.8.2:
@@ -6833,37 +7457,35 @@
     estree-walker "^0.6.1"
 
 rollup@^1.3.0:
-  version "1.30.0"
-  resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.30.0.tgz#ae9c893804e8eaa8f8f74b0aaf7e7fb4374a9d01"
-  integrity sha512-ANcmfaSQwpcJtZUTA0ZMNBtFcQ1B4A5FldlNqEK0WdWm9sHSKu93ffa2KV1ux8HA/yKIV/ZARV28m7rNdXJgEw==
+  version "1.32.1"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.32.1.tgz#4480e52d9d9e2ae4b46ba0d9ddeaf3163940f9c4"
+  integrity sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==
   dependencies:
     "@types/estree" "*"
     "@types/node" "*"
     acorn "^7.1.0"
 
 rollup@^2.3.4:
-  version "2.35.1"
-  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.35.1.tgz#e6bc8d10893556a638066f89e8c97f422d03968c"
-  integrity sha512-q5KxEyWpprAIcainhVy6HfRttD9kutQpHbeqDTWnqAFNJotiojetK6uqmcydNMymBEtC4I8bCYR+J3mTMqeaUA==
+  version "2.56.3"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.56.3.tgz#b63edadd9851b0d618a6d0e6af8201955a77aeff"
+  integrity sha512-Au92NuznFklgQCUcV96iXlxUbHuB1vQMaH76DHl5M11TotjOHwqk9CwcrT78+Tnv4FN9uTBxq6p4EJoYkpyekg==
   optionalDependencies:
-    fsevents "~2.1.2"
+    fsevents "~2.3.2"
 
-run-async@^2.0.0, run-async@^2.2.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
-  integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA=
-  dependencies:
-    is-promise "^2.1.0"
+run-async@^2.0.0, run-async@^2.2.0, run-async@^2.4.0:
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
+  integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==
 
 rx@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
   integrity sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=
 
-rxjs@^6.4.0:
-  version "6.5.4"
-  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
-  integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==
+rxjs@^6.4.0, rxjs@^6.6.0:
+  version "6.6.7"
+  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
+  integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
   dependencies:
     tslib "^1.9.0"
 
@@ -6872,10 +7494,10 @@
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
 
-safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
-  integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
+safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+  integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
 
 safe-regex@^1.1.0:
   version "1.1.0"
@@ -6895,13 +7517,13 @@
   integrity sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==
 
 sauce-connect-launcher@^1.0.0:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/sauce-connect-launcher/-/sauce-connect-launcher-1.3.1.tgz#31137f57b0f7176e1c0525b7fb09c6da746647cf"
-  integrity sha512-vIf9qDol3q2FlYzrKt0dr3kvec6LSjX2WS+/mVnAJIhqh1evSkPKCR2AzcJrnSmx9Xt9PtV0tLY7jYh0wsQi8A==
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/sauce-connect-launcher/-/sauce-connect-launcher-1.3.2.tgz#dfc675a258550809a8eaf457eb9162b943ddbaf0"
+  integrity sha512-wf0coUlidJ7rmeClgVVBh6Kw55/yalZCY/Un5RgjSnTXRAeGqagnTsTYpZaqC4dCtrY4myuYpOAZXCdbO7lHfQ==
   dependencies:
     adm-zip "~0.4.3"
     async "^2.1.2"
-    https-proxy-agent "^3.0.0"
+    https-proxy-agent "^5.0.0"
     lodash "^4.16.6"
     rimraf "^2.5.4"
 
@@ -6916,22 +7538,21 @@
   integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=
 
 selenium-standalone@^6.7.0:
-  version "6.17.0"
-  resolved "https://registry.yarnpkg.com/selenium-standalone/-/selenium-standalone-6.17.0.tgz#0f24b691836205ee9bc3d7a6f207ebcb28170cd9"
-  integrity sha512-5PSnDHwMiq+OCiAGlhwQ8BM9xuwFfvBOZ7Tfbw+ifkTnOy0PWbZmI1B9gPGuyGHpbQ/3J3CzIK7BYwrQ7EjtWQ==
+  version "6.24.0"
+  resolved "https://registry.yarnpkg.com/selenium-standalone/-/selenium-standalone-6.24.0.tgz#cca7c1c36bfa3429078a8e6a1a4fd373f641a7c8"
+  integrity sha512-Dun2XgNAgCfJNrrSzuv7Z7Wj7QTvBKpqx0VXFz7bW9T9FUe5ytzgzoCEEshwDVMh0Dv6sCgdZg7VDhM/q2yPPQ==
   dependencies:
-    async "^2.6.2"
-    commander "^2.19.0"
-    cross-spawn "^6.0.5"
-    debug "^4.1.1"
-    lodash "^4.17.11"
-    minimist "^1.2.0"
-    mkdirp "^0.5.1"
+    commander "^2.20.3"
+    cross-spawn "^7.0.3"
+    debug "^4.3.1"
+    got "^11.8.2"
+    lodash.mapvalues "^4.6.0"
+    lodash.merge "^4.6.2"
+    minimist "^1.2.5"
+    mkdirp "^1.0.4"
     progress "2.0.3"
-    request "2.88.0"
-    tar-stream "2.0.0"
-    urijs "^1.19.1"
-    which "^1.3.1"
+    tar-stream "2.2.0"
+    which "^2.0.2"
     yauzl "^2.10.0"
 
 semver-diff@^2.0.0:
@@ -6941,7 +7562,7 @@
   dependencies:
     semver "^5.0.3"
 
-"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0:
+"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.5.0:
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
   integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@@ -6951,11 +7572,18 @@
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
   integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
 
-semver@^6.3.0:
+semver@^6.0.0, semver@^6.3.0:
   version "6.3.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
 
+semver@^7.1.3, semver@^7.2.1:
+  version "7.3.5"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
+  integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
+  dependencies:
+    lru-cache "^6.0.0"
+
 send@0.17.1:
   version "0.17.1"
   resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
@@ -6994,10 +7622,12 @@
     range-parser "~1.2.0"
     statuses "~1.4.0"
 
-serialize-javascript@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
-  integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==
+serialize-javascript@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
+  integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==
+  dependencies:
+    randombytes "^2.1.0"
 
 serve-static@1.14.1:
   version "1.14.1"
@@ -7019,6 +7649,13 @@
   resolved "https://registry.yarnpkg.com/serviceworker-cache-polyfill/-/serviceworker-cache-polyfill-4.0.0.tgz#de19ee73bef21ab3c0740a37b33db62464babdeb"
   integrity sha1-3hnuc77yGrPAdAo3sz22JGS6ves=
 
+set-getter@^0.1.0:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.1.tgz#a3110e1b461d31a9cfc8c5c9ee2e9737ad447102"
+  integrity sha512-9sVWOy+gthr+0G9DzqqLaYNA7+5OKkSmcqjL9cBpDEaZrr3ShQlyX2cZ/O/ozE41oxn/Tt0LGEM/w4Rub3A3gw==
+  dependencies:
+    to-object-path "^0.3.0"
+
 set-value@^2.0.0, set-value@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
@@ -7044,6 +7681,13 @@
   resolved "https://registry.yarnpkg.com/shady-css-parser/-/shady-css-parser-0.1.0.tgz#534dc79c8ca5884c5ed92a4e5a13d6d863bca428"
   integrity sha512-irfJUUkEuDlNHKZNAp2r7zOyMlmbfVJ+kWSfjlCYYUx/7dJnANLCyTzQZsuxy5NJkvtNwSxY5Gj8MOlqXUQPyA==
 
+shallow-clone@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
+  integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
+  dependencies:
+    kind-of "^6.0.2"
+
 shebang-command@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -7051,24 +7695,36 @@
   dependencies:
     shebang-regex "^1.0.0"
 
+shebang-command@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
+  integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+  dependencies:
+    shebang-regex "^3.0.0"
+
 shebang-regex@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
   integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
 
-shelljs@^0.8.0:
-  version "0.8.3"
-  resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097"
-  integrity sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==
+shebang-regex@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
+  integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+shelljs@^0.8.0, shelljs@^0.8.4:
+  version "0.8.4"
+  resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2"
+  integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==
   dependencies:
     glob "^7.0.0"
     interpret "^1.0.0"
     rechoir "^0.6.2"
 
 signal-exit@^3.0.0, signal-exit@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
-  integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
+  integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
 
 simple-swizzle@^0.2.2:
   version "0.2.2"
@@ -7101,6 +7757,11 @@
   resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
   integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=
 
+slash@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
+  integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
+
 slide@^1.1.5:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
@@ -7141,54 +7802,51 @@
   resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz#ab3f0d6f66b8fc7fca3959ab5991f82221789be9"
   integrity sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==
 
-socket.io-client@2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.3.0.tgz#14d5ba2e00b9bcd145ae443ab96b3f86cbcc1bb4"
-  integrity sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==
+socket.io-client@2.4.0:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.4.0.tgz#aafb5d594a3c55a34355562fc8aea22ed9119a35"
+  integrity sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==
   dependencies:
     backo2 "1.0.2"
-    base64-arraybuffer "0.1.5"
     component-bind "1.0.0"
-    component-emitter "1.2.1"
-    debug "~4.1.0"
-    engine.io-client "~3.4.0"
+    component-emitter "~1.3.0"
+    debug "~3.1.0"
+    engine.io-client "~3.5.0"
     has-binary2 "~1.0.2"
-    has-cors "1.1.0"
     indexof "0.0.1"
-    object-component "0.0.3"
-    parseqs "0.0.5"
-    parseuri "0.0.5"
+    parseqs "0.0.6"
+    parseuri "0.0.6"
     socket.io-parser "~3.3.0"
     to-array "0.1.4"
 
 socket.io-parser@~3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.0.tgz#2b52a96a509fdf31440ba40fed6094c7d4f1262f"
-  integrity sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==
+  version "3.3.2"
+  resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.2.tgz#ef872009d0adcf704f2fbe830191a14752ad50b6"
+  integrity sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==
   dependencies:
-    component-emitter "1.2.1"
+    component-emitter "~1.3.0"
     debug "~3.1.0"
     isarray "2.0.1"
 
 socket.io-parser@~3.4.0:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.4.0.tgz#370bb4a151df2f77ce3345ff55a7072cc6e9565a"
-  integrity sha512-/G/VOI+3DBp0+DJKW4KesGnQkQPFmUCbA/oO2QGT6CWxU7hLGWqU3tyuzeSK/dqcyeHsQg1vTe9jiZI8GU9SCQ==
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.4.1.tgz#b06af838302975837eab2dc980037da24054d64a"
+  integrity sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==
   dependencies:
     component-emitter "1.2.1"
     debug "~4.1.0"
     isarray "2.0.1"
 
 socket.io@^2.0.3:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.3.0.tgz#cd762ed6a4faeca59bc1f3e243c0969311eb73fb"
-  integrity sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.4.1.tgz#95ad861c9a52369d7f1a68acf0d4a1b16da451d2"
+  integrity sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==
   dependencies:
     debug "~4.1.0"
-    engine.io "~3.4.0"
+    engine.io "~3.5.0"
     has-binary2 "~1.0.2"
     socket.io-adapter "~1.1.0"
-    socket.io-client "2.3.0"
+    socket.io-client "2.4.0"
     socket.io-parser "~3.4.0"
 
 sort-keys-length@^1.0.0:
@@ -7225,17 +7883,17 @@
     source-map "^0.6.0"
 
 source-map-support@~0.5.12:
-  version "0.5.16"
-  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
-  integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==
+  version "0.5.19"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
+  integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
   dependencies:
     buffer-from "^1.0.0"
     source-map "^0.6.0"
 
 source-map-url@^0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
-  integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56"
+  integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==
 
 source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7:
   version "0.5.7"
@@ -7256,30 +7914,30 @@
     os-shim "^0.1.2"
 
 spdx-correct@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4"
-  integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
+  integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==
   dependencies:
     spdx-expression-parse "^3.0.0"
     spdx-license-ids "^3.0.0"
 
 spdx-exceptions@^2.1.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977"
-  integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d"
+  integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==
 
 spdx-expression-parse@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0"
-  integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679"
+  integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==
   dependencies:
     spdx-exceptions "^2.1.0"
     spdx-license-ids "^3.0.0"
 
 spdx-license-ids@^3.0.0:
-  version "3.0.5"
-  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654"
-  integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==
+  version "3.0.10"
+  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz#0d9becccde7003d6c658d487dd48a32f0bf3014b"
+  integrity sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==
 
 spdy-transport@^2.0.18:
   version "2.1.1"
@@ -7395,7 +8053,7 @@
     is-fullwidth-code-point "^1.0.0"
     strip-ansi "^3.0.0"
 
-string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
+string-width@^2.0.0, string-width@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
   integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
@@ -7403,6 +8061,15 @@
     is-fullwidth-code-point "^2.0.0"
     strip-ansi "^4.0.0"
 
+string-width@^4.1.0:
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5"
+  integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==
+  dependencies:
+    emoji-regex "^8.0.0"
+    is-fullwidth-code-point "^3.0.0"
+    strip-ansi "^6.0.0"
+
 string_decoder@^1.1.1:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
@@ -7436,18 +8103,25 @@
   dependencies:
     ansi-regex "^3.0.0"
 
-strip-ansi@^5.1.0:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
-  integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
+strip-ansi@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
+  integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==
   dependencies:
-    ansi-regex "^4.1.0"
+    ansi-regex "^5.0.0"
 
 strip-ansi@~0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991"
   integrity sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=
 
+strip-bom-buf@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz#1cb45aaf57530f4caf86c7f75179d2c9a51dd572"
+  integrity sha1-HLRar1dTD0yvhsf3UXnSyaUd1XI=
+  dependencies:
+    is-utf8 "^0.2.1"
+
 strip-bom-stream@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz#e7144398577d51a6bed0fa1994fa05f43fd988ee"
@@ -7481,6 +8155,11 @@
   resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
   integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
 
+strip-final-newline@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
+  integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
+
 strip-indent@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
@@ -7518,9 +8197,9 @@
     has-flag "^3.0.0"
 
 supports-color@^7.1.0:
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
-  integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+  integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
   dependencies:
     has-flag "^4.0.0"
 
@@ -7581,12 +8260,12 @@
     pump "^1.0.0"
     tar-stream "^1.1.2"
 
-tar-stream@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.0.0.tgz#8829bbf83067bc0288a9089db49c56be395b6aea"
-  integrity sha512-n2vtsWshZOVr/SY4KtslPoUlyNh06I2SGgAOCZmquCEjlbV/LjY2CY80rDtdQRHFOYXNlgBDo6Fr3ww2CWPOtA==
+tar-stream@2.2.0, tar-stream@^2.1.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
+  integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
   dependencies:
-    bl "^2.2.0"
+    bl "^4.0.3"
     end-of-stream "^1.4.1"
     fs-constants "^1.0.0"
     inherits "^2.0.3"
@@ -7605,17 +8284,6 @@
     to-buffer "^1.1.1"
     xtend "^4.0.0"
 
-tar-stream@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3"
-  integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw==
-  dependencies:
-    bl "^3.0.0"
-    end-of-stream "^1.4.1"
-    fs-constants "^1.0.0"
-    inherits "^2.0.3"
-    readable-stream "^3.1.1"
-
 temp@^0.8.1, temp@^0.8.3:
   version "0.8.4"
   resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.4.tgz#8c97a33a4770072e0a05f919396c7665a7dd59f2"
@@ -7641,9 +8309,9 @@
     through2 "^2.0.1"
 
 terser@^4.6.2:
-  version "4.6.3"
-  resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.3.tgz#e33aa42461ced5238d352d2df2a67f21921f8d87"
-  integrity sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ==
+  version "4.8.0"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17"
+  integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==
   dependencies:
     commander "^2.20.0"
     source-map "~0.6.1"
@@ -7685,9 +8353,9 @@
     thenify ">= 3.1.0 < 4"
 
 "thenify@>= 3.1.0 < 4":
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839"
-  integrity sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f"
+  integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==
   dependencies:
     any-promise "^1.0.0"
 
@@ -7723,14 +8391,15 @@
     readable-stream "~2.3.6"
     xtend "~4.0.1"
 
-through2@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.1.tgz#39276e713c3302edf9e388dd9c812dd3b825bd5a"
-  integrity sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==
+through2@^3.0.0, through2@^3.0.1, through2@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.2.tgz#99f88931cfc761ec7678b41d5d7336b5b6a07bf4"
+  integrity sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==
   dependencies:
+    inherits "^2.0.4"
     readable-stream "2 || 3"
 
-through@^2.3.6:
+"through@>=2.2.7 <3", through@^2.3.6:
   version "2.3.8"
   resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
   integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
@@ -7824,6 +8493,14 @@
     psl "^1.1.24"
     punycode "^1.4.1"
 
+tough-cookie@~2.5.0:
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
+  integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
+  dependencies:
+    psl "^1.1.28"
+    punycode "^2.1.1"
+
 tr46@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
@@ -7846,16 +8523,11 @@
   resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
   integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==
 
-tslib@^1.8.1:
+tslib@^1.8.1, tslib@^1.9.0:
   version "1.14.1"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
   integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
 
-tslib@^1.9.0:
-  version "1.10.0"
-  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
-  integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
-
 tsutils@2.27.2:
   version "2.27.2"
   resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.27.2.tgz#60ba88a23d6f785ec4b89c6e8179cac9b431f1c7"
@@ -7880,6 +8552,16 @@
   resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
   integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
 
+type-fest@^0.21.3:
+  version "0.21.3"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
+  integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
+
+type-fest@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
+  integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==
+
 type-is@^1.6.4, type-is@~1.6.17, type-is@~1.6.18:
   version "1.6.18"
   resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
@@ -7909,9 +8591,9 @@
   integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==
 
 ua-parser-js@^0.7.15:
-  version "0.7.21"
-  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777"
-  integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==
+  version "0.7.28"
+  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31"
+  integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==
 
 uglify-js@3.4.x:
   version "3.4.10"
@@ -7922,9 +8604,9 @@
     source-map "~0.6.1"
 
 underscore@^1.8.3:
-  version "1.9.2"
-  resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.2.tgz#0c8d6f536d6f378a5af264a72f7bec50feb7cf2f"
-  integrity sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ==
+  version "1.13.1"
+  resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1"
+  integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==
 
 underscore@~1.6.0:
   version "1.6.0"
@@ -7944,15 +8626,15 @@
     unicode-canonical-property-names-ecmascript "^1.0.4"
     unicode-property-aliases-ecmascript "^1.0.4"
 
-unicode-match-property-value-ecmascript@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz#5b4b426e08d13a80365e0d657ac7a6c1ec46a277"
-  integrity sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==
+unicode-match-property-value-ecmascript@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531"
+  integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==
 
 unicode-property-aliases-ecmascript@^1.0.4:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57"
-  integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4"
+  integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==
 
 union-value@^1.0.0:
   version "1.0.1"
@@ -7980,12 +8662,17 @@
     crypto-random-string "^1.0.0"
 
 universal-user-agent@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-4.0.0.tgz#27da2ec87e32769619f68a14996465ea1cb9df16"
-  integrity sha512-eM8knLpev67iBDizr/YtqkJsF3GK8gzDc6st/WKzrTuPtcsOKW/0IdL4cnMBsU69pOx0otavLWBDGTwg+dB0aA==
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-4.0.1.tgz#fd8d6cb773a679a709e967ef8288a31fcc03e557"
+  integrity sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg==
   dependencies:
     os-name "^3.1.0"
 
+universal-user-agent@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee"
+  integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==
+
 unpipe@1.0.0, unpipe@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -8057,16 +8744,16 @@
   integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=
 
 uri-js@^4.2.2:
-  version "4.2.2"
-  resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
-  integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==
+  version "4.4.1"
+  resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
+  integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
   dependencies:
     punycode "^2.1.0"
 
-urijs@^1.16.1, urijs@^1.19.1:
-  version "1.19.2"
-  resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.2.tgz#f9be09f00c4c5134b7cb3cf475c1dd394526265a"
-  integrity sha512-s/UIq9ap4JPZ7H1EB5ULo/aOUbWqfDi7FKzMC2Nz+0Si8GiT1rIEaprt8hy3Vy2Ex2aJPpOQv4P4DuOZ+K1c6w==
+urijs@^1.16.1:
+  version "1.19.7"
+  resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.7.tgz#4f594e59113928fea63c00ce688fb395b1168ab9"
+  integrity sha512-Id+IKjdU0Hx+7Zx717jwLPsPeUqz7rAtuVBRLLs+qn+J2nf9NGITWVCxcijgYxBqe83C7sqsQPs6H1pyz3x9gA==
 
 urix@^0.1.0:
   version "0.1.0"
@@ -8151,17 +8838,16 @@
     core-util-is "1.0.2"
     extsprintf "^1.2.0"
 
-vinyl-file@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/vinyl-file/-/vinyl-file-2.0.0.tgz#a7ebf5ffbefda1b7d18d140fcb07b223efb6751a"
-  integrity sha1-p+v1/779obfRjRQPyweyI++2dRo=
+vinyl-file@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/vinyl-file/-/vinyl-file-3.0.0.tgz#b104d9e4409ffa325faadd520642d0a3b488b365"
+  integrity sha1-sQTZ5ECf+jJfqt1SBkLQo7SIs2U=
   dependencies:
     graceful-fs "^4.1.2"
     pify "^2.3.0"
-    pinkie-promise "^2.0.0"
-    strip-bom "^2.0.0"
+    strip-bom-buf "^1.0.0"
     strip-bom-stream "^2.0.0"
-    vinyl "^1.1.0"
+    vinyl "^2.0.1"
 
 vinyl-fs@^2.4.3, vinyl-fs@^2.4.4:
   version "2.4.4"
@@ -8186,7 +8872,7 @@
     vali-date "^1.0.0"
     vinyl "^1.0.0"
 
-vinyl@^1.0.0, vinyl@^1.1.0, vinyl@^1.1.1, vinyl@^1.2.0:
+vinyl@^1.0.0, vinyl@^1.1.1, vinyl@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884"
   integrity sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=
@@ -8195,10 +8881,10 @@
     clone-stats "^0.0.1"
     replace-ext "0.0.1"
 
-vinyl@^2.0.1:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.0.tgz#d85b07da96e458d25b2ffe19fece9f2caa13ed86"
-  integrity sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==
+vinyl@^2.0.1, vinyl@^2.2.0, vinyl@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.1.tgz#23cfb8bbab5ece3803aa2c0a1eb28af7cbba1974"
+  integrity sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==
   dependencies:
     clone "^2.1.1"
     clone-buffer "^1.0.0"
@@ -8254,9 +8940,9 @@
     uuid "^3.2.1"
 
 wd@^1.2.0:
-  version "1.12.1"
-  resolved "https://registry.yarnpkg.com/wd/-/wd-1.12.1.tgz#067eb3674db00eeb9e506701f9314657c44d5a89"
-  integrity sha512-O99X8OnOgkqfmsPyLIRzG9LmZ+rjmdGFBCyhGpnsSL4MB4xzHoeWmSVcumDiQ5QqPZcwGkszTgeJvjk2VjtiNw==
+  version "1.14.0"
+  resolved "https://registry.yarnpkg.com/wd/-/wd-1.14.0.tgz#1fe6450b5baef37caa135e7755292c6998ca8a90"
+  integrity sha512-X7ZfGHHYlQ5zYpRlgP16LUsvYti+Al/6fz3T/ClVyivVCpCZQpESTDdz6zbK910O5OIvujO23Ym2DBBo3XsQlA==
   dependencies:
     archiver "^3.0.0"
     async "^2.0.0"
@@ -8315,14 +9001,14 @@
     tr46 "^1.0.1"
     webidl-conversions "^4.0.2"
 
-which@^1.0.8, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.1:
+which@^1.0.8, which@^1.2.12, which@^1.2.14, which@^1.2.9:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
   integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
   dependencies:
     isexe "^2.0.0"
 
-which@^2.0.2:
+which@^2.0.1, which@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
   integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
@@ -8344,34 +9030,34 @@
     string-width "^2.1.1"
 
 windows-release@^3.1.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.2.0.tgz#8122dad5afc303d833422380680a79cdfa91785f"
-  integrity sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA==
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.3.3.tgz#1c10027c7225743eec6b89df160d64c2e0293999"
+  integrity sha512-OSOGH1QYiW5yVor9TtmXKQvt2vjQqbYS+DqmsZw+r7xDwLXEeT3JGW0ZppFmHx4diyXmxt238KFR3N9jzevBRg==
   dependencies:
     execa "^1.0.0"
 
-winston-transport@^4.2.0, winston-transport@^4.3.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.3.0.tgz#df68c0c202482c448d9b47313c07304c2d7c2c66"
-  integrity sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A==
+winston-transport@^4.2.0, winston-transport@^4.4.0:
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.4.0.tgz#17af518daa690d5b2ecccaa7acf7b20ca7925e59"
+  integrity sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==
   dependencies:
-    readable-stream "^2.3.6"
+    readable-stream "^2.3.7"
     triple-beam "^1.2.0"
 
 winston@^3.0.0:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/winston/-/winston-3.2.1.tgz#63061377976c73584028be2490a1846055f77f07"
-  integrity sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170"
+  integrity sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==
   dependencies:
-    async "^2.6.1"
-    diagnostics "^1.1.1"
-    is-stream "^1.1.0"
-    logform "^2.1.1"
-    one-time "0.0.4"
-    readable-stream "^3.1.1"
+    "@dabh/diagnostics" "^2.0.2"
+    async "^3.1.0"
+    is-stream "^2.0.0"
+    logform "^2.2.0"
+    one-time "^1.0.0"
+    readable-stream "^3.4.0"
     stack-trace "0.0.x"
     triple-beam "^1.3.0"
-    winston-transport "^4.3.0"
+    winston-transport "^4.4.0"
 
 with-open-file@^0.1.6:
   version "0.1.7"
@@ -8382,7 +9068,7 @@
     p-try "^2.1.0"
     pify "^4.0.1"
 
-wordwrap@~0.0.2:
+wordwrap@^0.0.3:
   version "0.0.3"
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
   integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
@@ -8428,17 +9114,10 @@
     imurmurhash "^0.1.4"
     signal-exit "^3.0.2"
 
-ws@^7.1.2:
-  version "7.2.1"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.1.tgz#03ed52423cd744084b2cf42ed197c8b65a936b8e"
-  integrity sha512-sucePNSafamSKoOqoNfBd8V0StlkzJKL2ZAhGQinCfNQ+oacw+Pk7lcdAElecBF2VkLNZRiIb5Oi1Q5lVUVt2A==
-
-ws@~6.1.0:
-  version "6.1.4"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9"
-  integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==
-  dependencies:
-    async-limiter "~1.0.0"
+ws@~7.4.2:
+  version "7.4.6"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
+  integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==
 
 xdg-basedir@^2.0.0:
   version "2.0.0"
@@ -8462,10 +9141,10 @@
   resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff"
   integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==
 
-xmlhttprequest-ssl@~1.5.4:
-  version "1.5.5"
-  resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
-  integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=
+xmlhttprequest-ssl@~1.6.2:
+  version "1.6.3"
+  resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6"
+  integrity sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q==
 
 "xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1:
   version "4.0.2"
@@ -8477,6 +9156,11 @@
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
   integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
 
+yallist@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+  integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+
 yauzl@^2.10.0:
   version "2.10.0"
   resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
@@ -8508,26 +9192,30 @@
     text-table "^0.2.0"
     untildify "^2.0.0"
 
-yeoman-environment@^2.0.5:
-  version "2.7.0"
-  resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-2.7.0.tgz#d1b6679de883ce14a68b869c4b19d55a0d66f477"
-  integrity sha512-YNzSUWgJVSgnm0qgLON4Gb2nTm+kywBiWjK4MbvosjUP2YJJ30lNhEx7ukyzKRPUlsavd5IsuALtF6QaVrq81A==
+yeoman-environment@^2.0.5, yeoman-environment@^2.9.5:
+  version "2.10.3"
+  resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-2.10.3.tgz#9d8f42b77317414434cc0e51fb006a4bdd54688e"
+  integrity sha512-pLIhhU9z/G+kjOXmJ2bPFm3nejfbH+f1fjYRSOteEXDBrv1EoJE/e+kuHixSXfCYfTkxjYsvRaDX+1QykLCnpQ==
   dependencies:
     chalk "^2.4.1"
-    cross-spawn "^6.0.5"
     debug "^3.1.0"
     diff "^3.5.0"
     escape-string-regexp "^1.0.2"
+    execa "^4.0.0"
     globby "^8.0.1"
-    grouped-queue "^0.3.3"
-    inquirer "^6.0.0"
+    grouped-queue "^1.1.0"
+    inquirer "^7.1.0"
     is-scoped "^1.0.0"
     lodash "^4.17.10"
     log-symbols "^2.2.0"
     mem-fs "^1.1.0"
+    mem-fs-editor "^6.0.0"
+    npm-api "^1.0.0"
+    semver "^7.1.3"
     strip-ansi "^4.0.0"
     text-table "^0.2.0"
     untildify "^3.0.3"
+    yeoman-generator "^4.8.2"
 
 yeoman-generator@^3.1.1:
   version "3.2.0"
@@ -8560,6 +9248,40 @@
     through2 "^3.0.0"
     yeoman-environment "^2.0.5"
 
+yeoman-generator@^4.8.2:
+  version "4.13.0"
+  resolved "https://registry.yarnpkg.com/yeoman-generator/-/yeoman-generator-4.13.0.tgz#a6caeed8491fceea1f84f53e31795f25888b4672"
+  integrity sha512-f2/5N5IR3M2Ozm+QocvZQudlQITv2DwI6Mcxfy7R7gTTzaKgvUpgo/pQMJ+WQKm0KN0YMWCFOZpj0xFGxevc1w==
+  dependencies:
+    async "^2.6.2"
+    chalk "^2.4.2"
+    cli-table "^0.3.1"
+    cross-spawn "^6.0.5"
+    dargs "^6.1.0"
+    dateformat "^3.0.3"
+    debug "^4.1.1"
+    diff "^4.0.1"
+    error "^7.0.2"
+    find-up "^3.0.0"
+    github-username "^3.0.0"
+    istextorbinary "^2.5.1"
+    lodash "^4.17.11"
+    make-dir "^3.0.0"
+    mem-fs-editor "^7.0.1"
+    minimist "^1.2.5"
+    pretty-bytes "^5.2.0"
+    read-chunk "^3.2.0"
+    read-pkg-up "^5.0.0"
+    rimraf "^2.6.3"
+    run-async "^2.0.0"
+    semver "^7.2.1"
+    shelljs "^0.8.4"
+    text-table "^0.2.0"
+    through2 "^3.0.1"
+  optionalDependencies:
+    grouped-queue "^1.1.0"
+    yeoman-environment "^2.9.5"
+
 zip-stream@^2.1.2:
   version "2.1.3"
   resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-2.1.3.tgz#26cc4bdb93641a8590dd07112e1f77af1758865b"
diff --git a/tools/polygerrit-updater/tsconfig.json b/tools/polygerrit-updater/tsconfig.json
index 37ff1b2..80f60c1 100644
--- a/tools/polygerrit-updater/tsconfig.json
+++ b/tools/polygerrit-updater/tsconfig.json
@@ -2,8 +2,8 @@
   "compilerOptions": {
     /* Basic Options */
     // "incremental": true,                   /* Enable incremental compilation */
-    "target": "es2015",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
-    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
+    "target": "es2019", 		      /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
+    "module": "es2015", 		      /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
     // "lib": [],                             /* Specify library files to be included in the compilation. */
     // "allowJs": true,                       /* Allow javascript files to be compiled. */
     // "checkJs": true,                       /* Report errors in .js files. */
diff --git a/yarn.lock b/yarn.lock
index 32d86fc..7e13131 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,727 +2,127 @@
 # yarn lockfile v1
 
 
-"@babel/code-frame@7.12.11", "@babel/code-frame@^7.0.0":
+"@babel/code-frame@7.12.11":
   version "7.12.11"
-  resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
   integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==
   dependencies:
     "@babel/highlight" "^7.10.4"
 
-"@babel/code-frame@^7.12.13":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658"
-  integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==
+"@babel/code-frame@^7.0.0":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb"
+  integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==
   dependencies:
-    "@babel/highlight" "^7.12.13"
+    "@babel/highlight" "^7.14.5"
 
-"@babel/compat-data@^7.13.12", "@babel/compat-data@^7.13.8":
-  version "7.13.15"
-  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.15.tgz#7e8eea42d0b64fda2b375b22d06c605222e848f4"
-  integrity sha512-ltnibHKR1VnrU4ymHyQ/CXtNXI6yZC0oJThyW78Hft8XndANwi+9H+UIklBDraIjFEJzw8wmcM427oDd9KS5wA==
+"@babel/helper-validator-identifier@^7.14.5":
+  version "7.14.9"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48"
+  integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==
 
-"@babel/core@^7.0.0":
-  version "7.13.15"
-  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.15.tgz#a6d40917df027487b54312202a06812c4f7792d0"
-  integrity sha512-6GXmNYeNjS2Uz+uls5jalOemgIhnTMeaXo+yBUA72kC2uX/8VW6XyhVIo2L8/q0goKQA3EVKx0KOQpVKSeWadQ==
+"@babel/highlight@^7.10.4", "@babel/highlight@^7.14.5":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9"
+  integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==
   dependencies:
-    "@babel/code-frame" "^7.12.13"
-    "@babel/generator" "^7.13.9"
-    "@babel/helper-compilation-targets" "^7.13.13"
-    "@babel/helper-module-transforms" "^7.13.14"
-    "@babel/helpers" "^7.13.10"
-    "@babel/parser" "^7.13.15"
-    "@babel/template" "^7.12.13"
-    "@babel/traverse" "^7.13.15"
-    "@babel/types" "^7.13.14"
-    convert-source-map "^1.7.0"
-    debug "^4.1.0"
-    gensync "^1.0.0-beta.2"
-    json5 "^2.1.2"
-    semver "^6.3.0"
-    source-map "^0.5.0"
-
-"@babel/generator@^7.0.0-beta.42", "@babel/generator@^7.13.9":
-  version "7.13.9"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.9.tgz#3a7aa96f9efb8e2be42d38d80e2ceb4c64d8de39"
-  integrity sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==
-  dependencies:
-    "@babel/types" "^7.13.0"
-    jsesc "^2.5.1"
-    source-map "^0.5.0"
-
-"@babel/helper-annotate-as-pure@^7.12.13":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz#0f58e86dfc4bb3b1fcd7db806570e177d439b6ab"
-  integrity sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==
-  dependencies:
-    "@babel/types" "^7.12.13"
-
-"@babel/helper-builder-binary-assignment-operator-visitor@^7.12.13":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz#6bc20361c88b0a74d05137a65cac8d3cbf6f61fc"
-  integrity sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA==
-  dependencies:
-    "@babel/helper-explode-assignable-expression" "^7.12.13"
-    "@babel/types" "^7.12.13"
-
-"@babel/helper-compilation-targets@^7.13.13", "@babel/helper-compilation-targets@^7.13.8":
-  version "7.13.13"
-  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.13.tgz#2b2972a0926474853f41e4adbc69338f520600e5"
-  integrity sha512-q1kcdHNZehBwD9jYPh3WyXcsFERi39X4I59I3NadciWtNDyZ6x+GboOxncFK0kXlKIv6BJm5acncehXWUjWQMQ==
-  dependencies:
-    "@babel/compat-data" "^7.13.12"
-    "@babel/helper-validator-option" "^7.12.17"
-    browserslist "^4.14.5"
-    semver "^6.3.0"
-
-"@babel/helper-create-regexp-features-plugin@^7.12.13":
-  version "7.12.17"
-  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz#a2ac87e9e319269ac655b8d4415e94d38d663cb7"
-  integrity sha512-p2VGmBu9oefLZ2nQpgnEnG0ZlRPvL8gAGvPUMQwUdaE8k49rOMuZpOwdQoy5qJf6K8jL3bcAMhVUlHAjIgJHUg==
-  dependencies:
-    "@babel/helper-annotate-as-pure" "^7.12.13"
-    regexpu-core "^4.7.1"
-
-"@babel/helper-explode-assignable-expression@^7.12.13":
-  version "7.13.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz#17b5c59ff473d9f956f40ef570cf3a76ca12657f"
-  integrity sha512-qS0peLTDP8kOisG1blKbaoBg/o9OSa1qoumMjTK5pM+KDTtpxpsiubnCGP34vK8BXGcb2M9eigwgvoJryrzwWA==
-  dependencies:
-    "@babel/types" "^7.13.0"
-
-"@babel/helper-function-name@^7.12.13":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz#93ad656db3c3c2232559fd7b2c3dbdcbe0eb377a"
-  integrity sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==
-  dependencies:
-    "@babel/helper-get-function-arity" "^7.12.13"
-    "@babel/template" "^7.12.13"
-    "@babel/types" "^7.12.13"
-
-"@babel/helper-get-function-arity@^7.12.13":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz#bc63451d403a3b3082b97e1d8b3fe5bd4091e583"
-  integrity sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==
-  dependencies:
-    "@babel/types" "^7.12.13"
-
-"@babel/helper-member-expression-to-functions@^7.13.12":
-  version "7.13.12"
-  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz#dfe368f26d426a07299d8d6513821768216e6d72"
-  integrity sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==
-  dependencies:
-    "@babel/types" "^7.13.12"
-
-"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.13.12":
-  version "7.13.12"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz#c6a369a6f3621cb25da014078684da9196b61977"
-  integrity sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==
-  dependencies:
-    "@babel/types" "^7.13.12"
-
-"@babel/helper-module-transforms@^7.13.0", "@babel/helper-module-transforms@^7.13.14":
-  version "7.13.14"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.13.14.tgz#e600652ba48ccb1641775413cb32cfa4e8b495ef"
-  integrity sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g==
-  dependencies:
-    "@babel/helper-module-imports" "^7.13.12"
-    "@babel/helper-replace-supers" "^7.13.12"
-    "@babel/helper-simple-access" "^7.13.12"
-    "@babel/helper-split-export-declaration" "^7.12.13"
-    "@babel/helper-validator-identifier" "^7.12.11"
-    "@babel/template" "^7.12.13"
-    "@babel/traverse" "^7.13.13"
-    "@babel/types" "^7.13.14"
-
-"@babel/helper-optimise-call-expression@^7.12.13":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz#5c02d171b4c8615b1e7163f888c1c81c30a2aaea"
-  integrity sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==
-  dependencies:
-    "@babel/types" "^7.12.13"
-
-"@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.8.0":
-  version "7.13.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz#806526ce125aed03373bc416a828321e3a6a33af"
-  integrity sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==
-
-"@babel/helper-remap-async-to-generator@^7.13.0":
-  version "7.13.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz#376a760d9f7b4b2077a9dd05aa9c3927cadb2209"
-  integrity sha512-pUQpFBE9JvC9lrQbpX0TmeNIy5s7GnZjna2lhhcHC7DzgBs6fWn722Y5cfwgrtrqc7NAJwMvOa0mKhq6XaE4jg==
-  dependencies:
-    "@babel/helper-annotate-as-pure" "^7.12.13"
-    "@babel/helper-wrap-function" "^7.13.0"
-    "@babel/types" "^7.13.0"
-
-"@babel/helper-replace-supers@^7.12.13", "@babel/helper-replace-supers@^7.13.0", "@babel/helper-replace-supers@^7.13.12":
-  version "7.13.12"
-  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz#6442f4c1ad912502481a564a7386de0c77ff3804"
-  integrity sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==
-  dependencies:
-    "@babel/helper-member-expression-to-functions" "^7.13.12"
-    "@babel/helper-optimise-call-expression" "^7.12.13"
-    "@babel/traverse" "^7.13.0"
-    "@babel/types" "^7.13.12"
-
-"@babel/helper-simple-access@^7.13.12":
-  version "7.13.12"
-  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz#dd6c538afb61819d205a012c31792a39c7a5eaf6"
-  integrity sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==
-  dependencies:
-    "@babel/types" "^7.13.12"
-
-"@babel/helper-skip-transparent-expression-wrappers@^7.12.1":
-  version "7.12.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz#462dc63a7e435ade8468385c63d2b84cce4b3cbf"
-  integrity sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==
-  dependencies:
-    "@babel/types" "^7.12.1"
-
-"@babel/helper-split-export-declaration@^7.12.13":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz#e9430be00baf3e88b0e13e6f9d4eaf2136372b05"
-  integrity sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==
-  dependencies:
-    "@babel/types" "^7.12.13"
-
-"@babel/helper-validator-identifier@^7.12.11":
-  version "7.12.11"
-  resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz"
-  integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==
-
-"@babel/helper-validator-option@^7.12.17":
-  version "7.12.17"
-  resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz#d1fbf012e1a79b7eebbfdc6d270baaf8d9eb9831"
-  integrity sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==
-
-"@babel/helper-wrap-function@^7.13.0":
-  version "7.13.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz#bdb5c66fda8526ec235ab894ad53a1235c79fcc4"
-  integrity sha512-1UX9F7K3BS42fI6qd2A4BjKzgGjToscyZTdp1DjknHLCIvpgne6918io+aL5LXFcER/8QWiwpoY902pVEqgTXA==
-  dependencies:
-    "@babel/helper-function-name" "^7.12.13"
-    "@babel/template" "^7.12.13"
-    "@babel/traverse" "^7.13.0"
-    "@babel/types" "^7.13.0"
-
-"@babel/helpers@^7.13.10":
-  version "7.13.10"
-  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.13.10.tgz#fd8e2ba7488533cdeac45cc158e9ebca5e3c7df8"
-  integrity sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ==
-  dependencies:
-    "@babel/template" "^7.12.13"
-    "@babel/traverse" "^7.13.0"
-    "@babel/types" "^7.13.0"
-
-"@babel/highlight@^7.10.4", "@babel/highlight@^7.12.13":
-  version "7.13.10"
-  resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz"
-  integrity sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==
-  dependencies:
-    "@babel/helper-validator-identifier" "^7.12.11"
+    "@babel/helper-validator-identifier" "^7.14.5"
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.12.13", "@babel/parser@^7.13.15":
-  version "7.13.15"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.15.tgz#8e66775fb523599acb6a289e12929fa5ab0954d8"
-  integrity sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==
-
-"@babel/plugin-external-helpers@^7.0.0":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-external-helpers/-/plugin-external-helpers-7.12.13.tgz#65ef9f4576297250dc601d2aa334769790d9966d"
-  integrity sha512-ClvAsk4RqpE6iacYUjdU9PtvIwC9yAefZENsPfGeG5FckX3jFZLDlWPuyv5gi9/9C2VgwX6H8q1ukBifC0ha+Q==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.12.13"
-
-"@babel/plugin-proposal-async-generator-functions@^7.0.0":
-  version "7.13.15"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.15.tgz#80e549df273a3b3050431b148c892491df1bcc5b"
-  integrity sha512-VapibkWzFeoa6ubXy/NgV5U2U4MVnUlvnx6wo1XhlsaTrLYWE0UFpDQsVrmn22q5CzeloqJ8gEMHSKxuee6ZdA==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.13.0"
-    "@babel/helper-remap-async-to-generator" "^7.13.0"
-    "@babel/plugin-syntax-async-generators" "^7.8.4"
-
-"@babel/plugin-proposal-object-rest-spread@^7.0.0":
-  version "7.13.8"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz#5d210a4d727d6ce3b18f9de82cc99a3964eed60a"
-  integrity sha512-DhB2EuB1Ih7S3/IRX5AFVgZ16k3EzfRbq97CxAVI1KSYcW+lexV8VZb7G7L8zuPVSdQMRn0kiBpf/Yzu9ZKH0g==
-  dependencies:
-    "@babel/compat-data" "^7.13.8"
-    "@babel/helper-compilation-targets" "^7.13.8"
-    "@babel/helper-plugin-utils" "^7.13.0"
-    "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
-    "@babel/plugin-transform-parameters" "^7.13.0"
-
-"@babel/plugin-syntax-async-generators@^7.0.0", "@babel/plugin-syntax-async-generators@^7.8.4":
-  version "7.8.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
-  integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.8.0"
-
-"@babel/plugin-syntax-dynamic-import@^7.0.0":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3"
-  integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.8.0"
-
-"@babel/plugin-syntax-import-meta@^7.0.0":
-  version "7.10.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51"
-  integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.10.4"
-
-"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
-  integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.8.0"
-
-"@babel/plugin-transform-arrow-functions@^7.0.0":
-  version "7.13.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz#10a59bebad52d637a027afa692e8d5ceff5e3dae"
-  integrity sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.13.0"
-
-"@babel/plugin-transform-async-to-generator@^7.0.0":
-  version "7.13.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz#8e112bf6771b82bf1e974e5e26806c5c99aa516f"
-  integrity sha512-3j6E004Dx0K3eGmhxVJxwwI89CTJrce7lg3UrtFuDAVQ/2+SJ/h/aSFOeE6/n0WB1GsOffsJp6MnPQNQ8nmwhg==
-  dependencies:
-    "@babel/helper-module-imports" "^7.12.13"
-    "@babel/helper-plugin-utils" "^7.13.0"
-    "@babel/helper-remap-async-to-generator" "^7.13.0"
-
-"@babel/plugin-transform-block-scoped-functions@^7.0.0":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz#a9bf1836f2a39b4eb6cf09967739de29ea4bf4c4"
-  integrity sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.12.13"
-
-"@babel/plugin-transform-block-scoping@^7.0.0":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.13.tgz#f36e55076d06f41dfd78557ea039c1b581642e61"
-  integrity sha512-Pxwe0iqWJX4fOOM2kEZeUuAxHMWb9nK+9oh5d11bsLoB0xMg+mkDpt0eYuDZB7ETrY9bbcVlKUGTOGWy7BHsMQ==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.12.13"
-
-"@babel/plugin-transform-classes@^7.0.0":
-  version "7.13.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz#0265155075c42918bf4d3a4053134176ad9b533b"
-  integrity sha512-9BtHCPUARyVH1oXGcSJD3YpsqRLROJx5ZNP6tN5vnk17N0SVf9WCtf8Nuh1CFmgByKKAIMstitKduoCmsaDK5g==
-  dependencies:
-    "@babel/helper-annotate-as-pure" "^7.12.13"
-    "@babel/helper-function-name" "^7.12.13"
-    "@babel/helper-optimise-call-expression" "^7.12.13"
-    "@babel/helper-plugin-utils" "^7.13.0"
-    "@babel/helper-replace-supers" "^7.13.0"
-    "@babel/helper-split-export-declaration" "^7.12.13"
-    globals "^11.1.0"
-
-"@babel/plugin-transform-computed-properties@^7.0.0":
-  version "7.13.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz#845c6e8b9bb55376b1fa0b92ef0bdc8ea06644ed"
-  integrity sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.13.0"
-
-"@babel/plugin-transform-destructuring@^7.0.0":
-  version "7.13.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.0.tgz#c5dce270014d4e1ebb1d806116694c12b7028963"
-  integrity sha512-zym5em7tePoNT9s964c0/KU3JPPnuq7VhIxPRefJ4/s82cD+q1mgKfuGRDMCPL0HTyKz4dISuQlCusfgCJ86HA==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.13.0"
-
-"@babel/plugin-transform-duplicate-keys@^7.0.0":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz#6f06b87a8b803fd928e54b81c258f0a0033904de"
-  integrity sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.12.13"
-
-"@babel/plugin-transform-exponentiation-operator@^7.0.0":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz#4d52390b9a273e651e4aba6aee49ef40e80cd0a1"
-  integrity sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA==
-  dependencies:
-    "@babel/helper-builder-binary-assignment-operator-visitor" "^7.12.13"
-    "@babel/helper-plugin-utils" "^7.12.13"
-
-"@babel/plugin-transform-for-of@^7.0.0":
-  version "7.13.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz#c799f881a8091ac26b54867a845c3e97d2696062"
-  integrity sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.13.0"
-
-"@babel/plugin-transform-function-name@^7.0.0":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz#bb024452f9aaed861d374c8e7a24252ce3a50051"
-  integrity sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ==
-  dependencies:
-    "@babel/helper-function-name" "^7.12.13"
-    "@babel/helper-plugin-utils" "^7.12.13"
-
-"@babel/plugin-transform-instanceof@^7.0.0":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-instanceof/-/plugin-transform-instanceof-7.12.13.tgz#5df8ead82ed421b728662c9e0ed943536c872ced"
-  integrity sha512-lYZ6F2xmM797Nk8PzDn2AreAEjKb96S3JkZkaMUlRTIThaYjvo1+aLa7oegnVc42lJY7Hr4yT1M/i6kwRcPlsQ==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.12.13"
-
-"@babel/plugin-transform-literals@^7.0.0":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz#2ca45bafe4a820197cf315794a4d26560fe4bdb9"
-  integrity sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.12.13"
-
-"@babel/plugin-transform-modules-amd@^7.0.0":
-  version "7.13.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.13.0.tgz#19f511d60e3d8753cc5a6d4e775d3a5184866cc3"
-  integrity sha512-EKy/E2NHhY/6Vw5d1k3rgoobftcNUmp9fGjb9XZwQLtTctsRBOTRO7RHHxfIky1ogMN5BxN7p9uMA3SzPfotMQ==
-  dependencies:
-    "@babel/helper-module-transforms" "^7.13.0"
-    "@babel/helper-plugin-utils" "^7.13.0"
-    babel-plugin-dynamic-import-node "^2.3.3"
-
-"@babel/plugin-transform-object-super@^7.0.0":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz#b4416a2d63b8f7be314f3d349bd55a9c1b5171f7"
-  integrity sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.12.13"
-    "@babel/helper-replace-supers" "^7.12.13"
-
-"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.13.0":
-  version "7.13.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.13.0.tgz#8fa7603e3097f9c0b7ca1a4821bc2fb52e9e5007"
-  integrity sha512-Jt8k/h/mIwE2JFEOb3lURoY5C85ETcYPnbuAJ96zRBzh1XHtQZfs62ChZ6EP22QlC8c7Xqr9q+e1SU5qttwwjw==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.13.0"
-
-"@babel/plugin-transform-regenerator@^7.0.0":
-  version "7.13.15"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.13.15.tgz#e5eb28945bf8b6563e7f818945f966a8d2997f39"
-  integrity sha512-Bk9cOLSz8DiurcMETZ8E2YtIVJbFCPGW28DJWUakmyVWtQSm6Wsf0p4B4BfEr/eL2Nkhe/CICiUiMOCi1TPhuQ==
-  dependencies:
-    regenerator-transform "^0.14.2"
-
-"@babel/plugin-transform-shorthand-properties@^7.0.0":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz#db755732b70c539d504c6390d9ce90fe64aff7ad"
-  integrity sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.12.13"
-
-"@babel/plugin-transform-spread@^7.0.0":
-  version "7.13.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz#84887710e273c1815ace7ae459f6f42a5d31d5fd"
-  integrity sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.13.0"
-    "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1"
-
-"@babel/plugin-transform-sticky-regex@^7.0.0":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz#760ffd936face73f860ae646fb86ee82f3d06d1f"
-  integrity sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.12.13"
-
-"@babel/plugin-transform-template-literals@^7.0.0":
-  version "7.13.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz#a36049127977ad94438dee7443598d1cefdf409d"
-  integrity sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.13.0"
-
-"@babel/plugin-transform-typeof-symbol@^7.0.0":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz#785dd67a1f2ea579d9c2be722de8c84cb85f5a7f"
-  integrity sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.12.13"
-
-"@babel/plugin-transform-unicode-regex@^7.0.0":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz#b52521685804e155b1202e83fc188d34bb70f5ac"
-  integrity sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA==
-  dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.12.13"
-    "@babel/helper-plugin-utils" "^7.12.13"
-
-"@babel/runtime@^7.8.4":
-  version "7.13.10"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d"
-  integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
-  dependencies:
-    regenerator-runtime "^0.13.4"
-
-"@babel/template@^7.12.13":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327"
-  integrity sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==
-  dependencies:
-    "@babel/code-frame" "^7.12.13"
-    "@babel/parser" "^7.12.13"
-    "@babel/types" "^7.12.13"
-
-"@babel/traverse@^7.0.0", "@babel/traverse@^7.0.0-beta.42", "@babel/traverse@^7.13.0", "@babel/traverse@^7.13.13", "@babel/traverse@^7.13.15":
-  version "7.13.15"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.15.tgz#c38bf7679334ddd4028e8e1f7b3aa5019f0dada7"
-  integrity sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==
-  dependencies:
-    "@babel/code-frame" "^7.12.13"
-    "@babel/generator" "^7.13.9"
-    "@babel/helper-function-name" "^7.12.13"
-    "@babel/helper-split-export-declaration" "^7.12.13"
-    "@babel/parser" "^7.13.15"
-    "@babel/types" "^7.13.14"
-    debug "^4.1.0"
-    globals "^11.1.0"
-
-"@babel/types@^7.0.0-beta.42", "@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.13.0", "@babel/types@^7.13.12", "@babel/types@^7.13.14":
-  version "7.13.14"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.14.tgz#c35a4abb15c7cd45a2746d78ab328e362cbace0d"
-  integrity sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==
-  dependencies:
-    "@babel/helper-validator-identifier" "^7.12.11"
-    lodash "^4.17.19"
-    to-fast-properties "^2.0.0"
-
 "@bazel/rollup@^3.5.0":
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/@bazel/rollup/-/rollup-3.5.0.tgz#3de2db08cbc62c3cffbbabaa4517ec250cf6419a"
-  integrity sha512-sFPqbzSbIn6h66uuZdXgK5oitSmEGtnDPfL3TwTS4ZWy75SpYvk9X1TFGlvkralEkVnFfdH15sq80/1t+YgQow==
+  version "3.8.0"
+  resolved "https://registry.yarnpkg.com/@bazel/rollup/-/rollup-3.8.0.tgz#850f56176d73e3b7d99a43c7e33df21ecc6ac161"
+  integrity sha512-u63ubqYtfQhOu8Km3uYdhKa6qiLSlOKYsWwMP1xGkkXzu1hOiUznN1N7q8gCF1BV2DMy1D5IYkv+Xg4a+LEiBA==
 
 "@bazel/terser@^3.5.0":
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/@bazel/terser/-/terser-3.5.0.tgz#4b1c3a3b781e65547694aa05bc600c251e4d8c0b"
-  integrity sha512-dpWHn1Iu+w0uA/kvPb0pP+4Io0PrVuzCCbVg2Ow4uRt/gTFKQJJWp4EiTitEZlPA2dHlW7PHThAb93lGo2c8qA==
+  version "3.8.0"
+  resolved "https://registry.yarnpkg.com/@bazel/terser/-/terser-3.8.0.tgz#96d337b62b2ba18e2fe00984cca950cda899d165"
+  integrity sha512-c7cGIltFUI7prRocMDZ3qZVERnew81SFheuI5B9RQ3qeqTlJmlV8B8GI9FPG+7Ut69bmwn8es6UyPaH0iBnsQw==
 
 "@bazel/typescript@^3.5.0":
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-3.5.0.tgz#605493f4f0a5297df8a7fcccb86a1a80ea2090bb"
-  integrity sha512-BtGFp4nYFkQTmnONCzomk7dkmOwaINBL3piq+lykBlcc6UxLe9iCAnZpOyPypB1ReN3k3SRNAa53x6oGScQxMg==
+  version "3.8.0"
+  resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-3.8.0.tgz#725d51a1c25e314a1d8cddb8b880ac05ba97acd4"
+  integrity sha512-4C1pLe4V7aidWqcPsWNqXFS7uHAB1nH5SUKG5uWoVv4JT9XhkNSvzzQIycMwXs2tZeCylX4KYNeNvfKrmkyFlw==
   dependencies:
     protobufjs "6.8.8"
     semver "5.6.0"
     source-map-support "0.5.9"
     tsutils "2.27.2"
 
-"@dabh/diagnostics@^2.0.2":
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.2.tgz#290d08f7b381b8f94607dc8f471a12c675f9db31"
-  integrity sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==
-  dependencies:
-    colorspace "1.1.x"
-    enabled "2.0.x"
-    kuler "^2.0.0"
-
-"@eslint/eslintrc@^0.4.0":
-  version "0.4.0"
-  resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz"
-  integrity sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==
+"@eslint/eslintrc@^0.4.3":
+  version "0.4.3"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c"
+  integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==
   dependencies:
     ajv "^6.12.4"
     debug "^4.1.1"
     espree "^7.3.0"
-    globals "^12.1.0"
+    globals "^13.9.0"
     ignore "^4.0.6"
     import-fresh "^3.2.1"
     js-yaml "^3.13.1"
     minimatch "^3.0.4"
     strip-json-comments "^3.1.1"
 
-"@mrmlnc/readdir-enhanced@^2.2.1":
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
-  integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==
+"@humanwhocodes/config-array@^0.5.0":
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9"
+  integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==
   dependencies:
-    call-me-maybe "^1.0.1"
-    glob-to-regexp "^0.3.0"
+    "@humanwhocodes/object-schema" "^1.2.0"
+    debug "^4.1.1"
+    minimatch "^3.0.4"
 
-"@nodelib/fs.scandir@2.1.4":
-  version "2.1.4"
-  resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz"
-  integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==
+"@humanwhocodes/object-schema@^1.2.0":
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf"
+  integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==
+
+"@nodelib/fs.scandir@2.1.5":
+  version "2.1.5"
+  resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
+  integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
   dependencies:
-    "@nodelib/fs.stat" "2.0.4"
+    "@nodelib/fs.stat" "2.0.5"
     run-parallel "^1.1.9"
 
-"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2":
-  version "2.0.4"
-  resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz"
-  integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==
-
-"@nodelib/fs.stat@^1.1.2":
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
-  integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
+"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
+  integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
 
 "@nodelib/fs.walk@^1.2.3":
-  version "1.2.6"
-  resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz"
-  integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==
+  version "1.2.8"
+  resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
+  integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
   dependencies:
-    "@nodelib/fs.scandir" "2.1.4"
+    "@nodelib/fs.scandir" "2.1.5"
     fastq "^1.6.0"
 
-"@octokit/auth-token@^2.4.0":
-  version "2.4.5"
-  resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.5.tgz#568ccfb8cb46f36441fac094ce34f7a875b197f3"
-  integrity sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==
-  dependencies:
-    "@octokit/types" "^6.0.3"
-
-"@octokit/endpoint@^6.0.1":
-  version "6.0.11"
-  resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.11.tgz#082adc2aebca6dcefa1fb383f5efb3ed081949d1"
-  integrity sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ==
-  dependencies:
-    "@octokit/types" "^6.0.3"
-    is-plain-object "^5.0.0"
-    universal-user-agent "^6.0.0"
-
-"@octokit/openapi-types@^6.0.0":
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-6.0.0.tgz#7da8d7d5a72d3282c1a3ff9f951c8133a707480d"
-  integrity sha512-CnDdK7ivHkBtJYzWzZm7gEkanA7gKH6a09Eguz7flHw//GacPJLmkHA3f3N++MJmlxD1Fl+mB7B32EEpSCwztQ==
-
-"@octokit/plugin-paginate-rest@^1.1.1":
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-1.1.2.tgz#004170acf8c2be535aba26727867d692f7b488fc"
-  integrity sha512-jbsSoi5Q1pj63sC16XIUboklNw+8tL9VOnJsWycWYR78TKss5PVpIPb1TUUcMQ+bBh7cY579cVAWmf5qG+dw+Q==
-  dependencies:
-    "@octokit/types" "^2.0.1"
-
-"@octokit/plugin-request-log@^1.0.0":
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.3.tgz#70a62be213e1edc04bb8897ee48c311482f9700d"
-  integrity sha512-4RFU4li238jMJAzLgAwkBAw+4Loile5haQMQr+uhFq27BmyJXcXSKvoQKqh0agsZEiUlW6iSv3FAgvmGkur7OQ==
-
-"@octokit/plugin-rest-endpoint-methods@2.4.0":
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-2.4.0.tgz#3288ecf5481f68c494dd0602fc15407a59faf61e"
-  integrity sha512-EZi/AWhtkdfAYi01obpX0DF7U6b1VRr30QNQ5xSFPITMdLSfhcBqjamE3F+sKcxPbD7eZuMHu3Qkk2V+JGxBDQ==
-  dependencies:
-    "@octokit/types" "^2.0.1"
-    deprecation "^2.3.1"
-
-"@octokit/request-error@^1.0.2":
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-1.2.1.tgz#ede0714c773f32347576c25649dc013ae6b31801"
-  integrity sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==
-  dependencies:
-    "@octokit/types" "^2.0.0"
-    deprecation "^2.0.0"
-    once "^1.4.0"
-
-"@octokit/request-error@^2.0.0":
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.5.tgz#72cc91edc870281ad583a42619256b380c600143"
-  integrity sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg==
-  dependencies:
-    "@octokit/types" "^6.0.3"
-    deprecation "^2.0.0"
-    once "^1.4.0"
-
-"@octokit/request@^5.2.0":
-  version "5.4.15"
-  resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.15.tgz#829da413dc7dd3aa5e2cdbb1c7d0ebe1f146a128"
-  integrity sha512-6UnZfZzLwNhdLRreOtTkT9n57ZwulCve8q3IT/Z477vThu6snfdkBuhxnChpOKNGxcQ71ow561Qoa6uqLdPtag==
-  dependencies:
-    "@octokit/endpoint" "^6.0.1"
-    "@octokit/request-error" "^2.0.0"
-    "@octokit/types" "^6.7.1"
-    is-plain-object "^5.0.0"
-    node-fetch "^2.6.1"
-    universal-user-agent "^6.0.0"
-
-"@octokit/rest@^16.2.0":
-  version "16.43.2"
-  resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.43.2.tgz#c53426f1e1d1044dee967023e3279c50993dd91b"
-  integrity sha512-ngDBevLbBTFfrHZeiS7SAMAZ6ssuVmXuya+F/7RaVvlysgGa1JKJkKWY+jV6TCJYcW0OALfJ7nTIGXcBXzycfQ==
-  dependencies:
-    "@octokit/auth-token" "^2.4.0"
-    "@octokit/plugin-paginate-rest" "^1.1.1"
-    "@octokit/plugin-request-log" "^1.0.0"
-    "@octokit/plugin-rest-endpoint-methods" "2.4.0"
-    "@octokit/request" "^5.2.0"
-    "@octokit/request-error" "^1.0.2"
-    atob-lite "^2.0.0"
-    before-after-hook "^2.0.0"
-    btoa-lite "^1.0.0"
-    deprecation "^2.0.0"
-    lodash.get "^4.4.2"
-    lodash.set "^4.3.2"
-    lodash.uniq "^4.5.0"
-    octokit-pagination-methods "^1.1.0"
-    once "^1.4.0"
-    universal-user-agent "^4.0.0"
-
-"@octokit/types@^2.0.0", "@octokit/types@^2.0.1":
-  version "2.16.2"
-  resolved "https://registry.yarnpkg.com/@octokit/types/-/types-2.16.2.tgz#4c5f8da3c6fecf3da1811aef678fda03edac35d2"
-  integrity sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==
-  dependencies:
-    "@types/node" ">= 8"
-
-"@octokit/types@^6.0.3", "@octokit/types@^6.7.1":
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.13.0.tgz#779e5b7566c8dde68f2f6273861dd2f0409480d0"
-  integrity sha512-W2J9qlVIU11jMwKHUp5/rbVUeErqelCsO5vW5PKNb7wAXQVUz87Rc+imjlEvpvbH8yUb+KHmv8NEjVZdsdpyxA==
-  dependencies:
-    "@octokit/openapi-types" "^6.0.0"
-
-"@polymer/esm-amd-loader@^1.0.0":
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/@polymer/esm-amd-loader/-/esm-amd-loader-1.0.4.tgz#4e77f2f59b29b01e0ad02aa83d33716cddc5f9f9"
-  integrity sha512-h+hqYkL+tQV/y2ESD5gFXMl5z4cC+XY1jTlBeGSBaTcj3VbB5OBEScbvRXm63NcEbBneQQYbHfBAXAkF9i9wIA==
-
-"@polymer/sinonjs@^1.14.1":
-  version "1.17.1"
-  resolved "https://registry.yarnpkg.com/@polymer/sinonjs/-/sinonjs-1.17.1.tgz#e47d3785b7d0e8c29feb97f7e924b0fc597e2e9b"
-  integrity sha512-/U8F/cOTrbF2iVVYgINYmvKbtbexs+89Q3v8AaHADRYabTg7aOZGOb0RyWpOI+sUJt04kj63U4FwMhzW5r4wZA==
-
-"@polymer/test-fixture@^0.0.3":
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/@polymer/test-fixture/-/test-fixture-0.0.3.tgz#4443752697d4d9293bbc412ea0b5e4d341f149d9"
-  integrity sha1-REN1JpfU2Sk7vEEuoLXk00HxSdk=
-
 "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
   version "1.1.2"
-  resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz"
+  resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
   integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78=
 
 "@protobufjs/base64@^1.1.2":
   version "1.1.2"
-  resolved "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz"
+  resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
   integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
 
 "@protobufjs/codegen@^2.0.4":
   version "2.0.4"
-  resolved "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz"
+  resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb"
   integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==
 
 "@protobufjs/eventemitter@^1.1.0":
   version "1.1.0"
-  resolved "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
   integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A=
 
 "@protobufjs/fetch@^1.1.0":
   version "1.1.0"
-  resolved "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
   integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=
   dependencies:
     "@protobufjs/aspromise" "^1.1.1"
@@ -730,314 +130,41 @@
 
 "@protobufjs/float@^1.0.2":
   version "1.0.2"
-  resolved "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz"
+  resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
   integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=
 
 "@protobufjs/inquire@^1.1.0":
   version "1.1.0"
-  resolved "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
   integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=
 
 "@protobufjs/path@^1.1.2":
   version "1.1.2"
-  resolved "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz"
+  resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
   integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=
 
 "@protobufjs/pool@^1.1.0":
   version "1.1.0"
-  resolved "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
   integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=
 
 "@protobufjs/utf8@^1.1.0":
   version "1.1.0"
-  resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
   integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
 
 "@sindresorhus/is@^0.14.0":
   version "0.14.0"
-  resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz"
+  resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
   integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
 
-"@sindresorhus/is@^4.0.0":
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.0.tgz#2ff674e9611b45b528896d820d3d7a812de2f0e4"
-  integrity sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ==
-
 "@szmarczak/http-timer@^1.1.2":
   version "1.1.2"
-  resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz"
+  resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
   integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==
   dependencies:
     defer-to-connect "^1.0.1"
 
-"@szmarczak/http-timer@^4.0.5":
-  version "4.0.5"
-  resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152"
-  integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==
-  dependencies:
-    defer-to-connect "^2.0.0"
-
-"@types/babel-generator@^6.25.1":
-  version "6.25.3"
-  resolved "https://registry.yarnpkg.com/@types/babel-generator/-/babel-generator-6.25.3.tgz#8f06caa12d0595a0538560abe771966d77d29286"
-  integrity sha512-pGgnuxVddKcYIc+VJkRDop7gxLhqclNKBdlsm/5Vp8d+37pQkkDK7fef8d9YYImRzw9xcojEPc18pUYnbxmjqA==
-  dependencies:
-    "@types/babel-types" "*"
-
-"@types/babel-traverse@^6.25.2", "@types/babel-traverse@^6.25.3":
-  version "6.25.5"
-  resolved "https://registry.yarnpkg.com/@types/babel-traverse/-/babel-traverse-6.25.5.tgz#6d293cf7523e48b524faa7b86dc3c488191484e5"
-  integrity sha512-WrMbwmu+MWf8FiUMbmVOGkc7bHPzndUafn1CivMaBHthBBoo0VNIcYk1KV71UovYguhsNOwf3UF5oRmkkGOU3w==
-  dependencies:
-    "@types/babel-types" "*"
-
-"@types/babel-types@*":
-  version "7.0.9"
-  resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-7.0.9.tgz#01d7b86949f455402a94c788883fe4ba574cad41"
-  integrity sha512-qZLoYeXSTgQuK1h7QQS16hqLGdmqtRmN8w/rl3Au/l5x/zkHx+a4VHrHyBsi1I1vtK2oBHxSzKIu0R5p6spdOA==
-
-"@types/babel-types@^6.25.1":
-  version "6.25.2"
-  resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-6.25.2.tgz#5c57f45973e4f13742dbc5273dd84cffe7373a9e"
-  integrity sha512-+3bMuktcY4a70a0KZc8aPJlEOArPuAKQYHU5ErjkOqGJdx8xuEEVK6nWogqigBOJ8nKPxRpyCUDTCPmZ3bUxGA==
-
-"@types/babylon@^6.16.2":
-  version "6.16.5"
-  resolved "https://registry.yarnpkg.com/@types/babylon/-/babylon-6.16.5.tgz#1c5641db69eb8cdf378edd25b4be7754beeb48b4"
-  integrity sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==
-  dependencies:
-    "@types/babel-types" "*"
-
-"@types/bluebird@*":
-  version "3.5.33"
-  resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.33.tgz#d79c020f283bd50bd76101d7d300313c107325fc"
-  integrity sha512-ndEo1xvnYeHxm7I/5sF6tBvnsA4Tdi3zj1keRKRs12SP+2ye2A27NDJ1B6PqkfMbGAcT+mqQVqbZRIrhfOp5PQ==
-
-"@types/body-parser@*":
-  version "1.19.0"
-  resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f"
-  integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==
-  dependencies:
-    "@types/connect" "*"
-    "@types/node" "*"
-
-"@types/cacheable-request@^6.0.1":
-  version "6.0.1"
-  resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976"
-  integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==
-  dependencies:
-    "@types/http-cache-semantics" "*"
-    "@types/keyv" "*"
-    "@types/node" "*"
-    "@types/responselike" "*"
-
-"@types/chai-subset@^1.3.0":
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94"
-  integrity sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==
-  dependencies:
-    "@types/chai" "*"
-
-"@types/chai@*":
-  version "4.2.16"
-  resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.16.tgz#f09cc36e18d28274f942e7201147cce34d97e8c8"
-  integrity sha512-vI5iOAsez9+roLS3M3+Xx7w+WRuDtSmF8bQkrbcIJ2sC1PcDgVoA0WGpa+bIrJ+y8zqY2oi//fUctkxtIcXJCw==
-
-"@types/chalk@^0.4.30":
-  version "0.4.31"
-  resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-0.4.31.tgz#a31d74241a6b1edbb973cf36d97a2896834a51f9"
-  integrity sha1-ox10JBprHtu5c8822XooloNKUfk=
-
-"@types/chalk@^2.2.0":
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-2.2.0.tgz#b7f6e446f4511029ee8e3f43075fb5b73fbaa0ba"
-  integrity sha512-1zzPV9FDe1I/WHhRkf9SNgqtRJWZqrBWgu7JGveuHmmyR9CnAPCie2N/x+iHrgnpYBIcCJWHBoMRv2TRWktsvw==
-  dependencies:
-    chalk "*"
-
-"@types/clean-css@*":
-  version "4.2.4"
-  resolved "https://registry.yarnpkg.com/@types/clean-css/-/clean-css-4.2.4.tgz#4fe4705c384e6ec9ee8454bc3d49089f38dc038a"
-  integrity sha512-x8xEbfTtcv5uyQDrBXKg9Beo5QhTPqO4vM0uq4iU27/nhyRRWNEMKHjxvAb0WDvp2Mnt4Sw0jKmIi5yQF/k2Ag==
-  dependencies:
-    "@types/node" "*"
-    source-map "^0.6.0"
-
-"@types/clone@^0.1.30":
-  version "0.1.30"
-  resolved "https://registry.yarnpkg.com/@types/clone/-/clone-0.1.30.tgz#e7365648c1b42136a59c7d5040637b3b5c83b614"
-  integrity sha1-5zZWSMG0ITalnH1QQGN7O1yDthQ=
-
-"@types/compression@^0.0.33":
-  version "0.0.33"
-  resolved "https://registry.yarnpkg.com/@types/compression/-/compression-0.0.33.tgz#95dc733a2339aa846381d7f1377792d2553dc27d"
-  integrity sha1-ldxzOiM5qoRjgdfxN3eS0lU9wn0=
-  dependencies:
-    "@types/express" "*"
-
-"@types/connect@*":
-  version "3.4.34"
-  resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901"
-  integrity sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==
-  dependencies:
-    "@types/node" "*"
-
-"@types/content-type@^1.1.0":
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/@types/content-type/-/content-type-1.1.3.tgz#3688bd77fc12f935548eef102a4e34c512b03a07"
-  integrity sha512-pv8VcFrZ3fN93L4rTNIbbUzdkzjEyVMp5mPVjsFfOYTDOZMZiZ8P1dhu+kEv3faYyKzZgLlSvnyQNFg+p/v5ug==
-
-"@types/cssbeautify@^0.3.1":
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/@types/cssbeautify/-/cssbeautify-0.3.1.tgz#8e0bee8f7decb952250da0caebe05e30591c17ef"
-  integrity sha1-jgvuj33suVIlDaDK6+BeMFkcF+8=
-
-"@types/del@^3.0.0":
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/@types/del/-/del-3.0.1.tgz#4712da8c119873cbbf533ad8dbf1baac5940ac5d"
-  integrity sha512-y6qRq6raBuu965clKgx6FHuiPu3oHdtmzMPXi8Uahsjdq1L6DL5fS/aY5/s71YwM7k6K1QIWvem5vNwlnNGIkQ==
-  dependencies:
-    "@types/glob" "*"
-
-"@types/doctrine@^0.0.1":
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/@types/doctrine/-/doctrine-0.0.1.tgz#b999f2d9f7b43cabe0a1a2f39bc203bc7dcada9d"
-  integrity sha1-uZny2fe0PKvgoaLzm8IDvH3K2p0=
-
-"@types/escape-html@0.0.20":
-  version "0.0.20"
-  resolved "https://registry.yarnpkg.com/@types/escape-html/-/escape-html-0.0.20.tgz#cae698714dd61ebee5ab3f2aeb9a34ba1011735a"
-  integrity sha512-6dhZJLbA7aOwkYB2GDGdIqJ20wmHnkDzaxV9PJXe7O02I2dSFTERzRB6JrX6cWKaS+VqhhY7cQUMCbO5kloFUw==
-
-"@types/estree@*":
-  version "0.0.47"
-  resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.47.tgz#d7a51db20f0650efec24cd04994f523d93172ed4"
-  integrity sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg==
-
-"@types/events@*":
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
-  integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
-
-"@types/expect@^1.20.4":
-  version "1.20.4"
-  resolved "https://registry.yarnpkg.com/@types/expect/-/expect-1.20.4.tgz#8288e51737bf7e3ab5d7c77bfa695883745264e5"
-  integrity sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==
-
-"@types/express-serve-static-core@^4.17.18":
-  version "4.17.19"
-  resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz#00acfc1632e729acac4f1530e9e16f6dd1508a1d"
-  integrity sha512-DJOSHzX7pCiSElWaGR8kCprwibCB/3yW6vcT8VG3P0SJjnv19gnWG/AZMfM60Xj/YJIp/YCaDHyvzsFVeniARA==
-  dependencies:
-    "@types/node" "*"
-    "@types/qs" "*"
-    "@types/range-parser" "*"
-
-"@types/express@*", "@types/express@^4.0.30", "@types/express@^4.0.36":
-  version "4.17.11"
-  resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.11.tgz#debe3caa6f8e5fcda96b47bd54e2f40c4ee59545"
-  integrity sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg==
-  dependencies:
-    "@types/body-parser" "*"
-    "@types/express-serve-static-core" "^4.17.18"
-    "@types/qs" "*"
-    "@types/serve-static" "*"
-
-"@types/fast-levenshtein@0.0.1":
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/@types/fast-levenshtein/-/fast-levenshtein-0.0.1.tgz#3a3615cf173645c8fca58d051e4e32824e4bd286"
-  integrity sha1-OjYVzxc2Rcj8pY0FHk4ygk5L0oY=
-
-"@types/findup-sync@^0.3.29":
-  version "0.3.30"
-  resolved "https://registry.yarnpkg.com/@types/findup-sync/-/findup-sync-0.3.30.tgz#8ab7bdbd6ba7cbf4f33b6596fde6fff1129c738d"
-  integrity sha512-Dpt1x3rhz6t8BMTS4vziTVos8VLkF4RngIxMBCSE6w0STmnVEEaoe3w+BG5xHyZXshye9lyZE99lpBDoLGY8eA==
-  dependencies:
-    "@types/minimatch" "*"
-
-"@types/form-data@*":
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.5.0.tgz#5025f7433016f923348434c40006d9a797c1b0e8"
-  integrity sha512-23/wYiuckYYtFpL+4RPWiWmRQH2BjFuqCUi2+N3amB1a1Drv+i/byTrGvlLwRVLFNAZbwpbQ7JvTK+VCAPMbcg==
-  dependencies:
-    form-data "*"
-
-"@types/freeport@^1.0.19":
-  version "1.0.21"
-  resolved "https://registry.yarnpkg.com/@types/freeport/-/freeport-1.0.21.tgz#73f6543ed67d3ca3fff97b985591598b7092066f"
-  integrity sha1-c/ZUPtZ9PKP/+XuYVZFZi3CSBm8=
-
-"@types/glob-stream@*":
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/@types/glob-stream/-/glob-stream-6.1.0.tgz#7ede8a33e59140534f8d8adfb8ac9edfb31897bc"
-  integrity sha512-RHv6ZQjcTncXo3thYZrsbAVwoy4vSKosSWhuhuQxLOTv74OJuFQxXkmUuZCr3q9uNBEVCvIzmZL/FeRNbHZGUg==
-  dependencies:
-    "@types/glob" "*"
-    "@types/node" "*"
-
-"@types/glob@*", "@types/glob@^7.1.1":
-  version "7.1.3"
-  resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
-  integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==
-  dependencies:
-    "@types/minimatch" "*"
-    "@types/node" "*"
-
-"@types/globby@^6.1.0":
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/@types/globby/-/globby-6.1.0.tgz#7c25b975512a89effea2a656ca8cf6db7fb29d11"
-  integrity sha512-j3XSDNoK4LO5T+ZviQD6PqfEjm07QFEacOTbJR3hnLWuWX0ZMLJl9oRPgj1PyrfGbXhfHFkksC9QZ9HFltJyrw==
-  dependencies:
-    "@types/glob" "*"
-
-"@types/gulp-if@0.0.33":
-  version "0.0.33"
-  resolved "https://registry.yarnpkg.com/@types/gulp-if/-/gulp-if-0.0.33.tgz#edece22b7925d9a6db5f9c8c0d7882aa776fb678"
-  integrity sha512-J5lzff21X7r1x/4hSzn02GgIUEyjCqYIXZ9GgGBLhbsD3RiBdqwnkFWgF16/0jO5rcVZ52Zp+6MQMQdvIsWuKg==
-  dependencies:
-    "@types/node" "*"
-    "@types/vinyl" "*"
-
-"@types/html-minifier@^3.5.1":
-  version "3.5.3"
-  resolved "https://registry.yarnpkg.com/@types/html-minifier/-/html-minifier-3.5.3.tgz#5276845138db2cebc54c789e0aaf87621a21e84f"
-  integrity sha512-j1P/4PcWVVCPEy5lofcHnQ6BtXz9tHGiFPWzqm7TtGuWZEfCHEP446HlkSNc9fQgNJaJZ6ewPtp2aaFla/Uerg==
-  dependencies:
-    "@types/clean-css" "*"
-    "@types/relateurl" "*"
-    "@types/uglify-js" "*"
-
-"@types/http-cache-semantics@*":
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a"
-  integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==
-
-"@types/inquirer@*":
-  version "7.3.1"
-  resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-7.3.1.tgz#1f231224e7df11ccfaf4cf9acbcc3b935fea292d"
-  integrity sha512-osD38QVIfcdgsPCT0V3lD7eH0OFurX71Jft18bZrsVQWVRt6TuxRzlr0GJLrxoHZR2V5ph7/qP8se/dcnI7o0g==
-  dependencies:
-    "@types/through" "*"
-    rxjs "^6.4.0"
-
-"@types/inquirer@0.0.32":
-  version "0.0.32"
-  resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-0.0.32.tgz#a4a08e83741c500a7c3c8e7776014f7f8a65870d"
-  integrity sha1-pKCOg3QcUAp8PI53dgFPf4plhw0=
-  dependencies:
-    "@types/rx" "*"
-    "@types/through" "*"
-
-"@types/is-windows@^0.2.0":
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/@types/is-windows/-/is-windows-0.2.0.tgz#6f24ee48731d31168ea510610d6dd15e5fc9c6ff"
-  integrity sha1-byTuSHMdMRaOpRBhDW3RXl/Jxv8=
-
-"@types/json-schema@^7.0.3":
-  version "7.0.7"
-  resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz"
-  integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
-
 "@types/json-schema@^7.0.7":
   version "7.0.9"
   resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
@@ -1045,587 +172,121 @@
 
 "@types/json5@^0.0.29":
   version "0.0.29"
-  resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz"
+  resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
   integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
 
-"@types/keyv@*":
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7"
-  integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==
-  dependencies:
-    "@types/node" "*"
-
-"@types/launchpad@^0.6.0":
-  version "0.6.0"
-  resolved "https://registry.yarnpkg.com/@types/launchpad/-/launchpad-0.6.0.tgz#37296109b7f277f6e6c5fd7e0c0706bc918fbb51"
-  integrity sha1-NylhCbfyd/bmxf1+DAcGvJGPu1E=
-
 "@types/long@^4.0.0":
   version "4.0.1"
-  resolved "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
   integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
 
-"@types/merge-stream@^1.0.28":
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/@types/merge-stream/-/merge-stream-1.1.2.tgz#a880ff66b1fbbb5eef4958d015c5947a9334dbb1"
-  integrity sha512-7faLmaE99g/yX0Y9pF1neh2IUqOf/fXMOWCVzsXjqI1EJ91lrgXmaBKf6bRWM164lLyiHxHt6t/ZO/cIzq61XA==
-  dependencies:
-    "@types/node" "*"
-
-"@types/mime@^1":
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
-  integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
-
-"@types/mime@^2.0.0":
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a"
-  integrity sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==
-
-"@types/minimatch@*", "@types/minimatch@^3.0.1", "@types/minimatch@^3.0.3":
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21"
-  integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==
-
 "@types/minimatch@3.0.3":
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
   integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
 
 "@types/minimist@^1.2.0":
-  version "1.2.1"
-  resolved "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz"
-  integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c"
+  integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==
 
-"@types/mz@0.0.29":
-  version "0.0.29"
-  resolved "https://registry.yarnpkg.com/@types/mz/-/mz-0.0.29.tgz#bc24728c649973f1c7851e9033f9ce525668c27b"
-  integrity sha1-vCRyjGSZc/HHhR6QM/nOUlZowns=
-  dependencies:
-    "@types/bluebird" "*"
-    "@types/node" "*"
-
-"@types/mz@0.0.31", "@types/mz@^0.0.31":
-  version "0.0.31"
-  resolved "https://registry.yarnpkg.com/@types/mz/-/mz-0.0.31.tgz#a4d80c082fefe71e40a7c0f07d1e6555bbbc7b52"
-  integrity sha1-pNgMCC/v5x5Ap8DwfR5lVbu8e1I=
-  dependencies:
-    "@types/node" "*"
-
-"@types/node@*", "@types/node@>= 8":
-  version "14.14.37"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.37.tgz#a3dd8da4eb84a996c36e331df98d82abd76b516e"
-  integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==
+"@types/node@*":
+  version "16.7.10"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.10.tgz#7aa732cc47341c12a16b7d562f519c2383b6d4fc"
+  integrity sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==
 
 "@types/node@^10.1.0":
-  version "10.17.56"
-  resolved "https://registry.npmjs.org/@types/node/-/node-10.17.56.tgz"
-  integrity sha512-LuAa6t1t0Bfw4CuSR0UITsm1hP17YL+u82kfHGrHUWdhlBtH7sa7jGY5z7glGaIj/WDYDkRtgGd+KCjCzxBW1w==
-
-"@types/node@^4.0.30":
-  version "4.9.5"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-4.9.5.tgz#a3785db96b07a4b56466cc99fd624838746f2e25"
-  integrity sha512-+8fpgbXsbATKRF2ayAlYhPl2E9MPdLjrnK/79ZEpyPJ+k7dZwJm9YM8FK+l4rqL//xHk7PgQhGwz6aA2ckxbCQ==
+  version "10.17.60"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b"
+  integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==
 
 "@types/normalize-package-data@^2.4.0":
-  version "2.4.0"
-  resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz"
-  integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
+  integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==
 
-"@types/opn@^3.0.28":
-  version "3.0.28"
-  resolved "https://registry.yarnpkg.com/@types/opn/-/opn-3.0.28.tgz#097d0d1c9b5749573a5d96df132387bb6d02118a"
-  integrity sha1-CX0NHJtXSVc6XZbfEyOHu20CEYo=
+"@typescript-eslint/eslint-plugin@^4.2.0", "@typescript-eslint/eslint-plugin@^4.29.0":
+  version "4.30.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.30.0.tgz#4a0c1ae96b953f4e67435e20248d812bfa55e4fb"
+  integrity sha512-NgAnqk55RQ/SD+tZFD9aPwNSeHmDHHe5rtUyhIq0ZeCWZEvo4DK9rYz7v9HDuQZFvn320Ot+AikaCKMFKLlD0g==
   dependencies:
-    "@types/node" "*"
-
-"@types/parse5@^2.2.34":
-  version "2.2.34"
-  resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-2.2.34.tgz#e3870a10e82735a720f62d71dcd183ba78ef3a9d"
-  integrity sha1-44cKEOgnNacg9i1x3NGDunjvOp0=
-  dependencies:
-    "@types/node" "*"
-
-"@types/path-is-inside@^1.0.0":
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/@types/path-is-inside/-/path-is-inside-1.0.0.tgz#02d6ff38975d684bdec96204494baf9f29f0e17f"
-  integrity sha512-hfnXRGugz+McgX2jxyy5qz9sB21LRzlGn24zlwN2KEgoPtEvjzNRrLtUkOOebPDPZl3Rq7ywKxYvylVcEZDnEw==
-
-"@types/pem@^1.8.1":
-  version "1.9.5"
-  resolved "https://registry.yarnpkg.com/@types/pem/-/pem-1.9.5.tgz#cd5548b5e0acb4b41a9e21067e9fcd8c57089c99"
-  integrity sha512-C0txxEw8B7DCoD85Ko7SEvzUogNd5VDJ5/YBG8XUcacsOGqxr5Oo4g3OUAfdEDUbhXanwUoVh/ZkMFw77FGPQQ==
-  dependencies:
-    "@types/node" "*"
-
-"@types/qs@*":
-  version "6.9.6"
-  resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.6.tgz#df9c3c8b31a247ec315e6996566be3171df4b3b1"
-  integrity sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==
-
-"@types/range-parser@*":
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
-  integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
-
-"@types/relateurl@*":
-  version "0.2.28"
-  resolved "https://registry.yarnpkg.com/@types/relateurl/-/relateurl-0.2.28.tgz#6bda7db8653fa62643f5ee69e9f69c11a392e3a6"
-  integrity sha1-a9p9uGU/piZD9e5p6facEaOS46Y=
-
-"@types/request@2.0.3":
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/@types/request/-/request-2.0.3.tgz#bdf0fba9488c822f77e97de3dd8fe357b2fb8c06"
-  integrity sha512-cIvnyFRARxwE4OHpCyYue7H+SxaKFPpeleRCHJicft8QhyTNbVYsMwjvEzEPqG06D2LGHZ+sN5lXc8+bTu6D8A==
-  dependencies:
-    "@types/form-data" "*"
-    "@types/node" "*"
-
-"@types/resolve@0.0.4":
-  version "0.0.4"
-  resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.4.tgz#9b586d65a947dea88c4bc24da0b905fe9520a0d5"
-  integrity sha1-m1htZalH3qiMS8JNoLkF/pUgoNU=
-  dependencies:
-    "@types/node" "*"
-
-"@types/resolve@0.0.6":
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.6.tgz#0bd2f236c2e1cebb98b79885df57edd71a8d770e"
-  integrity sha512-g+Rg8uMWY76oYTyaL+m7ZcblqF/oj7pE6uEUyACluJx4zcop1Lk14qQiocdEkEVMDFm6DmKpxJhsER+ZuTwG3g==
-  dependencies:
-    "@types/node" "*"
-
-"@types/resolve@0.0.7":
-  version "0.0.7"
-  resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.7.tgz#b299c13be8d712b1b502fb14a084252acef84f4d"
-  integrity sha512-GPewdjkb0Q76o459qgp6pBLzJj/bD3oveS2kfLhIkZ9U3t3AFKtl5DlFB6lGTw0iZmcmxoGC8lpLW3NNJKrN9A==
-  dependencies:
-    "@types/node" "*"
-
-"@types/responselike@*", "@types/responselike@^1.0.0":
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29"
-  integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==
-  dependencies:
-    "@types/node" "*"
-
-"@types/rimraf@^0.0.28":
-  version "0.0.28"
-  resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-0.0.28.tgz#5562519bc7963caca8abf7f128cae3b594d41d06"
-  integrity sha1-VWJRm8eWPKyoq/fxKMrjtZTUHQY=
-
-"@types/rx-core-binding@*":
-  version "4.0.4"
-  resolved "https://registry.yarnpkg.com/@types/rx-core-binding/-/rx-core-binding-4.0.4.tgz#d969d32f15a62b89e2862c17b3ee78fe329818d3"
-  integrity sha512-5pkfxnC4w810LqBPUwP5bg7SFR/USwhMSaAeZQQbEHeBp57pjKXRlXmqpMrLJB4y1oglR/c2502853uN0I+DAQ==
-  dependencies:
-    "@types/rx-core" "*"
-
-"@types/rx-core@*":
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/@types/rx-core/-/rx-core-4.0.3.tgz#0b3354b1238cedbe2b74f6326f139dbc7a591d60"
-  integrity sha1-CzNUsSOM7b4rdPYybxOdvHpZHWA=
-
-"@types/rx-lite-aggregates@*":
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/@types/rx-lite-aggregates/-/rx-lite-aggregates-4.0.3.tgz#6efb2b7f3d5f07183a1cb2bd4b1371d7073384c2"
-  integrity sha512-MAGDAHy8cRatm94FDduhJF+iNS5//jrZ/PIfm+QYw9OCeDgbymFHChM8YVIvN2zArwsRftKgE33QfRWvQk4DPg==
-  dependencies:
-    "@types/rx-lite" "*"
-
-"@types/rx-lite-async@*":
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/@types/rx-lite-async/-/rx-lite-async-4.0.2.tgz#27fbf0caeff029f41e2d2aae638b05e91ceb600c"
-  integrity sha512-vTEv5o8l6702ZwfAM5aOeVDfUwBSDOs+ARoGmWAKQ6LOInQ8J4/zjM7ov12fuTpktUKdMQjkeCp07Vd73mPkxw==
-  dependencies:
-    "@types/rx-lite" "*"
-
-"@types/rx-lite-backpressure@*":
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/@types/rx-lite-backpressure/-/rx-lite-backpressure-4.0.3.tgz#05abb19bdf87cc740196c355e5d0b37bb50b5d56"
-  integrity sha512-Y6aIeQCtNban5XSAF4B8dffhIKu6aAy/TXFlScHzSxh6ivfQBQw6UjxyEJxIOt3IT49YkS+siuayM2H/Q0cmgA==
-  dependencies:
-    "@types/rx-lite" "*"
-
-"@types/rx-lite-coincidence@*":
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/@types/rx-lite-coincidence/-/rx-lite-coincidence-4.0.3.tgz#80bd69acc4054a15cdc1638e2dc8843498cd85c0"
-  integrity sha512-1VNJqzE9gALUyMGypDXZZXzR0Tt7LC9DdAZQ3Ou/Q0MubNU35agVUNXKGHKpNTba+fr8GdIdkC26bRDqtCQBeQ==
-  dependencies:
-    "@types/rx-lite" "*"
-
-"@types/rx-lite-experimental@*":
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/@types/rx-lite-experimental/-/rx-lite-experimental-4.0.1.tgz#c532f5cbdf3f2c15da16ded8930d1b2984023cbd"
-  integrity sha1-xTL1y98/LBXaFt7Ykw0bKYQCPL0=
-  dependencies:
-    "@types/rx-lite" "*"
-
-"@types/rx-lite-joinpatterns@*":
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/@types/rx-lite-joinpatterns/-/rx-lite-joinpatterns-4.0.1.tgz#f70fe370518a8432f29158cc92ffb56b4e4afc3e"
-  integrity sha1-9w/jcFGKhDLykVjMkv+1a05K/D4=
-  dependencies:
-    "@types/rx-lite" "*"
-
-"@types/rx-lite-testing@*":
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/@types/rx-lite-testing/-/rx-lite-testing-4.0.1.tgz#21b19d11f4dfd6ffef5a9d1648e9c8879bfe21e9"
-  integrity sha1-IbGdEfTf1v/vWp0WSOnIh5v+Iek=
-  dependencies:
-    "@types/rx-lite-virtualtime" "*"
-
-"@types/rx-lite-time@*":
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/@types/rx-lite-time/-/rx-lite-time-4.0.3.tgz#0eda65474570237598f3448b845d2696f2dbb1c4"
-  integrity sha512-ukO5sPKDRwCGWRZRqPlaAU0SKVxmWwSjiOrLhoQDoWxZWg6vyB9XLEZViKOzIO6LnTIQBlk4UylYV0rnhJLxQw==
-  dependencies:
-    "@types/rx-lite" "*"
-
-"@types/rx-lite-virtualtime@*":
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/@types/rx-lite-virtualtime/-/rx-lite-virtualtime-4.0.3.tgz#4b30cacd0fe2e53af29f04f7438584c7d3959537"
-  integrity sha512-3uC6sGmjpOKatZSVHI2xB1+dedgml669ZRvqxy+WqmGJDVusOdyxcKfyzjW0P3/GrCiN4nmRkLVMhPwHCc5QLg==
-  dependencies:
-    "@types/rx-lite" "*"
-
-"@types/rx-lite@*":
-  version "4.0.6"
-  resolved "https://registry.yarnpkg.com/@types/rx-lite/-/rx-lite-4.0.6.tgz#3c02921c4244074234f26b772241bcc20c18c253"
-  integrity sha512-oYiDrFIcor9zDm0VDUca1UbROiMYBxMLMaM6qzz4ADAfOmA9r1dYEcAFH+2fsPI5BCCjPvV9pWC3X3flbrvs7w==
-  dependencies:
-    "@types/rx-core" "*"
-    "@types/rx-core-binding" "*"
-
-"@types/rx@*":
-  version "4.1.2"
-  resolved "https://registry.yarnpkg.com/@types/rx/-/rx-4.1.2.tgz#a4061b3d72b03cf11a38d69e2022a17334c54dc0"
-  integrity sha512-1r8ZaT26Nigq7o4UBGl+aXB2UMFUIdLPP/8bLIP0x3d0pZL46ybKKjhWKaJQWIkLl5QCLD0nK3qTOO1QkwdFaA==
-  dependencies:
-    "@types/rx-core" "*"
-    "@types/rx-core-binding" "*"
-    "@types/rx-lite" "*"
-    "@types/rx-lite-aggregates" "*"
-    "@types/rx-lite-async" "*"
-    "@types/rx-lite-backpressure" "*"
-    "@types/rx-lite-coincidence" "*"
-    "@types/rx-lite-experimental" "*"
-    "@types/rx-lite-joinpatterns" "*"
-    "@types/rx-lite-testing" "*"
-    "@types/rx-lite-time" "*"
-    "@types/rx-lite-virtualtime" "*"
-
-"@types/semver@^5.3.30":
-  version "5.5.0"
-  resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
-  integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==
-
-"@types/serve-static@*", "@types/serve-static@^1.7.31":
-  version "1.13.9"
-  resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e"
-  integrity sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA==
-  dependencies:
-    "@types/mime" "^1"
-    "@types/node" "*"
-
-"@types/spdy@^3.4.1":
-  version "3.4.4"
-  resolved "https://registry.yarnpkg.com/@types/spdy/-/spdy-3.4.4.tgz#3282fd4ad8c4603aa49f7017dd520a08a345b2bc"
-  integrity sha512-N9LBlbVRRYq6HgYpPkqQc3a9HJ/iEtVZToW6xlTtJiMhmRJ7jJdV7TaZQJw/Ve/1ePUsQiCTDc4JMuzzag94GA==
-  dependencies:
-    "@types/node" "*"
-
-"@types/temp@^0.8.28":
-  version "0.8.34"
-  resolved "https://registry.yarnpkg.com/@types/temp/-/temp-0.8.34.tgz#03e4b3cb67cbb48c425bbf54b12230fef85540ac"
-  integrity sha512-oLa9c5LHXgS6UimpEVp08De7QvZ+Dfu5bMQuWyMhf92Z26Q10ubEMOWy9OEfUdzW7Y/sDWVHmUaLFtmnX/2j0w==
-  dependencies:
-    "@types/node" "*"
-
-"@types/through@*":
-  version "0.0.30"
-  resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895"
-  integrity sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==
-  dependencies:
-    "@types/node" "*"
-
-"@types/ua-parser-js@^0.7.31":
-  version "0.7.35"
-  resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.35.tgz#cca67a95deb9165e4b1f449471801e6489d3fe93"
-  integrity sha512-PsPx0RLbo2Un8+ff2buzYJnZjzwhD3jQHPOG2PtVIeOhkRDddMcKU8vJtHpzzfLB95dkUi0qAkfLg2l2Fd0yrQ==
-
-"@types/uglify-js@*":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.13.0.tgz#1cad8df1fb0b143c5aba08de5712ea9d1ff71124"
-  integrity sha512-EGkrJD5Uy+Pg0NUR8uA4bJ5WMfljyad0G+784vLCNUkD+QwOJXUbBYExXfVGf7YtyzdQp3L/XMYcliB987kL5Q==
-  dependencies:
-    source-map "^0.6.1"
-
-"@types/update-notifier@^1.0.0":
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/@types/update-notifier/-/update-notifier-1.0.3.tgz#3c7ee1921af6f16149cdcaef356baf57d7a0b806"
-  integrity sha512-BLStNhP2DFF7funARwTcoD6tetRte8NK3Sc59mn7GNALCN975jOlKX3dGvsFxXr/HwQMxxCuRn9IWB3WQ7odHQ==
-
-"@types/uuid@^3.4.3":
-  version "3.4.9"
-  resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.9.tgz#fcf01997bbc9f7c09ae5f91383af076d466594e1"
-  integrity sha512-XDwyIlt/47l2kWLTzw/mtrpLdB+GPSskR2n/PIcPn+VYhVO77rGhRncIR5GPU0KRzXuqkDO+J5qqrG0Y8P6jzQ==
-
-"@types/vinyl-fs@0.0.28":
-  version "0.0.28"
-  resolved "https://registry.yarnpkg.com/@types/vinyl-fs/-/vinyl-fs-0.0.28.tgz#4663017bc802c6570eae4f3409fd5cabf97cbfde"
-  integrity sha1-RmMBe8gCxlcOrk80Cf1cq/l8v94=
-  dependencies:
-    "@types/glob-stream" "*"
-    "@types/node" "*"
-    "@types/vinyl" "*"
-
-"@types/vinyl-fs@^2.4.8":
-  version "2.4.11"
-  resolved "https://registry.yarnpkg.com/@types/vinyl-fs/-/vinyl-fs-2.4.11.tgz#b98119b8bb2494141eaf649b09fbfeb311161206"
-  integrity sha512-2OzQSfIr9CqqWMGqmcERE6Hnd2KY3eBVtFaulVo3sJghplUcaeMdL9ZjEiljcQQeHjheWY9RlNmumjIAvsBNaA==
-  dependencies:
-    "@types/glob-stream" "*"
-    "@types/node" "*"
-    "@types/vinyl" "*"
-
-"@types/vinyl@*", "@types/vinyl@^2.0.0":
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/@types/vinyl/-/vinyl-2.0.4.tgz#9a7a8071c8d14d3a95d41ebe7135babe4ad5995a"
-  integrity sha512-2o6a2ixaVI2EbwBPg1QYLGQoHK56p/8X/sGfKbFC8N6sY9lfjsMf/GprtkQkSya0D4uRiutRZ2BWj7k3JvLsAQ==
-  dependencies:
-    "@types/expect" "^1.20.4"
-    "@types/node" "*"
-
-"@types/whatwg-url@^6.4.0":
-  version "6.4.0"
-  resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-6.4.0.tgz#1e59b8c64bc0dbdf66d037cf8449d1c3d5270237"
-  integrity sha512-tonhlcbQ2eho09am6RHnHOgvtDfDYINd5rgxD+2YSkKENooVCFsWizJz139MQW/PV8FfClyKrNe9ZbdHrSCxGg==
-  dependencies:
-    "@types/node" "*"
-
-"@types/which@^1.3.1":
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/@types/which/-/which-1.3.2.tgz#9c246fc0c93ded311c8512df2891fb41f6227fdf"
-  integrity sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==
-
-"@types/yeoman-generator@^2.0.3":
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/@types/yeoman-generator/-/yeoman-generator-2.0.3.tgz#f4b161ee354078b526e0901a5a5f87d4f8e085f6"
-  integrity sha512-vch2UFd6k7DdfWEv/alRwZIRXQoxZNUDpfLOK24+005dzE1HVnwSWfETF3WxJnWlsOcH87wU4uzldAE/7F/6Lw==
-  dependencies:
-    "@types/events" "*"
-    "@types/inquirer" "*"
-
-"@typescript-eslint/eslint-plugin@^4.2.0":
-  version "4.21.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.21.0.tgz"
-  integrity sha512-FPUyCPKZbVGexmbCFI3EQHzCZdy2/5f+jv6k2EDljGdXSRc0cKvbndd2nHZkSLqCNOPk0jB6lGzwIkglXcYVsQ==
-  dependencies:
-    "@typescript-eslint/experimental-utils" "4.21.0"
-    "@typescript-eslint/scope-manager" "4.21.0"
-    debug "^4.1.1"
-    functional-red-black-tree "^1.0.1"
-    lodash "^4.17.15"
-    regexpp "^3.0.0"
-    semver "^7.3.2"
-    tsutils "^3.17.1"
-
-"@typescript-eslint/eslint-plugin@^4.25.0":
-  version "4.29.2"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.2.tgz#f54dc0a32b8f61c6024ab8755da05363b733838d"
-  integrity sha512-x4EMgn4BTfVd9+Z+r+6rmWxoAzBaapt4QFqE+d8L8sUtYZYLDTK6VG/y/SMMWA5t1/BVU5Kf+20rX4PtWzUYZg==
-  dependencies:
-    "@typescript-eslint/experimental-utils" "4.29.2"
-    "@typescript-eslint/scope-manager" "4.29.2"
+    "@typescript-eslint/experimental-utils" "4.30.0"
+    "@typescript-eslint/scope-manager" "4.30.0"
     debug "^4.3.1"
     functional-red-black-tree "^1.0.1"
     regexpp "^3.1.0"
     semver "^7.3.5"
     tsutils "^3.21.0"
 
-"@typescript-eslint/experimental-utils@4.21.0":
-  version "4.21.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.21.0.tgz"
-  integrity sha512-cEbgosW/tUFvKmkg3cU7LBoZhvUs+ZPVM9alb25XvR0dal4qHL3SiUqHNrzoWSxaXA9gsifrYrS1xdDV6w/gIA==
-  dependencies:
-    "@types/json-schema" "^7.0.3"
-    "@typescript-eslint/scope-manager" "4.21.0"
-    "@typescript-eslint/types" "4.21.0"
-    "@typescript-eslint/typescript-estree" "4.21.0"
-    eslint-scope "^5.0.0"
-    eslint-utils "^2.0.0"
-
-"@typescript-eslint/experimental-utils@4.29.2":
-  version "4.29.2"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.2.tgz#5f67fb5c5757ef2cb3be64817468ba35c9d4e3b7"
-  integrity sha512-P6mn4pqObhftBBPAv4GQtEK7Yos1fz/MlpT7+YjH9fTxZcALbiiPKuSIfYP/j13CeOjfq8/fr9Thr2glM9ub7A==
+"@typescript-eslint/experimental-utils@4.30.0":
+  version "4.30.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.30.0.tgz#9e49704fef568432ae16fc0d6685c13d67db0fd5"
+  integrity sha512-K8RNIX9GnBsv5v4TjtwkKtqMSzYpjqAQg/oSphtxf3xxdt6T0owqnpojztjjTcatSteH3hLj3t/kklKx87NPqw==
   dependencies:
     "@types/json-schema" "^7.0.7"
-    "@typescript-eslint/scope-manager" "4.29.2"
-    "@typescript-eslint/types" "4.29.2"
-    "@typescript-eslint/typescript-estree" "4.29.2"
+    "@typescript-eslint/scope-manager" "4.30.0"
+    "@typescript-eslint/types" "4.30.0"
+    "@typescript-eslint/typescript-estree" "4.30.0"
     eslint-scope "^5.1.1"
     eslint-utils "^3.0.0"
 
 "@typescript-eslint/parser@^4.2.0":
-  version "4.21.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.21.0.tgz"
-  integrity sha512-eyNf7QmE5O/l1smaQgN0Lj2M/1jOuNg2NrBm1dqqQN0sVngTLyw8tdCbih96ixlhbF1oINoN8fDCyEH9SjLeIA==
+  version "4.30.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.30.0.tgz#6abd720f66bd790f3e0e80c3be77180c8fcb192d"
+  integrity sha512-HJ0XuluSZSxeboLU7Q2VQ6eLlCwXPBOGnA7CqgBnz2Db3JRQYyBDJgQnop6TZ+rsbSx5gEdWhw4rE4mDa1FnZg==
   dependencies:
-    "@typescript-eslint/scope-manager" "4.21.0"
-    "@typescript-eslint/types" "4.21.0"
-    "@typescript-eslint/typescript-estree" "4.21.0"
-    debug "^4.1.1"
+    "@typescript-eslint/scope-manager" "4.30.0"
+    "@typescript-eslint/types" "4.30.0"
+    "@typescript-eslint/typescript-estree" "4.30.0"
+    debug "^4.3.1"
 
-"@typescript-eslint/scope-manager@4.21.0":
-  version "4.21.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.21.0.tgz"
-  integrity sha512-kfOjF0w1Ix7+a5T1knOw00f7uAP9Gx44+OEsNQi0PvvTPLYeXJlsCJ4tYnDj5PQEYfpcgOH5yBlw7K+UEI9Agw==
+"@typescript-eslint/scope-manager@4.30.0":
+  version "4.30.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.30.0.tgz#1a3ffbb385b1a06be85cd5165a22324f069a85ee"
+  integrity sha512-VJ/jAXovxNh7rIXCQbYhkyV2Y3Ac/0cVHP/FruTJSAUUm4Oacmn/nkN5zfWmWFEanN4ggP0vJSHOeajtHq3f8A==
   dependencies:
-    "@typescript-eslint/types" "4.21.0"
-    "@typescript-eslint/visitor-keys" "4.21.0"
+    "@typescript-eslint/types" "4.30.0"
+    "@typescript-eslint/visitor-keys" "4.30.0"
 
-"@typescript-eslint/scope-manager@4.29.2":
-  version "4.29.2"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.29.2.tgz#442b0f029d981fa402942715b1718ac7fcd5aa1b"
-  integrity sha512-mfHmvlQxmfkU8D55CkZO2sQOueTxLqGvzV+mG6S/6fIunDiD2ouwsAoiYCZYDDK73QCibYjIZmGhpvKwAB5BOA==
+"@typescript-eslint/types@4.30.0":
+  version "4.30.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.30.0.tgz#fb9d9b0358426f18687fba82eb0b0f869780204f"
+  integrity sha512-YKldqbNU9K4WpTNwBqtAerQKLLW/X2A/j4yw92e3ZJYLx+BpKLeheyzoPfzIXHfM8BXfoleTdiYwpsvVPvHrDw==
+
+"@typescript-eslint/typescript-estree@4.30.0":
+  version "4.30.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.30.0.tgz#ae57833da72a753f4846cd3053758c771670c2ac"
+  integrity sha512-6WN7UFYvykr/U0Qgy4kz48iGPWILvYL34xXJxvDQeiRE018B7POspNRVtAZscWntEPZpFCx4hcz/XBT+erenfg==
   dependencies:
-    "@typescript-eslint/types" "4.29.2"
-    "@typescript-eslint/visitor-keys" "4.29.2"
-
-"@typescript-eslint/types@4.21.0":
-  version "4.21.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.21.0.tgz"
-  integrity sha512-+OQaupjGVVc8iXbt6M1oZMwyKQNehAfLYJJ3SdvnofK2qcjfor9pEM62rVjBknhowTkh+2HF+/KdRAc/wGBN2w==
-
-"@typescript-eslint/types@4.29.2":
-  version "4.29.2"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.29.2.tgz#fc0489c6b89773f99109fb0aa0aaddff21f52fcd"
-  integrity sha512-K6ApnEXId+WTGxqnda8z4LhNMa/pZmbTFkDxEBLQAbhLZL50DjeY0VIDCml/0Y3FlcbqXZrABqrcKxq+n0LwzQ==
-
-"@typescript-eslint/typescript-estree@4.21.0":
-  version "4.21.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.21.0.tgz"
-  integrity sha512-ZD3M7yLaVGVYLw4nkkoGKumb7Rog7QID9YOWobFDMQKNl+vPxqVIW/uDk+MDeGc+OHcoG2nJ2HphwiPNajKw3w==
-  dependencies:
-    "@typescript-eslint/types" "4.21.0"
-    "@typescript-eslint/visitor-keys" "4.21.0"
-    debug "^4.1.1"
-    globby "^11.0.1"
-    is-glob "^4.0.1"
-    semver "^7.3.2"
-    tsutils "^3.17.1"
-
-"@typescript-eslint/typescript-estree@4.29.2":
-  version "4.29.2"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.2.tgz#a0ea8b98b274adbb2577100ba545ddf8bf7dc219"
-  integrity sha512-TJ0/hEnYxapYn9SGn3dCnETO0r+MjaxtlWZ2xU+EvytF0g4CqTpZL48SqSNn2hXsPolnewF30pdzR9a5Lj3DNg==
-  dependencies:
-    "@typescript-eslint/types" "4.29.2"
-    "@typescript-eslint/visitor-keys" "4.29.2"
+    "@typescript-eslint/types" "4.30.0"
+    "@typescript-eslint/visitor-keys" "4.30.0"
     debug "^4.3.1"
     globby "^11.0.3"
     is-glob "^4.0.1"
     semver "^7.3.5"
     tsutils "^3.21.0"
 
-"@typescript-eslint/visitor-keys@4.21.0":
-  version "4.21.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.21.0.tgz"
-  integrity sha512-dH22dROWGi5Z6p+Igc8bLVLmwy7vEe8r+8c+raPQU0LxgogPUrRAtRGtvBWmlr9waTu3n+QLt/qrS/hWzk1x5w==
+"@typescript-eslint/visitor-keys@4.30.0":
+  version "4.30.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.30.0.tgz#a47c6272fc71b0c627d1691f68eaecf4ad71445e"
+  integrity sha512-pNaaxDt/Ol/+JZwzP7MqWc8PJQTUhZwoee/PVlQ+iYoYhagccvoHnC9e4l+C/krQYYkENxznhVSDwClIbZVxRw==
   dependencies:
-    "@typescript-eslint/types" "4.21.0"
+    "@typescript-eslint/types" "4.30.0"
     eslint-visitor-keys "^2.0.0"
 
-"@typescript-eslint/visitor-keys@4.29.2":
-  version "4.29.2"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.2.tgz#d2da7341f3519486f50655159f4e5ecdcb2cd1df"
-  integrity sha512-bDgJLQ86oWHJoZ1ai4TZdgXzJxsea3Ee9u9wsTAvjChdj2WLcVsgWYAPeY7RQMn16tKrlQaBnpKv7KBfs4EQag==
-  dependencies:
-    "@typescript-eslint/types" "4.29.2"
-    eslint-visitor-keys "^2.0.0"
-
-"@webcomponents/webcomponentsjs@^1.0.7":
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/@webcomponents/webcomponentsjs/-/webcomponentsjs-1.3.3.tgz#5bb82a0d3210c836bd4623e13a4a93145cb9dc27"
-  integrity sha512-eLH04VBMpuZGzBIhOnUjECcQPEPcmfhWEijW9u1B5I+2PPYdWf3vWUExdDxu4Y3GljRSTCOlWnGtS9tpzmXMyQ==
-
-JSONStream@^1.2.1, JSONStream@^1.3.5:
-  version "1.3.5"
-  resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
-  integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==
-  dependencies:
-    jsonparse "^1.2.0"
-    through ">=2.2.7 <3"
-
-accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
-  version "1.3.7"
-  resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
-  integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
-  dependencies:
-    mime-types "~2.1.24"
-    negotiator "0.6.2"
-
-accessibility-developer-tools@^2.12.0:
-  version "2.12.0"
-  resolved "https://registry.yarnpkg.com/accessibility-developer-tools/-/accessibility-developer-tools-2.12.0.tgz#3da0cce9d6ec6373964b84f35db7cfc3df7ab514"
-  integrity sha1-PaDM6dbsY3OWS4TzXbfPw996tRQ=
-
-acorn-jsx@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
-  integrity sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=
-  dependencies:
-    acorn "^3.0.4"
-
 acorn-jsx@^5.3.1:
-  version "5.3.1"
-  resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz"
-  integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==
+  version "5.3.2"
+  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
+  integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
 
-acorn@^3.0.4:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
-  integrity sha1-ReN/s56No/JbruP/U2niu18iAXo=
-
-acorn@^5.5.0:
-  version "5.7.4"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
-  integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==
-
-acorn@^7.1.0, acorn@^7.4.0:
+acorn@^7.4.0:
   version "7.4.1"
-  resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
   integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
 
-adm-zip@~0.4.3:
-  version "0.4.16"
-  resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365"
-  integrity sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==
-
-after@0.8.2:
-  version "0.8.2"
-  resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
-  integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
-
-agent-base@6:
-  version "6.0.2"
-  resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
-  integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
-  dependencies:
-    debug "4"
-
-agent-base@^4.3.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
-  integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
-  dependencies:
-    es6-promisify "^5.0.0"
-
-ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4:
+ajv@^6.10.0, ajv@^6.12.4:
   version "6.12.6"
-  resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
   integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
   dependencies:
     fast-deep-equal "^3.1.1"
@@ -1634,208 +295,68 @@
     uri-js "^4.2.2"
 
 ajv@^8.0.1:
-  version "8.0.5"
-  resolved "https://registry.npmjs.org/ajv/-/ajv-8.0.5.tgz"
-  integrity sha512-RkiLa/AeJx7+9OvniQ/qeWu0w74A8DiPPBclQ6ji3ZQkv5KamO+QGpqmi7O4JIw3rHGUXZ6CoP9tsAkn3gyazg==
+  version "8.6.2"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.2.tgz#2fb45e0e5fcbc0813326c1c3da535d1881bb0571"
+  integrity sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==
   dependencies:
     fast-deep-equal "^3.1.1"
     json-schema-traverse "^1.0.0"
     require-from-string "^2.0.2"
     uri-js "^4.2.2"
 
-ansi-align@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-1.1.0.tgz#2f0c1658829739add5ebb15e6b0c6e3423f016ba"
-  integrity sha1-LwwWWIKXOa3V67FeawxuNCPwFro=
-  dependencies:
-    string-width "^1.0.1"
-
-ansi-align@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f"
-  integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=
-  dependencies:
-    string-width "^2.0.0"
-
 ansi-align@^3.0.0:
   version "3.0.0"
-  resolved "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb"
   integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==
   dependencies:
     string-width "^3.0.0"
 
 ansi-colors@^4.1.1:
   version "4.1.1"
-  resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz"
+  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
   integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
 
-ansi-escapes@^1.1.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
-  integrity sha1-06ioOzGapneTZisT52HHkRQiMG4=
-
 ansi-escapes@^4.2.1:
   version "4.3.2"
-  resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz"
+  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
   integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
   dependencies:
     type-fest "^0.21.3"
 
-ansi-regex@^2.0.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
-  integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
-
-ansi-regex@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
-  integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
-
 ansi-regex@^4.1.0:
   version "4.1.0"
-  resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
   integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
 
 ansi-regex@^5.0.0:
   version "5.0.0"
-  resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
   integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
 
-ansi-styles@^2.2.1:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
-  integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=
-
 ansi-styles@^3.2.1:
   version "3.2.1"
-  resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
   integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
   dependencies:
     color-convert "^1.9.0"
 
 ansi-styles@^4.0.0, ansi-styles@^4.1.0:
   version "4.3.0"
-  resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
   integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
   dependencies:
     color-convert "^2.0.1"
 
-ansi-styles@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178"
-  integrity sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=
-
-any-promise@^1.0.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
-  integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
-
-anymatch@^1.3.0:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a"
-  integrity sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==
-  dependencies:
-    micromatch "^2.1.5"
-    normalize-path "^2.0.0"
-
-append-field@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56"
-  integrity sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=
-
-archiver-utils@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2"
-  integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==
-  dependencies:
-    glob "^7.1.4"
-    graceful-fs "^4.2.0"
-    lazystream "^1.0.0"
-    lodash.defaults "^4.2.0"
-    lodash.difference "^4.5.0"
-    lodash.flatten "^4.4.0"
-    lodash.isplainobject "^4.0.6"
-    lodash.union "^4.6.0"
-    normalize-path "^3.0.0"
-    readable-stream "^2.0.0"
-
-archiver@^3.0.0:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/archiver/-/archiver-3.1.1.tgz#9db7819d4daf60aec10fe86b16cb9258ced66ea0"
-  integrity sha512-5Hxxcig7gw5Jod/8Gq0OneVgLYET+oNHcxgWItq4TbhOzRLKNAFUb9edAftiMKXvXfCB0vbGrJdZDNq0dWMsxg==
-  dependencies:
-    archiver-utils "^2.1.0"
-    async "^2.6.3"
-    buffer-crc32 "^0.2.1"
-    glob "^7.1.4"
-    readable-stream "^3.4.0"
-    tar-stream "^2.1.0"
-    zip-stream "^2.1.2"
-
 argparse@^1.0.7:
   version "1.0.10"
-  resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz"
+  resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
   integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
   dependencies:
     sprintf-js "~1.0.2"
 
-arr-diff@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
-  integrity sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=
-  dependencies:
-    arr-flatten "^1.0.1"
-
-arr-diff@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
-  integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=
-
-arr-flatten@^1.0.1, arr-flatten@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
-  integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==
-
-arr-union@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
-  integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
-
-array-back@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/array-back/-/array-back-2.0.0.tgz#6877471d51ecc9c9bfa6136fb6c7d5fe69748022"
-  integrity sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==
-  dependencies:
-    typical "^2.6.1"
-
-array-back@^3.0.1:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0"
-  integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==
-
-array-differ@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031"
-  integrity sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=
-
-array-differ@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b"
-  integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==
-
-array-find-index@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
-  integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=
-
-array-flatten@1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
-  integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
-
-array-includes@^3.1.1:
+array-includes@^3.1.3:
   version "3.1.3"
-  resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz"
+  resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.3.tgz#c7f619b382ad2afaf5326cddfdc0afc61af7690a"
   integrity sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==
   dependencies:
     call-bind "^1.0.2"
@@ -1844,597 +365,43 @@
     get-intrinsic "^1.1.1"
     is-string "^1.0.5"
 
-array-union@^1.0.1, array-union@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
-  integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=
-  dependencies:
-    array-uniq "^1.0.1"
-
 array-union@^2.1.0:
   version "2.1.0"
-  resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
   integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
 
-array-uniq@^1.0.1:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
-  integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=
-
-array-unique@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
-  integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=
-
-array-unique@^0.3.2:
-  version "0.3.2"
-  resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
-  integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
-
-array.prototype.flat@^1.2.3:
+array.prototype.flat@^1.2.4:
   version "1.2.4"
-  resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz"
+  resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123"
   integrity sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==
   dependencies:
     call-bind "^1.0.0"
     define-properties "^1.1.3"
     es-abstract "^1.18.0-next.1"
 
-arraybuffer.slice@~0.0.7:
-  version "0.0.7"
-  resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
-  integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==
-
-arrify@^1.0.0, arrify@^1.0.1:
+arrify@^1.0.1:
   version "1.0.1"
-  resolved "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
   integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
 
-arrify@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
-  integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
-
-asn1@~0.2.3:
-  version "0.2.4"
-  resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
-  integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
-  dependencies:
-    safer-buffer "~2.1.0"
-
-assert-plus@1.0.0, assert-plus@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
-  integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
-
-assign-symbols@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
-  integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
-
 astral-regex@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
   integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
 
-async-each@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
-  integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
-
-async@0.9.x:
-  version "0.9.2"
-  resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
-  integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=
-
-async@^2.0.0, async@^2.0.1, async@^2.1.2, async@^2.4.1, async@^2.6.0, async@^2.6.2, async@^2.6.3:
-  version "2.6.3"
-  resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
-  integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
-  dependencies:
-    lodash "^4.17.14"
-
-async@^3.1.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
-  integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==
-
-async@~0.2.9:
-  version "0.2.10"
-  resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
-  integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E=
-
-asynckit@^0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
-  integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
-
-atob-lite@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696"
-  integrity sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY=
-
-atob@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
-  integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
-
-aws-sign2@~0.7.0:
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
-  integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
-
-aws4@^1.8.0:
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
-  integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
-
-axios@^0.21.1:
-  version "0.21.1"
-  resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
-  integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
-  dependencies:
-    follow-redirects "^1.10.0"
-
-babel-code-frame@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
-  integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=
-  dependencies:
-    chalk "^1.1.3"
-    esutils "^2.0.2"
-    js-tokens "^3.0.2"
-
-babel-generator@^6.26.1:
-  version "6.26.1"
-  resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90"
-  integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==
-  dependencies:
-    babel-messages "^6.23.0"
-    babel-runtime "^6.26.0"
-    babel-types "^6.26.0"
-    detect-indent "^4.0.0"
-    jsesc "^1.3.0"
-    lodash "^4.17.4"
-    source-map "^0.5.7"
-    trim-right "^1.0.1"
-
-babel-helper-evaluate-path@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/babel-helper-evaluate-path/-/babel-helper-evaluate-path-0.5.0.tgz#a62fa9c4e64ff7ea5cea9353174ef023a900a67c"
-  integrity sha512-mUh0UhS607bGh5wUMAQfOpt2JX2ThXMtppHRdRU1kL7ZLRWIXxoV2UIV1r2cAeeNeU1M5SB5/RSUgUxrK8yOkA==
-
-babel-helper-flip-expressions@^0.4.3:
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/babel-helper-flip-expressions/-/babel-helper-flip-expressions-0.4.3.tgz#3696736a128ac18bc25254b5f40a22ceb3c1d3fd"
-  integrity sha1-NpZzahKKwYvCUlS19AoizrPB0/0=
-
-babel-helper-is-nodes-equiv@^0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-is-nodes-equiv/-/babel-helper-is-nodes-equiv-0.0.1.tgz#34e9b300b1479ddd98ec77ea0bbe9342dfe39684"
-  integrity sha1-NOmzALFHnd2Y7HfqC76TQt/jloQ=
-
-babel-helper-is-void-0@^0.4.3:
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/babel-helper-is-void-0/-/babel-helper-is-void-0-0.4.3.tgz#7d9c01b4561e7b95dbda0f6eee48f5b60e67313e"
-  integrity sha1-fZwBtFYee5Xb2g9u7kj1tg5nMT4=
-
-babel-helper-mark-eval-scopes@^0.4.3:
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/babel-helper-mark-eval-scopes/-/babel-helper-mark-eval-scopes-0.4.3.tgz#d244a3bef9844872603ffb46e22ce8acdf551562"
-  integrity sha1-0kSjvvmESHJgP/tG4izorN9VFWI=
-
-babel-helper-remove-or-void@^0.4.3:
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/babel-helper-remove-or-void/-/babel-helper-remove-or-void-0.4.3.tgz#a4f03b40077a0ffe88e45d07010dee241ff5ae60"
-  integrity sha1-pPA7QAd6D/6I5F0HAQ3uJB/1rmA=
-
-babel-helper-to-multiple-sequence-expressions@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/babel-helper-to-multiple-sequence-expressions/-/babel-helper-to-multiple-sequence-expressions-0.5.0.tgz#a3f924e3561882d42fcf48907aa98f7979a4588d"
-  integrity sha512-m2CvfDW4+1qfDdsrtf4dwOslQC3yhbgyBFptncp4wvtdrDHqueW7slsYv4gArie056phvQFhT2nRcGS4bnm6mA==
-
-babel-messages@^6.23.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
-  integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-dynamic-import-node@^2.3.3:
-  version "2.3.3"
-  resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
-  integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==
-  dependencies:
-    object.assign "^4.1.0"
-
-babel-plugin-minify-builtins@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-minify-builtins/-/babel-plugin-minify-builtins-0.5.0.tgz#31eb82ed1a0d0efdc31312f93b6e4741ce82c36b"
-  integrity sha512-wpqbN7Ov5hsNwGdzuzvFcjgRlzbIeVv1gMIlICbPj0xkexnfoIDe7q+AZHMkQmAE/F9R5jkrB6TLfTegImlXag==
-
-babel-plugin-minify-constant-folding@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-minify-constant-folding/-/babel-plugin-minify-constant-folding-0.5.0.tgz#f84bc8dbf6a561e5e350ff95ae216b0ad5515b6e"
-  integrity sha512-Vj97CTn/lE9hR1D+jKUeHfNy+m1baNiJ1wJvoGyOBUx7F7kJqDZxr9nCHjO/Ad+irbR3HzR6jABpSSA29QsrXQ==
-  dependencies:
-    babel-helper-evaluate-path "^0.5.0"
-
-babel-plugin-minify-dead-code-elimination@^0.5.1:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.5.1.tgz#1a0c68e44be30de4976ca69ffc535e08be13683f"
-  integrity sha512-x8OJOZIrRmQBcSqxBcLbMIK8uPmTvNWPXH2bh5MDCW1latEqYiRMuUkPImKcfpo59pTUB2FT7HfcgtG8ZlR5Qg==
-  dependencies:
-    babel-helper-evaluate-path "^0.5.0"
-    babel-helper-mark-eval-scopes "^0.4.3"
-    babel-helper-remove-or-void "^0.4.3"
-    lodash "^4.17.11"
-
-babel-plugin-minify-flip-comparisons@^0.4.3:
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/babel-plugin-minify-flip-comparisons/-/babel-plugin-minify-flip-comparisons-0.4.3.tgz#00ca870cb8f13b45c038b3c1ebc0f227293c965a"
-  integrity sha1-AMqHDLjxO0XAOLPB68DyJyk8llo=
-  dependencies:
-    babel-helper-is-void-0 "^0.4.3"
-
-babel-plugin-minify-guarded-expressions@^0.4.3, babel-plugin-minify-guarded-expressions@^0.4.4:
-  version "0.4.4"
-  resolved "https://registry.yarnpkg.com/babel-plugin-minify-guarded-expressions/-/babel-plugin-minify-guarded-expressions-0.4.4.tgz#818960f64cc08aee9d6c75bec6da974c4d621135"
-  integrity sha512-RMv0tM72YuPPfLT9QLr3ix9nwUIq+sHT6z8Iu3sLbqldzC1Dls8DPCywzUIzkTx9Zh1hWX4q/m9BPoPed9GOfA==
-  dependencies:
-    babel-helper-evaluate-path "^0.5.0"
-    babel-helper-flip-expressions "^0.4.3"
-
-babel-plugin-minify-infinity@^0.4.3:
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/babel-plugin-minify-infinity/-/babel-plugin-minify-infinity-0.4.3.tgz#dfb876a1b08a06576384ef3f92e653ba607b39ca"
-  integrity sha1-37h2obCKBldjhO8/kuZTumB7Oco=
-
-babel-plugin-minify-mangle-names@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-minify-mangle-names/-/babel-plugin-minify-mangle-names-0.5.0.tgz#bcddb507c91d2c99e138bd6b17a19c3c271e3fd3"
-  integrity sha512-3jdNv6hCAw6fsX1p2wBGPfWuK69sfOjfd3zjUXkbq8McbohWy23tpXfy5RnToYWggvqzuMOwlId1PhyHOfgnGw==
-  dependencies:
-    babel-helper-mark-eval-scopes "^0.4.3"
-
-babel-plugin-minify-numeric-literals@^0.4.3:
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/babel-plugin-minify-numeric-literals/-/babel-plugin-minify-numeric-literals-0.4.3.tgz#8e4fd561c79f7801286ff60e8c5fd9deee93c0bc"
-  integrity sha1-jk/VYcefeAEob/YOjF/Z3u6TwLw=
-
-babel-plugin-minify-replace@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-minify-replace/-/babel-plugin-minify-replace-0.5.0.tgz#d3e2c9946c9096c070efc96761ce288ec5c3f71c"
-  integrity sha512-aXZiaqWDNUbyNNNpWs/8NyST+oU7QTpK7J9zFEFSA0eOmtUNMU3fczlTTTlnCxHmq/jYNFEmkkSG3DDBtW3Y4Q==
-
-babel-plugin-minify-simplify@^0.5.1:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-minify-simplify/-/babel-plugin-minify-simplify-0.5.1.tgz#f21613c8b95af3450a2ca71502fdbd91793c8d6a"
-  integrity sha512-OSYDSnoCxP2cYDMk9gxNAed6uJDiDz65zgL6h8d3tm8qXIagWGMLWhqysT6DY3Vs7Fgq7YUDcjOomhVUb+xX6A==
-  dependencies:
-    babel-helper-evaluate-path "^0.5.0"
-    babel-helper-flip-expressions "^0.4.3"
-    babel-helper-is-nodes-equiv "^0.0.1"
-    babel-helper-to-multiple-sequence-expressions "^0.5.0"
-
-babel-plugin-minify-type-constructors@^0.4.3:
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/babel-plugin-minify-type-constructors/-/babel-plugin-minify-type-constructors-0.4.3.tgz#1bc6f15b87f7ab1085d42b330b717657a2156500"
-  integrity sha1-G8bxW4f3qxCF1CszC3F2V6IVZQA=
-  dependencies:
-    babel-helper-is-void-0 "^0.4.3"
-
-babel-plugin-transform-inline-consecutive-adds@^0.4.3:
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-inline-consecutive-adds/-/babel-plugin-transform-inline-consecutive-adds-0.4.3.tgz#323d47a3ea63a83a7ac3c811ae8e6941faf2b0d1"
-  integrity sha1-Mj1Ho+pjqDp6w8gRro5pQfrysNE=
-
-babel-plugin-transform-member-expression-literals@^6.9.4:
-  version "6.9.4"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-member-expression-literals/-/babel-plugin-transform-member-expression-literals-6.9.4.tgz#37039c9a0c3313a39495faac2ff3a6b5b9d038bf"
-  integrity sha1-NwOcmgwzE6OUlfqsL/OmtbnQOL8=
-
-babel-plugin-transform-merge-sibling-variables@^6.9.4:
-  version "6.9.4"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.9.4.tgz#85b422fc3377b449c9d1cde44087203532401dae"
-  integrity sha1-hbQi/DN3tEnJ0c3kQIcgNTJAHa4=
-
-babel-plugin-transform-minify-booleans@^6.9.4:
-  version "6.9.4"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-minify-booleans/-/babel-plugin-transform-minify-booleans-6.9.4.tgz#acbb3e56a3555dd23928e4b582d285162dd2b198"
-  integrity sha1-rLs+VqNVXdI5KOS1gtKFFi3SsZg=
-
-babel-plugin-transform-property-literals@^6.9.4:
-  version "6.9.4"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-property-literals/-/babel-plugin-transform-property-literals-6.9.4.tgz#98c1d21e255736573f93ece54459f6ce24985d39"
-  integrity sha1-mMHSHiVXNlc/k+zlRFn2ziSYXTk=
-  dependencies:
-    esutils "^2.0.2"
-
-babel-plugin-transform-regexp-constructors@^0.4.3:
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-regexp-constructors/-/babel-plugin-transform-regexp-constructors-0.4.3.tgz#58b7775b63afcf33328fae9a5f88fbd4fb0b4965"
-  integrity sha1-WLd3W2OvzzMyj66aX4j71PsLSWU=
-
-babel-plugin-transform-remove-console@^6.9.4:
-  version "6.9.4"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz#b980360c067384e24b357a588d807d3c83527780"
-  integrity sha1-uYA2DAZzhOJLNXpYjYB9PINSd4A=
-
-babel-plugin-transform-remove-debugger@^6.9.4:
-  version "6.9.4"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-remove-debugger/-/babel-plugin-transform-remove-debugger-6.9.4.tgz#42b727631c97978e1eb2d199a7aec84a18339ef2"
-  integrity sha1-QrcnYxyXl44estGZp67IShgznvI=
-
-babel-plugin-transform-remove-undefined@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-remove-undefined/-/babel-plugin-transform-remove-undefined-0.5.0.tgz#80208b31225766c630c97fa2d288952056ea22dd"
-  integrity sha512-+M7fJYFaEE/M9CXa0/IRkDbiV3wRELzA1kKQFCJ4ifhrzLKn/9VCCgj9OFmYWwBd8IB48YdgPkHYtbYq+4vtHQ==
-  dependencies:
-    babel-helper-evaluate-path "^0.5.0"
-
-babel-plugin-transform-simplify-comparison-operators@^6.9.4:
-  version "6.9.4"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-simplify-comparison-operators/-/babel-plugin-transform-simplify-comparison-operators-6.9.4.tgz#f62afe096cab0e1f68a2d753fdf283888471ceb9"
-  integrity sha1-9ir+CWyrDh9ootdT/fKDiIRxzrk=
-
-babel-plugin-transform-undefined-to-void@^6.9.4:
-  version "6.9.4"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.9.4.tgz#be241ca81404030678b748717322b89d0c8fe280"
-  integrity sha1-viQcqBQEAwZ4t0hxcyK4nQyP4oA=
-
-babel-preset-minify@^0.5.0:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-minify/-/babel-preset-minify-0.5.1.tgz#25f5d0bce36ec818be80338d0e594106e21eaa9f"
-  integrity sha512-1IajDumYOAPYImkHbrKeiN5AKKP9iOmRoO2IPbIuVp0j2iuCcj0n7P260z38siKMZZ+85d3mJZdtW8IgOv+Tzg==
-  dependencies:
-    babel-plugin-minify-builtins "^0.5.0"
-    babel-plugin-minify-constant-folding "^0.5.0"
-    babel-plugin-minify-dead-code-elimination "^0.5.1"
-    babel-plugin-minify-flip-comparisons "^0.4.3"
-    babel-plugin-minify-guarded-expressions "^0.4.4"
-    babel-plugin-minify-infinity "^0.4.3"
-    babel-plugin-minify-mangle-names "^0.5.0"
-    babel-plugin-minify-numeric-literals "^0.4.3"
-    babel-plugin-minify-replace "^0.5.0"
-    babel-plugin-minify-simplify "^0.5.1"
-    babel-plugin-minify-type-constructors "^0.4.3"
-    babel-plugin-transform-inline-consecutive-adds "^0.4.3"
-    babel-plugin-transform-member-expression-literals "^6.9.4"
-    babel-plugin-transform-merge-sibling-variables "^6.9.4"
-    babel-plugin-transform-minify-booleans "^6.9.4"
-    babel-plugin-transform-property-literals "^6.9.4"
-    babel-plugin-transform-regexp-constructors "^0.4.3"
-    babel-plugin-transform-remove-console "^6.9.4"
-    babel-plugin-transform-remove-debugger "^6.9.4"
-    babel-plugin-transform-remove-undefined "^0.5.0"
-    babel-plugin-transform-simplify-comparison-operators "^6.9.4"
-    babel-plugin-transform-undefined-to-void "^6.9.4"
-    lodash "^4.17.11"
-
-babel-runtime@^6.22.0, babel-runtime@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
-  integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
-  dependencies:
-    core-js "^2.4.0"
-    regenerator-runtime "^0.11.0"
-
-babel-traverse@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
-  integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=
-  dependencies:
-    babel-code-frame "^6.26.0"
-    babel-messages "^6.23.0"
-    babel-runtime "^6.26.0"
-    babel-types "^6.26.0"
-    babylon "^6.18.0"
-    debug "^2.6.8"
-    globals "^9.18.0"
-    invariant "^2.2.2"
-    lodash "^4.17.4"
-
-babel-types@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
-  integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=
-  dependencies:
-    babel-runtime "^6.26.0"
-    esutils "^2.0.2"
-    lodash "^4.17.4"
-    to-fast-properties "^1.0.3"
-
-babylon@^6.18.0:
-  version "6.18.0"
-  resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
-  integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==
-
-babylon@^7.0.0-beta.42:
-  version "7.0.0-beta.47"
-  resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.47.tgz#6d1fa44f0abec41ab7c780481e62fd9aafbdea80"
-  integrity sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==
-
-backo2@1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
-  integrity sha1-MasayLEpNjRj41s+u2n038+6eUc=
-
 balanced-match@^1.0.0:
   version "1.0.2"
-  resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
+  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
   integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
 
-base64-arraybuffer@0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812"
-  integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=
-
-base64-js@1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1"
-  integrity sha1-o5mS1yNYSBGYK+XikLtqU9hnAPE=
-
-base64-js@^1.3.1:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
-  integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
-
-base64id@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6"
-  integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==
-
-base@^0.11.1:
-  version "0.11.2"
-  resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
-  integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==
-  dependencies:
-    cache-base "^1.0.1"
-    class-utils "^0.3.5"
-    component-emitter "^1.2.1"
-    define-property "^1.0.0"
-    isobject "^3.0.1"
-    mixin-deep "^1.2.0"
-    pascalcase "^0.1.1"
-
-bcrypt-pbkdf@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
-  integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
-  dependencies:
-    tweetnacl "^0.14.3"
-
-before-after-hook@^2.0.0:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.1.tgz#73540563558687586b52ed217dad6a802ab1549c"
-  integrity sha512-/6FKxSTWoJdbsLDF8tdIjaRiFXiE6UHsEHE3OPI/cwPURCVi1ukP0gmLn7XWEiFk5TcwQjjY5PWsU+j+tgXgmw==
-
-binary-extensions@^1.0.0:
-  version "1.13.1"
-  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
-  integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
-
-binaryextensions@^2.1.2:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.3.0.tgz#1d269cbf7e6243ea886aa41453c3651ccbe13c22"
-  integrity sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==
-
-bindings@^1.5.0:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
-  integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
-  dependencies:
-    file-uri-to-path "1.0.0"
-
-bl@^1.0.0:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7"
-  integrity sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==
-  dependencies:
-    readable-stream "^2.3.5"
-    safe-buffer "^5.1.1"
-
-bl@^4.0.3:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
-  integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
-  dependencies:
-    buffer "^5.5.0"
-    inherits "^2.0.4"
-    readable-stream "^3.4.0"
-
-blob@0.0.5:
-  version "0.0.5"
-  resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
-  integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==
-
-body-parser@1.19.0, body-parser@^1.17.2:
-  version "1.19.0"
-  resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
-  integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
-  dependencies:
-    bytes "3.1.0"
-    content-type "~1.0.4"
-    debug "2.6.9"
-    depd "~1.1.2"
-    http-errors "1.7.2"
-    iconv-lite "0.4.24"
-    on-finished "~2.3.0"
-    qs "6.7.0"
-    raw-body "2.4.0"
-    type-is "~1.6.17"
-
 boolbase@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
   integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
 
-bower-config@^1.4.0, bower-config@^1.4.1:
-  version "1.4.3"
-  resolved "https://registry.yarnpkg.com/bower-config/-/bower-config-1.4.3.tgz#3454fecdc5f08e7aa9cc6d556e492be0669689ae"
-  integrity sha512-MVyyUk3d1S7d2cl6YISViwJBc2VXCkxF5AUFykvN0PQj5FsUiMNSgAYTso18oRFfyZ6XEtjrgg9MAaufHbOwNw==
-  dependencies:
-    graceful-fs "^4.1.3"
-    minimist "^0.2.1"
-    mout "^1.0.0"
-    osenv "^0.1.3"
-    untildify "^2.1.0"
-    wordwrap "^0.0.3"
-
-bower-json@^0.8.1:
-  version "0.8.4"
-  resolved "https://registry.yarnpkg.com/bower-json/-/bower-json-0.8.4.tgz#9c3b375870dcd9581350c1f403f6383dbf6a18b1"
-  integrity sha512-mMKghvq9ivbuzSsY5nrOLnDtZIJMUCpysqbGaGW3mj88JAcuSi8ZAzIt34vNZjohy0aR9VXLwgPTZGnBX2Vpjg==
-  dependencies:
-    deep-extend "^0.5.1"
-    ends-with "^0.2.0"
-    ext-list "^2.0.0"
-    graceful-fs "^4.1.3"
-    intersect "^1.0.1"
-    sort-keys-length "^1.0.0"
-
-bower-logger@^0.2.2:
-  version "0.2.2"
-  resolved "https://registry.yarnpkg.com/bower-logger/-/bower-logger-0.2.2.tgz#39be07e979b2fc8e03a94634205ed9422373d381"
-  integrity sha1-Ob4H6Xmy/I4DqUY0IF7ZQiNz04E=
-
-bower@^1.8.8:
-  version "1.8.12"
-  resolved "https://registry.yarnpkg.com/bower/-/bower-1.8.12.tgz#44cfca2a5e04b8d9a066621e24c8b179d8ac321e"
-  integrity sha512-u1xy9SrwwoPlgjuHNjhV+YUPVdqyBj2ALBxuzeIUKXaPI2i2xypGgxqXkuHcITGdi5yBj5JuXgyMvgiWiS1S3Q==
-
-boxen@^0.6.0:
-  version "0.6.0"
-  resolved "https://registry.yarnpkg.com/boxen/-/boxen-0.6.0.tgz#8364d4248ac34ff0ef1b2f2bf49a6c60ce0d81b6"
-  integrity sha1-g2TUJIrDT/DvGy8r9JpsYM4NgbY=
-  dependencies:
-    ansi-align "^1.1.0"
-    camelcase "^2.1.0"
-    chalk "^1.1.1"
-    cli-boxes "^1.0.0"
-    filled-array "^1.0.0"
-    object-assign "^4.0.1"
-    repeating "^2.0.0"
-    string-width "^1.0.1"
-    widest-line "^1.0.0"
-
-boxen@^1.2.1:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b"
-  integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==
-  dependencies:
-    ansi-align "^2.0.0"
-    camelcase "^4.0.0"
-    chalk "^2.0.1"
-    cli-boxes "^1.0.0"
-    string-width "^2.0.0"
-    term-size "^1.2.0"
-    widest-line "^2.0.0"
-
 boxen@^5.0.0:
   version "5.0.1"
-  resolved "https://registry.npmjs.org/boxen/-/boxen-5.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.0.1.tgz#657528bdd3f59a772b8279b831f27ec2c744664b"
   integrity sha512-49VBlw+PrWEF51aCmy7QIteYPIFZxSpvqBdP/2itCPPlJ49kj9zg/XPRFrdkne2W+CfwXUls8exMvu1RysZpKA==
   dependencies:
     ansi-align "^3.0.0"
@@ -2448,159 +415,27 @@
 
 brace-expansion@^1.0.0, brace-expansion@^1.1.7:
   version "1.1.11"
-  resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz"
+  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
   integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
   dependencies:
     balanced-match "^1.0.0"
     concat-map "0.0.1"
 
-braces@^1.8.2:
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
-  integrity sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=
-  dependencies:
-    expand-range "^1.8.1"
-    preserve "^0.2.0"
-    repeat-element "^1.1.2"
-
-braces@^2.3.1:
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
-  integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==
-  dependencies:
-    arr-flatten "^1.1.0"
-    array-unique "^0.3.2"
-    extend-shallow "^2.0.1"
-    fill-range "^4.0.0"
-    isobject "^3.0.1"
-    repeat-element "^1.1.2"
-    snapdragon "^0.8.1"
-    snapdragon-node "^2.0.1"
-    split-string "^3.0.2"
-    to-regex "^3.0.1"
-
 braces@^3.0.1:
   version "3.0.2"
-  resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz"
+  resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
   integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
   dependencies:
     fill-range "^7.0.1"
 
-browser-capabilities@^1.0.0:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/browser-capabilities/-/browser-capabilities-1.1.4.tgz#a6bd657a07a134532ad66c722b8949904478b973"
-  integrity sha512-BezMQhbQklxjRQpZZQ8tnbzEo6AldUwMh8/PeWt5/CTBSwByQRXZEAK2fbnEahQ4poeeaI0suAYRq25A1YGOmw==
-  dependencies:
-    "@types/ua-parser-js" "^0.7.31"
-    ua-parser-js "^0.7.15"
-
-browserify-zlib@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d"
-  integrity sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=
-  dependencies:
-    pako "~0.2.0"
-
-browserslist@^4.14.5:
-  version "4.16.3"
-  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717"
-  integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==
-  dependencies:
-    caniuse-lite "^1.0.30001181"
-    colorette "^1.2.1"
-    electron-to-chromium "^1.3.649"
-    escalade "^3.1.1"
-    node-releases "^1.1.70"
-
-browserstack@^1.2.0:
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/browserstack/-/browserstack-1.6.1.tgz#e051f9733ec3b507659f395c7a4765a1b1e358b3"
-  integrity sha512-GxtFjpIaKdbAyzHfFDKixKO8IBT7wR3NjbzrGc78nNs/Ciys9wU3/nBtsqsWv5nDSrdI5tz0peKuzCPuNXNUiw==
-  dependencies:
-    https-proxy-agent "^2.2.1"
-
-btoa-lite@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337"
-  integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc=
-
-buffer-alloc-unsafe@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
-  integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
-
-buffer-alloc@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
-  integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
-  dependencies:
-    buffer-alloc-unsafe "^1.1.0"
-    buffer-fill "^1.0.0"
-
-buffer-crc32@^0.2.1, buffer-crc32@^0.2.13, buffer-crc32@~0.2.3:
-  version "0.2.13"
-  resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
-  integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
-
-buffer-fill@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
-  integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
-
 buffer-from@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz"
-  integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
-
-buffer@^5.1.0, buffer@^5.5.0:
-  version "5.7.1"
-  resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
-  integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
-  dependencies:
-    base64-js "^1.3.1"
-    ieee754 "^1.1.13"
-
-busboy@^0.2.11:
-  version "0.2.14"
-  resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453"
-  integrity sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=
-  dependencies:
-    dicer "0.2.5"
-    readable-stream "1.1.x"
-
-bytes@3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
-  integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
-
-bytes@3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
-  integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
-
-cache-base@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
-  integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==
-  dependencies:
-    collection-visit "^1.0.0"
-    component-emitter "^1.2.1"
-    get-value "^2.0.6"
-    has-value "^1.0.0"
-    isobject "^3.0.1"
-    set-value "^2.0.0"
-    to-object-path "^0.3.0"
-    union-value "^1.0.0"
-    unset-value "^1.0.0"
-
-cacheable-lookup@^5.0.3:
-  version "5.0.4"
-  resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005"
-  integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
+  integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
 
 cacheable-request@^6.0.0:
   version "6.1.0"
-  resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
   integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==
   dependencies:
     clone-response "^1.0.2"
@@ -2611,151 +446,60 @@
     normalize-url "^4.1.0"
     responselike "^1.0.2"
 
-cacheable-request@^7.0.1:
-  version "7.0.1"
-  resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58"
-  integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==
-  dependencies:
-    clone-response "^1.0.2"
-    get-stream "^5.1.0"
-    http-cache-semantics "^4.0.0"
-    keyv "^4.0.0"
-    lowercase-keys "^2.0.0"
-    normalize-url "^4.1.0"
-    responselike "^2.0.0"
-
 call-bind@^1.0.0, call-bind@^1.0.2:
   version "1.0.2"
-  resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz"
+  resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
   integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
   dependencies:
     function-bind "^1.1.1"
     get-intrinsic "^1.0.2"
 
-call-me-maybe@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
-  integrity sha1-JtII6onje1y95gJQoV8DHBak1ms=
-
 callsites@^3.0.0:
   version "3.1.0"
-  resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
   integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
 
-camel-case@3.0.x:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73"
-  integrity sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=
-  dependencies:
-    no-case "^2.2.0"
-    upper-case "^1.1.1"
-
-camelcase-keys@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7"
-  integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc=
-  dependencies:
-    camelcase "^2.0.0"
-    map-obj "^1.0.0"
-
 camelcase-keys@^6.2.2:
   version "6.2.2"
-  resolved "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz"
+  resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0"
   integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==
   dependencies:
     camelcase "^5.3.1"
     map-obj "^4.0.0"
     quick-lru "^4.0.1"
 
-camelcase@^2.0.0, camelcase@^2.1.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
-  integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=
-
-camelcase@^4.0.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
-  integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
-
 camelcase@^5.3.1:
   version "5.3.1"
-  resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
   integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
 
 camelcase@^6.2.0:
   version "6.2.0"
-  resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
   integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
 
-cancel-token@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/cancel-token/-/cancel-token-0.1.1.tgz#c18197674bb1c84c1d6933ebf15d8d5a5ce79b4f"
-  integrity sha1-wYGXZ0uxyEwdaTPr8V2NWlznm08=
-  dependencies:
-    "@types/node" "^4.0.30"
-
-caniuse-lite@^1.0.30001181:
-  version "1.0.30001208"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz#a999014a35cebd4f98c405930a057a0d75352eb9"
-  integrity sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA==
-
-capture-stack-trace@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d"
-  integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==
-
-caseless@~0.12.0:
-  version "0.12.0"
-  resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
-  integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
-
-chalk@*, chalk@^4.0.0, chalk@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz"
-  integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
-  dependencies:
-    ansi-styles "^4.1.0"
-    supports-color "^7.1.0"
-
-chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
-  integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=
-  dependencies:
-    ansi-styles "^2.2.1"
-    escape-string-regexp "^1.0.2"
-    has-ansi "^2.0.0"
-    strip-ansi "^3.0.0"
-    supports-color "^2.0.0"
-
-chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2:
+chalk@^2.0.0:
   version "2.4.2"
-  resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
   integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
   dependencies:
     ansi-styles "^3.2.1"
     escape-string-regexp "^1.0.5"
     supports-color "^5.3.0"
 
-chalk@~0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f"
-  integrity sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=
+chalk@^4.0.0, chalk@^4.1.0:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
+  integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
   dependencies:
-    ansi-styles "~1.0.0"
-    has-color "~0.1.0"
-    strip-ansi "~0.1.0"
+    ansi-styles "^4.1.0"
+    supports-color "^7.1.0"
 
 chardet@^0.7.0:
   version "0.7.0"
-  resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz"
+  resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
   integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
 
-charenc@0.0.2:
-  version "0.0.2"
-  resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
-  integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
-
 cheerio@1.0.0-rc.2:
   version "1.0.0-rc.2"
   resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db"
@@ -2768,380 +512,77 @@
     lodash "^4.15.0"
     parse5 "^3.0.1"
 
-chokidar@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
-  integrity sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=
-  dependencies:
-    anymatch "^1.3.0"
-    async-each "^1.0.0"
-    glob-parent "^2.0.0"
-    inherits "^2.0.1"
-    is-binary-path "^1.0.0"
-    is-glob "^2.0.0"
-    path-is-absolute "^1.0.0"
-    readdirp "^2.0.0"
-  optionalDependencies:
-    fsevents "^1.0.0"
-
-chownr@^1.0.1:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
-  integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
-
-ci-info@^1.5.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497"
-  integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==
-
 ci-info@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
   integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
 
-class-utils@^0.3.5:
-  version "0.3.6"
-  resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
-  integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==
-  dependencies:
-    arr-union "^3.1.0"
-    define-property "^0.2.5"
-    isobject "^3.0.0"
-    static-extend "^0.1.1"
-
-clean-css@4.2.x:
-  version "4.2.3"
-  resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78"
-  integrity sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==
-  dependencies:
-    source-map "~0.6.0"
-
-cleankill@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/cleankill/-/cleankill-2.0.0.tgz#59830dfc8b411d53dc72ad09d45a78ea33161a91"
-  integrity sha1-WYMN/ItBHVPccq0J1Fp46jMWGpE=
-
-cli-boxes@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"
-  integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM=
-
 cli-boxes@^2.2.1:
   version "2.2.1"
-  resolved "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz"
+  resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
   integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==
 
-cli-cursor@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
-  integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=
-  dependencies:
-    restore-cursor "^1.0.1"
-
 cli-cursor@^3.1.0:
   version "3.1.0"
-  resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
   integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
   dependencies:
     restore-cursor "^3.1.0"
 
-cli-table@^0.3.1:
-  version "0.3.6"
-  resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.6.tgz#e9d6aa859c7fe636981fd3787378c2a20bce92fc"
-  integrity sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ==
-  dependencies:
-    colors "1.0.3"
-
-cli-width@^2.0.0:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
-  integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==
-
 cli-width@^3.0.0:
   version "3.0.0"
-  resolved "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
   integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
 
-clone-buffer@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
-  integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg=
-
-clone-deep@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
-  integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
-  dependencies:
-    is-plain-object "^2.0.4"
-    kind-of "^6.0.2"
-    shallow-clone "^3.0.0"
-
 clone-response@^1.0.2:
   version "1.0.2"
-  resolved "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz"
+  resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
   integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
   dependencies:
     mimic-response "^1.0.0"
 
-clone-stats@^0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1"
-  integrity sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=
-
-clone-stats@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680"
-  integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=
-
-clone@^1.0.0:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
-  integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
-
-clone@^2.0.0, clone@^2.1.0, clone@^2.1.1:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
-  integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
-
-cloneable-readable@^1.0.0:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.3.tgz#120a00cb053bfb63a222e709f9683ea2e11d8cec"
-  integrity sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==
-  dependencies:
-    inherits "^2.0.1"
-    process-nextick-args "^2.0.0"
-    readable-stream "^2.3.5"
-
-code-point-at@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
-  integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
-
-collection-visit@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
-  integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=
-  dependencies:
-    map-visit "^1.0.0"
-    object-visit "^1.0.0"
-
-color-convert@^1.9.0, color-convert@^1.9.1:
+color-convert@^1.9.0:
   version "1.9.3"
-  resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz"
+  resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
   integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
   dependencies:
     color-name "1.1.3"
 
 color-convert@^2.0.1:
   version "2.0.1"
-  resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
   integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
   dependencies:
     color-name "~1.1.4"
 
 color-name@1.1.3:
   version "1.1.3"
-  resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
+  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
   integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
 
-color-name@^1.0.0, color-name@~1.1.4:
+color-name@~1.1.4:
   version "1.1.4"
-  resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
+  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
   integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
 
-color-string@^1.5.2:
-  version "1.5.5"
-  resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014"
-  integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==
-  dependencies:
-    color-name "^1.0.0"
-    simple-swizzle "^0.2.2"
-
-color@3.0.x:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/color/-/color-3.0.0.tgz#d920b4328d534a3ac8295d68f7bd4ba6c427be9a"
-  integrity sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==
-  dependencies:
-    color-convert "^1.9.1"
-    color-string "^1.5.2"
-
-colorette@^1.2.1:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
-  integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
-
-colors@1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
-  integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
-
-colors@^1.2.1:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
-  integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
-
-colorspace@1.1.x:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.2.tgz#e0128950d082b86a2168580796a0aa5d6c68d8c5"
-  integrity sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==
-  dependencies:
-    color "3.0.x"
-    text-hex "1.0.x"
-
-combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
-  version "1.0.8"
-  resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
-  integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
-  dependencies:
-    delayed-stream "~1.0.0"
-
-command-line-args@^5.0.2:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.1.1.tgz#88e793e5bb3ceb30754a86863f0401ac92fd369a"
-  integrity sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg==
-  dependencies:
-    array-back "^3.0.1"
-    find-replace "^3.0.0"
-    lodash.camelcase "^4.3.0"
-    typical "^4.0.0"
-
-command-line-commands@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/command-line-commands/-/command-line-commands-2.0.1.tgz#c58aa13dc78c06038ed67077e57ad09a6f858f46"
-  integrity sha512-m8c2p1DrNd2ruIAggxd/y6DgygQayf6r8RHwchhXryaLF8I6koYjoYroVP+emeROE9DXN5b9sP1Gh+WtvTTdtQ==
-  dependencies:
-    array-back "^2.0.0"
-
-command-line-usage@^5.0.5:
-  version "5.0.5"
-  resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-5.0.5.tgz#5f25933ffe6dedd983c635d38a21d7e623fda357"
-  integrity sha512-d8NrGylA5oCXSbGoKz05FkehDAzSmIm4K03S5VDh4d5lZAtTWfc3D1RuETtuQCn8129nYfJfDdF7P/lwcz1BlA==
-  dependencies:
-    array-back "^2.0.0"
-    chalk "^2.4.1"
-    table-layout "^0.4.3"
-    typical "^2.6.1"
-
-commander@2.17.x:
-  version "2.17.1"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
-  integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
-
-commander@^2.20.0, commander@^2.20.3:
+commander@^2.20.0:
   version "2.20.3"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
   integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
 
-commander@~2.19.0:
-  version "2.19.0"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
-  integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
-
-comment-parser@1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.1.2.tgz#e5317d7a2ec22b470dcb54a29b25426c30bf39d8"
-  integrity sha512-AOdq0i8ghZudnYv8RUnHrhTgafUGs61Rdz9jemU5x2lnZwAWyOq7vySo626K59e1fVKH1xSRorJwPVRLSWOoAQ==
-
-commondir@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
-  integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
-
-component-bind@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1"
-  integrity sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=
-
-component-emitter@1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
-  integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=
-
-component-emitter@^1.2.1, component-emitter@~1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
-  integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
-
-component-inherit@0.0.3:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
-  integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=
-
-compress-commons@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-2.1.1.tgz#9410d9a534cf8435e3fbbb7c6ce48de2dc2f0610"
-  integrity sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q==
-  dependencies:
-    buffer-crc32 "^0.2.13"
-    crc32-stream "^3.0.1"
-    normalize-path "^3.0.0"
-    readable-stream "^2.3.6"
-
-compressible@~2.0.16:
-  version "2.0.18"
-  resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
-  integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
-  dependencies:
-    mime-db ">= 1.43.0 < 2"
-
-compression@^1.6.2:
-  version "1.7.4"
-  resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
-  integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==
-  dependencies:
-    accepts "~1.3.5"
-    bytes "3.0.0"
-    compressible "~2.0.16"
-    debug "2.6.9"
-    on-headers "~1.0.2"
-    safe-buffer "5.1.2"
-    vary "~1.1.2"
+comment-parser@1.1.5:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.1.5.tgz#453627ef8f67dbcec44e79a9bd5baa37f0bce9b2"
+  integrity sha512-RePCE4leIhBlmrqiYTvaqEeGYg7qpSl4etaIabKtdOQVi+mSTIBBklGUwIr79GXYnl3LpMwmDw4KeR2stNc6FA==
 
 concat-map@0.0.1:
   version "0.0.1"
-  resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
   integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
 
-concat-stream@^1.4.7, concat-stream@^1.5.2:
-  version "1.6.2"
-  resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
-  integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
-  dependencies:
-    buffer-from "^1.0.0"
-    inherits "^2.0.3"
-    readable-stream "^2.2.2"
-    typedarray "^0.0.6"
-
-configstore@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/configstore/-/configstore-2.1.0.tgz#737a3a7036e9886102aa6099e47bb33ab1aba1a1"
-  integrity sha1-c3o6cDbpiGECqmCZ5HuzOrGroaE=
-  dependencies:
-    dot-prop "^3.0.0"
-    graceful-fs "^4.1.2"
-    mkdirp "^0.5.0"
-    object-assign "^4.0.1"
-    os-tmpdir "^1.0.0"
-    osenv "^0.1.0"
-    uuid "^2.0.1"
-    write-file-atomic "^1.1.2"
-    xdg-basedir "^2.0.0"
-
-configstore@^3.0.0:
-  version "3.1.5"
-  resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.5.tgz#e9af331fadc14dabd544d3e7e76dc446a09a530f"
-  integrity sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==
-  dependencies:
-    dot-prop "^4.2.1"
-    graceful-fs "^4.1.2"
-    make-dir "^1.0.0"
-    unique-string "^1.0.0"
-    write-file-atomic "^2.0.0"
-    xdg-basedir "^3.0.0"
-
 configstore@^5.0.1:
   version "5.0.1"
-  resolved "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96"
   integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==
   dependencies:
     dot-prop "^5.2.0"
@@ -3151,132 +592,18 @@
     write-file-atomic "^3.0.0"
     xdg-basedir "^4.0.0"
 
-contains-path@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz"
-  integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=
-
-content-disposition@0.5.3:
-  version "0.5.3"
-  resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
-  integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
-  dependencies:
-    safe-buffer "5.1.2"
-
-content-type@^1.0.2, content-type@~1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
-  integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
-
-convert-source-map@^1.1.1, convert-source-map@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
-  integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==
-  dependencies:
-    safe-buffer "~5.1.1"
-
-cookie-signature@1.0.6:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
-  integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
-
-cookie@0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
-  integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
-
-cookie@~0.4.1:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
-  integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
-
-copy-descriptor@^0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
-  integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
-
-core-js@^2.4.0:
-  version "2.6.12"
-  resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
-  integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
-
-core-util-is@1.0.2, core-util-is@~1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
-  integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
-
-cors@^2.8.4:
-  version "2.8.5"
-  resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
-  integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
-  dependencies:
-    object-assign "^4"
-    vary "^1"
-
-crc32-stream@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-3.0.1.tgz#cae6eeed003b0e44d739d279de5ae63b171b4e85"
-  integrity sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==
-  dependencies:
-    crc "^3.4.4"
-    readable-stream "^3.4.0"
-
-crc@^3.4.4:
-  version "3.8.0"
-  resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6"
-  integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==
-  dependencies:
-    buffer "^5.1.0"
-
-create-error-class@^3.0.0, create-error-class@^3.0.1:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6"
-  integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=
-  dependencies:
-    capture-stack-trace "^1.0.0"
-
-cross-spawn@^5.0.1:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
-  integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=
-  dependencies:
-    lru-cache "^4.0.1"
-    shebang-command "^1.2.0"
-    which "^1.2.9"
-
-cross-spawn@^6.0.0, cross-spawn@^6.0.5:
-  version "6.0.5"
-  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
-  integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
-  dependencies:
-    nice-try "^1.0.4"
-    path-key "^2.0.1"
-    semver "^5.5.0"
-    shebang-command "^1.2.0"
-    which "^1.2.9"
-
-cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
+cross-spawn@^7.0.2, cross-spawn@^7.0.3:
   version "7.0.3"
-  resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz"
+  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
   integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
   dependencies:
     path-key "^3.1.0"
     shebang-command "^2.0.0"
     which "^2.0.1"
 
-crypt@0.0.2:
-  version "0.0.2"
-  resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
-  integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=
-
-crypto-random-string@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
-  integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=
-
 crypto-random-string@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
   integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
 
 css-select@~1.2.0:
@@ -3289,287 +616,82 @@
     domutils "1.5.1"
     nth-check "~1.0.1"
 
-css-slam@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/css-slam/-/css-slam-2.1.2.tgz#3d35b1922cb3e0002a45c89ab189492508c493e5"
-  integrity sha512-cObrY+mhFEmepWpua6EpMrgRNTQ0eeym+kvR0lukI6hDEzK7F8himEDS4cJ9+fPHCoArTzVrrR0d+oAUbTR1NA==
-  dependencies:
-    command-line-args "^5.0.2"
-    command-line-usage "^5.0.5"
-    dom5 "^3.0.0"
-    parse5 "^4.0.0"
-    shady-css-parser "^0.1.0"
-
-css-what@2.1, css-what@^2.1.0:
+css-what@2.1:
   version "2.1.3"
   resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
   integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==
 
-cssbeautify@^0.3.1:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/cssbeautify/-/cssbeautify-0.3.1.tgz#12dd1f734035c2e6faca67dcbdcef74e42811397"
-  integrity sha1-Et0fc0A1wub6ymfcvc73TkKBE5c=
-
-currently-unhandled@^0.4.1:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
-  integrity sha1-mI3zP+qxke95mmE2nddsF635V+o=
-  dependencies:
-    array-find-index "^1.0.1"
-
-dargs@^6.0.0, dargs@^6.1.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/dargs/-/dargs-6.1.0.tgz#1f3b9b56393ecf8caa7cbfd6c31496ffcfb9b272"
-  integrity sha512-5dVBvpBLBnPwSsYXqfybFyehMmC/EenKEcf23AhCTgTf48JFBbmJKqoZBsERDnjL0FyiVTYWdFsRfTLHxLyKdQ==
-
-dashdash@^1.12.0:
-  version "1.14.1"
-  resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
-  integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
-  dependencies:
-    assert-plus "^1.0.0"
-
-dateformat@^3.0.3:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
-  integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==
-
-debug@2.6.9, debug@^2.0.0, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
+debug@^2.6.9:
   version "2.6.9"
-  resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
   dependencies:
     ms "2.0.0"
 
-debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
-  version "4.3.1"
-  resolved "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz"
-  integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
-  dependencies:
-    ms "2.1.2"
-
-debug@^3.1.0:
+debug@^3.2.7:
   version "3.2.7"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
   integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
   dependencies:
     ms "^2.1.1"
 
-debug@~3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
-  integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
+debug@^4.0.1, debug@^4.1.1, debug@^4.3.1:
+  version "4.3.2"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
+  integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
   dependencies:
-    ms "2.0.0"
-
-debug@~4.1.0:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
-  integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
-  dependencies:
-    ms "^2.1.1"
+    ms "2.1.2"
 
 decamelize-keys@^1.1.0:
   version "1.1.0"
-  resolved "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9"
   integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=
   dependencies:
     decamelize "^1.1.0"
     map-obj "^1.0.0"
 
-decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0:
+decamelize@^1.1.0, decamelize@^1.2.0:
   version "1.2.0"
-  resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
 
-decode-uri-component@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
-  integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
-
-decompress-response@^3.2.0, decompress-response@^3.3.0:
+decompress-response@^3.3.0:
   version "3.3.0"
-  resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz"
+  resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
   integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=
   dependencies:
     mimic-response "^1.0.0"
 
-decompress-response@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
-  integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
-  dependencies:
-    mimic-response "^3.1.0"
-
-deep-extend@^0.5.1:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.1.tgz#b894a9dd90d3023fbf1c55a394fb858eb2066f1f"
-  integrity sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==
-
-deep-extend@^0.6.0, deep-extend@~0.6.0:
+deep-extend@^0.6.0:
   version "0.6.0"
-  resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz"
+  resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
   integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
 
 deep-is@^0.1.3:
   version "0.1.3"
-  resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz"
+  resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
   integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
 
 defer-to-connect@^1.0.1:
   version "1.1.3"
-  resolved "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz"
+  resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591"
   integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==
 
-defer-to-connect@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587"
-  integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==
-
 define-properties@^1.1.3:
   version "1.1.3"
-  resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz"
+  resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
   integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
   dependencies:
     object-keys "^1.0.12"
 
-define-property@^0.2.5:
-  version "0.2.5"
-  resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
-  integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=
-  dependencies:
-    is-descriptor "^0.1.0"
-
-define-property@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6"
-  integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY=
-  dependencies:
-    is-descriptor "^1.0.0"
-
-define-property@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d"
-  integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==
-  dependencies:
-    is-descriptor "^1.0.2"
-    isobject "^3.0.1"
-
-del@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5"
-  integrity sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=
-  dependencies:
-    globby "^6.1.0"
-    is-path-cwd "^1.0.0"
-    is-path-in-cwd "^1.0.0"
-    p-map "^1.1.1"
-    pify "^3.0.0"
-    rimraf "^2.2.8"
-
-delayed-stream@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
-  integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
-
-depd@~1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
-  integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
-
-deprecation@^2.0.0, deprecation@^2.3.1:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
-  integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
-
-destroy@~1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
-  integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
-
-detect-conflict@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/detect-conflict/-/detect-conflict-1.0.1.tgz#088657a66a961c05019db7c4230883b1c6b4176e"
-  integrity sha1-CIZXpmqWHAUBnbfEIwiDsca0F24=
-
-detect-file@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-0.1.0.tgz#4935dedfd9488648e006b0129566e9386711ea63"
-  integrity sha1-STXe39lIhkjgBrASlWbpOGcR6mM=
-  dependencies:
-    fs-exists-sync "^0.1.0"
-
-detect-file@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
-  integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
-
-detect-indent@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
-  integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg=
-  dependencies:
-    repeating "^2.0.0"
-
-detect-node@^2.0.3:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.5.tgz#9d270aa7eaa5af0b72c4c9d9b814e7f4ce738b79"
-  integrity sha512-qi86tE6hRcFHy8jI1m2VG+LaPUR1LhqDa5G8tVjuUXmOrpuAgqsA1pN0+ldgr3aKUH+QLI9hCY/OcRYisERejw==
-
-dicer@0.2.5:
-  version "0.2.5"
-  resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f"
-  integrity sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=
-  dependencies:
-    readable-stream "1.1.x"
-    streamsearch "0.1.2"
-
-diff@^2.1.2:
-  version "2.2.3"
-  resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99"
-  integrity sha1-YOr9DSjukG5Oj/ClLBIpUhAzv5k=
-
-diff@^3.1.0, diff@^3.5.0:
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
-  integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
-
-diff@^4.0.1:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
-  integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
-
-dir-glob@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034"
-  integrity sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==
-  dependencies:
-    arrify "^1.0.1"
-    path-type "^3.0.0"
-
-dir-glob@^2.2.2:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4"
-  integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==
-  dependencies:
-    path-type "^3.0.0"
-
 dir-glob@^3.0.1:
   version "3.0.1"
-  resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
   integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
   dependencies:
     path-type "^4.0.0"
 
-doctrine@1.5.0:
-  version "1.5.0"
-  resolved "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz"
-  integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=
-  dependencies:
-    esutils "^2.0.2"
-    isarray "^1.0.0"
-
-doctrine@^2.0.2:
+doctrine@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
   integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==
@@ -3578,7 +700,7 @@
 
 doctrine@^3.0.0:
   version "3.0.0"
-  resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
   integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
   dependencies:
     esutils "^2.0.2"
@@ -3592,12 +714,12 @@
     entities "^2.0.0"
 
 dom-serializer@^1.0.1:
-  version "1.3.1"
-  resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.1.tgz"
-  integrity sha512-Pv2ZluG5ife96udGgEDovOOOA5UELkltfJpnIExPrAk1LTvecolUGn6lIaoLh86d83GiB86CjzciMd9BuRB71Q==
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91"
+  integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==
   dependencies:
     domelementtype "^2.0.1"
-    domhandler "^4.0.0"
+    domhandler "^4.2.0"
     entities "^2.0.0"
 
 dom-serializer@~0.1.0:
@@ -3608,22 +730,6 @@
     domelementtype "^1.3.0"
     entities "^1.1.1"
 
-dom-urls@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/dom-urls/-/dom-urls-1.1.0.tgz#001ddf81628cd1e706125c7176f53ccec55d918e"
-  integrity sha1-AB3fgWKM0ecGElxxdvU8zsVdkY4=
-  dependencies:
-    urijs "^1.16.1"
-
-dom5@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/dom5/-/dom5-3.0.1.tgz#cdfc7331f376e284bf379e6ea054afc136702944"
-  integrity sha512-JPFiouQIr16VQ4dX6i0+Hpbg3H2bMKPmZ+WZgBOSSvOPx9QHwwY8sPzeM2baUtViESYto6wC2nuZOMC/6gulcA==
-  dependencies:
-    "@types/parse5" "^2.2.34"
-    clone "^2.1.0"
-    parse5 "^4.0.0"
-
 domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
@@ -3631,7 +737,7 @@
 
 domelementtype@^2.0.1, domelementtype@^2.2.0:
   version "2.2.0"
-  resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
   integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
 
 domhandler@^2.3.0:
@@ -3641,10 +747,10 @@
   dependencies:
     domelementtype "1"
 
-domhandler@^4.0.0, domhandler@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.npmjs.org/domhandler/-/domhandler-4.1.0.tgz"
-  integrity sha512-/6/kmsGlMY4Tup/nGVutdrK9yQi4YjWVcVeoQmixpzjOUK1U7pQkvAPHBJeUxOgxF0J8f8lwCJSlCfD0V4CMGQ==
+domhandler@^4.0.0, domhandler@^4.2.0:
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f"
+  integrity sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==
   dependencies:
     domelementtype "^2.2.0"
 
@@ -3665,184 +771,46 @@
     domelementtype "1"
 
 domutils@^2.5.2:
-  version "2.5.2"
-  resolved "https://registry.npmjs.org/domutils/-/domutils-2.5.2.tgz"
-  integrity sha512-MHTthCb1zj8f1GVfRpeZUbohQf/HdBos0oX5gZcQFepOZPLLRyj6Wn7XS7EMnY7CVpwv8863u2vyE83Hfu28HQ==
+  version "2.8.0"
+  resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
+  integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
   dependencies:
     dom-serializer "^1.0.1"
     domelementtype "^2.2.0"
-    domhandler "^4.1.0"
-
-dot-prop@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177"
-  integrity sha1-G3CK8JSknJoOfbyteQq6U52sEXc=
-  dependencies:
-    is-obj "^1.0.0"
-
-dot-prop@^4.2.1:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.1.tgz#45884194a71fc2cda71cbb4bceb3a4dd2f433ba4"
-  integrity sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==
-  dependencies:
-    is-obj "^1.0.0"
+    domhandler "^4.2.0"
 
 dot-prop@^5.2.0:
   version "5.3.0"
-  resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz"
+  resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
   integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==
   dependencies:
     is-obj "^2.0.0"
 
-download-stats@^0.3.4:
-  version "0.3.4"
-  resolved "https://registry.yarnpkg.com/download-stats/-/download-stats-0.3.4.tgz#67ea0c32f14acd9f639da704eef509684ba2dae7"
-  integrity sha512-ic2BigbyUWx7/CBbsfGjf71zUNZB4edBGC3oRliSzsoNmvyVx3Ycfp1w3vp2Y78Ee0eIIkjIEO5KzW0zThDGaA==
-  dependencies:
-    JSONStream "^1.2.1"
-    lazy-cache "^2.0.1"
-    moment "^2.15.1"
-
-duplexer2@^0.1.2, duplexer2@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
-  integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=
-  dependencies:
-    readable-stream "^2.0.2"
-
 duplexer3@^0.1.4:
   version "0.1.4"
-  resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz"
+  resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
   integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
 
-duplexify@^3.2.0, duplexify@^3.5.0, duplexify@^3.6.0:
-  version "3.7.1"
-  resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
-  integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==
-  dependencies:
-    end-of-stream "^1.0.0"
-    inherits "^2.0.1"
-    readable-stream "^2.0.0"
-    stream-shift "^1.0.0"
-
-ecc-jsbn@~0.1.1:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
-  integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
-  dependencies:
-    jsbn "~0.1.0"
-    safer-buffer "^2.1.0"
-
-editions@^2.2.0:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/editions/-/editions-2.3.1.tgz#3bc9962f1978e801312fbd0aebfed63b49bfe698"
-  integrity sha512-ptGvkwTvGdGfC0hfhKg0MT+TRLRKGtUiWGBInxOm5pz7ssADezahjCUaYuZ8Dr+C05FW0AECIIPt4WBxVINEhA==
-  dependencies:
-    errlop "^2.0.0"
-    semver "^6.3.0"
-
-ee-first@1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
-  integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
-
-ejs@^2.5.9, ejs@^2.6.1:
-  version "2.7.4"
-  resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba"
-  integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==
-
-ejs@^3.1.5:
-  version "3.1.6"
-  resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a"
-  integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==
-  dependencies:
-    jake "^10.6.1"
-
-electron-to-chromium@^1.3.649:
-  version "1.3.712"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.712.tgz#ae467ffe5f95961c6d41ceefe858fc36eb53b38f"
-  integrity sha512-3kRVibBeCM4vsgoHHGKHmPocLqtFAGTrebXxxtgKs87hNUzXrX2NuS3jnBys7IozCnw7viQlozxKkmty2KNfrw==
-
-emitter-component@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/emitter-component/-/emitter-component-1.1.1.tgz#065e2dbed6959bf470679edabeaf7981d1003ab6"
-  integrity sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY=
-
 emoji-regex@^7.0.1:
   version "7.0.3"
-  resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz"
+  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
   integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
 
 emoji-regex@^8.0.0:
   version "8.0.0"
-  resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
   integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
 
-enabled@2.0.x:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2"
-  integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==
-
-encodeurl@~1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
-  integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
-
-end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1:
+end-of-stream@^1.1.0:
   version "1.4.4"
-  resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz"
+  resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
   integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
   dependencies:
     once "^1.4.0"
 
-ends-with@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/ends-with/-/ends-with-0.2.0.tgz#2f9da98d57a50cfda4571ce4339000500f4e6b8a"
-  integrity sha1-L52pjVelDP2kVxzkM5AAUA9Oa4o=
-
-engine.io-client@~3.5.0:
-  version "3.5.1"
-  resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.5.1.tgz#b500458a39c0cd197a921e0e759721a746d0bdb9"
-  integrity sha512-oVu9kBkGbcggulyVF0kz6BV3ganqUeqXvD79WOFKa+11oK692w1NyFkuEj4xrkFRpZhn92QOqTk4RQq5LiBXbQ==
-  dependencies:
-    component-emitter "~1.3.0"
-    component-inherit "0.0.3"
-    debug "~3.1.0"
-    engine.io-parser "~2.2.0"
-    has-cors "1.1.0"
-    indexof "0.0.1"
-    parseqs "0.0.6"
-    parseuri "0.0.6"
-    ws "~7.4.2"
-    xmlhttprequest-ssl "~1.5.4"
-    yeast "0.1.2"
-
-engine.io-parser@~2.2.0:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.2.1.tgz#57ce5611d9370ee94f99641b589f94c97e4f5da7"
-  integrity sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==
-  dependencies:
-    after "0.8.2"
-    arraybuffer.slice "~0.0.7"
-    base64-arraybuffer "0.1.4"
-    blob "0.0.5"
-    has-binary2 "~1.0.2"
-
-engine.io@~3.5.0:
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.5.0.tgz#9d6b985c8a39b1fe87cd91eb014de0552259821b"
-  integrity sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==
-  dependencies:
-    accepts "~1.3.4"
-    base64id "2.0.0"
-    cookie "~0.4.1"
-    debug "~4.1.0"
-    engine.io-parser "~2.2.0"
-    ws "~7.4.2"
-
 enquirer@^2.3.5:
   version "2.3.6"
-  resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz"
+  resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
   integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
   dependencies:
     ansi-colors "^4.1.1"
@@ -3854,32 +822,20 @@
 
 entities@^2.0.0:
   version "2.2.0"
-  resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
   integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
 
-errlop@^2.0.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/errlop/-/errlop-2.2.0.tgz#1ff383f8f917ae328bebb802d6ca69666a42d21b"
-  integrity sha512-e64Qj9+4aZzjzzFpZC7p5kmm/ccCrbLhAJplhsDXQFs87XTsXwOpH4s1Io2s90Tau/8r2j9f4l/thhDevRjzxw==
-
-error-ex@^1.2.0, error-ex@^1.3.1:
+error-ex@^1.3.1:
   version "1.3.2"
-  resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz"
+  resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
   integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
   dependencies:
     is-arrayish "^0.2.1"
 
-error@^7.0.2:
-  version "7.2.1"
-  resolved "https://registry.yarnpkg.com/error/-/error-7.2.1.tgz#eab21a4689b5f684fc83da84a0e390de82d94894"
-  integrity sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==
-  dependencies:
-    string-template "~0.2.1"
-
-es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2:
-  version "1.18.0"
-  resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz"
-  integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==
+es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2:
+  version "1.18.5"
+  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.5.tgz#9b10de7d4c206a3581fd5b2124233e04db49ae19"
+  integrity sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA==
   dependencies:
     call-bind "^1.0.2"
     es-to-primitive "^1.2.1"
@@ -3887,92 +843,71 @@
     get-intrinsic "^1.1.1"
     has "^1.0.3"
     has-symbols "^1.0.2"
+    internal-slot "^1.0.3"
     is-callable "^1.2.3"
     is-negative-zero "^2.0.1"
-    is-regex "^1.1.2"
-    is-string "^1.0.5"
-    object-inspect "^1.9.0"
+    is-regex "^1.1.3"
+    is-string "^1.0.6"
+    object-inspect "^1.11.0"
     object-keys "^1.1.1"
     object.assign "^4.1.2"
     string.prototype.trimend "^1.0.4"
     string.prototype.trimstart "^1.0.4"
-    unbox-primitive "^1.0.0"
+    unbox-primitive "^1.0.1"
 
 es-to-primitive@^1.2.1:
   version "1.2.1"
-  resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz"
+  resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
   integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
   dependencies:
     is-callable "^1.1.4"
     is-date-object "^1.0.1"
     is-symbol "^1.0.2"
 
-es6-promise@^4.0.3, es6-promise@^4.0.5:
-  version "4.2.8"
-  resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
-  integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
-
-es6-promisify@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
-  integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
-  dependencies:
-    es6-promise "^4.0.3"
-
-es6-promisify@^6.0.0:
-  version "6.1.1"
-  resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-6.1.1.tgz#46837651b7b06bf6fff893d03f29393668d01621"
-  integrity sha512-HBL8I3mIki5C1Cc9QjKUenHtnG0A5/xA8Q/AllRcfiwl2CZFXGK7ddBiCoRwAix4i2KxcQfjtIVcrVbB3vbmwg==
-
-escalade@^3.1.1:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
-  integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
-
 escape-goat@^2.0.0:
   version "2.1.1"
-  resolved "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz"
+  resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
   integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==
 
-escape-html@^1.0.3, escape-html@~1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
-  integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
-
-escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.4, escape-string-regexp@^1.0.5:
+escape-string-regexp@^1.0.5:
   version "1.0.5"
-  resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
+  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
   integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
 
+escape-string-regexp@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+  integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
 eslint-config-google@^0.14.0:
   version "0.14.0"
-  resolved "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz"
+  resolved "https://registry.yarnpkg.com/eslint-config-google/-/eslint-config-google-0.14.0.tgz#4f5f8759ba6e11b424294a219dbfa18c508bcc1a"
   integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==
 
 eslint-config-prettier@^7.0.0:
   version "7.2.0"
-  resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz#f4a4bd2832e810e8cc7c1411ec85b3e85c0c53f9"
   integrity sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg==
 
-eslint-import-resolver-node@^0.3.4:
-  version "0.3.4"
-  resolved "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz"
-  integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==
+eslint-import-resolver-node@^0.3.6:
+  version "0.3.6"
+  resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd"
+  integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==
   dependencies:
-    debug "^2.6.9"
-    resolve "^1.13.1"
+    debug "^3.2.7"
+    resolve "^1.20.0"
 
-eslint-module-utils@^2.6.0:
-  version "2.6.0"
-  resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz"
-  integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==
+eslint-module-utils@^2.6.2:
+  version "2.6.2"
+  resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.2.tgz#94e5540dd15fe1522e8ffa3ec8db3b7fa7e7a534"
+  integrity sha512-QG8pcgThYOuqxupd06oYTZoNOGaUdTY1PqK+oS6ElF6vs4pBdk/aYxFVQQXzcrAqp9m7cl7lb2ubazX+g16k2Q==
   dependencies:
-    debug "^2.6.9"
+    debug "^3.2.7"
     pkg-dir "^2.0.0"
 
 eslint-plugin-es@^3.0.0:
   version "3.0.1"
-  resolved "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893"
   integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==
   dependencies:
     eslint-utils "^2.0.0"
@@ -3986,40 +921,42 @@
     htmlparser2 "^6.0.1"
 
 eslint-plugin-import@^2.22.1:
-  version "2.22.1"
-  resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz"
-  integrity sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==
+  version "2.24.2"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.24.2.tgz#2c8cd2e341f3885918ee27d18479910ade7bb4da"
+  integrity sha512-hNVtyhiEtZmpsabL4neEj+6M5DCLgpYyG9nzJY8lZQeQXEn5UPW1DpUdsMHMXsq98dbNm7nt1w9ZMSVpfJdi8Q==
   dependencies:
-    array-includes "^3.1.1"
-    array.prototype.flat "^1.2.3"
-    contains-path "^0.1.0"
+    array-includes "^3.1.3"
+    array.prototype.flat "^1.2.4"
     debug "^2.6.9"
-    doctrine "1.5.0"
-    eslint-import-resolver-node "^0.3.4"
-    eslint-module-utils "^2.6.0"
+    doctrine "^2.1.0"
+    eslint-import-resolver-node "^0.3.6"
+    eslint-module-utils "^2.6.2"
+    find-up "^2.0.0"
     has "^1.0.3"
+    is-core-module "^2.6.0"
     minimatch "^3.0.4"
-    object.values "^1.1.1"
-    read-pkg-up "^2.0.0"
-    resolve "^1.17.0"
-    tsconfig-paths "^3.9.0"
+    object.values "^1.1.4"
+    pkg-up "^2.0.0"
+    read-pkg-up "^3.0.0"
+    resolve "^1.20.0"
+    tsconfig-paths "^3.11.0"
 
 eslint-plugin-jsdoc@^32.3.0:
-  version "32.3.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-32.3.0.tgz#7c9fa5da8c72bd6ad7d97bbf8dee8bc29bec3f9e"
-  integrity sha512-zyx7kajDK+tqS1bHuY5sapkad8P8KT0vdd/lE55j47VPG2MeenSYuIY/M/Pvmzq5g0+3JB+P3BJGUXmHxtuKPQ==
+  version "32.3.4"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-32.3.4.tgz#6888f3b2dbb9f73fb551458c639a4e8c84fe9ddc"
+  integrity sha512-xSWfsYvffXnN0OkwLnB7MoDDDDjqcp46W7YlY1j7JyfAQBQ+WnGCfLov3gVNZjUGtK9Otj8mEhTZTqJu4QtIGA==
   dependencies:
-    comment-parser "1.1.2"
+    comment-parser "1.1.5"
     debug "^4.3.1"
     jsdoctypeparser "^9.0.0"
-    lodash "^4.17.20"
+    lodash "^4.17.21"
     regextras "^0.7.1"
-    semver "^7.3.4"
+    semver "^7.3.5"
     spdx-expression-parse "^3.0.1"
 
 eslint-plugin-node@^11.1.0:
   version "11.1.0"
-  resolved "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d"
   integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==
   dependencies:
     eslint-plugin-es "^3.0.0"
@@ -4029,23 +966,16 @@
     resolve "^1.10.1"
     semver "^6.1.0"
 
-eslint-plugin-prettier@^3.1.4:
-  version "3.3.1"
-  resolved "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.1.tgz"
-  integrity sha512-Rq3jkcFY8RYeQLgk2cCwuc0P7SEFwDravPhsJZOQ5N4YI4DSg50NyqJ/9gdZHzQlHf8MvafSesbNJCcP/FF6pQ==
+eslint-plugin-prettier@^3.1.4, eslint-plugin-prettier@^3.4.0:
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz#e9ddb200efb6f3d05ffe83b1665a716af4a387e5"
+  integrity sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==
   dependencies:
     prettier-linter-helpers "^1.0.0"
 
-eslint-plugin-prettier@^3.4.0:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.0.tgz#cdbad3bf1dbd2b177e9825737fe63b476a08f0c7"
-  integrity sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw==
-  dependencies:
-    prettier-linter-helpers "^1.0.0"
-
-eslint-scope@^5.0.0, eslint-scope@^5.1.1:
+eslint-scope@^5.1.1:
   version "5.1.1"
-  resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
   integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
   dependencies:
     esrecurse "^4.3.0"
@@ -4053,7 +983,7 @@
 
 eslint-utils@^2.0.0, eslint-utils@^2.1.0:
   version "2.1.0"
-  resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27"
   integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==
   dependencies:
     eslint-visitor-keys "^1.1.0"
@@ -4067,36 +997,39 @@
 
 eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
   version "1.3.0"
-  resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
   integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
 
 eslint-visitor-keys@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz"
-  integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303"
+  integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==
 
-eslint@^7.10.0:
-  version "7.23.0"
-  resolved "https://registry.npmjs.org/eslint/-/eslint-7.23.0.tgz"
-  integrity sha512-kqvNVbdkjzpFy0XOszNwjkKzZ+6TcwCQ/h+ozlcIWwaimBBuhlQ4nN6kbiM2L+OjDcznkTJxzYfRFH92sx4a0Q==
+eslint@^7.10.0, eslint@^7.24.0:
+  version "7.32.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
+  integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==
   dependencies:
     "@babel/code-frame" "7.12.11"
-    "@eslint/eslintrc" "^0.4.0"
+    "@eslint/eslintrc" "^0.4.3"
+    "@humanwhocodes/config-array" "^0.5.0"
     ajv "^6.10.0"
     chalk "^4.0.0"
     cross-spawn "^7.0.2"
     debug "^4.0.1"
     doctrine "^3.0.0"
     enquirer "^2.3.5"
+    escape-string-regexp "^4.0.0"
     eslint-scope "^5.1.1"
     eslint-utils "^2.1.0"
     eslint-visitor-keys "^2.0.0"
     espree "^7.3.1"
     esquery "^1.4.0"
     esutils "^2.0.2"
+    fast-deep-equal "^3.1.3"
     file-entry-cache "^6.0.1"
     functional-red-black-tree "^1.0.1"
-    glob-parent "^5.0.0"
+    glob-parent "^5.1.2"
     globals "^13.6.0"
     ignore "^4.0.6"
     import-fresh "^3.0.0"
@@ -4105,7 +1038,7 @@
     js-yaml "^3.13.1"
     json-stable-stringify-without-jsonify "^1.0.1"
     levn "^0.4.1"
-    lodash "^4.17.21"
+    lodash.merge "^4.6.2"
     minimatch "^3.0.4"
     natural-compare "^1.4.0"
     optionator "^0.9.1"
@@ -4114,64 +1047,13 @@
     semver "^7.2.1"
     strip-ansi "^6.0.0"
     strip-json-comments "^3.1.0"
-    table "^6.0.4"
+    table "^6.0.9"
     text-table "^0.2.0"
     v8-compile-cache "^2.0.3"
 
-eslint@^7.24.0:
-  version "7.24.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.24.0.tgz#2e44fa62d93892bfdb100521f17345ba54b8513a"
-  integrity sha512-k9gaHeHiFmGCDQ2rEfvULlSLruz6tgfA8DEn+rY9/oYPFFTlz55mM/Q/Rij1b2Y42jwZiK3lXvNTw6w6TXzcKQ==
-  dependencies:
-    "@babel/code-frame" "7.12.11"
-    "@eslint/eslintrc" "^0.4.0"
-    ajv "^6.10.0"
-    chalk "^4.0.0"
-    cross-spawn "^7.0.2"
-    debug "^4.0.1"
-    doctrine "^3.0.0"
-    enquirer "^2.3.5"
-    eslint-scope "^5.1.1"
-    eslint-utils "^2.1.0"
-    eslint-visitor-keys "^2.0.0"
-    espree "^7.3.1"
-    esquery "^1.4.0"
-    esutils "^2.0.2"
-    file-entry-cache "^6.0.1"
-    functional-red-black-tree "^1.0.1"
-    glob-parent "^5.0.0"
-    globals "^13.6.0"
-    ignore "^4.0.6"
-    import-fresh "^3.0.0"
-    imurmurhash "^0.1.4"
-    is-glob "^4.0.0"
-    js-yaml "^3.13.1"
-    json-stable-stringify-without-jsonify "^1.0.1"
-    levn "^0.4.1"
-    lodash "^4.17.21"
-    minimatch "^3.0.4"
-    natural-compare "^1.4.0"
-    optionator "^0.9.1"
-    progress "^2.0.0"
-    regexpp "^3.1.0"
-    semver "^7.2.1"
-    strip-ansi "^6.0.0"
-    strip-json-comments "^3.1.0"
-    table "^6.0.4"
-    text-table "^0.2.0"
-    v8-compile-cache "^2.0.3"
-
-espree@^3.5.2:
-  version "3.5.4"
-  resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7"
-  integrity sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==
-  dependencies:
-    acorn "^5.5.0"
-    acorn-jsx "^3.0.0"
-
 espree@^7.3.0, espree@^7.3.1:
   version "7.3.1"
-  resolved "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6"
   integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==
   dependencies:
     acorn "^7.4.0"
@@ -4180,93 +1062,42 @@
 
 esprima@^4.0.0:
   version "4.0.1"
-  resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
   integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
 
 esquery@^1.4.0:
   version "1.4.0"
-  resolved "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz"
+  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5"
   integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==
   dependencies:
     estraverse "^5.1.0"
 
 esrecurse@^4.3.0:
   version "4.3.0"
-  resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz"
+  resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
   integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
   dependencies:
     estraverse "^5.2.0"
 
 estraverse@^4.1.1:
   version "4.3.0"
-  resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz"
+  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
   integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
 
 estraverse@^5.1.0, estraverse@^5.2.0:
   version "5.2.0"
-  resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880"
   integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==
 
 esutils@^2.0.2:
   version "2.0.3"
-  resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"
+  resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
   integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
 
-etag@~1.8.1:
-  version "1.8.1"
-  resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
-  integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
-
-eventemitter3@^4.0.0:
-  version "4.0.7"
-  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
-  integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
-
-execa@^0.7.0:
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
-  integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=
-  dependencies:
-    cross-spawn "^5.0.1"
-    get-stream "^3.0.0"
-    is-stream "^1.1.0"
-    npm-run-path "^2.0.0"
-    p-finally "^1.0.0"
-    signal-exit "^3.0.0"
-    strip-eof "^1.0.0"
-
-execa@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
-  integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==
-  dependencies:
-    cross-spawn "^6.0.0"
-    get-stream "^4.0.0"
-    is-stream "^1.1.0"
-    npm-run-path "^2.0.0"
-    p-finally "^1.0.0"
-    signal-exit "^3.0.0"
-    strip-eof "^1.0.0"
-
-execa@^4.0.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
-  integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==
-  dependencies:
-    cross-spawn "^7.0.0"
-    get-stream "^5.0.0"
-    human-signals "^1.1.1"
-    is-stream "^2.0.0"
-    merge-stream "^2.0.0"
-    npm-run-path "^4.0.0"
-    onetime "^5.1.0"
-    signal-exit "^3.0.2"
-    strip-final-newline "^2.0.0"
-
 execa@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz"
-  integrity sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
+  integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
   dependencies:
     cross-spawn "^7.0.3"
     get-stream "^6.0.0"
@@ -4278,688 +1109,161 @@
     signal-exit "^3.0.3"
     strip-final-newline "^2.0.0"
 
-exit-hook@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
-  integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=
-
-expand-brackets@^0.1.4:
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
-  integrity sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=
-  dependencies:
-    is-posix-bracket "^0.1.0"
-
-expand-brackets@^2.1.4:
-  version "2.1.4"
-  resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622"
-  integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI=
-  dependencies:
-    debug "^2.3.3"
-    define-property "^0.2.5"
-    extend-shallow "^2.0.1"
-    posix-character-classes "^0.1.0"
-    regex-not "^1.0.0"
-    snapdragon "^0.8.1"
-    to-regex "^3.0.1"
-
-expand-range@^1.8.1:
-  version "1.8.2"
-  resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337"
-  integrity sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=
-  dependencies:
-    fill-range "^2.1.0"
-
-expand-tilde@^1.2.2:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449"
-  integrity sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=
-  dependencies:
-    os-homedir "^1.0.1"
-
-expand-tilde@^2.0.0, expand-tilde@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
-  integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=
-  dependencies:
-    homedir-polyfill "^1.0.1"
-
-express@^4.15.3, express@^4.8.5:
-  version "4.17.1"
-  resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
-  integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
-  dependencies:
-    accepts "~1.3.7"
-    array-flatten "1.1.1"
-    body-parser "1.19.0"
-    content-disposition "0.5.3"
-    content-type "~1.0.4"
-    cookie "0.4.0"
-    cookie-signature "1.0.6"
-    debug "2.6.9"
-    depd "~1.1.2"
-    encodeurl "~1.0.2"
-    escape-html "~1.0.3"
-    etag "~1.8.1"
-    finalhandler "~1.1.2"
-    fresh "0.5.2"
-    merge-descriptors "1.0.1"
-    methods "~1.1.2"
-    on-finished "~2.3.0"
-    parseurl "~1.3.3"
-    path-to-regexp "0.1.7"
-    proxy-addr "~2.0.5"
-    qs "6.7.0"
-    range-parser "~1.2.1"
-    safe-buffer "5.1.2"
-    send "0.17.1"
-    serve-static "1.14.1"
-    setprototypeof "1.1.1"
-    statuses "~1.5.0"
-    type-is "~1.6.18"
-    utils-merge "1.0.1"
-    vary "~1.1.2"
-
-ext-list@^2.0.0:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37"
-  integrity sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==
-  dependencies:
-    mime-db "^1.28.0"
-
-extend-shallow@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
-  integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=
-  dependencies:
-    is-extendable "^0.1.0"
-
-extend-shallow@^3.0.0, extend-shallow@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8"
-  integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=
-  dependencies:
-    assign-symbols "^1.0.0"
-    is-extendable "^1.0.1"
-
-extend@^3.0.0, extend@~3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
-  integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
-
-external-editor@^1.1.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-1.1.1.tgz#12d7b0db850f7ff7e7081baf4005700060c4600b"
-  integrity sha1-Etew24UPf/fnCBuvQAVwAGDEYAs=
-  dependencies:
-    extend "^3.0.0"
-    spawn-sync "^1.0.15"
-    tmp "^0.0.29"
-
 external-editor@^3.0.3:
   version "3.1.0"
-  resolved "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495"
   integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==
   dependencies:
     chardet "^0.7.0"
     iconv-lite "^0.4.24"
     tmp "^0.0.33"
 
-extglob@^0.3.1:
-  version "0.3.2"
-  resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
-  integrity sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=
-  dependencies:
-    is-extglob "^1.0.0"
-
-extglob@^2.0.4:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543"
-  integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==
-  dependencies:
-    array-unique "^0.3.2"
-    define-property "^1.0.0"
-    expand-brackets "^2.1.4"
-    extend-shallow "^2.0.1"
-    fragment-cache "^0.2.1"
-    regex-not "^1.0.0"
-    snapdragon "^0.8.1"
-    to-regex "^3.0.1"
-
-extsprintf@1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
-  integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
-
-extsprintf@^1.2.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
-  integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
-
-fast-deep-equal@^3.1.1:
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
   version "3.1.3"
-  resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
+  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
   integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
 
 fast-diff@^1.1.2:
   version "1.2.0"
-  resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
   integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
 
-fast-glob@^2.0.2, fast-glob@^2.2.6:
-  version "2.2.7"
-  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d"
-  integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==
-  dependencies:
-    "@mrmlnc/readdir-enhanced" "^2.2.1"
-    "@nodelib/fs.stat" "^1.1.2"
-    glob-parent "^3.1.0"
-    is-glob "^4.0.0"
-    merge2 "^1.2.3"
-    micromatch "^3.1.10"
-
 fast-glob@^3.1.1:
-  version "3.2.5"
-  resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz"
-  integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==
+  version "3.2.7"
+  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1"
+  integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==
   dependencies:
     "@nodelib/fs.stat" "^2.0.2"
     "@nodelib/fs.walk" "^1.2.3"
-    glob-parent "^5.1.0"
+    glob-parent "^5.1.2"
     merge2 "^1.3.0"
-    micromatch "^4.0.2"
-    picomatch "^2.2.1"
+    micromatch "^4.0.4"
 
 fast-json-stable-stringify@^2.0.0:
   version "2.1.0"
-  resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
   integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
 
 fast-levenshtein@^2.0.6:
   version "2.0.6"
-  resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz"
+  resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
   integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
 
-fast-safe-stringify@^2.0.4:
-  version "2.0.7"
-  resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743"
-  integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==
-
 fastq@^1.6.0:
-  version "1.11.0"
-  resolved "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz"
-  integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==
+  version "1.12.0"
+  resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.12.0.tgz#ed7b6ab5d62393fb2cc591c853652a5c318bf794"
+  integrity sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg==
   dependencies:
     reusify "^1.0.4"
 
-fd-slicer@~1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
-  integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
-  dependencies:
-    pend "~1.2.0"
-
-fecha@^2.3.3:
-  version "2.3.3"
-  resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd"
-  integrity sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==
-
-fecha@^4.2.0:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.1.tgz#0a83ad8f86ef62a091e22bb5a039cd03d23eecce"
-  integrity sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==
-
-figures@^1.3.5:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
-  integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=
-  dependencies:
-    escape-string-regexp "^1.0.5"
-    object-assign "^4.1.0"
-
 figures@^3.0.0:
   version "3.2.0"
-  resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
   integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
   dependencies:
     escape-string-regexp "^1.0.5"
 
 file-entry-cache@^6.0.1:
   version "6.0.1"
-  resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
   integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
   dependencies:
     flat-cache "^3.0.4"
 
-file-uri-to-path@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
-  integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
-
-filelist@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b"
-  integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==
-  dependencies:
-    minimatch "^3.0.4"
-
-filename-regex@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
-  integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=
-
-fill-range@^2.1.0:
-  version "2.2.4"
-  resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565"
-  integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==
-  dependencies:
-    is-number "^2.1.0"
-    isobject "^2.0.0"
-    randomatic "^3.0.0"
-    repeat-element "^1.1.2"
-    repeat-string "^1.5.2"
-
-fill-range@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
-  integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=
-  dependencies:
-    extend-shallow "^2.0.1"
-    is-number "^3.0.0"
-    repeat-string "^1.6.1"
-    to-regex-range "^2.1.0"
-
 fill-range@^7.0.1:
   version "7.0.1"
-  resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
   integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
   dependencies:
     to-regex-range "^5.0.1"
 
-filled-array@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/filled-array/-/filled-array-1.1.0.tgz#c3c4f6c663b923459a9aa29912d2d031f1507f84"
-  integrity sha1-w8T2xmO5I0WamqKZEtLQMfFQf4Q=
-
-finalhandler@~1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
-  integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
-  dependencies:
-    debug "2.6.9"
-    encodeurl "~1.0.2"
-    escape-html "~1.0.3"
-    on-finished "~2.3.0"
-    parseurl "~1.3.3"
-    statuses "~1.5.0"
-    unpipe "~1.0.0"
-
-find-port@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/find-port/-/find-port-1.0.1.tgz#db084a6cbf99564d99869ae79fbdecf66e8a185c"
-  integrity sha1-2whKbL+ZVk2Zhprnn73s9m6KGFw=
-  dependencies:
-    async "~0.2.9"
-
-find-replace@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38"
-  integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==
-  dependencies:
-    array-back "^3.0.1"
-
-find-up@^1.0.0:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
-  integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=
-  dependencies:
-    path-exists "^2.0.0"
-    pinkie-promise "^2.0.0"
-
 find-up@^2.0.0, find-up@^2.1.0:
   version "2.1.0"
-  resolved "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
   integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c=
   dependencies:
     locate-path "^2.0.0"
 
-find-up@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
-  integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
-  dependencies:
-    locate-path "^3.0.0"
-
 find-up@^4.1.0:
   version "4.1.0"
-  resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
   integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
   dependencies:
     locate-path "^5.0.0"
     path-exists "^4.0.0"
 
-findup-sync@^0.4.2:
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12"
-  integrity sha1-QAQ5Kee8YK3wt/SCfExudaDeyhI=
-  dependencies:
-    detect-file "^0.1.0"
-    is-glob "^2.0.1"
-    micromatch "^2.3.7"
-    resolve-dir "^0.1.0"
-
-findup-sync@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc"
-  integrity sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=
-  dependencies:
-    detect-file "^1.0.0"
-    is-glob "^3.1.0"
-    micromatch "^3.0.4"
-    resolve-dir "^1.0.1"
-
-first-chunk-stream@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e"
-  integrity sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=
-
-first-chunk-stream@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz#1bdecdb8e083c0664b91945581577a43a9f31d70"
-  integrity sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=
-  dependencies:
-    readable-stream "^2.0.2"
-
 flat-cache@^3.0.4:
   version "3.0.4"
-  resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz"
+  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
   integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==
   dependencies:
     flatted "^3.1.0"
     rimraf "^3.0.2"
 
 flatted@^3.1.0:
-  version "3.1.1"
-  resolved "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz"
-  integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==
-
-fn.name@1.x.x:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
-  integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
-
-follow-redirects@^1.0.0, follow-redirects@^1.10.0:
-  version "1.13.3"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"
-  integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==
-
-for-in@^1.0.1, for-in@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
-  integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
-
-for-own@^0.1.4:
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce"
-  integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=
-  dependencies:
-    for-in "^1.0.1"
-
-forever-agent@~0.6.1:
-  version "0.6.1"
-  resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
-  integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
-
-fork-stream@^0.0.4:
-  version "0.0.4"
-  resolved "https://registry.yarnpkg.com/fork-stream/-/fork-stream-0.0.4.tgz#db849fce77f6708a5f8f386ae533a0907b54ae70"
-  integrity sha1-24Sfznf2cIpfjzhq5TOgkHtUrnA=
-
-form-data@*:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
-  integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
-  dependencies:
-    asynckit "^0.4.0"
-    combined-stream "^1.0.8"
-    mime-types "^2.1.12"
-
-form-data@~2.3.2:
-  version "2.3.3"
-  resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
-  integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
-  dependencies:
-    asynckit "^0.4.0"
-    combined-stream "^1.0.6"
-    mime-types "^2.1.12"
-
-formatio@1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.2.0.tgz#f3b2167d9068c4698a8d51f4f760a39a54d818eb"
-  integrity sha1-87IWfZBoxGmKjVH092CjmlTYGOs=
-  dependencies:
-    samsam "1.x"
-
-forwarded@~0.1.2:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
-  integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
-
-fragment-cache@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
-  integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=
-  dependencies:
-    map-cache "^0.2.2"
-
-freeport@^1.0.4:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/freeport/-/freeport-1.0.5.tgz#255e8ab84170c33ba85d990e821ae5f4a1a9bc5d"
-  integrity sha1-JV6KuEFwwzuoXZkOghrl9KGpvF0=
-
-fresh@0.5.2:
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
-  integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
-
-fs-constants@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
-  integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
-
-fs-exists-sync@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add"
-  integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561"
+  integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==
 
 fs.realpath@^1.0.0:
   version "1.0.0"
-  resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
   integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
 
-fsevents@^1.0.0:
-  version "1.2.13"
-  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38"
-  integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==
-  dependencies:
-    bindings "^1.5.0"
-    nan "^2.12.1"
-
-fsevents@~2.3.1:
+fsevents@~2.3.2:
   version "2.3.2"
   resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
   integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
 
 function-bind@^1.1.1:
   version "1.1.1"
-  resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
+  resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
   integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
 
 functional-red-black-tree@^1.0.1:
   version "1.0.1"
-  resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
   integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
 
-gensync@^1.0.0-beta.2:
-  version "1.0.0-beta.2"
-  resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
-  integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
-
-get-intrinsic@^1.0.2, get-intrinsic@^1.1.1:
+get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
   version "1.1.1"
-  resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz"
+  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
   integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
   dependencies:
     function-bind "^1.1.1"
     has "^1.0.3"
     has-symbols "^1.0.1"
 
-get-stdin@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
-  integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=
-
-get-stream@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
-  integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
-
-get-stream@^4.0.0, get-stream@^4.1.0:
+get-stream@^4.1.0:
   version "4.1.0"
-  resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
   integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
   dependencies:
     pump "^3.0.0"
 
-get-stream@^5.0.0, get-stream@^5.1.0:
+get-stream@^5.1.0:
   version "5.2.0"
-  resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
   integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
   dependencies:
     pump "^3.0.0"
 
 get-stream@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz"
-  integrity sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
+  integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
 
-get-value@^2.0.3, get-value@^2.0.6:
-  version "2.0.6"
-  resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
-  integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
-
-getpass@^0.1.1:
-  version "0.1.7"
-  resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
-  integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
-  dependencies:
-    assert-plus "^1.0.0"
-
-gh-got@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/gh-got/-/gh-got-5.0.0.tgz#ee95be37106fd8748a96f8d1db4baea89e1bfa8a"
-  integrity sha1-7pW+NxBv2HSKlvjR20uuqJ4b+oo=
-  dependencies:
-    got "^6.2.0"
-    is-plain-obj "^1.1.0"
-
-gh-got@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/gh-got/-/gh-got-6.0.0.tgz#d74353004c6ec466647520a10bd46f7299d268d0"
-  integrity sha512-F/mS+fsWQMo1zfgG9MD8KWvTWPPzzhuVwY++fhQ5Ggd+0P+CAMHtzMZhNxG+TqGfHDChJKsbh6otfMGqO2AKBw==
-  dependencies:
-    got "^7.0.0"
-    is-plain-obj "^1.1.0"
-
-github-username@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/github-username/-/github-username-3.0.0.tgz#0a772219b3130743429f2456d0bdd3db55dce7b1"
-  integrity sha1-CnciGbMTB0NCnyRW0L3T21Xc57E=
-  dependencies:
-    gh-got "^5.0.0"
-
-github-username@^4.0.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/github-username/-/github-username-4.1.0.tgz#cbe280041883206da4212ae9e4b5f169c30bf417"
-  integrity sha1-y+KABBiDIG2kISrp5LXxacML9Bc=
-  dependencies:
-    gh-got "^6.0.0"
-
-glob-base@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
-  integrity sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=
-  dependencies:
-    glob-parent "^2.0.0"
-    is-glob "^2.0.0"
-
-glob-parent@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
-  integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=
-  dependencies:
-    is-glob "^2.0.0"
-
-glob-parent@^3.0.0, glob-parent@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
-  integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=
-  dependencies:
-    is-glob "^3.1.0"
-    path-dirname "^1.0.0"
-
-glob-parent@^5.0.0, glob-parent@^5.1.0:
+glob-parent@^5.1.2:
   version "5.1.2"
-  resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz"
+  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
   integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
   dependencies:
     is-glob "^4.0.1"
 
-glob-stream@^5.3.2:
-  version "5.3.5"
-  resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-5.3.5.tgz#a55665a9a8ccdc41915a87c701e32d4e016fad22"
-  integrity sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=
-  dependencies:
-    extend "^3.0.0"
-    glob "^5.0.3"
-    glob-parent "^3.0.0"
-    micromatch "^2.3.7"
-    ordered-read-streams "^0.3.0"
-    through2 "^0.6.0"
-    to-absolute-glob "^0.1.1"
-    unique-stream "^2.0.2"
-
-glob-to-regexp@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
-  integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
-
-glob@^5.0.3:
-  version "5.0.15"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
-  integrity sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=
-  dependencies:
-    inflight "^1.0.4"
-    inherits "2"
-    minimatch "2 || 3"
-    once "^1.3.0"
-    path-is-absolute "^1.0.0"
-
-glob@^6.0.1:
-  version "6.0.4"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
-  integrity sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=
-  dependencies:
-    inflight "^1.0.4"
-    inherits "2"
-    minimatch "2 || 3"
-    once "^1.3.0"
-    path-is-absolute "^1.0.0"
-
-glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
-  version "7.1.6"
-  resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz"
-  integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
+glob@^7.1.3:
+  version "7.1.7"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
+  integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
   dependencies:
     fs.realpath "^1.0.0"
     inflight "^1.0.4"
@@ -4968,94 +1272,20 @@
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-global-dirs@^0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445"
-  integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=
-  dependencies:
-    ini "^1.3.4"
-
 global-dirs@^3.0.0:
   version "3.0.0"
-  resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686"
   integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==
   dependencies:
     ini "2.0.0"
 
-global-modules@^0.2.3:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d"
-  integrity sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=
-  dependencies:
-    global-prefix "^0.1.4"
-    is-windows "^0.2.0"
-
-global-modules@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
-  integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==
-  dependencies:
-    global-prefix "^1.0.1"
-    is-windows "^1.0.1"
-    resolve-dir "^1.0.0"
-
-global-prefix@^0.1.4:
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f"
-  integrity sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=
-  dependencies:
-    homedir-polyfill "^1.0.0"
-    ini "^1.3.4"
-    is-windows "^0.2.0"
-    which "^1.2.12"
-
-global-prefix@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe"
-  integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=
-  dependencies:
-    expand-tilde "^2.0.2"
-    homedir-polyfill "^1.0.1"
-    ini "^1.3.4"
-    is-windows "^1.0.1"
-    which "^1.2.14"
-
-globals@^11.1.0:
-  version "11.12.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
-  integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
-
-globals@^12.1.0:
-  version "12.4.0"
-  resolved "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz"
-  integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==
-  dependencies:
-    type-fest "^0.8.1"
-
-globals@^13.6.0:
-  version "13.8.0"
-  resolved "https://registry.npmjs.org/globals/-/globals-13.8.0.tgz"
-  integrity sha512-rHtdA6+PDBIjeEvA91rpqzEvk/k3/i7EeNQiryiWuJH0Hw9cpyJMAt2jtbAwUaRdhD+573X4vWw6IcjKPasi9Q==
+globals@^13.6.0, globals@^13.9.0:
+  version "13.11.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-13.11.0.tgz#40ef678da117fe7bd2e28f1fab24951bd0255be7"
+  integrity sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==
   dependencies:
     type-fest "^0.20.2"
 
-globals@^9.18.0:
-  version "9.18.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
-  integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==
-
-globby@^11.0.1:
-  version "11.0.3"
-  resolved "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz"
-  integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==
-  dependencies:
-    array-union "^2.1.0"
-    dir-glob "^3.0.1"
-    fast-glob "^3.1.1"
-    ignore "^5.1.4"
-    merge2 "^1.3.0"
-    slash "^3.0.0"
-
 globby@^11.0.3:
   version "11.0.4"
   resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5"
@@ -5068,134 +1298,9 @@
     merge2 "^1.3.0"
     slash "^3.0.0"
 
-globby@^4.0.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-4.1.0.tgz#080f54549ec1b82a6c60e631fc82e1211dbe95f8"
-  integrity sha1-CA9UVJ7BuCpsYOYx/ILhIR2+lfg=
-  dependencies:
-    array-union "^1.0.1"
-    arrify "^1.0.0"
-    glob "^6.0.1"
-    object-assign "^4.0.1"
-    pify "^2.0.0"
-    pinkie-promise "^2.0.0"
-
-globby@^6.1.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
-  integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=
-  dependencies:
-    array-union "^1.0.1"
-    glob "^7.0.3"
-    object-assign "^4.0.1"
-    pify "^2.0.0"
-    pinkie-promise "^2.0.0"
-
-globby@^8.0.1:
-  version "8.0.2"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.2.tgz#5697619ccd95c5275dbb2d6faa42087c1a941d8d"
-  integrity sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==
-  dependencies:
-    array-union "^1.0.1"
-    dir-glob "2.0.0"
-    fast-glob "^2.0.2"
-    glob "^7.1.2"
-    ignore "^3.3.5"
-    pify "^3.0.0"
-    slash "^1.0.0"
-
-globby@^9.2.0:
-  version "9.2.0"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d"
-  integrity sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==
-  dependencies:
-    "@types/glob" "^7.1.1"
-    array-union "^1.0.2"
-    dir-glob "^2.2.2"
-    fast-glob "^2.2.6"
-    glob "^7.1.3"
-    ignore "^4.0.3"
-    pify "^4.0.1"
-    slash "^2.0.0"
-
-got@^11.8.0:
-  version "11.8.2"
-  resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599"
-  integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==
-  dependencies:
-    "@sindresorhus/is" "^4.0.0"
-    "@szmarczak/http-timer" "^4.0.5"
-    "@types/cacheable-request" "^6.0.1"
-    "@types/responselike" "^1.0.0"
-    cacheable-lookup "^5.0.3"
-    cacheable-request "^7.0.1"
-    decompress-response "^6.0.0"
-    http2-wrapper "^1.0.0-beta.5.2"
-    lowercase-keys "^2.0.0"
-    p-cancelable "^2.0.0"
-    responselike "^2.0.0"
-
-got@^5.0.0:
-  version "5.7.1"
-  resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35"
-  integrity sha1-X4FjWmHkplifGAVp6k44FoClHzU=
-  dependencies:
-    create-error-class "^3.0.1"
-    duplexer2 "^0.1.4"
-    is-redirect "^1.0.0"
-    is-retry-allowed "^1.0.0"
-    is-stream "^1.0.0"
-    lowercase-keys "^1.0.0"
-    node-status-codes "^1.0.0"
-    object-assign "^4.0.1"
-    parse-json "^2.1.0"
-    pinkie-promise "^2.0.0"
-    read-all-stream "^3.0.0"
-    readable-stream "^2.0.5"
-    timed-out "^3.0.0"
-    unzip-response "^1.0.2"
-    url-parse-lax "^1.0.0"
-
-got@^6.2.0, got@^6.7.1:
-  version "6.7.1"
-  resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
-  integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=
-  dependencies:
-    create-error-class "^3.0.0"
-    duplexer3 "^0.1.4"
-    get-stream "^3.0.0"
-    is-redirect "^1.0.0"
-    is-retry-allowed "^1.0.0"
-    is-stream "^1.0.0"
-    lowercase-keys "^1.0.0"
-    safe-buffer "^5.0.1"
-    timed-out "^4.0.0"
-    unzip-response "^2.0.1"
-    url-parse-lax "^1.0.0"
-
-got@^7.0.0:
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a"
-  integrity sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==
-  dependencies:
-    decompress-response "^3.2.0"
-    duplexer3 "^0.1.4"
-    get-stream "^3.0.0"
-    is-plain-obj "^1.1.0"
-    is-retry-allowed "^1.0.0"
-    is-stream "^1.0.0"
-    isurl "^1.0.0-alpha5"
-    lowercase-keys "^1.0.0"
-    p-cancelable "^0.3.0"
-    p-timeout "^1.1.1"
-    safe-buffer "^5.0.1"
-    timed-out "^4.0.0"
-    url-parse-lax "^1.0.0"
-    url-to-options "^1.0.1"
-
 got@^9.6.0:
   version "9.6.0"
-  resolved "https://registry.npmjs.org/got/-/got-9.6.0.tgz"
+  resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
   integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==
   dependencies:
     "@sindresorhus/is" "^0.14.0"
@@ -5210,24 +1315,10 @@
     to-readable-stream "^1.0.0"
     url-parse-lax "^3.0.0"
 
-graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.2.0:
-  version "4.2.6"
-  resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz"
-  integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
-
-grouped-queue@^0.3.0:
-  version "0.3.3"
-  resolved "https://registry.yarnpkg.com/grouped-queue/-/grouped-queue-0.3.3.tgz#c167d2a5319c5a0e0964ef6a25b7c2df8996c85c"
-  integrity sha1-wWfSpTGcWg4JZO9qJbfC34mWyFw=
-  dependencies:
-    lodash "^4.17.2"
-
-grouped-queue@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/grouped-queue/-/grouped-queue-1.1.0.tgz#63e3f9ca90af952269d1d40879e41221eacc74cb"
-  integrity sha512-rZOFKfCqLhsu5VqjBjEWiwrYqJR07KxIkH4mLZlNlGDfntbb4FbMyGFP14TlvRPrU9S3Hnn/sgxbC5ZeN0no3Q==
-  dependencies:
-    lodash "^4.17.15"
+graceful-fs@^4.1.2:
+  version "4.2.8"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
+  integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
 
 gts@^3.1.0:
   version "3.1.0"
@@ -5251,214 +1342,62 @@
     update-notifier "^5.0.0"
     write-file-atomic "^3.0.3"
 
-gulp-if@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/gulp-if/-/gulp-if-2.0.2.tgz#a497b7e7573005041caa2bc8b7dda3c80444d629"
-  integrity sha1-pJe351cwBQQcqivIt92jyARE1ik=
-  dependencies:
-    gulp-match "^1.0.3"
-    ternary-stream "^2.0.1"
-    through2 "^2.0.1"
-
-gulp-match@^1.0.3:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/gulp-match/-/gulp-match-1.1.0.tgz#552b7080fc006ee752c90563f9fec9d61aafdf4f"
-  integrity sha512-DlyVxa1Gj24DitY2OjEsS+X6tDpretuxD6wTfhXE/Rw2hweqc1f6D/XtsJmoiCwLWfXgR87W9ozEityPCVzGtQ==
-  dependencies:
-    minimatch "^3.0.3"
-
-gulp-sourcemaps@1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz#b86ff349d801ceb56e1d9e7dc7bbcb4b7dee600c"
-  integrity sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw=
-  dependencies:
-    convert-source-map "^1.1.1"
-    graceful-fs "^4.1.2"
-    strip-bom "^2.0.0"
-    through2 "^2.0.0"
-    vinyl "^1.0.0"
-
-gunzip-maybe@^1.3.1:
-  version "1.4.2"
-  resolved "https://registry.yarnpkg.com/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz#b913564ae3be0eda6f3de36464837a9cd94b98ac"
-  integrity sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==
-  dependencies:
-    browserify-zlib "^0.1.4"
-    is-deflate "^1.0.0"
-    is-gzip "^1.0.0"
-    peek-stream "^1.1.0"
-    pumpify "^1.3.3"
-    through2 "^2.0.3"
-
-handle-thing@^1.2.5:
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"
-  integrity sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=
-
-har-schema@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
-  integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
-
-har-validator@~5.1.0, har-validator@~5.1.3:
-  version "5.1.5"
-  resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
-  integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==
-  dependencies:
-    ajv "^6.12.3"
-    har-schema "^2.0.0"
-
 hard-rejection@^2.1.0:
   version "2.1.0"
-  resolved "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883"
   integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==
 
-has-ansi@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
-  integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=
-  dependencies:
-    ansi-regex "^2.0.0"
-
 has-bigints@^1.0.1:
   version "1.0.1"
-  resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
   integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==
 
-has-binary2@~1.0.2:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d"
-  integrity sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==
-  dependencies:
-    isarray "2.0.1"
-
-has-color@~0.1.0:
-  version "0.1.7"
-  resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f"
-  integrity sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=
-
-has-cors@1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39"
-  integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=
-
 has-flag@^3.0.0:
   version "3.0.0"
-  resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
   integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
 
 has-flag@^4.0.0:
   version "4.0.0"
-  resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
   integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
 
-has-symbol-support-x@^1.4.1:
-  version "1.4.2"
-  resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455"
-  integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==
-
 has-symbols@^1.0.1, has-symbols@^1.0.2:
   version "1.0.2"
-  resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz"
+  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
   integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
 
-has-to-string-tag-x@^1.2.0:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d"
-  integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==
-  dependencies:
-    has-symbol-support-x "^1.4.1"
-
-has-value@^0.3.1:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
-  integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=
-  dependencies:
-    get-value "^2.0.3"
-    has-values "^0.1.4"
-    isobject "^2.0.0"
-
-has-value@^1.0.0:
+has-tostringtag@^1.0.0:
   version "1.0.0"
-  resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177"
-  integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=
+  resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
+  integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
   dependencies:
-    get-value "^2.0.6"
-    has-values "^1.0.0"
-    isobject "^3.0.0"
-
-has-values@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771"
-  integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E=
-
-has-values@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f"
-  integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=
-  dependencies:
-    is-number "^3.0.0"
-    kind-of "^4.0.0"
+    has-symbols "^1.0.2"
 
 has-yarn@^2.1.0:
   version "2.1.0"
-  resolved "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77"
   integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==
 
 has@^1.0.3:
   version "1.0.3"
-  resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz"
+  resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
   integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
   dependencies:
     function-bind "^1.1.1"
 
-he@1.2.x:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
-  integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
-
-homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"
-  integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==
-  dependencies:
-    parse-passwd "^1.0.0"
-
 hosted-git-info@^2.1.4:
   version "2.8.9"
-  resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz"
+  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
   integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
 
 hosted-git-info@^4.0.1:
   version "4.0.2"
-  resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz"
+  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961"
   integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==
   dependencies:
     lru-cache "^6.0.0"
 
-hpack.js@^2.1.6:
-  version "2.1.6"
-  resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2"
-  integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=
-  dependencies:
-    inherits "^2.0.1"
-    obuf "^1.0.0"
-    readable-stream "^2.0.1"
-    wbuf "^1.1.0"
-
-html-minifier@^3.5.10:
-  version "3.5.21"
-  resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.21.tgz#d0040e054730e354db008463593194015212d20c"
-  integrity sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==
-  dependencies:
-    camel-case "3.0.x"
-    clean-css "4.2.x"
-    commander "2.17.x"
-    he "1.2.x"
-    param-case "2.1.x"
-    relateurl "0.2.x"
-    uglify-js "3.4.x"
-
 htmlparser2@^3.9.1:
   version "3.10.1"
   resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
@@ -5473,7 +1412,7 @@
 
 htmlparser2@^6.0.1:
   version "6.1.0"
-  resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
   integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
   dependencies:
     domelementtype "^2.0.1"
@@ -5483,138 +1422,34 @@
 
 http-cache-semantics@^4.0.0:
   version "4.1.0"
-  resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
   integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
 
-http-deceiver@^1.2.7:
-  version "1.2.7"
-  resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
-  integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=
-
-http-errors@1.7.2:
-  version "1.7.2"
-  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
-  integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
-  dependencies:
-    depd "~1.1.2"
-    inherits "2.0.3"
-    setprototypeof "1.1.1"
-    statuses ">= 1.5.0 < 2"
-    toidentifier "1.0.0"
-
-http-errors@~1.6.2:
-  version "1.6.3"
-  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
-  integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=
-  dependencies:
-    depd "~1.1.2"
-    inherits "2.0.3"
-    setprototypeof "1.1.0"
-    statuses ">= 1.4.0 < 2"
-
-http-errors@~1.7.2:
-  version "1.7.3"
-  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
-  integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
-  dependencies:
-    depd "~1.1.2"
-    inherits "2.0.4"
-    setprototypeof "1.1.1"
-    statuses ">= 1.5.0 < 2"
-    toidentifier "1.0.0"
-
-http-proxy-middleware@^0.17.2:
-  version "0.17.4"
-  resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833"
-  integrity sha1-ZC6ISIUdZvCdTxJJEoRtuutBuDM=
-  dependencies:
-    http-proxy "^1.16.2"
-    is-glob "^3.1.0"
-    lodash "^4.17.2"
-    micromatch "^2.3.11"
-
-http-proxy@^1.16.2:
-  version "1.18.1"
-  resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
-  integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
-  dependencies:
-    eventemitter3 "^4.0.0"
-    follow-redirects "^1.0.0"
-    requires-port "^1.0.0"
-
-http-signature@~1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
-  integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
-  dependencies:
-    assert-plus "^1.0.0"
-    jsprim "^1.2.2"
-    sshpk "^1.7.0"
-
-http2-wrapper@^1.0.0-beta.5.2:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d"
-  integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==
-  dependencies:
-    quick-lru "^5.1.1"
-    resolve-alpn "^1.0.0"
-
-https-proxy-agent@^2.2.1:
-  version "2.2.4"
-  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
-  integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
-  dependencies:
-    agent-base "^4.3.0"
-    debug "^3.1.0"
-
-https-proxy-agent@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
-  integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
-  dependencies:
-    agent-base "6"
-    debug "4"
-
-human-signals@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
-  integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
-
 human-signals@^2.1.0:
   version "2.1.0"
-  resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
   integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
 
-iconv-lite@0.4.24, iconv-lite@^0.4.24:
+iconv-lite@^0.4.24:
   version "0.4.24"
-  resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz"
+  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
   integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
-ieee754@^1.1.13:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
-  integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
-
-ignore@^3.3.5:
-  version "3.3.10"
-  resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
-  integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==
-
-ignore@^4.0.3, ignore@^4.0.6:
+ignore@^4.0.6:
   version "4.0.6"
-  resolved "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
   integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
 
 ignore@^5.1.1, ignore@^5.1.4:
   version "5.1.8"
-  resolved "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
   integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
 
 import-fresh@^3.0.0, import-fresh@^3.2.1:
   version "3.3.0"
-  resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz"
+  resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
   integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
   dependencies:
     parent-module "^1.0.0"
@@ -5622,87 +1457,45 @@
 
 import-lazy@^2.1.0:
   version "2.1.0"
-  resolved "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
   integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=
 
 imurmurhash@^0.1.4:
   version "0.1.4"
-  resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz"
+  resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
   integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
 
-indent-string@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
-  integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=
-  dependencies:
-    repeating "^2.0.0"
-
 indent-string@^4.0.0:
   version "4.0.0"
-  resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
   integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
 
-indent@0.0.2:
-  version "0.0.2"
-  resolved "https://registry.yarnpkg.com/indent/-/indent-0.0.2.tgz#8c79f080190559b687034b84c7aefa97d5a911d9"
-  integrity sha1-jHnwgBkFWbaHA0uEx676l9WpEdk=
-
-indexof@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
-  integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=
-
 inflight@^1.0.4:
   version "1.0.6"
-  resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz"
+  resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
   integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
   dependencies:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@^2.0.1, inherits@^2.0.3:
   version "2.0.4"
-  resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
 
-inherits@2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
-  integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
-
 ini@2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5"
   integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==
 
-ini@^1.3.4, ini@~1.3.0:
+ini@~1.3.0:
   version "1.3.8"
-  resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz"
+  resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
   integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
 
-inquirer@^1.0.2:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-1.2.3.tgz#4dec6f32f37ef7bb0b2ed3f1d1a5c3f545074918"
-  integrity sha1-TexvMvN+97sLLtPx0aXD9UUHSRg=
-  dependencies:
-    ansi-escapes "^1.1.0"
-    chalk "^1.0.0"
-    cli-cursor "^1.0.1"
-    cli-width "^2.0.0"
-    external-editor "^1.1.0"
-    figures "^1.3.5"
-    lodash "^4.3.0"
-    mute-stream "0.0.6"
-    pinkie-promise "^2.0.0"
-    run-async "^2.2.0"
-    rx "^4.1.0"
-    string-width "^1.0.1"
-    strip-ansi "^3.0.0"
-    through "^2.3.6"
-
-inquirer@^7.1.0, inquirer@^7.3.3:
+inquirer@^7.3.3:
   version "7.3.3"
-  resolved "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz"
+  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003"
   integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==
   dependencies:
     ansi-escapes "^4.2.1"
@@ -5719,237 +1512,86 @@
     strip-ansi "^6.0.0"
     through "^2.3.6"
 
-interpret@^1.0.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
-  integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
-
-intersect@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/intersect/-/intersect-1.0.1.tgz#332650e10854d8c0ac58c192bdc27a8bf7e7a30c"
-  integrity sha1-MyZQ4QhU2MCsWMGSvcJ6i/fnoww=
-
-invariant@^2.2.2:
-  version "2.2.4"
-  resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
-  integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
+internal-slot@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
+  integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==
   dependencies:
-    loose-envify "^1.0.0"
-
-ipaddr.js@1.9.1:
-  version "1.9.1"
-  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
-  integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
-
-is-accessor-descriptor@^0.1.6:
-  version "0.1.6"
-  resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6"
-  integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=
-  dependencies:
-    kind-of "^3.0.2"
-
-is-accessor-descriptor@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656"
-  integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==
-  dependencies:
-    kind-of "^6.0.0"
+    get-intrinsic "^1.1.0"
+    has "^1.0.3"
+    side-channel "^1.0.4"
 
 is-arrayish@^0.2.1:
   version "0.2.1"
-  resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz"
+  resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
   integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
 
-is-arrayish@^0.3.1:
-  version "0.3.2"
-  resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
-  integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
-
 is-bigint@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz"
-  integrity sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==
-
-is-binary-path@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
-  integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
+  integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==
   dependencies:
-    binary-extensions "^1.0.0"
+    has-bigints "^1.0.1"
 
 is-boolean-object@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz"
-  integrity sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719"
+  integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==
   dependencies:
-    call-bind "^1.0.0"
-
-is-buffer@^1.1.5, is-buffer@~1.1.6:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
-  integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
+    call-bind "^1.0.2"
+    has-tostringtag "^1.0.0"
 
 is-callable@^1.1.4, is-callable@^1.2.3:
-  version "1.2.3"
-  resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz"
-  integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==
-
-is-ci@^1.0.10:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c"
-  integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==
-  dependencies:
-    ci-info "^1.5.0"
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945"
+  integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==
 
 is-ci@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
   integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==
   dependencies:
     ci-info "^2.0.0"
 
-is-core-module@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz"
-  integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==
+is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.6.0:
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.6.0.tgz#d7553b2526fe59b92ba3e40c8df757ec8a709e19"
+  integrity sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==
   dependencies:
     has "^1.0.3"
 
-is-data-descriptor@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
-  integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=
-  dependencies:
-    kind-of "^3.0.2"
-
-is-data-descriptor@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7"
-  integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==
-  dependencies:
-    kind-of "^6.0.0"
-
 is-date-object@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz"
-  integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
-
-is-deflate@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-deflate/-/is-deflate-1.0.0.tgz#c862901c3c161fb09dac7cdc7e784f80e98f2f14"
-  integrity sha1-yGKQHDwWH7CdrHzcfnhPgOmPLxQ=
-
-is-descriptor@^0.1.0:
-  version "0.1.6"
-  resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
-  integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f"
+  integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==
   dependencies:
-    is-accessor-descriptor "^0.1.6"
-    is-data-descriptor "^0.1.4"
-    kind-of "^5.0.0"
+    has-tostringtag "^1.0.0"
 
-is-descriptor@^1.0.0, is-descriptor@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec"
-  integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==
-  dependencies:
-    is-accessor-descriptor "^1.0.0"
-    is-data-descriptor "^1.0.0"
-    kind-of "^6.0.2"
-
-is-dotfile@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
-  integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=
-
-is-equal-shallow@^0.1.3:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534"
-  integrity sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=
-  dependencies:
-    is-primitive "^2.0.0"
-
-is-extendable@^0.1.0, is-extendable@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
-  integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=
-
-is-extendable@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4"
-  integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==
-  dependencies:
-    is-plain-object "^2.0.4"
-
-is-extglob@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
-  integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=
-
-is-extglob@^2.1.0, is-extglob@^2.1.1:
+is-extglob@^2.1.1:
   version "2.1.1"
-  resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
+  resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
   integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
 
-is-finite@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3"
-  integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==
-
-is-fullwidth-code-point@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
-  integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs=
-  dependencies:
-    number-is-nan "^1.0.0"
-
 is-fullwidth-code-point@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
   integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
 
 is-fullwidth-code-point@^3.0.0:
   version "3.0.0"
-  resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
   integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
 
-is-glob@^2.0.0, is-glob@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
-  integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=
-  dependencies:
-    is-extglob "^1.0.0"
-
-is-glob@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
-  integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=
-  dependencies:
-    is-extglob "^2.1.0"
-
 is-glob@^4.0.0, is-glob@^4.0.1:
   version "4.0.1"
-  resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
   integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
   dependencies:
     is-extglob "^2.1.1"
 
-is-gzip@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-gzip/-/is-gzip-1.0.0.tgz#6ca8b07b99c77998025900e555ced8ed80879a83"
-  integrity sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM=
-
-is-installed-globally@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80"
-  integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=
-  dependencies:
-    global-dirs "^0.1.0"
-    is-path-inside "^1.0.0"
-
 is-installed-globally@^0.4.0:
   version "0.4.0"
-  resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz"
+  resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520"
   integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==
   dependencies:
     global-dirs "^3.0.0"
@@ -5957,325 +1599,106 @@
 
 is-negative-zero@^2.0.1:
   version "2.0.1"
-  resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
   integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
 
-is-npm@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4"
-  integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ=
-
 is-npm@^5.0.0:
   version "5.0.0"
-  resolved "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8"
   integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==
 
 is-number-object@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz"
-  integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==
-
-is-number@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
-  integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0"
+  integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==
   dependencies:
-    kind-of "^3.0.2"
-
-is-number@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
-  integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=
-  dependencies:
-    kind-of "^3.0.2"
-
-is-number@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff"
-  integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==
+    has-tostringtag "^1.0.0"
 
 is-number@^7.0.0:
   version "7.0.0"
-  resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
   integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
 
-is-obj@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
-  integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
-
 is-obj@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982"
   integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
 
-is-object@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf"
-  integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==
-
-is-path-cwd@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
-  integrity sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=
-
-is-path-in-cwd@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52"
-  integrity sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==
-  dependencies:
-    is-path-inside "^1.0.0"
-
-is-path-inside@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
-  integrity sha1-jvW33lBDej/cprToZe96pVy0gDY=
-  dependencies:
-    path-is-inside "^1.0.1"
-
 is-path-inside@^3.0.2:
   version "3.0.3"
-  resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz"
+  resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
   integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
 
-is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
+is-plain-obj@^1.1.0:
   version "1.1.0"
-  resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
   integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4=
 
-is-plain-object@^2.0.3, is-plain-object@^2.0.4:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
-  integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==
-  dependencies:
-    isobject "^3.0.1"
-
-is-plain-object@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
-  integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
-
-is-posix-bracket@^0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
-  integrity sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=
-
-is-potential-custom-element-name@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
-  integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
-
-is-primitive@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
-  integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU=
-
-is-redirect@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
-  integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=
-
-is-regex@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz"
-  integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==
+is-regex@^1.1.3:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
+  integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
   dependencies:
     call-bind "^1.0.2"
-    has-symbols "^1.0.1"
-
-is-retry-allowed@^1.0.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4"
-  integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==
-
-is-scoped@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-scoped/-/is-scoped-1.0.0.tgz#449ca98299e713038256289ecb2b540dc437cb30"
-  integrity sha1-RJypgpnnEwOCViieyytUDcQ3yzA=
-  dependencies:
-    scoped-regex "^1.0.0"
-
-is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
-  integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
+    has-tostringtag "^1.0.0"
 
 is-stream@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz"
-  integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
+  integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
 
-is-string@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz"
-  integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==
+is-string@^1.0.5, is-string@^1.0.6:
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd"
+  integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==
+  dependencies:
+    has-tostringtag "^1.0.0"
 
 is-symbol@^1.0.2, is-symbol@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz"
-  integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
+  integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==
   dependencies:
-    has-symbols "^1.0.1"
+    has-symbols "^1.0.2"
 
-is-typedarray@^1.0.0, is-typedarray@~1.0.0:
+is-typedarray@^1.0.0:
   version "1.0.0"
-  resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
   integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
 
-is-utf8@^0.2.0, is-utf8@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
-  integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
-
-is-valid-glob@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-0.3.0.tgz#d4b55c69f51886f9b65c70d6c2622d37e29f48fe"
-  integrity sha1-1LVcafUYhvm2XHDWwmItN+KfSP4=
-
-is-windows@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c"
-  integrity sha1-3hqm1j6indJIc3tp8f+LgALSEIw=
-
-is-windows@^1.0.1, is-windows@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
-  integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
-
 is-yarn-global@^0.3.0:
   version "0.3.0"
-  resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz"
+  resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
   integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==
 
-isarray@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
-  integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
-
-isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
-  integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
-
-isarray@2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
-  integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=
-
-isbinaryfile@^3.0.2:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.3.tgz#5d6def3edebf6e8ca8cae9c30183a804b5f8be80"
-  integrity sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==
-  dependencies:
-    buffer-alloc "^1.2.0"
-
-isbinaryfile@^4.0.0:
-  version "4.0.6"
-  resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b"
-  integrity sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==
-
 isexe@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
   integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
 
-isobject@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
-  integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=
-  dependencies:
-    isarray "1.0.0"
-
-isobject@^3.0.0, isobject@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
-  integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
-
-isstream@~0.1.2:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
-  integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
-
-istextorbinary@^2.2.1, istextorbinary@^2.5.1:
-  version "2.6.0"
-  resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-2.6.0.tgz#60776315fb0fa3999add276c02c69557b9ca28ab"
-  integrity sha512-+XRlFseT8B3L9KyjxxLjfXSLMuErKDsd8DBNrsaxoViABMEZlOSCstwmw0qpoFX3+U6yWU1yhLudAe6/lETGGA==
-  dependencies:
-    binaryextensions "^2.1.2"
-    editions "^2.2.0"
-    textextensions "^2.5.0"
-
-isurl@^1.0.0-alpha5:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67"
-  integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==
-  dependencies:
-    has-to-string-tag-x "^1.2.0"
-    is-object "^1.0.1"
-
-jake@^10.6.1:
-  version "10.8.2"
-  resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b"
-  integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==
-  dependencies:
-    async "0.9.x"
-    chalk "^2.4.2"
-    filelist "^1.0.1"
-    minimatch "^3.0.4"
-
-"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
+js-tokens@^4.0.0:
   version "4.0.0"
-  resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
   integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
 
-js-tokens@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
-  integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
-
 js-yaml@^3.13.1:
   version "3.14.1"
-  resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
   integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
   dependencies:
     argparse "^1.0.7"
     esprima "^4.0.0"
 
-jsbn@~0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
-  integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
-
 jsdoctypeparser@^9.0.0:
   version "9.0.0"
-  resolved "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-9.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/jsdoctypeparser/-/jsdoctypeparser-9.0.0.tgz#8c97e2fb69315eb274b0f01377eaa5c940bd7b26"
   integrity sha512-jrTA2jJIL6/DAEILBEh2/w9QxCuwmvNXIry39Ay/HVfhE3o2yVV0U44blYkqdHA/OKloJEqvJy0xU+GSdE2SIw==
 
-jsesc@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
-  integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s=
-
-jsesc@^2.5.1:
-  version "2.5.2"
-  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
-  integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
-
-jsesc@~0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
-  integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
-
 json-buffer@3.0.0:
   version "3.0.0"
-  resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
   integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
 
-json-buffer@3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
-  integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
-
 json-parse-better-errors@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
@@ -6283,168 +1706,60 @@
 
 json-parse-even-better-errors@^2.3.0:
   version "2.3.1"
-  resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz"
+  resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
   integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
 
 json-schema-traverse@^0.4.1:
   version "0.4.1"
-  resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz"
+  resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
   integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
 
 json-schema-traverse@^1.0.0:
   version "1.0.0"
-  resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
   integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
 
-json-schema@0.2.3:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
-  integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
-
 json-stable-stringify-without-jsonify@^1.0.1:
   version "1.0.1"
-  resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
   integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
 
-json-stringify-safe@~5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
-  integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
-
 json5@^1.0.1:
   version "1.0.1"
-  resolved "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
   integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
   dependencies:
     minimist "^1.2.0"
 
-json5@^2.1.2, json5@^2.1.3:
+json5@^2.1.3:
   version "2.2.0"
-  resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
   integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
   dependencies:
     minimist "^1.2.5"
 
-jsonparse@^1.2.0:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
-  integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=
-
-jsonschema@^1.1.0, jsonschema@^1.1.1:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.0.tgz#1afa34c4bc22190d8e42271ec17ac8b3404f87b2"
-  integrity sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw==
-
-jsprim@^1.2.2:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
-  integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=
-  dependencies:
-    assert-plus "1.0.0"
-    extsprintf "1.3.0"
-    json-schema "0.2.3"
-    verror "1.10.0"
-
 keyv@^3.0.0:
   version "3.1.0"
-  resolved "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
   integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==
   dependencies:
     json-buffer "3.0.0"
 
-keyv@^4.0.0:
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254"
-  integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==
-  dependencies:
-    json-buffer "3.0.1"
-
-kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
-  version "3.2.2"
-  resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
-  integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=
-  dependencies:
-    is-buffer "^1.1.5"
-
-kind-of@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57"
-  integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc=
-  dependencies:
-    is-buffer "^1.1.5"
-
-kind-of@^5.0.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
-  integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==
-
-kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3:
+kind-of@^6.0.3:
   version "6.0.3"
-  resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz"
+  resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
   integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
 
-kuler@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3"
-  integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==
-
-latest-version@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-2.0.0.tgz#56f8d6139620847b8017f8f1f4d78e211324168b"
-  integrity sha1-VvjWE5YghHuAF/jx9NeOIRMkFos=
-  dependencies:
-    package-json "^2.0.0"
-
-latest-version@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15"
-  integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=
-  dependencies:
-    package-json "^4.0.0"
-
 latest-version@^5.1.0:
   version "5.1.0"
-  resolved "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face"
   integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==
   dependencies:
     package-json "^6.3.0"
 
-launchpad@^0.7.0:
-  version "0.7.5"
-  resolved "https://registry.yarnpkg.com/launchpad/-/launchpad-0.7.5.tgz#a16950c937572f10ef01c9be945a96f7aef8e427"
-  integrity sha512-gsYFgT8XKL3X2XZHPPPrgwM0JqeQwGpSWnzg7EYadBY3MirbQrTVq6L4fm6l7UE2T+7gnfuhiGkKr/xxuU/fdw==
-  dependencies:
-    async "^2.0.1"
-    browserstack "^1.2.0"
-    debug "^2.2.0"
-    mkdirp "^0.5.1"
-    plist "^2.0.1"
-    q "^1.4.1"
-    rimraf "^3.0.0"
-    underscore "^1.8.3"
-
-lazy-cache@^2.0.1:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264"
-  integrity sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=
-  dependencies:
-    set-getter "^0.1.0"
-
-lazy-req@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/lazy-req/-/lazy-req-1.1.0.tgz#bdaebead30f8d824039ce0ce149d4daa07ba1fac"
-  integrity sha1-va6+rTD42CQDnODOFJ1Nqge6H6w=
-
-lazystream@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4"
-  integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=
-  dependencies:
-    readable-stream "^2.0.5"
-
 levn@^0.4.1:
   version "0.4.1"
-  resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz"
+  resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
   integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
   dependencies:
     prelude-ls "^1.2.1"
@@ -6452,30 +1767,9 @@
 
 lines-and-columns@^1.1.6:
   version "1.1.6"
-  resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz"
+  resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
   integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
 
-load-json-file@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
-  integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=
-  dependencies:
-    graceful-fs "^4.1.2"
-    parse-json "^2.2.0"
-    pify "^2.0.0"
-    pinkie-promise "^2.0.0"
-    strip-bom "^2.0.0"
-
-load-json-file@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz"
-  integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=
-  dependencies:
-    graceful-fs "^4.1.2"
-    parse-json "^2.2.0"
-    pify "^2.0.0"
-    strip-bom "^3.0.0"
-
 load-json-file@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
@@ -6488,381 +1782,81 @@
 
 locate-path@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
   integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=
   dependencies:
     p-locate "^2.0.0"
     path-exists "^3.0.0"
 
-locate-path@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
-  integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
-  dependencies:
-    p-locate "^3.0.0"
-    path-exists "^3.0.0"
-
 locate-path@^5.0.0:
   version "5.0.0"
-  resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
   integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
   dependencies:
     p-locate "^4.1.0"
 
-lodash._reinterpolate@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
-  integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
-
-lodash.camelcase@^4.3.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
-  integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
-
 lodash.clonedeep@^4.5.0:
   version "4.5.0"
-  resolved "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz"
+  resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
   integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
 
-lodash.defaults@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
-  integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
-
-lodash.difference@^4.5.0:
-  version "4.5.0"
-  resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c"
-  integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=
-
-lodash.flatten@^4.4.0:
-  version "4.4.0"
-  resolved "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz"
-  integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
-
-lodash.get@^4.4.2:
-  version "4.4.2"
-  resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
-  integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
-
-lodash.isequal@^4.0.0:
-  version "4.5.0"
-  resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
-  integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
-
-lodash.isplainobject@^4.0.6:
-  version "4.0.6"
-  resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
-  integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
-
-lodash.mapvalues@^4.6.0:
-  version "4.6.0"
-  resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c"
-  integrity sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=
-
 lodash.merge@^4.6.2:
   version "4.6.2"
   resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
   integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
 
-lodash.padend@^4.6.1:
-  version "4.6.1"
-  resolved "https://registry.yarnpkg.com/lodash.padend/-/lodash.padend-4.6.1.tgz#53ccba047d06e158d311f45da625f4e49e6f166e"
-  integrity sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=
-
-lodash.set@^4.3.2:
-  version "4.3.2"
-  resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"
-  integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=
-
-lodash.sortby@^4.7.0:
-  version "4.7.0"
-  resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
-  integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
-
-lodash.template@^4.4.0:
-  version "4.5.0"
-  resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
-  integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==
-  dependencies:
-    lodash._reinterpolate "^3.0.0"
-    lodash.templatesettings "^4.0.0"
-
-lodash.templatesettings@^4.0.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33"
-  integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==
-  dependencies:
-    lodash._reinterpolate "^3.0.0"
-
 lodash.truncate@^4.4.2:
   version "4.4.2"
-  resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz"
+  resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
   integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
 
-lodash.union@^4.6.0:
-  version "4.6.0"
-  resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88"
-  integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=
-
-lodash.uniq@^4.5.0:
-  version "4.5.0"
-  resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
-  integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
-
-lodash@^3.0.0, lodash@^3.10.1:
-  version "3.10.1"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
-  integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=
-
-lodash@^4.0.0, lodash@^4.11.1, lodash@^4.15.0, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.3.0:
+lodash@4.17.21, lodash@^4.15.0, lodash@^4.17.19, lodash@^4.17.21:
   version "4.17.21"
-  resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
 
-log-symbols@^1.0.0, log-symbols@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
-  integrity sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=
-  dependencies:
-    chalk "^1.0.0"
-
-log-symbols@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
-  integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==
-  dependencies:
-    chalk "^2.0.1"
-
-logform@^1.9.1:
-  version "1.10.0"
-  resolved "https://registry.yarnpkg.com/logform/-/logform-1.10.0.tgz#c9d5598714c92b546e23f4e78147c40f1e02012e"
-  integrity sha512-em5ojIhU18fIMOw/333mD+ZLE2fis0EzXl1ZwHx4iQzmpQi6odNiY/t+ITNr33JZhT9/KEaH+UPIipr6a9EjWg==
-  dependencies:
-    colors "^1.2.1"
-    fast-safe-stringify "^2.0.4"
-    fecha "^2.3.3"
-    ms "^2.1.1"
-    triple-beam "^1.2.0"
-
-logform@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/logform/-/logform-2.2.0.tgz#40f036d19161fc76b68ab50fdc7fe495544492f2"
-  integrity sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==
-  dependencies:
-    colors "^1.2.1"
-    fast-safe-stringify "^2.0.4"
-    fecha "^4.2.0"
-    ms "^2.1.1"
-    triple-beam "^1.3.0"
-
-lolex@^1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6"
-  integrity sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=
-
 long@^4.0.0:
   version "4.0.0"
-  resolved "https://registry.npmjs.org/long/-/long-4.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
   integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
 
-loose-envify@^1.0.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
-  integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
-  dependencies:
-    js-tokens "^3.0.0 || ^4.0.0"
-
-loud-rejection@^1.0.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
-  integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=
-  dependencies:
-    currently-unhandled "^0.4.1"
-    signal-exit "^3.0.0"
-
-lower-case@^1.1.1:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
-  integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw=
-
 lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
   version "1.0.1"
-  resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
   integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
 
 lowercase-keys@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
   integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
 
-lru-cache@^4.0.1, lru-cache@^4.0.2:
-  version "4.1.5"
-  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
-  integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
-  dependencies:
-    pseudomap "^1.0.2"
-    yallist "^2.1.2"
-
 lru-cache@^6.0.0:
   version "6.0.0"
-  resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
   integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
   dependencies:
     yallist "^4.0.0"
 
-macos-release@^2.2.0:
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.4.1.tgz#64033d0ec6a5e6375155a74b1a1eba8e509820ac"
-  integrity sha512-H/QHeBIN1fIGJX517pvK8IEK53yQOW7YcEI55oYtgjDdoCQQz7eJS94qt5kNrscReEyuD/JcdFCm2XBEcGOITg==
-
-magic-string@^0.22.4:
-  version "0.22.5"
-  resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e"
-  integrity sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==
-  dependencies:
-    vlq "^0.2.2"
-
-make-dir@^1.0.0, make-dir@^1.1.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
-  integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
-  dependencies:
-    pify "^3.0.0"
-
 make-dir@^3.0.0:
   version "3.1.0"
-  resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
   integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
   dependencies:
     semver "^6.0.0"
 
-map-cache@^0.2.2:
-  version "0.2.2"
-  resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
-  integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=
-
-map-obj@^1.0.0, map-obj@^1.0.1:
+map-obj@^1.0.0:
   version "1.0.1"
-  resolved "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
   integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=
 
 map-obj@^4.0.0:
   version "4.2.1"
-  resolved "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz"
+  resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.2.1.tgz#e4ea399dbc979ae735c83c863dd31bdf364277b7"
   integrity sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==
 
-map-visit@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f"
-  integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=
-  dependencies:
-    object-visit "^1.0.0"
-
-matcher@^1.1.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/matcher/-/matcher-1.1.1.tgz#51d8301e138f840982b338b116bb0c09af62c1c2"
-  integrity sha512-+BmqxWIubKTRKNWx/ahnCkk3mG8m7OturVlqq6HiojGJTd5hVYbgZm6WzcYPCoB+KBT4Vd6R7WSRG2OADNaCjg==
-  dependencies:
-    escape-string-regexp "^1.0.4"
-
-math-random@^1.0.1:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c"
-  integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==
-
-md5@^2.2.1:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f"
-  integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==
-  dependencies:
-    charenc "0.0.2"
-    crypt "0.0.2"
-    is-buffer "~1.1.6"
-
-media-typer@0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
-  integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
-
-mem-fs-editor@^5.0.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/mem-fs-editor/-/mem-fs-editor-5.1.0.tgz#51972241640be8567680a04f7adaffe5fc603667"
-  integrity sha512-2Yt2GCYEbcotYbIJagmow4gEtHDqzpq5XN94+yAx/NT5+bGqIjkXnm3KCUQfE6kRfScGp9IZknScoGRKu8L78w==
-  dependencies:
-    commondir "^1.0.1"
-    deep-extend "^0.6.0"
-    ejs "^2.5.9"
-    glob "^7.0.3"
-    globby "^8.0.1"
-    isbinaryfile "^3.0.2"
-    mkdirp "^0.5.0"
-    multimatch "^2.0.0"
-    rimraf "^2.2.8"
-    through2 "^2.0.0"
-    vinyl "^2.0.1"
-
-mem-fs-editor@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/mem-fs-editor/-/mem-fs-editor-6.0.0.tgz#d63607cf0a52fe6963fc376c6a7aa52db3edabab"
-  integrity sha512-e0WfJAMm8Gv1mP5fEq/Blzy6Lt1VbLg7gNnZmZak7nhrBTibs+c6nQ4SKs/ZyJYHS1mFgDJeopsLAv7Ow0FMFg==
-  dependencies:
-    commondir "^1.0.1"
-    deep-extend "^0.6.0"
-    ejs "^2.6.1"
-    glob "^7.1.4"
-    globby "^9.2.0"
-    isbinaryfile "^4.0.0"
-    mkdirp "^0.5.0"
-    multimatch "^4.0.0"
-    rimraf "^2.6.3"
-    through2 "^3.0.1"
-    vinyl "^2.2.0"
-
-mem-fs-editor@^7.0.1:
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/mem-fs-editor/-/mem-fs-editor-7.1.0.tgz#2a16f143228df87bf918874556723a7ee73bfe88"
-  integrity sha512-BH6QEqCXSqGeX48V7zu+e3cMwHU7x640NB8Zk8VNvVZniz+p4FK60pMx/3yfkzo6miI6G3a8pH6z7FeuIzqrzA==
-  dependencies:
-    commondir "^1.0.1"
-    deep-extend "^0.6.0"
-    ejs "^3.1.5"
-    glob "^7.1.4"
-    globby "^9.2.0"
-    isbinaryfile "^4.0.0"
-    mkdirp "^1.0.0"
-    multimatch "^4.0.0"
-    rimraf "^3.0.0"
-    through2 "^3.0.2"
-    vinyl "^2.2.1"
-
-mem-fs@^1.1.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/mem-fs/-/mem-fs-1.2.0.tgz#5f29b2d02a5875cd14cd836c388385892d556cde"
-  integrity sha512-b8g0jWKdl8pM0LqAPdK9i8ERL7nYrzmJfRhxMiWH2uYdfYnb7uXnmwVb0ZGe7xyEl4lj+nLIU3yf4zPUT+XsVQ==
-  dependencies:
-    through2 "^3.0.0"
-    vinyl "^2.0.1"
-    vinyl-file "^3.0.0"
-
-meow@^3.7.0:
-  version "3.7.0"
-  resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
-  integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=
-  dependencies:
-    camelcase-keys "^2.0.0"
-    decamelize "^1.1.2"
-    loud-rejection "^1.0.0"
-    map-obj "^1.0.1"
-    minimist "^1.1.3"
-    normalize-package-data "^2.3.4"
-    object-assign "^4.0.1"
-    read-pkg-up "^1.0.1"
-    redent "^1.0.0"
-    trim-newlines "^1.0.0"
-
 meow@^9.0.0:
   version "9.0.0"
-  resolved "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364"
   integrity sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==
   dependencies:
     "@types/minimist" "^1.2.0"
@@ -6878,145 +1872,39 @@
     type-fest "^0.18.0"
     yargs-parser "^20.2.3"
 
-merge-descriptors@1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
-  integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
-
-merge-stream@^1.0.0, merge-stream@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1"
-  integrity sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=
-  dependencies:
-    readable-stream "^2.0.1"
-
 merge-stream@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
   integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
 
-merge2@^1.2.3, merge2@^1.3.0:
+merge2@^1.3.0:
   version "1.4.1"
-  resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz"
+  resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
   integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
 
-methods@~1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
-  integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
-
-micromatch@^2.1.5, micromatch@^2.3.11, micromatch@^2.3.7:
-  version "2.3.11"
-  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
-  integrity sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=
-  dependencies:
-    arr-diff "^2.0.0"
-    array-unique "^0.2.1"
-    braces "^1.8.2"
-    expand-brackets "^0.1.4"
-    extglob "^0.3.1"
-    filename-regex "^2.0.0"
-    is-extglob "^1.0.0"
-    is-glob "^2.0.1"
-    kind-of "^3.0.2"
-    normalize-path "^2.0.1"
-    object.omit "^2.0.0"
-    parse-glob "^3.0.4"
-    regex-cache "^0.4.2"
-
-micromatch@^3.0.4, micromatch@^3.1.10:
-  version "3.1.10"
-  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
-  integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
-  dependencies:
-    arr-diff "^4.0.0"
-    array-unique "^0.3.2"
-    braces "^2.3.1"
-    define-property "^2.0.2"
-    extend-shallow "^3.0.2"
-    extglob "^2.0.4"
-    fragment-cache "^0.2.1"
-    kind-of "^6.0.2"
-    nanomatch "^1.2.9"
-    object.pick "^1.3.0"
-    regex-not "^1.0.0"
-    snapdragon "^0.8.1"
-    to-regex "^3.0.2"
-
-micromatch@^4.0.2:
-  version "4.0.2"
-  resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz"
-  integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
+micromatch@^4.0.4:
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9"
+  integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==
   dependencies:
     braces "^3.0.1"
-    picomatch "^2.0.5"
-
-mime-db@1.47.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.28.0:
-  version "1.47.0"
-  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c"
-  integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==
-
-mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24:
-  version "2.1.30"
-  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d"
-  integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==
-  dependencies:
-    mime-db "1.47.0"
-
-mime@1.4.1:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
-  integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==
-
-mime@1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
-  integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
-
-mime@^2.3.1:
-  version "2.5.2"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe"
-  integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==
+    picomatch "^2.2.3"
 
 mimic-fn@^2.1.0:
   version "2.1.0"
-  resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
   integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
 
 mimic-response@^1.0.0, mimic-response@^1.0.1:
   version "1.0.1"
-  resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
   integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
 
-mimic-response@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
-  integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
-
 min-indent@^1.0.0:
   version "1.0.1"
-  resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
   integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
 
-minimalistic-assert@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
-  integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
-
-minimatch-all@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/minimatch-all/-/minimatch-all-1.1.0.tgz#40c496a27a2e128d19bf758e76bb01a0c7145787"
-  integrity sha1-QMSWonouEo0Zv3WOdrsBoMcUV4c=
-  dependencies:
-    minimatch "^3.0.2"
-
-"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
-  version "3.0.4"
-  resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz"
-  integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
-  dependencies:
-    brace-expansion "^1.1.7"
-
 minimatch@3.0.3:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774"
@@ -7024,68 +1912,35 @@
   dependencies:
     brace-expansion "^1.0.0"
 
+minimatch@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+  integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
+  dependencies:
+    brace-expansion "^1.1.7"
+
 minimist-options@4.1.0:
   version "4.1.0"
-  resolved "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619"
   integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==
   dependencies:
     arrify "^1.0.1"
     is-plain-obj "^1.1.0"
     kind-of "^6.0.3"
 
-minimist@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.1.tgz#827ba4e7593464e7c221e8c5bed930904ee2c455"
-  integrity sha512-GY8fANSrTMfBVfInqJAY41QkOM+upUTytK1jZ0c8+3HdHrJxBJ3rF5i9moClXTE8uUSnUo8cAsCoxDXvSY4DHg==
-
-minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5:
+minimist@^1.2.0, minimist@^1.2.5:
   version "1.2.5"
-  resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
   integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
 
-mixin-deep@^1.2.0:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
-  integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==
-  dependencies:
-    for-in "^1.0.2"
-    is-extendable "^1.0.1"
-
-mkdirp@^0.5.0, mkdirp@^0.5.1:
-  version "0.5.5"
-  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
-  integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
-  dependencies:
-    minimist "^1.2.5"
-
-mkdirp@^1.0.0, mkdirp@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
-  integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
-
-moment@^2.15.1, moment@^2.24.0:
-  version "2.29.1"
-  resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
-  integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
-
-mout@^1.0.0:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/mout/-/mout-1.2.2.tgz#c9b718a499806a0632cede178e80f436259e777d"
-  integrity sha512-w0OUxFEla6z3d7sVpMZGBCpQvYh8PHS1wZ6Wu9GNKHMpAHWJ0if0LsQZh3DlOqw55HlhJEOMLpFnwtxp99Y5GA==
-
 ms@2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
   integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
 
-ms@2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
-  integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
-
 ms@2.1.2:
   version "2.1.2"
-  resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
   integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
 
 ms@^2.1.1:
@@ -7093,148 +1948,24 @@
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
   integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
 
-multer@^1.3.0:
-  version "1.4.2"
-  resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.2.tgz#2f1f4d12dbaeeba74cb37e623f234bf4d3d2057a"
-  integrity sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==
-  dependencies:
-    append-field "^1.0.0"
-    busboy "^0.2.11"
-    concat-stream "^1.5.2"
-    mkdirp "^0.5.1"
-    object-assign "^4.1.1"
-    on-finished "^2.3.0"
-    type-is "^1.6.4"
-    xtend "^4.0.0"
-
-multimatch@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b"
-  integrity sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=
-  dependencies:
-    array-differ "^1.0.0"
-    array-union "^1.0.1"
-    arrify "^1.0.0"
-    minimatch "^3.0.0"
-
-multimatch@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-4.0.0.tgz#8c3c0f6e3e8449ada0af3dd29efb491a375191b3"
-  integrity sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==
-  dependencies:
-    "@types/minimatch" "^3.0.3"
-    array-differ "^3.0.0"
-    array-union "^2.1.0"
-    arrify "^2.0.1"
-    minimatch "^3.0.4"
-
-multipipe@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-1.0.2.tgz#cc13efd833c9cda99f224f868461b8e1a3fd939d"
-  integrity sha1-zBPv2DPJzamfIk+GhGG44aP9k50=
-  dependencies:
-    duplexer2 "^0.1.2"
-    object-assign "^4.1.0"
-
-mute-stream@0.0.6:
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db"
-  integrity sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=
-
 mute-stream@0.0.8:
   version "0.0.8"
-  resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz"
+  resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
   integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
 
-mz@^2.4.0, mz@^2.6.0:
-  version "2.7.0"
-  resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
-  integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
-  dependencies:
-    any-promise "^1.0.0"
-    object-assign "^4.0.1"
-    thenify-all "^1.0.0"
-
-nan@^2.12.1:
-  version "2.14.2"
-  resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
-  integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
-
-nanomatch@^1.2.9:
-  version "1.2.13"
-  resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
-  integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==
-  dependencies:
-    arr-diff "^4.0.0"
-    array-unique "^0.3.2"
-    define-property "^2.0.2"
-    extend-shallow "^3.0.2"
-    fragment-cache "^0.2.1"
-    is-windows "^1.0.2"
-    kind-of "^6.0.2"
-    object.pick "^1.3.0"
-    regex-not "^1.0.0"
-    snapdragon "^0.8.1"
-    to-regex "^3.0.1"
-
-native-promise-only@^0.8.1:
-  version "0.8.1"
-  resolved "https://registry.yarnpkg.com/native-promise-only/-/native-promise-only-0.8.1.tgz#20a318c30cb45f71fe7adfbf7b21c99c1472ef11"
-  integrity sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=
-
 natural-compare@^1.4.0:
   version "1.4.0"
-  resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
+  resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
   integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
 
 ncp@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
   integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=
 
-negotiator@0.6.2:
-  version "0.6.2"
-  resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
-  integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
-
-nice-try@^1.0.4:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
-  integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
-
-no-case@^2.2.0:
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac"
-  integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==
-  dependencies:
-    lower-case "^1.1.1"
-
-node-fetch@^2.6.0, node-fetch@^2.6.1:
-  version "2.6.1"
-  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
-  integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
-
-node-releases@^1.1.70:
-  version "1.1.71"
-  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb"
-  integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==
-
-node-status-codes@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f"
-  integrity sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=
-
-nomnom@^1.8.1:
-  version "1.8.1"
-  resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.8.1.tgz#2151f722472ba79e50a76fc125bb8c8f2e4dc2a7"
-  integrity sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=
-  dependencies:
-    chalk "~0.4.0"
-    underscore "~1.6.0"
-
-normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.5.0:
+normalize-package-data@^2.3.2, normalize-package-data@^2.5.0:
   version "2.5.0"
-  resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz"
+  resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
   integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
   dependencies:
     hosted-git-info "^2.1.4"
@@ -7243,54 +1974,23 @@
     validate-npm-package-license "^3.0.1"
 
 normalize-package-data@^3.0.0:
-  version "3.0.2"
-  resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.2.tgz"
-  integrity sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e"
+  integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==
   dependencies:
     hosted-git-info "^4.0.1"
-    resolve "^1.20.0"
+    is-core-module "^2.5.0"
     semver "^7.3.4"
     validate-npm-package-license "^3.0.1"
 
-normalize-path@^2.0.0, normalize-path@^2.0.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
-  integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=
-  dependencies:
-    remove-trailing-separator "^1.0.1"
-
-normalize-path@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
-  integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
-
 normalize-url@^4.1.0:
-  version "4.5.0"
-  resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz"
-  integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==
+  version "4.5.1"
+  resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
+  integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
 
-npm-api@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/npm-api/-/npm-api-1.0.1.tgz#3def9b51afedca57db14ca0c970d92442d21c9c5"
-  integrity sha512-4sITrrzEbPcr0aNV28QyOmgn6C9yKiF8k92jn4buYAK8wmA5xo1qL3II5/gT1r7wxbXBflSduZ2K3FbtOrtGkA==
-  dependencies:
-    JSONStream "^1.3.5"
-    clone-deep "^4.0.1"
-    download-stats "^0.3.4"
-    moment "^2.24.0"
-    node-fetch "^2.6.0"
-    paged-request "^2.0.1"
-
-npm-run-path@^2.0.0:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
-  integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=
-  dependencies:
-    path-key "^2.0.0"
-
-npm-run-path@^4.0.0, npm-run-path@^4.0.1:
+npm-run-path@^4.0.1:
   version "4.0.1"
-  resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
   integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
   dependencies:
     path-key "^3.0.0"
@@ -7302,50 +2002,19 @@
   dependencies:
     boolbase "~1.0.0"
 
-number-is-nan@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
-  integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
-
-oauth-sign@~0.9.0:
-  version "0.9.0"
-  resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
-  integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
-
-object-assign@^4, object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
-  integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
-
-object-copy@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
-  integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw=
-  dependencies:
-    copy-descriptor "^0.1.0"
-    define-property "^0.2.5"
-    kind-of "^3.0.3"
-
-object-inspect@^1.9.0:
-  version "1.9.0"
-  resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz"
-  integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==
+object-inspect@^1.11.0, object-inspect@^1.9.0:
+  version "1.11.0"
+  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"
+  integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==
 
 object-keys@^1.0.12, object-keys@^1.1.1:
   version "1.1.1"
-  resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz"
+  resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
   integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
 
-object-visit@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
-  integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=
-  dependencies:
-    isobject "^3.0.0"
-
-object.assign@^4.1.0, object.assign@^4.1.2:
+object.assign@^4.1.2:
   version "4.1.2"
-  resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz"
+  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
   integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
   dependencies:
     call-bind "^1.0.0"
@@ -7353,89 +2022,32 @@
     has-symbols "^1.0.1"
     object-keys "^1.1.1"
 
-object.omit@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
-  integrity sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=
-  dependencies:
-    for-own "^0.1.4"
-    is-extendable "^0.1.1"
-
-object.pick@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
-  integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=
-  dependencies:
-    isobject "^3.0.1"
-
-object.values@^1.1.1:
-  version "1.1.3"
-  resolved "https://registry.npmjs.org/object.values/-/object.values-1.1.3.tgz"
-  integrity sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw==
+object.values@^1.1.4:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.4.tgz#0d273762833e816b693a637d30073e7051535b30"
+  integrity sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==
   dependencies:
     call-bind "^1.0.2"
     define-properties "^1.1.3"
-    es-abstract "^1.18.0-next.2"
-    has "^1.0.3"
-
-obuf@^1.0.0, obuf@^1.1.1:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
-  integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
-
-octokit-pagination-methods@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4"
-  integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==
-
-on-finished@^2.3.0, on-finished@~2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
-  integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
-  dependencies:
-    ee-first "1.1.1"
-
-on-headers@~1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
-  integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
+    es-abstract "^1.18.2"
 
 once@^1.3.0, once@^1.3.1, once@^1.4.0:
   version "1.4.0"
-  resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
+  resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
   integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
   dependencies:
     wrappy "1"
 
-one-time@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45"
-  integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==
-  dependencies:
-    fn.name "1.x.x"
-
-onetime@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
-  integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=
-
 onetime@^5.1.0, onetime@^5.1.2:
   version "5.1.2"
-  resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz"
+  resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
   integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
   dependencies:
     mimic-fn "^2.1.0"
 
-opn@^3.0.2:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/opn/-/opn-3.0.3.tgz#b6d99e7399f78d65c3baaffef1fb288e9b85243a"
-  integrity sha1-ttmec5n3jWXDuq/+8fsojpuFJDo=
-  dependencies:
-    object-assign "^4.0.1"
-
 optionator@^0.9.1:
   version "0.9.1"
-  resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz"
+  resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
   integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==
   dependencies:
     deep-is "^0.1.3"
@@ -7445,145 +2057,57 @@
     type-check "^0.4.0"
     word-wrap "^1.2.3"
 
-ordered-read-streams@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b"
-  integrity sha1-cTfmmzKYuzQiR6G77jiByA4v14s=
-  dependencies:
-    is-stream "^1.0.1"
-    readable-stream "^2.0.1"
-
-os-homedir@^1.0.0, os-homedir@^1.0.1:
+os-tmpdir@~1.0.2:
   version "1.0.2"
-  resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
-  integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
-
-os-name@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801"
-  integrity sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==
-  dependencies:
-    macos-release "^2.2.0"
-    windows-release "^3.1.0"
-
-os-shim@^0.1.2:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917"
-  integrity sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=
-
-os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2:
-  version "1.0.2"
-  resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz"
+  resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
   integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
 
-osenv@^0.1.0, osenv@^0.1.3:
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"
-  integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==
-  dependencies:
-    os-homedir "^1.0.0"
-    os-tmpdir "^1.0.0"
-
-p-cancelable@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa"
-  integrity sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==
-
 p-cancelable@^1.0.0:
   version "1.1.0"
-  resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
   integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
 
-p-cancelable@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.0.tgz#4d51c3b91f483d02a0d300765321fca393d758dd"
-  integrity sha512-HAZyB3ZodPo+BDpb4/Iu7Jv4P6cSazBz9ZM0ChhEXp70scx834aWCEjQRwgt41UzzejUAPdbqqONfRWTPYrPAQ==
-
-p-finally@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
-  integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
-
 p-limit@^1.1.0:
   version "1.3.0"
-  resolved "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
   integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==
   dependencies:
     p-try "^1.0.0"
 
-p-limit@^2.0.0, p-limit@^2.2.0:
+p-limit@^2.2.0:
   version "2.3.0"
-  resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
   integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
   dependencies:
     p-try "^2.0.0"
 
 p-locate@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
   integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=
   dependencies:
     p-limit "^1.1.0"
 
-p-locate@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
-  integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
-  dependencies:
-    p-limit "^2.0.0"
-
 p-locate@^4.1.0:
   version "4.1.0"
-  resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
   integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
   dependencies:
     p-limit "^2.2.0"
 
-p-map@^1.1.1:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
-  integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==
-
-p-timeout@^1.1.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386"
-  integrity sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=
-  dependencies:
-    p-finally "^1.0.0"
-
 p-try@^1.0.0:
   version "1.0.0"
-  resolved "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
   integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
 
-p-try@^2.0.0, p-try@^2.1.0:
+p-try@^2.0.0:
   version "2.2.0"
-  resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
   integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
 
-package-json@^2.0.0:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/package-json/-/package-json-2.4.0.tgz#0d15bd67d1cbbddbb2ca222ff2edb86bcb31a8bb"
-  integrity sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=
-  dependencies:
-    got "^5.0.0"
-    registry-auth-token "^3.0.1"
-    registry-url "^3.0.3"
-    semver "^5.1.0"
-
-package-json@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed"
-  integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=
-  dependencies:
-    got "^6.7.1"
-    registry-auth-token "^3.0.1"
-    registry-url "^3.0.3"
-    semver "^5.1.0"
-
 package-json@^6.3.0:
   version "6.5.0"
-  resolved "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz"
+  resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0"
   integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==
   dependencies:
     got "^9.6.0"
@@ -7591,49 +2115,13 @@
     registry-url "^5.0.0"
     semver "^6.2.0"
 
-paged-request@^2.0.1:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/paged-request/-/paged-request-2.0.2.tgz#4d621a08b8d6bee4440a0a92112354eeece5b5b0"
-  integrity sha512-NWrGqneZImDdcMU/7vMcAOo1bIi5h/pmpJqe7/jdsy85BA/s5MSaU/KlpxwW/IVPmIwBcq2uKPrBWWhEWhtxag==
-  dependencies:
-    axios "^0.21.1"
-
-pako@~0.2.0:
-  version "0.2.9"
-  resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
-  integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=
-
-param-case@2.1.x:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247"
-  integrity sha1-35T9jPZTHs915r75oIWPvHK+Ikc=
-  dependencies:
-    no-case "^2.2.0"
-
 parent-module@^1.0.0:
   version "1.0.1"
-  resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
   integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
   dependencies:
     callsites "^3.0.0"
 
-parse-glob@^3.0.4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
-  integrity sha1-ssN2z7EfNVE7rdFz7wu246OIORw=
-  dependencies:
-    glob-base "^0.3.0"
-    is-dotfile "^1.0.0"
-    is-extglob "^1.0.0"
-    is-glob "^2.0.0"
-
-parse-json@^2.1.0, parse-json@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz"
-  integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=
-  dependencies:
-    error-ex "^1.2.0"
-
 parse-json@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
@@ -7644,7 +2132,7 @@
 
 parse-json@^5.0.0:
   version "5.2.0"
-  resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
   integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
   dependencies:
     "@babel/code-frame" "^7.0.0"
@@ -7652,11 +2140,6 @@
     json-parse-even-better-errors "^2.3.0"
     lines-and-columns "^1.1.6"
 
-parse-passwd@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
-  integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=
-
 parse5@^3.0.1:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c"
@@ -7664,105 +2147,30 @@
   dependencies:
     "@types/node" "*"
 
-parse5@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
-  integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==
-
-parseqs@0.0.6:
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5"
-  integrity sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==
-
-parseuri@0.0.6:
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.6.tgz#e1496e829e3ac2ff47f39a4dd044b32823c4a25a"
-  integrity sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==
-
-parseurl@~1.3.3:
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
-  integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
-
-pascalcase@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
-  integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
-
-path-dirname@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
-  integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=
-
-path-exists@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
-  integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=
-  dependencies:
-    pinkie-promise "^2.0.0"
-
 path-exists@^3.0.0:
   version "3.0.0"
-  resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
   integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
 
 path-exists@^4.0.0:
   version "4.0.0"
-  resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
   integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
 
 path-is-absolute@^1.0.0:
   version "1.0.1"
-  resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
   integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
 
-path-is-inside@^1.0.1, path-is-inside@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
-  integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
-
-path-key@^2.0.0, path-key@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
-  integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
-
 path-key@^3.0.0, path-key@^3.1.0:
   version "3.1.1"
-  resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz"
+  resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
   integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
 
 path-parse@^1.0.6:
-  version "1.0.6"
-  resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz"
-  integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
-
-path-to-regexp@0.1.7:
-  version "0.1.7"
-  resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
-  integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
-
-path-to-regexp@^1.0.1, path-to-regexp@^1.7.0:
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
-  integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
-  dependencies:
-    isarray "0.0.1"
-
-path-type@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
-  integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=
-  dependencies:
-    graceful-fs "^4.1.2"
-    pify "^2.0.0"
-    pinkie-promise "^2.0.0"
-
-path-type@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz"
-  integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=
-  dependencies:
-    pify "^2.0.0"
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
+  integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
 
 path-type@^3.0.0:
   version "3.0.0"
@@ -7773,389 +2181,46 @@
 
 path-type@^4.0.0:
   version "4.0.0"
-  resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
   integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
 
-peek-stream@^1.1.0:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/peek-stream/-/peek-stream-1.1.3.tgz#3b35d84b7ccbbd262fff31dc10da56856ead6d67"
-  integrity sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==
-  dependencies:
-    buffer-from "^1.0.0"
-    duplexify "^3.5.0"
-    through2 "^2.0.3"
-
-pem@^1.8.3:
-  version "1.14.4"
-  resolved "https://registry.yarnpkg.com/pem/-/pem-1.14.4.tgz#a68c70c6e751ccc5b3b5bcd7af78b0aec1177ff9"
-  integrity sha512-v8lH3NpirgiEmbOqhx0vwQTxwi0ExsiWBGYh0jYNq7K6mQuO4gI6UEFlr6fLAdv9TPXRt6GqiwE37puQdIDS8g==
-  dependencies:
-    es6-promisify "^6.0.0"
-    md5 "^2.2.1"
-    os-tmpdir "^1.0.1"
-    which "^2.0.2"
-
-pend@~1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
-  integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
-
-performance-now@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
-  integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
-
-picomatch@^2.0.5, picomatch@^2.2.1:
-  version "2.2.2"
-  resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz"
-  integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
-
-pify@^2.0.0, pify@^2.3.0:
+picomatch@^2.2.3:
   version "2.3.0"
-  resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz"
-  integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
+  resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
+  integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
 
 pify@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
   integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
 
-pify@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
-  integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
-
-pinkie-promise@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
-  integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o=
-  dependencies:
-    pinkie "^2.0.0"
-
-pinkie@^2.0.0:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
-  integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
-
 pkg-dir@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
   integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=
   dependencies:
     find-up "^2.1.0"
 
-plist@^2.0.1:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/plist/-/plist-2.1.0.tgz#57ccdb7a0821df21831217a3cad54e3e146a1025"
-  integrity sha1-V8zbeggh3yGDEhejytVOPhRqECU=
+pkg-up@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f"
+  integrity sha1-yBmscoBZpGHKscOImivjxJoATX8=
   dependencies:
-    base64-js "1.2.0"
-    xmlbuilder "8.2.2"
-    xmldom "0.1.x"
-
-plylog@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/plylog/-/plylog-1.1.0.tgz#f6f354e2ae0b01f6db4ed111f4b3855da9c37417"
-  integrity sha512-/QnY5aSVaP54va6hruzNtAj02HpsLlAt7V5EndMrtq6ZUTZJKUja43rgiUtGXqm95yrSJjbZoPW0yQQQwLpoJA==
-  dependencies:
-    logform "^1.9.1"
-    winston "^3.0.0"
-    winston-transport "^4.2.0"
-
-polymer-analyzer@^3.0.0, polymer-analyzer@^3.1.3, polymer-analyzer@^3.2.2:
-  version "3.2.4"
-  resolved "https://registry.yarnpkg.com/polymer-analyzer/-/polymer-analyzer-3.2.4.tgz#7d76356620a2328e8bc9e30e47069f9729260ca1"
-  integrity sha512-JmxUhMajTuC18tLXbTtu2+aN2x9bTX+4MvCD4IZKJ0rtAL8jWi1iRLfogpHJB4Ig9Dc8EEEuEYipLuzPFl3vqA==
-  dependencies:
-    "@babel/generator" "^7.0.0-beta.42"
-    "@babel/traverse" "^7.0.0-beta.42"
-    "@babel/types" "^7.0.0-beta.42"
-    "@types/babel-generator" "^6.25.1"
-    "@types/babel-traverse" "^6.25.2"
-    "@types/babel-types" "^6.25.1"
-    "@types/babylon" "^6.16.2"
-    "@types/chai-subset" "^1.3.0"
-    "@types/chalk" "^0.4.30"
-    "@types/clone" "^0.1.30"
-    "@types/cssbeautify" "^0.3.1"
-    "@types/doctrine" "^0.0.1"
-    "@types/is-windows" "^0.2.0"
-    "@types/minimatch" "^3.0.1"
-    "@types/parse5" "^2.2.34"
-    "@types/path-is-inside" "^1.0.0"
-    "@types/resolve" "0.0.6"
-    "@types/whatwg-url" "^6.4.0"
-    babylon "^7.0.0-beta.42"
-    cancel-token "^0.1.1"
-    chalk "^1.1.3"
-    clone "^2.0.0"
-    cssbeautify "^0.3.1"
-    doctrine "^2.0.2"
-    dom5 "^3.0.0"
-    indent "0.0.2"
-    is-windows "^1.0.2"
-    jsonschema "^1.1.0"
-    minimatch "^3.0.4"
-    parse5 "^4.0.0"
-    path-is-inside "^1.0.2"
-    resolve "^1.5.0"
-    shady-css-parser "^0.1.0"
-    stable "^0.1.6"
-    strip-indent "^2.0.0"
-    vscode-uri "=1.0.6"
-    whatwg-url "^6.4.0"
-
-polymer-build@^3.1.0, polymer-build@^3.1.4:
-  version "3.1.4"
-  resolved "https://registry.yarnpkg.com/polymer-build/-/polymer-build-3.1.4.tgz#ab539f1a13d803518b13b73ffd09198431d98142"
-  integrity sha512-OhTOPG5Y/tK2HqGZ5XA/CVDh+TuOaDv7wTZWXDCg6hxeMgNKuljDMn2coyGU5NLM0pLbS+gwFAc2ZJ5cWHCHNg==
-  dependencies:
-    "@babel/core" "^7.0.0"
-    "@babel/plugin-external-helpers" "^7.0.0"
-    "@babel/plugin-proposal-async-generator-functions" "^7.0.0"
-    "@babel/plugin-proposal-object-rest-spread" "^7.0.0"
-    "@babel/plugin-syntax-async-generators" "^7.0.0"
-    "@babel/plugin-syntax-dynamic-import" "^7.0.0"
-    "@babel/plugin-syntax-import-meta" "^7.0.0"
-    "@babel/plugin-syntax-object-rest-spread" "^7.0.0"
-    "@babel/plugin-transform-arrow-functions" "^7.0.0"
-    "@babel/plugin-transform-async-to-generator" "^7.0.0"
-    "@babel/plugin-transform-block-scoped-functions" "^7.0.0"
-    "@babel/plugin-transform-block-scoping" "^7.0.0"
-    "@babel/plugin-transform-classes" "^7.0.0"
-    "@babel/plugin-transform-computed-properties" "^7.0.0"
-    "@babel/plugin-transform-destructuring" "^7.0.0"
-    "@babel/plugin-transform-duplicate-keys" "^7.0.0"
-    "@babel/plugin-transform-exponentiation-operator" "^7.0.0"
-    "@babel/plugin-transform-for-of" "^7.0.0"
-    "@babel/plugin-transform-function-name" "^7.0.0"
-    "@babel/plugin-transform-instanceof" "^7.0.0"
-    "@babel/plugin-transform-literals" "^7.0.0"
-    "@babel/plugin-transform-modules-amd" "^7.0.0"
-    "@babel/plugin-transform-object-super" "^7.0.0"
-    "@babel/plugin-transform-parameters" "^7.0.0"
-    "@babel/plugin-transform-regenerator" "^7.0.0"
-    "@babel/plugin-transform-shorthand-properties" "^7.0.0"
-    "@babel/plugin-transform-spread" "^7.0.0"
-    "@babel/plugin-transform-sticky-regex" "^7.0.0"
-    "@babel/plugin-transform-template-literals" "^7.0.0"
-    "@babel/plugin-transform-typeof-symbol" "^7.0.0"
-    "@babel/plugin-transform-unicode-regex" "^7.0.0"
-    "@babel/traverse" "^7.0.0"
-    "@polymer/esm-amd-loader" "^1.0.0"
-    "@types/babel-types" "^6.25.1"
-    "@types/babylon" "^6.16.2"
-    "@types/gulp-if" "0.0.33"
-    "@types/html-minifier" "^3.5.1"
-    "@types/is-windows" "^0.2.0"
-    "@types/mz" "0.0.31"
-    "@types/parse5" "^2.2.34"
-    "@types/resolve" "0.0.7"
-    "@types/uuid" "^3.4.3"
-    "@types/vinyl" "^2.0.0"
-    "@types/vinyl-fs" "^2.4.8"
-    babel-plugin-minify-guarded-expressions "^0.4.3"
-    babel-preset-minify "^0.5.0"
-    babylon "^7.0.0-beta.42"
-    css-slam "^2.1.2"
-    dom5 "^3.0.0"
-    gulp-if "^2.0.2"
-    html-minifier "^3.5.10"
-    matcher "^1.1.0"
-    multipipe "^1.0.2"
-    mz "^2.6.0"
-    parse5 "^4.0.0"
-    plylog "^1.0.0"
-    polymer-analyzer "^3.1.3"
-    polymer-bundler "^4.0.9"
-    polymer-project-config "^4.0.3"
-    regenerator-runtime "^0.11.1"
-    stream "0.0.2"
-    sw-precache "^5.1.1"
-    uuid "^3.2.1"
-    vinyl "^1.2.0"
-    vinyl-fs "^2.4.4"
-
-polymer-bundler@^4.0.9:
-  version "4.0.10"
-  resolved "https://registry.yarnpkg.com/polymer-bundler/-/polymer-bundler-4.0.10.tgz#abc8d33977652f031068d034c8104841e80d4cbb"
-  integrity sha512-nwlN3LQlQDqbZ2sUH3394C/dHZUDHq8tpdS5HARvPDb0Q9IXWD+znOR1cr7wSjF0EZN4LiUH5hWyUoV4QSjhpQ==
-  dependencies:
-    "@types/babel-generator" "^6.25.1"
-    "@types/babel-traverse" "^6.25.3"
-    babel-generator "^6.26.1"
-    babel-traverse "^6.26.0"
-    babel-types "^6.26.0"
-    clone "^2.1.0"
-    command-line-args "^5.0.2"
-    command-line-usage "^5.0.5"
-    dom5 "^3.0.0"
-    espree "^3.5.2"
-    magic-string "^0.22.4"
-    mkdirp "^0.5.1"
-    parse5 "^4.0.0"
-    polymer-analyzer "^3.2.2"
-    rollup "^1.3.0"
-    source-map "^0.5.6"
-    vscode-uri "=1.0.6"
-
-polymer-cli@^1.9.11:
-  version "1.9.11"
-  resolved "https://registry.npmjs.org/polymer-cli/-/polymer-cli-1.9.11.tgz"
-  integrity sha512-tiURjHDCOUUtDVPuVYvrfFI9PXe4OOUmBbn6Sg5GJNQ2POtP7r7hv+I5yI8P9qsxmalHTa19chVtf5/t9IBXDg==
-  dependencies:
-    "@octokit/rest" "^16.2.0"
-    "@types/chalk" "^2.2.0"
-    "@types/del" "^3.0.0"
-    "@types/findup-sync" "^0.3.29"
-    "@types/globby" "^6.1.0"
-    "@types/inquirer" "0.0.32"
-    "@types/merge-stream" "^1.0.28"
-    "@types/mz" "^0.0.31"
-    "@types/request" "2.0.3"
-    "@types/resolve" "0.0.4"
-    "@types/rimraf" "^0.0.28"
-    "@types/semver" "^5.3.30"
-    "@types/temp" "^0.8.28"
-    "@types/update-notifier" "^1.0.0"
-    "@types/vinyl" "^2.0.0"
-    "@types/vinyl-fs" "0.0.28"
-    "@types/yeoman-generator" "^2.0.3"
-    bower "^1.8.8"
-    bower-json "^0.8.1"
-    bower-logger "^0.2.2"
-    chalk "^2.4.2"
-    chokidar "^1.7.0"
-    command-line-args "^5.0.2"
-    command-line-commands "^2.0.1"
-    command-line-usage "^5.0.5"
-    del "^3.0.0"
-    findup-sync "^0.4.2"
-    globby "^8.0.1"
-    gunzip-maybe "^1.3.1"
-    inquirer "^1.0.2"
-    merge-stream "^1.0.1"
-    mz "^2.6.0"
-    plylog "^1.0.0"
-    polymer-analyzer "^3.2.2"
-    polymer-build "^3.1.4"
-    polymer-bundler "^4.0.9"
-    polymer-linter "^3.0.0"
-    polymer-project-config "^4.0.3"
-    polyserve "^0.27.15"
-    request "^2.72.0"
-    rimraf "^2.6.1"
-    semver "^5.3.0"
-    tar-fs "^1.12.0"
-    temp "^0.8.3"
-    update-notifier "^1.0.0"
-    validate-element-name "^2.1.1"
-    vinyl "^1.1.1"
-    vinyl-fs "^2.4.3"
-    web-component-tester "^6.9.0"
-    yeoman-environment "^1.5.2"
-    yeoman-generator "^3.1.1"
-
-polymer-linter@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/polymer-linter/-/polymer-linter-3.0.1.tgz#8804e1705fa2a7c263467b8a22da11bb764ee26b"
-  integrity sha512-eDh2CeswZz4Rwf8gfYXpMN66pieq4qJvP9bH3m39LLGm81hRePo4N5OHoQzR5unen1PUdmtjDv0Iicz3dTYEZQ==
-  dependencies:
-    "@types/fast-levenshtein" "0.0.1"
-    "@types/parse5" "^2.2.34"
-    babel-traverse "^6.26.0"
-    babel-types "^6.26.0"
-    cancel-token "^0.1.1"
-    css-what "^2.1.0"
-    dom5 "^3.0.0"
-    fast-levenshtein "^2.0.6"
-    parse5 "^4.0.0"
-    polymer-analyzer "^3.0.0"
-    shady-css-parser "^0.1.0"
-    stable "^0.1.6"
-    strip-indent "^2.0.0"
-    validate-element-name "^2.1.1"
-
-polymer-project-config@^4.0.0, polymer-project-config@^4.0.3:
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/polymer-project-config/-/polymer-project-config-4.0.3.tgz#ef0c1a676ce4809907986c8e910745660de8024f"
-  integrity sha512-Drr+Imq+znhBC8XSt9pMlmPixoGnIOmleV5SD6mto1zOGC5oCDbSNsQL2v89DWOk+9aSUO79vnWwOmEPDSvYfw==
-  dependencies:
-    "@types/parse5" "^2.2.34"
-    browser-capabilities "^1.0.0"
-    jsonschema "^1.1.1"
-    minimatch-all "^1.1.0"
-    plylog "^1.0.0"
-    winston "^3.0.0"
-
-polyserve@^0.27.13, polyserve@^0.27.15:
-  version "0.27.15"
-  resolved "https://registry.yarnpkg.com/polyserve/-/polyserve-0.27.15.tgz#261fa5a0873c8d95fd7068598f44c9dac20cf9c4"
-  integrity sha512-AaFgANt+tUUVgHLw+BnaVYcn649JiwL1ru0TOZUKj1gGGn/Bq2S16gxql+1muGpRaAsgFu13Zu7k5XkwatwwSg==
-  dependencies:
-    "@types/compression" "^0.0.33"
-    "@types/content-type" "^1.1.0"
-    "@types/escape-html" "0.0.20"
-    "@types/express" "^4.0.36"
-    "@types/mime" "^2.0.0"
-    "@types/mz" "0.0.29"
-    "@types/opn" "^3.0.28"
-    "@types/parse5" "^2.2.34"
-    "@types/pem" "^1.8.1"
-    "@types/resolve" "0.0.6"
-    "@types/serve-static" "^1.7.31"
-    "@types/spdy" "^3.4.1"
-    bower-config "^1.4.1"
-    browser-capabilities "^1.0.0"
-    command-line-args "^5.0.2"
-    command-line-usage "^5.0.5"
-    compression "^1.6.2"
-    content-type "^1.0.2"
-    cors "^2.8.4"
-    escape-html "^1.0.3"
-    express "^4.8.5"
-    find-port "^1.0.1"
-    http-proxy-middleware "^0.17.2"
-    lru-cache "^4.0.2"
-    mime "^2.3.1"
-    mz "^2.4.0"
-    opn "^3.0.2"
-    pem "^1.8.3"
-    polymer-build "^3.1.0"
-    polymer-project-config "^4.0.0"
-    requirejs "^2.3.4"
-    resolve "^1.5.0"
-    send "^0.16.2"
-    spdy "^3.3.3"
-
-posix-character-classes@^0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
-  integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
+    find-up "^2.1.0"
 
 prelude-ls@^1.2.1:
   version "1.2.1"
-  resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
+  resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
   integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
 
-prepend-http@^1.0.1:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
-  integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
-
 prepend-http@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
   integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
 
-preserve@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
-  integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=
-
 prettier-linter-helpers@^1.0.0:
   version "1.0.0"
-  resolved "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
   integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
   dependencies:
     fast-diff "^1.1.2"
@@ -8166,33 +2231,18 @@
   integrity sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==
 
 prettier@^2.1.2:
-  version "2.2.1"
-  resolved "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz"
-  integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d"
+  integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==
 
-pretty-bytes@^4.0.2:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9"
-  integrity sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=
-
-pretty-bytes@^5.1.0, pretty-bytes@^5.2.0:
-  version "5.6.0"
-  resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
-  integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==
-
-process-nextick-args@^2.0.0, process-nextick-args@~2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
-  integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
-
-progress@2.0.3, progress@^2.0.0:
+progress@^2.0.0:
   version "2.0.3"
-  resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz"
+  resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
   integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
 
 protobufjs@6.8.8:
   version "6.8.8"
-  resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz"
+  resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.8.tgz#c8b4f1282fd7a90e6f5b109ed11c84af82908e7c"
   integrity sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==
   dependencies:
     "@protobufjs/aspromise" "^1.1.2"
@@ -8209,131 +2259,39 @@
     "@types/node" "^10.1.0"
     long "^4.0.0"
 
-proxy-addr@~2.0.5:
-  version "2.0.6"
-  resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
-  integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==
-  dependencies:
-    forwarded "~0.1.2"
-    ipaddr.js "1.9.1"
-
-pseudomap@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
-  integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
-
-psl@^1.1.24, psl@^1.1.28:
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
-  integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
-
-pump@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954"
-  integrity sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==
-  dependencies:
-    end-of-stream "^1.1.0"
-    once "^1.3.1"
-
-pump@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
-  integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==
-  dependencies:
-    end-of-stream "^1.1.0"
-    once "^1.3.1"
-
 pump@^3.0.0:
   version "3.0.0"
-  resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
   integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
   dependencies:
     end-of-stream "^1.1.0"
     once "^1.3.1"
 
-pumpify@^1.3.3:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce"
-  integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==
-  dependencies:
-    duplexify "^3.6.0"
-    inherits "^2.0.3"
-    pump "^2.0.0"
-
-punycode@^1.4.1:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
-  integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
-
-punycode@^2.1.0, punycode@^2.1.1:
+punycode@^2.1.0:
   version "2.1.1"
-  resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz"
+  resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
   integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
 
 pupa@^2.1.1:
   version "2.1.1"
-  resolved "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz"
+  resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62"
   integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==
   dependencies:
     escape-goat "^2.0.0"
 
-q@^1.4.1, q@^1.5.1:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
-  integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
-
-qs@6.7.0:
-  version "6.7.0"
-  resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
-  integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
-
-qs@~6.5.2:
-  version "6.5.2"
-  resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
-  integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
-
 queue-microtask@^1.2.2:
   version "1.2.3"
-  resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
+  resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
   integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
 
 quick-lru@^4.0.1:
   version "4.0.1"
-  resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
   integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
 
-quick-lru@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
-  integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
-
-randomatic@^3.0.0:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed"
-  integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==
-  dependencies:
-    is-number "^4.0.0"
-    kind-of "^6.0.0"
-    math-random "^1.0.1"
-
-range-parser@~1.2.0, range-parser@~1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
-  integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
-
-raw-body@2.4.0:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
-  integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
-  dependencies:
-    bytes "3.1.0"
-    http-errors "1.7.2"
-    iconv-lite "0.4.24"
-    unpipe "1.0.0"
-
-rc@^1.0.1, rc@^1.1.6, rc@^1.2.8:
+rc@^1.2.8:
   version "1.2.8"
-  resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz"
+  resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
   integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
   dependencies:
     deep-extend "^0.6.0"
@@ -8341,81 +2299,23 @@
     minimist "^1.2.0"
     strip-json-comments "~2.0.1"
 
-read-all-stream@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa"
-  integrity sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=
-  dependencies:
-    pinkie-promise "^2.0.0"
-    readable-stream "^2.0.0"
-
-read-chunk@^3.0.0, read-chunk@^3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-3.2.0.tgz#2984afe78ca9bfbbdb74b19387bf9e86289c16ca"
-  integrity sha512-CEjy9LCzhmD7nUpJ1oVOE6s/hBkejlcJEgLQHVnQznOSilOPb+kpKktlLfFDK3/WP43+F80xkUTM2VOkYoSYvQ==
-  dependencies:
-    pify "^4.0.1"
-    with-open-file "^0.1.6"
-
-read-pkg-up@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
-  integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=
-  dependencies:
-    find-up "^1.0.0"
-    read-pkg "^1.0.0"
-
-read-pkg-up@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz"
-  integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=
+read-pkg-up@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07"
+  integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=
   dependencies:
     find-up "^2.0.0"
-    read-pkg "^2.0.0"
-
-read-pkg-up@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978"
-  integrity sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==
-  dependencies:
-    find-up "^3.0.0"
     read-pkg "^3.0.0"
 
-read-pkg-up@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-5.0.0.tgz#b6a6741cb144ed3610554f40162aa07a6db621b8"
-  integrity sha512-XBQjqOBtTzyol2CpsQOw8LHV0XbDZVG7xMMjmXAJomlVY03WOBRmYgDJETlvcg0H63AJvPRwT7GFi5rvOzUOKg==
-  dependencies:
-    find-up "^3.0.0"
-    read-pkg "^5.0.0"
-
 read-pkg-up@^7.0.1:
   version "7.0.1"
-  resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507"
   integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==
   dependencies:
     find-up "^4.1.0"
     read-pkg "^5.2.0"
     type-fest "^0.8.1"
 
-read-pkg@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
-  integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=
-  dependencies:
-    load-json-file "^1.0.0"
-    normalize-package-data "^2.3.2"
-    path-type "^1.0.0"
-
-read-pkg@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz"
-  integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=
-  dependencies:
-    load-json-file "^2.0.0"
-    normalize-package-data "^2.3.2"
-    path-type "^2.0.0"
-
 read-pkg@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
@@ -8425,9 +2325,9 @@
     normalize-package-data "^2.3.2"
     path-type "^3.0.0"
 
-read-pkg@^5.0.0, read-pkg@^5.2.0:
+read-pkg@^5.2.0:
   version "5.2.0"
-  resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc"
   integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==
   dependencies:
     "@types/normalize-package-data" "^2.4.0"
@@ -8435,17 +2335,7 @@
     parse-json "^5.0.0"
     type-fest "^0.6.0"
 
-readable-stream@1.1.x:
-  version "1.1.14"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
-  integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
-  dependencies:
-    core-util-is "~1.0.0"
-    inherits "~2.0.1"
-    isarray "0.0.1"
-    string_decoder "~0.10.x"
-
-"readable-stream@2 || 3", readable-stream@^3.1.1, readable-stream@^3.4.0:
+readable-stream@^3.1.1:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
   integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
@@ -8454,311 +2344,51 @@
     string_decoder "^1.1.1"
     util-deprecate "^1.0.1"
 
-"readable-stream@>=1.0.33-1 <1.1.0-0":
-  version "1.0.34"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
-  integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=
-  dependencies:
-    core-util-is "~1.0.0"
-    inherits "~2.0.1"
-    isarray "0.0.1"
-    string_decoder "~0.10.x"
-
-readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@^2.3.7, readable-stream@~2.3.6:
-  version "2.3.7"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
-  integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
-  dependencies:
-    core-util-is "~1.0.0"
-    inherits "~2.0.3"
-    isarray "~1.0.0"
-    process-nextick-args "~2.0.0"
-    safe-buffer "~5.1.1"
-    string_decoder "~1.1.1"
-    util-deprecate "~1.0.1"
-
-readdirp@^2.0.0:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
-  integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==
-  dependencies:
-    graceful-fs "^4.1.11"
-    micromatch "^3.1.10"
-    readable-stream "^2.0.2"
-
-rechoir@^0.6.2:
-  version "0.6.2"
-  resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
-  integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=
-  dependencies:
-    resolve "^1.1.6"
-
-redent@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
-  integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=
-  dependencies:
-    indent-string "^2.1.0"
-    strip-indent "^1.0.1"
-
 redent@^3.0.0:
   version "3.0.0"
-  resolved "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
   integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==
   dependencies:
     indent-string "^4.0.0"
     strip-indent "^3.0.0"
 
-reduce-flatten@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-1.0.1.tgz#258c78efd153ddf93cb561237f61184f3696e327"
-  integrity sha1-JYx479FT3fk8tWEjf2EYTzaW4yc=
-
-regenerate-unicode-properties@^8.2.0:
-  version "8.2.0"
-  resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
-  integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==
-  dependencies:
-    regenerate "^1.4.0"
-
-regenerate@^1.4.0:
-  version "1.4.2"
-  resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
-  integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
-
-regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1:
-  version "0.11.1"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
-  integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
-
-regenerator-runtime@^0.13.4:
-  version "0.13.7"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
-  integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
-
-regenerator-transform@^0.14.2:
-  version "0.14.5"
-  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4"
-  integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==
-  dependencies:
-    "@babel/runtime" "^7.8.4"
-
-regex-cache@^0.4.2:
-  version "0.4.4"
-  resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd"
-  integrity sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==
-  dependencies:
-    is-equal-shallow "^0.1.3"
-
-regex-not@^1.0.0, regex-not@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
-  integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==
-  dependencies:
-    extend-shallow "^3.0.2"
-    safe-regex "^1.1.0"
-
 regexpp@^3.0.0, regexpp@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz"
-  integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==
-
-regexpu-core@^4.7.1:
-  version "4.7.1"
-  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6"
-  integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==
-  dependencies:
-    regenerate "^1.4.0"
-    regenerate-unicode-properties "^8.2.0"
-    regjsgen "^0.5.1"
-    regjsparser "^0.6.4"
-    unicode-match-property-ecmascript "^1.0.4"
-    unicode-match-property-value-ecmascript "^1.2.0"
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
+  integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
 
 regextras@^0.7.1:
   version "0.7.1"
-  resolved "https://registry.npmjs.org/regextras/-/regextras-0.7.1.tgz"
+  resolved "https://registry.yarnpkg.com/regextras/-/regextras-0.7.1.tgz#be95719d5f43f9ef0b9fa07ad89b7c606995a3b2"
   integrity sha512-9YXf6xtW+qzQ+hcMQXx95MOvfqXFgsKDZodX3qZB0x2n5Z94ioetIITsBtvJbiOyxa/6s9AtyweBLCdPmPko/w==
 
-registry-auth-token@^3.0.1:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e"
-  integrity sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==
-  dependencies:
-    rc "^1.1.6"
-    safe-buffer "^5.0.1"
-
 registry-auth-token@^4.0.0:
   version "4.2.1"
-  resolved "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz"
+  resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250"
   integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==
   dependencies:
     rc "^1.2.8"
 
-registry-url@^3.0.3:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942"
-  integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI=
-  dependencies:
-    rc "^1.0.1"
-
 registry-url@^5.0.0:
   version "5.1.0"
-  resolved "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009"
   integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==
   dependencies:
     rc "^1.2.8"
 
-regjsgen@^0.5.1:
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733"
-  integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==
-
-regjsparser@^0.6.4:
-  version "0.6.9"
-  resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.9.tgz#b489eef7c9a2ce43727627011429cf833a7183e6"
-  integrity sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ==
-  dependencies:
-    jsesc "~0.5.0"
-
-relateurl@0.2.x:
-  version "0.2.7"
-  resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
-  integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=
-
-remove-trailing-separator@^1.0.1:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
-  integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8=
-
-repeat-element@^1.1.2:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9"
-  integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==
-
-repeat-string@^1.5.2, repeat-string@^1.6.1:
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
-  integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
-
-repeating@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
-  integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=
-  dependencies:
-    is-finite "^1.0.0"
-
-replace-ext@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924"
-  integrity sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=
-
-replace-ext@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a"
-  integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==
-
-request@2.88.0:
-  version "2.88.0"
-  resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
-  integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
-  dependencies:
-    aws-sign2 "~0.7.0"
-    aws4 "^1.8.0"
-    caseless "~0.12.0"
-    combined-stream "~1.0.6"
-    extend "~3.0.2"
-    forever-agent "~0.6.1"
-    form-data "~2.3.2"
-    har-validator "~5.1.0"
-    http-signature "~1.2.0"
-    is-typedarray "~1.0.0"
-    isstream "~0.1.2"
-    json-stringify-safe "~5.0.1"
-    mime-types "~2.1.19"
-    oauth-sign "~0.9.0"
-    performance-now "^2.1.0"
-    qs "~6.5.2"
-    safe-buffer "^5.1.2"
-    tough-cookie "~2.4.3"
-    tunnel-agent "^0.6.0"
-    uuid "^3.3.2"
-
-request@^2.72.0, request@^2.85.0:
-  version "2.88.2"
-  resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
-  integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
-  dependencies:
-    aws-sign2 "~0.7.0"
-    aws4 "^1.8.0"
-    caseless "~0.12.0"
-    combined-stream "~1.0.6"
-    extend "~3.0.2"
-    forever-agent "~0.6.1"
-    form-data "~2.3.2"
-    har-validator "~5.1.3"
-    http-signature "~1.2.0"
-    is-typedarray "~1.0.0"
-    isstream "~0.1.2"
-    json-stringify-safe "~5.0.1"
-    mime-types "~2.1.19"
-    oauth-sign "~0.9.0"
-    performance-now "^2.1.0"
-    qs "~6.5.2"
-    safe-buffer "^5.1.2"
-    tough-cookie "~2.5.0"
-    tunnel-agent "^0.6.0"
-    uuid "^3.3.2"
-
 require-from-string@^2.0.2:
   version "2.0.2"
-  resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz"
+  resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
   integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
 
-requirejs@^2.3.4:
-  version "2.3.6"
-  resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.6.tgz#e5093d9601c2829251258c0b9445d4d19fa9e7c9"
-  integrity sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==
-
-requires-port@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
-  integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
-
-resolve-alpn@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.1.1.tgz#4a006a7d533c81a5dd04681612090fde227cd6e1"
-  integrity sha512-0KbFjFPR2bnJhNx1t8Ad6RqVc8+QPJC4y561FYyC/Q/6OzB3fhUzB5PEgitYhPK6aifwR5gXBSnDMllaDWixGQ==
-
-resolve-dir@^0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e"
-  integrity sha1-shklmlYC+sXFxJatiUpujMQwJh4=
-  dependencies:
-    expand-tilde "^1.2.2"
-    global-modules "^0.2.3"
-
-resolve-dir@^1.0.0, resolve-dir@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43"
-  integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=
-  dependencies:
-    expand-tilde "^2.0.0"
-    global-modules "^1.0.0"
-
 resolve-from@^4.0.0:
   version "4.0.0"
-  resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
   integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
 
-resolve-url@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
-  integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
-
-resolve@^1.1.6, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.20.0, resolve@^1.5.0:
+resolve@^1.10.0, resolve@^1.10.1, resolve@^1.20.0:
   version "1.20.0"
-  resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
   integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
   dependencies:
     is-core-module "^2.2.0"
@@ -8766,507 +2396,139 @@
 
 responselike@^1.0.2:
   version "1.0.2"
-  resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz"
+  resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
   integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=
   dependencies:
     lowercase-keys "^1.0.0"
 
-responselike@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723"
-  integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==
-  dependencies:
-    lowercase-keys "^2.0.0"
-
-restore-cursor@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
-  integrity sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=
-  dependencies:
-    exit-hook "^1.0.0"
-    onetime "^1.0.0"
-
 restore-cursor@^3.1.0:
   version "3.1.0"
-  resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
   integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
   dependencies:
     onetime "^5.1.0"
     signal-exit "^3.0.2"
 
-ret@~0.1.10:
-  version "0.1.15"
-  resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
-  integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
-
 reusify@^1.0.4:
   version "1.0.4"
-  resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz"
+  resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
   integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
 
-rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
-  version "2.7.1"
-  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
-  integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
-  dependencies:
-    glob "^7.1.3"
-
-rimraf@^3.0.0, rimraf@^3.0.2:
+rimraf@^3.0.2:
   version "3.0.2"
-  resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
   integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
   dependencies:
     glob "^7.1.3"
 
-rimraf@~2.6.2:
-  version "2.6.3"
-  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
-  integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
-  dependencies:
-    glob "^7.1.3"
-
-rollup@^1.3.0:
-  version "1.32.1"
-  resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.32.1.tgz#4480e52d9d9e2ae4b46ba0d9ddeaf3163940f9c4"
-  integrity sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==
-  dependencies:
-    "@types/estree" "*"
-    "@types/node" "*"
-    acorn "^7.1.0"
-
 rollup@^2.45.2:
-  version "2.45.2"
-  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.45.2.tgz#8fb85917c9f35605720e92328f3ccbfba6f78b48"
-  integrity sha512-kRRU7wXzFHUzBIv0GfoFFIN3m9oteY4uAsKllIpQDId5cfnkWF2J130l+27dzDju0E6MScKiV0ZM5Bw8m4blYQ==
+  version "2.56.3"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.56.3.tgz#b63edadd9851b0d618a6d0e6af8201955a77aeff"
+  integrity sha512-Au92NuznFklgQCUcV96iXlxUbHuB1vQMaH76DHl5M11TotjOHwqk9CwcrT78+Tnv4FN9uTBxq6p4EJoYkpyekg==
   optionalDependencies:
-    fsevents "~2.3.1"
+    fsevents "~2.3.2"
 
-run-async@^2.0.0, run-async@^2.2.0, run-async@^2.4.0:
+run-async@^2.4.0:
   version "2.4.1"
-  resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz"
+  resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
   integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==
 
 run-parallel@^1.1.9:
   version "1.2.0"
-  resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
   integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
   dependencies:
     queue-microtask "^1.2.2"
 
-rx@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
-  integrity sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=
-
-rxjs@^6.4.0, rxjs@^6.6.0:
+rxjs@^6.6.0:
   version "6.6.7"
-  resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz"
+  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
   integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
   dependencies:
     tslib "^1.9.0"
 
-safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
-  version "5.1.2"
-  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
-  integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-
-safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
+safe-buffer@~5.2.0:
   version "5.2.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
   integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
 
-safe-regex@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
-  integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4=
-  dependencies:
-    ret "~0.1.10"
-
-"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
+"safer-buffer@>= 2.1.2 < 3":
   version "2.1.2"
-  resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
+  resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
   integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
 
-samsam@1.x, samsam@^1.1.3:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50"
-  integrity sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==
-
-sauce-connect-launcher@^1.0.0:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/sauce-connect-launcher/-/sauce-connect-launcher-1.3.2.tgz#dfc675a258550809a8eaf457eb9162b943ddbaf0"
-  integrity sha512-wf0coUlidJ7rmeClgVVBh6Kw55/yalZCY/Un5RgjSnTXRAeGqagnTsTYpZaqC4dCtrY4myuYpOAZXCdbO7lHfQ==
-  dependencies:
-    adm-zip "~0.4.3"
-    async "^2.1.2"
-    https-proxy-agent "^5.0.0"
-    lodash "^4.16.6"
-    rimraf "^2.5.4"
-
-scoped-regex@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/scoped-regex/-/scoped-regex-1.0.0.tgz#a346bb1acd4207ae70bd7c0c7ca9e566b6baddb8"
-  integrity sha1-o0a7Gs1CB65wvXwMfKnlZra63bg=
-
-select-hose@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
-  integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=
-
-selenium-standalone@^6.7.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/selenium-standalone/-/selenium-standalone-6.23.0.tgz#91a7d12b1c8ba077a82b44323445c5882eb20ff1"
-  integrity sha512-6dVLSEvbixd/MRSEmrcRQD8dmABrzNsxRqroKFQY+RVzm1JVPgGHIlo6qJzG6akfjc2V8SadHslE6lN4BFVM3w==
-  dependencies:
-    commander "^2.20.3"
-    cross-spawn "^7.0.3"
-    debug "^4.3.1"
-    got "^11.8.0"
-    lodash.mapvalues "^4.6.0"
-    lodash.merge "^4.6.2"
-    minimist "^1.2.5"
-    mkdirp "^1.0.4"
-    progress "2.0.3"
-    tar-stream "2.1.4"
-    which "^2.0.2"
-    yauzl "^2.10.0"
-
-semver-diff@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
-  integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=
-  dependencies:
-    semver "^5.0.3"
-
 semver-diff@^3.1.1:
   version "3.1.1"
-  resolved "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz"
+  resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b"
   integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==
   dependencies:
     semver "^6.3.0"
 
-"semver@2 || 3 || 4 || 5", semver@5.6.0, semver@^5.3.0:
-  version "5.6.0"
-  resolved "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz"
-  integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
-
-semver@^5.0.3, semver@^5.1.0, semver@^5.5.0:
+"semver@2 || 3 || 4 || 5":
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
   integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
 
+semver@5.6.0:
+  version "5.6.0"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
+  integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
+
 semver@^6.0.0, semver@^6.1.0, semver@^6.2.0, semver@^6.3.0:
   version "6.3.0"
-  resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
 
-semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
+semver@^7.2.1, semver@^7.3.4, semver@^7.3.5:
   version "7.3.5"
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
   integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
   dependencies:
     lru-cache "^6.0.0"
 
-send@0.17.1:
-  version "0.17.1"
-  resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
-  integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
-  dependencies:
-    debug "2.6.9"
-    depd "~1.1.2"
-    destroy "~1.0.4"
-    encodeurl "~1.0.2"
-    escape-html "~1.0.3"
-    etag "~1.8.1"
-    fresh "0.5.2"
-    http-errors "~1.7.2"
-    mime "1.6.0"
-    ms "2.1.1"
-    on-finished "~2.3.0"
-    range-parser "~1.2.1"
-    statuses "~1.5.0"
-
-send@^0.16.1, send@^0.16.2:
-  version "0.16.2"
-  resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1"
-  integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==
-  dependencies:
-    debug "2.6.9"
-    depd "~1.1.2"
-    destroy "~1.0.4"
-    encodeurl "~1.0.2"
-    escape-html "~1.0.3"
-    etag "~1.8.1"
-    fresh "0.5.2"
-    http-errors "~1.6.2"
-    mime "1.4.1"
-    ms "2.0.0"
-    on-finished "~2.3.0"
-    range-parser "~1.2.0"
-    statuses "~1.4.0"
-
-serve-static@1.14.1:
-  version "1.14.1"
-  resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
-  integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
-  dependencies:
-    encodeurl "~1.0.2"
-    escape-html "~1.0.3"
-    parseurl "~1.3.3"
-    send "0.17.1"
-
-server-destroy@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/server-destroy/-/server-destroy-1.0.1.tgz#f13bf928e42b9c3e79383e61cc3998b5d14e6cdd"
-  integrity sha1-8Tv5KOQrnD55OD5hzDmYtdFObN0=
-
-serviceworker-cache-polyfill@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/serviceworker-cache-polyfill/-/serviceworker-cache-polyfill-4.0.0.tgz#de19ee73bef21ab3c0740a37b33db62464babdeb"
-  integrity sha1-3hnuc77yGrPAdAo3sz22JGS6ves=
-
-set-getter@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376"
-  integrity sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=
-  dependencies:
-    to-object-path "^0.3.0"
-
-set-value@^2.0.0, set-value@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
-  integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==
-  dependencies:
-    extend-shallow "^2.0.1"
-    is-extendable "^0.1.1"
-    is-plain-object "^2.0.3"
-    split-string "^3.0.1"
-
-setprototypeof@1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
-  integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
-
-setprototypeof@1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
-  integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
-
-shady-css-parser@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/shady-css-parser/-/shady-css-parser-0.1.0.tgz#534dc79c8ca5884c5ed92a4e5a13d6d863bca428"
-  integrity sha512-irfJUUkEuDlNHKZNAp2r7zOyMlmbfVJ+kWSfjlCYYUx/7dJnANLCyTzQZsuxy5NJkvtNwSxY5Gj8MOlqXUQPyA==
-
-shallow-clone@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
-  integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
-  dependencies:
-    kind-of "^6.0.2"
-
-shebang-command@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
-  integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=
-  dependencies:
-    shebang-regex "^1.0.0"
-
 shebang-command@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
   integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
   dependencies:
     shebang-regex "^3.0.0"
 
-shebang-regex@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
-  integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
-
 shebang-regex@^3.0.0:
   version "3.0.0"
-  resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
   integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
 
-shelljs@^0.8.0, shelljs@^0.8.4:
-  version "0.8.4"
-  resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2"
-  integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==
+side-channel@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
+  integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
   dependencies:
-    glob "^7.0.0"
-    interpret "^1.0.0"
-    rechoir "^0.6.2"
+    call-bind "^1.0.0"
+    get-intrinsic "^1.0.2"
+    object-inspect "^1.9.0"
 
-signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3:
+signal-exit@^3.0.2, signal-exit@^3.0.3:
   version "3.0.3"
-  resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz"
+  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
   integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
 
-simple-swizzle@^0.2.2:
-  version "0.2.2"
-  resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
-  integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=
-  dependencies:
-    is-arrayish "^0.3.1"
-
-sinon-chai@^2.10.0:
-  version "2.14.0"
-  resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-2.14.0.tgz#da7dd4cc83cd6a260b67cca0f7a9fdae26a1205d"
-  integrity sha512-9stIF1utB0ywNHNT7RgiXbdmen8QDCRsrTjw+G9TgKt1Yexjiv8TOWZ6WHsTPz57Yky3DIswZvEqX8fpuHNDtQ==
-
-sinon@^2.3.5:
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/sinon/-/sinon-2.4.1.tgz#021fd64b54cb77d9d2fb0d43cdedfae7629c3a36"
-  integrity sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==
-  dependencies:
-    diff "^3.1.0"
-    formatio "1.2.0"
-    lolex "^1.6.0"
-    native-promise-only "^0.8.1"
-    path-to-regexp "^1.7.0"
-    samsam "^1.1.3"
-    text-encoding "0.6.4"
-    type-detect "^4.0.0"
-
-slash@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
-  integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=
-
-slash@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
-  integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
-
 slash@^3.0.0:
   version "3.0.0"
-  resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
   integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
 
 slice-ansi@^4.0.0:
   version "4.0.0"
-  resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
   integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==
   dependencies:
     ansi-styles "^4.0.0"
     astral-regex "^2.0.0"
     is-fullwidth-code-point "^3.0.0"
 
-slide@^1.1.5:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
-  integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=
-
-snapdragon-node@^2.0.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
-  integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==
-  dependencies:
-    define-property "^1.0.0"
-    isobject "^3.0.0"
-    snapdragon-util "^3.0.1"
-
-snapdragon-util@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2"
-  integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==
-  dependencies:
-    kind-of "^3.2.0"
-
-snapdragon@^0.8.1:
-  version "0.8.2"
-  resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d"
-  integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==
-  dependencies:
-    base "^0.11.1"
-    debug "^2.2.0"
-    define-property "^0.2.5"
-    extend-shallow "^2.0.1"
-    map-cache "^0.2.2"
-    source-map "^0.5.6"
-    source-map-resolve "^0.5.0"
-    use "^3.1.0"
-
-socket.io-adapter@~1.1.0:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz#ab3f0d6f66b8fc7fca3959ab5991f82221789be9"
-  integrity sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==
-
-socket.io-client@2.4.0:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.4.0.tgz#aafb5d594a3c55a34355562fc8aea22ed9119a35"
-  integrity sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==
-  dependencies:
-    backo2 "1.0.2"
-    component-bind "1.0.0"
-    component-emitter "~1.3.0"
-    debug "~3.1.0"
-    engine.io-client "~3.5.0"
-    has-binary2 "~1.0.2"
-    indexof "0.0.1"
-    parseqs "0.0.6"
-    parseuri "0.0.6"
-    socket.io-parser "~3.3.0"
-    to-array "0.1.4"
-
-socket.io-parser@~3.3.0:
-  version "3.3.2"
-  resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.2.tgz#ef872009d0adcf704f2fbe830191a14752ad50b6"
-  integrity sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==
-  dependencies:
-    component-emitter "~1.3.0"
-    debug "~3.1.0"
-    isarray "2.0.1"
-
-socket.io-parser@~3.4.0:
-  version "3.4.1"
-  resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.4.1.tgz#b06af838302975837eab2dc980037da24054d64a"
-  integrity sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==
-  dependencies:
-    component-emitter "1.2.1"
-    debug "~4.1.0"
-    isarray "2.0.1"
-
-socket.io@^2.0.3:
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.4.1.tgz#95ad861c9a52369d7f1a68acf0d4a1b16da451d2"
-  integrity sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==
-  dependencies:
-    debug "~4.1.0"
-    engine.io "~3.5.0"
-    has-binary2 "~1.0.2"
-    socket.io-adapter "~1.1.0"
-    socket.io-client "2.4.0"
-    socket.io-parser "~3.4.0"
-
-sort-keys-length@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188"
-  integrity sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=
-  dependencies:
-    sort-keys "^1.0.0"
-
-sort-keys@^1.0.0:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
-  integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0=
-  dependencies:
-    is-plain-obj "^1.0.0"
-
-source-map-resolve@^0.5.0:
-  version "0.5.3"
-  resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a"
-  integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==
-  dependencies:
-    atob "^2.1.2"
-    decode-uri-component "^0.2.0"
-    resolve-url "^0.2.1"
-    source-map-url "^0.4.0"
-    urix "^0.1.0"
-
 source-map-support@0.5.9:
   version "0.5.9"
-  resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f"
   integrity sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==
   dependencies:
     buffer-from "^1.0.0"
@@ -9280,19 +2542,9 @@
     buffer-from "^1.0.0"
     source-map "^0.6.0"
 
-source-map-url@^0.4.0:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56"
-  integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==
-
-source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7:
-  version "0.5.7"
-  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
-  integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
-
-source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
+source-map@^0.6.0:
   version "0.6.1"
-  resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
+  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
   integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
 
 source-map@~0.7.2:
@@ -9300,17 +2552,9 @@
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
   integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
 
-spawn-sync@^1.0.15:
-  version "1.0.15"
-  resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476"
-  integrity sha1-sAeZVX63+wyDdsKdROih6mfldHY=
-  dependencies:
-    concat-stream "^1.4.7"
-    os-shim "^0.1.2"
-
 spdx-correct@^3.0.0:
   version "3.1.1"
-  resolved "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz"
+  resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
   integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==
   dependencies:
     spdx-expression-parse "^3.0.0"
@@ -9318,152 +2562,30 @@
 
 spdx-exceptions@^2.1.0:
   version "2.3.0"
-  resolved "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz"
+  resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d"
   integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==
 
 spdx-expression-parse@^3.0.0, spdx-expression-parse@^3.0.1:
   version "3.0.1"
-  resolved "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679"
   integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==
   dependencies:
     spdx-exceptions "^2.1.0"
     spdx-license-ids "^3.0.0"
 
 spdx-license-ids@^3.0.0:
-  version "3.0.7"
-  resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz"
-  integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==
-
-spdy-transport@^2.0.18:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.1.1.tgz#c54815d73858aadd06ce63001e7d25fa6441623b"
-  integrity sha512-q7D8c148escoB3Z7ySCASadkegMmUZW8Wb/Q1u0/XBgDKMO880rLQDj8Twiew/tYi7ghemKUi/whSYOwE17f5Q==
-  dependencies:
-    debug "^2.6.8"
-    detect-node "^2.0.3"
-    hpack.js "^2.1.6"
-    obuf "^1.1.1"
-    readable-stream "^2.2.9"
-    safe-buffer "^5.0.1"
-    wbuf "^1.7.2"
-
-spdy@^3.3.3:
-  version "3.4.7"
-  resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.7.tgz#42ff41ece5cc0f99a3a6c28aabb73f5c3b03acbc"
-  integrity sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=
-  dependencies:
-    debug "^2.6.8"
-    handle-thing "^1.2.5"
-    http-deceiver "^1.2.7"
-    safe-buffer "^5.0.1"
-    select-hose "^2.0.0"
-    spdy-transport "^2.0.18"
-
-split-string@^3.0.1, split-string@^3.0.2:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
-  integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==
-  dependencies:
-    extend-shallow "^3.0.0"
+  version "3.0.10"
+  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz#0d9becccde7003d6c658d487dd48a32f0bf3014b"
+  integrity sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==
 
 sprintf-js@~1.0.2:
   version "1.0.3"
-  resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
+  resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
   integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
 
-sshpk@^1.7.0:
-  version "1.16.1"
-  resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
-  integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==
-  dependencies:
-    asn1 "~0.2.3"
-    assert-plus "^1.0.0"
-    bcrypt-pbkdf "^1.0.0"
-    dashdash "^1.12.0"
-    ecc-jsbn "~0.1.1"
-    getpass "^0.1.1"
-    jsbn "~0.1.0"
-    safer-buffer "^2.0.2"
-    tweetnacl "~0.14.0"
-
-stable@^0.1.6:
-  version "0.1.8"
-  resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
-  integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
-
-stack-trace@0.0.x:
-  version "0.0.10"
-  resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
-  integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=
-
-stacky@^1.3.1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/stacky/-/stacky-1.3.1.tgz#3f117e5187b9a73d23f876d69f05c85b11804a12"
-  integrity sha1-PxF+UYe5pz0j+HbWnwXIWxGAShI=
-  dependencies:
-    chalk "^1.1.1"
-    lodash "^3.0.0"
-
-static-extend@^0.1.1:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
-  integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=
-  dependencies:
-    define-property "^0.2.5"
-    object-copy "^0.1.0"
-
-"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
-  integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
-
-statuses@~1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
-  integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==
-
-stream-shift@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
-  integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==
-
-stream@0.0.2:
-  version "0.0.2"
-  resolved "https://registry.yarnpkg.com/stream/-/stream-0.0.2.tgz#7f5363f057f6592c5595f00bc80a27f5cec1f0ef"
-  integrity sha1-f1Nj8Ff2WSxVlfALyAon9c7B8O8=
-  dependencies:
-    emitter-component "^1.1.1"
-
-streamsearch@0.1.2:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
-  integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
-
-string-template@~0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"
-  integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=
-
-string-width@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
-  integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=
-  dependencies:
-    code-point-at "^1.0.0"
-    is-fullwidth-code-point "^1.0.0"
-    strip-ansi "^3.0.0"
-
-string-width@^2.0.0, string-width@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
-  integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
-  dependencies:
-    is-fullwidth-code-point "^2.0.0"
-    strip-ansi "^4.0.0"
-
 string-width@^3.0.0:
   version "3.1.0"
-  resolved "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
   integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
   dependencies:
     emoji-regex "^7.0.1"
@@ -9472,7 +2594,7 @@
 
 string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0:
   version "4.2.2"
-  resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5"
   integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==
   dependencies:
     emoji-regex "^8.0.0"
@@ -9481,7 +2603,7 @@
 
 string.prototype.trimend@^1.0.4:
   version "1.0.4"
-  resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80"
   integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==
   dependencies:
     call-bind "^1.0.2"
@@ -9489,7 +2611,7 @@
 
 string.prototype.trimstart@^1.0.4:
   version "1.0.4"
-  resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed"
   integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==
   dependencies:
     call-bind "^1.0.2"
@@ -9502,491 +2624,120 @@
   dependencies:
     safe-buffer "~5.2.0"
 
-string_decoder@~0.10.x:
-  version "0.10.31"
-  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
-  integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
-
-string_decoder@~1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
-  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
-  dependencies:
-    safe-buffer "~5.1.0"
-
-strip-ansi@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
-  integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
-  dependencies:
-    ansi-regex "^2.0.0"
-
-strip-ansi@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
-  integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
-  dependencies:
-    ansi-regex "^3.0.0"
-
 strip-ansi@^5.1.0:
   version "5.2.0"
-  resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
   integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
   dependencies:
     ansi-regex "^4.1.0"
 
 strip-ansi@^6.0.0:
   version "6.0.0"
-  resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
   integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==
   dependencies:
     ansi-regex "^5.0.0"
 
-strip-ansi@~0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991"
-  integrity sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=
-
-strip-bom-buf@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz#1cb45aaf57530f4caf86c7f75179d2c9a51dd572"
-  integrity sha1-HLRar1dTD0yvhsf3UXnSyaUd1XI=
-  dependencies:
-    is-utf8 "^0.2.1"
-
-strip-bom-stream@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz#e7144398577d51a6bed0fa1994fa05f43fd988ee"
-  integrity sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=
-  dependencies:
-    first-chunk-stream "^1.0.0"
-    strip-bom "^2.0.0"
-
-strip-bom-stream@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz#f87db5ef2613f6968aa545abfe1ec728b6a829ca"
-  integrity sha1-+H217yYT9paKpUWr/h7HKLaoKco=
-  dependencies:
-    first-chunk-stream "^2.0.0"
-    strip-bom "^2.0.0"
-
-strip-bom@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
-  integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=
-  dependencies:
-    is-utf8 "^0.2.0"
-
 strip-bom@^3.0.0:
   version "3.0.0"
-  resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
   integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
 
-strip-eof@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
-  integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
-
 strip-final-newline@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
   integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
 
-strip-indent@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
-  integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=
-  dependencies:
-    get-stdin "^4.0.1"
-
-strip-indent@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
-  integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=
-
 strip-indent@^3.0.0:
   version "3.0.0"
-  resolved "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"
   integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==
   dependencies:
     min-indent "^1.0.0"
 
 strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
   version "3.1.1"
-  resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
+  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
   integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
 
 strip-json-comments@~2.0.1:
   version "2.0.1"
-  resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
   integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
 
-supports-color@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
-  integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
-
 supports-color@^5.3.0:
   version "5.5.0"
-  resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
   integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
   dependencies:
     has-flag "^3.0.0"
 
 supports-color@^7.1.0:
   version "7.2.0"
-  resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
   integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
   dependencies:
     has-flag "^4.0.0"
 
-sw-precache@^5.1.1:
-  version "5.2.1"
-  resolved "https://registry.yarnpkg.com/sw-precache/-/sw-precache-5.2.1.tgz#06134f319eec68f3b9583ce9a7036b1c119f7179"
-  integrity sha512-8FAy+BP/FXE+ILfiVTt+GQJ6UEf4CVHD9OfhzH0JX+3zoy2uFk7Vn9EfXASOtVmmIVbL3jE/W8Z66VgPSZcMhw==
-  dependencies:
-    dom-urls "^1.1.0"
-    es6-promise "^4.0.5"
-    glob "^7.1.1"
-    lodash.defaults "^4.2.0"
-    lodash.template "^4.4.0"
-    meow "^3.7.0"
-    mkdirp "^0.5.1"
-    pretty-bytes "^4.0.2"
-    sw-toolbox "^3.4.0"
-    update-notifier "^2.3.0"
-
-sw-toolbox@^3.4.0:
-  version "3.6.0"
-  resolved "https://registry.yarnpkg.com/sw-toolbox/-/sw-toolbox-3.6.0.tgz#26df1d1c70348658e4dea2884319149b7b3183b5"
-  integrity sha1-Jt8dHHA0hljk3qKIQxkUm3sxg7U=
-  dependencies:
-    path-to-regexp "^1.0.1"
-    serviceworker-cache-polyfill "^4.0.0"
-
-table-layout@^0.4.3:
-  version "0.4.5"
-  resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-0.4.5.tgz#d906de6a25fa09c0c90d1d08ecd833ecedcb7378"
-  integrity sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==
-  dependencies:
-    array-back "^2.0.0"
-    deep-extend "~0.6.0"
-    lodash.padend "^4.6.1"
-    typical "^2.6.1"
-    wordwrapjs "^3.0.0"
-
-table@^6.0.4:
-  version "6.0.9"
-  resolved "https://registry.npmjs.org/table/-/table-6.0.9.tgz"
-  integrity sha512-F3cLs9a3hL1Z7N4+EkSscsel3z55XT950AvB05bwayrNg5T1/gykXtigioTAjbltvbMSJvvhFCbnf6mX+ntnJQ==
+table@^6.0.9:
+  version "6.7.1"
+  resolved "https://registry.yarnpkg.com/table/-/table-6.7.1.tgz#ee05592b7143831a8c94f3cee6aae4c1ccef33e2"
+  integrity sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==
   dependencies:
     ajv "^8.0.1"
-    is-boolean-object "^1.1.0"
-    is-number-object "^1.0.4"
-    is-string "^1.0.5"
     lodash.clonedeep "^4.5.0"
-    lodash.flatten "^4.4.0"
     lodash.truncate "^4.4.2"
     slice-ansi "^4.0.0"
     string-width "^4.2.0"
-
-tar-fs@^1.12.0:
-  version "1.16.3"
-  resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509"
-  integrity sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==
-  dependencies:
-    chownr "^1.0.1"
-    mkdirp "^0.5.1"
-    pump "^1.0.0"
-    tar-stream "^1.1.2"
-
-tar-stream@2.1.4:
-  version "2.1.4"
-  resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.4.tgz#c4fb1a11eb0da29b893a5b25476397ba2d053bfa"
-  integrity sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==
-  dependencies:
-    bl "^4.0.3"
-    end-of-stream "^1.4.1"
-    fs-constants "^1.0.0"
-    inherits "^2.0.3"
-    readable-stream "^3.1.1"
-
-tar-stream@^1.1.2:
-  version "1.6.2"
-  resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
-  integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
-  dependencies:
-    bl "^1.0.0"
-    buffer-alloc "^1.2.0"
-    end-of-stream "^1.0.0"
-    fs-constants "^1.0.0"
-    readable-stream "^2.3.0"
-    to-buffer "^1.1.1"
-    xtend "^4.0.0"
-
-tar-stream@^2.1.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
-  integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
-  dependencies:
-    bl "^4.0.3"
-    end-of-stream "^1.4.1"
-    fs-constants "^1.0.0"
-    inherits "^2.0.3"
-    readable-stream "^3.1.1"
-
-temp@^0.8.1, temp@^0.8.3:
-  version "0.8.4"
-  resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.4.tgz#8c97a33a4770072e0a05f919396c7665a7dd59f2"
-  integrity sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==
-  dependencies:
-    rimraf "~2.6.2"
-
-term-size@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69"
-  integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=
-  dependencies:
-    execa "^0.7.0"
-
-ternary-stream@^2.0.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/ternary-stream/-/ternary-stream-2.1.1.tgz#4ad64b98668d796a085af2c493885a435a8a8bfc"
-  integrity sha512-j6ei9hxSoyGlqTmoMjOm+QNvUKDOIY6bNl4Uh1lhBvl6yjPW2iLqxDUYyfDPZknQ4KdRziFl+ec99iT4l7g0cw==
-  dependencies:
-    duplexify "^3.5.0"
-    fork-stream "^0.0.4"
-    merge-stream "^1.0.0"
-    through2 "^2.0.1"
+    strip-ansi "^6.0.0"
 
 terser@^5.6.1:
-  version "5.6.1"
-  resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.1.tgz#a48eeac5300c0a09b36854bf90d9c26fb201973c"
-  integrity sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw==
+  version "5.7.2"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-5.7.2.tgz#d4d95ed4f8bf735cb933e802f2a1829abf545e3f"
+  integrity sha512-0Omye+RD4X7X69O0eql3lC4Heh/5iLj3ggxR/B5ketZLOtLiOqukUgjw3q4PDnNQbsrkKr3UMypqStQG3XKRvw==
   dependencies:
     commander "^2.20.0"
     source-map "~0.7.2"
     source-map-support "~0.5.19"
 
-text-encoding@0.6.4:
-  version "0.6.4"
-  resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
-  integrity sha1-45mpgiV6J22uQou5KEXLcb3CbRk=
-
-text-hex@1.0.x:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"
-  integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==
-
 text-table@^0.2.0:
   version "0.2.0"
-  resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
   integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
 
-textextensions@^2.5.0:
-  version "2.6.0"
-  resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-2.6.0.tgz#d7e4ab13fe54e32e08873be40d51b74229b00fc4"
-  integrity sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ==
-
-thenify-all@^1.0.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
-  integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=
-  dependencies:
-    thenify ">= 3.1.0 < 4"
-
-"thenify@>= 3.1.0 < 4":
-  version "3.3.1"
-  resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f"
-  integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==
-  dependencies:
-    any-promise "^1.0.0"
-
-through2-filter@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec"
-  integrity sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=
-  dependencies:
-    through2 "~2.0.0"
-    xtend "~4.0.0"
-
-through2-filter@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254"
-  integrity sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==
-  dependencies:
-    through2 "~2.0.0"
-    xtend "~4.0.0"
-
-through2@^0.6.0:
-  version "0.6.5"
-  resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48"
-  integrity sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=
-  dependencies:
-    readable-stream ">=1.0.33-1 <1.1.0-0"
-    xtend ">=4.0.0 <4.1.0-0"
-
-through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
-  integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
-  dependencies:
-    readable-stream "~2.3.6"
-    xtend "~4.0.1"
-
-through2@^3.0.0, through2@^3.0.1, through2@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.2.tgz#99f88931cfc761ec7678b41d5d7336b5b6a07bf4"
-  integrity sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==
-  dependencies:
-    inherits "^2.0.4"
-    readable-stream "2 || 3"
-
-"through@>=2.2.7 <3", through@^2.3.6:
+through@^2.3.6:
   version "2.3.8"
-  resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
+  resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
   integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
 
-timed-out@^3.0.0:
-  version "3.1.3"
-  resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-3.1.3.tgz#95860bfcc5c76c277f8f8326fd0f5b2e20eba217"
-  integrity sha1-lYYL/MXHbCd/j4Mm/Q9bLiDrohc=
-
-timed-out@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
-  integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=
-
-tmp@^0.0.29:
-  version "0.0.29"
-  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0"
-  integrity sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=
-  dependencies:
-    os-tmpdir "~1.0.1"
-
 tmp@^0.0.33:
   version "0.0.33"
-  resolved "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz"
+  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
   integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
   dependencies:
     os-tmpdir "~1.0.2"
 
-to-absolute-glob@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz#1cdfa472a9ef50c239ee66999b662ca0eb39937f"
-  integrity sha1-HN+kcqnvUMI57maZm2YsoOs5k38=
-  dependencies:
-    extend-shallow "^2.0.1"
-
-to-array@0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
-  integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA=
-
-to-buffer@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
-  integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==
-
-to-fast-properties@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
-  integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=
-
-to-fast-properties@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
-  integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
-
-to-object-path@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af"
-  integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=
-  dependencies:
-    kind-of "^3.0.2"
-
 to-readable-stream@^1.0.0:
   version "1.0.0"
-  resolved "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771"
   integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==
 
-to-regex-range@^2.1.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38"
-  integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=
-  dependencies:
-    is-number "^3.0.0"
-    repeat-string "^1.6.1"
-
 to-regex-range@^5.0.1:
   version "5.0.1"
-  resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
   integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
   dependencies:
     is-number "^7.0.0"
 
-to-regex@^3.0.1, to-regex@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce"
-  integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==
-  dependencies:
-    define-property "^2.0.2"
-    extend-shallow "^3.0.2"
-    regex-not "^1.0.2"
-    safe-regex "^1.1.0"
-
-toidentifier@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
-  integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
-
-tough-cookie@~2.4.3:
-  version "2.4.3"
-  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
-  integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
-  dependencies:
-    psl "^1.1.24"
-    punycode "^1.4.1"
-
-tough-cookie@~2.5.0:
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
-  integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
-  dependencies:
-    psl "^1.1.28"
-    punycode "^2.1.1"
-
-tr46@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
-  integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=
-  dependencies:
-    punycode "^2.1.0"
-
-trim-newlines@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
-  integrity sha1-WIeWa7WCpFA6QetST301ARgVphM=
-
 trim-newlines@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz"
-  integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
+  integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==
 
-trim-right@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
-  integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=
-
-triple-beam@^1.2.0, triple-beam@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
-  integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==
-
-tsconfig-paths@^3.9.0:
-  version "3.9.0"
-  resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz"
-  integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==
+tsconfig-paths@^3.11.0:
+  version "3.11.0"
+  resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36"
+  integrity sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==
   dependencies:
     "@types/json5" "^0.0.29"
     json5 "^1.0.1"
@@ -9995,35 +2746,23 @@
 
 tslib@^1.8.1, tslib@^1.9.0:
   version "1.14.1"
-  resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
   integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
 
 tsutils@2.27.2:
   version "2.27.2"
-  resolved "https://registry.npmjs.org/tsutils/-/tsutils-2.27.2.tgz"
+  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.27.2.tgz#60ba88a23d6f785ec4b89c6e8179cac9b431f1c7"
   integrity sha512-qf6rmT84TFMuxAKez2pIfR8UCai49iQsfB7YWVjV1bKpy/d0PWT5rEOSM6La9PiHZ0k1RRZQiwVdVJfQ3BPHgg==
   dependencies:
     tslib "^1.8.1"
 
-tsutils@^3.17.1, tsutils@^3.21.0:
+tsutils@^3.21.0:
   version "3.21.0"
   resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
   integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
   dependencies:
     tslib "^1.8.1"
 
-tunnel-agent@^0.6.0:
-  version "0.6.0"
-  resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
-  integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=
-  dependencies:
-    safe-buffer "^5.0.1"
-
-tweetnacl@^0.14.3, tweetnacl@~0.14.0:
-  version "0.14.5"
-  resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
-  integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
-
 twinkie@^1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/twinkie/-/twinkie-1.1.2.tgz#c301e4fc26d00d61d3d7e5be030dc6a2264271da"
@@ -10036,61 +2775,43 @@
 
 type-check@^0.4.0, type-check@~0.4.0:
   version "0.4.0"
-  resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz"
+  resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
   integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
   dependencies:
     prelude-ls "^1.2.1"
 
-type-detect@^4.0.0:
-  version "4.0.8"
-  resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
-  integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
-
 type-fest@^0.18.0:
   version "0.18.1"
-  resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f"
   integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==
 
 type-fest@^0.20.2:
   version "0.20.2"
-  resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
   integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
 
 type-fest@^0.21.3:
   version "0.21.3"
-  resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
   integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
 
 type-fest@^0.6.0:
   version "0.6.0"
-  resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
   integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==
 
 type-fest@^0.8.1:
   version "0.8.1"
-  resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
   integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
 
-type-is@^1.6.4, type-is@~1.6.17, type-is@~1.6.18:
-  version "1.6.18"
-  resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
-  integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
-  dependencies:
-    media-typer "0.3.0"
-    mime-types "~2.1.24"
-
 typedarray-to-buffer@^3.1.5:
   version "3.1.5"
-  resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz"
+  resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
   integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
   dependencies:
     is-typedarray "^1.0.0"
 
-typedarray@^0.0.6:
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
-  integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
-
 typescript@4.0.5:
   version "4.0.5"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.5.tgz#ae9dddfd1069f1cb5beb3ef3b2170dd7c1332389"
@@ -10101,32 +2822,9 @@
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805"
   integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==
 
-typical@^2.6.1:
-  version "2.6.1"
-  resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d"
-  integrity sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=
-
-typical@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4"
-  integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==
-
-ua-parser-js@^0.7.15:
-  version "0.7.28"
-  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31"
-  integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==
-
-uglify-js@3.4.x:
-  version "3.4.10"
-  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f"
-  integrity sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==
-  dependencies:
-    commander "~2.19.0"
-    source-map "~0.6.1"
-
-unbox-primitive@^1.0.0:
+unbox-primitive@^1.0.1:
   version "1.0.1"
-  resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"
   integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==
   dependencies:
     function-bind "^1.1.1"
@@ -10134,151 +2832,16 @@
     has-symbols "^1.0.2"
     which-boxed-primitive "^1.0.2"
 
-underscore@^1.8.3:
-  version "1.13.0"
-  resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.0.tgz#3ccdcbb824230fc6bf234ad0ddcd83dff4eafe5f"
-  integrity sha512-sCs4H3pCytsb5K7i072FAEC9YlSYFIbosvM0tAKAlpSSUgD7yC1iXSEGdl5XrDKQ1YUB+p/HDzYrSG2H2Vl36g==
-
-underscore@~1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
-  integrity sha1-izixDKze9jM3uLJOT/htRa6lKag=
-
-unicode-canonical-property-names-ecmascript@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
-  integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==
-
-unicode-match-property-ecmascript@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c"
-  integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==
-  dependencies:
-    unicode-canonical-property-names-ecmascript "^1.0.4"
-    unicode-property-aliases-ecmascript "^1.0.4"
-
-unicode-match-property-value-ecmascript@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531"
-  integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==
-
-unicode-property-aliases-ecmascript@^1.0.4:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4"
-  integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==
-
-union-value@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
-  integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==
-  dependencies:
-    arr-union "^3.1.0"
-    get-value "^2.0.6"
-    is-extendable "^0.1.1"
-    set-value "^2.0.1"
-
-unique-stream@^2.0.2:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.3.1.tgz#c65d110e9a4adf9a6c5948b28053d9a8d04cbeac"
-  integrity sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==
-  dependencies:
-    json-stable-stringify-without-jsonify "^1.0.1"
-    through2-filter "^3.0.0"
-
-unique-string@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a"
-  integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=
-  dependencies:
-    crypto-random-string "^1.0.0"
-
 unique-string@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d"
   integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==
   dependencies:
     crypto-random-string "^2.0.0"
 
-universal-user-agent@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-4.0.1.tgz#fd8d6cb773a679a709e967ef8288a31fcc03e557"
-  integrity sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg==
-  dependencies:
-    os-name "^3.1.0"
-
-universal-user-agent@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee"
-  integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==
-
-unpipe@1.0.0, unpipe@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
-  integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
-
-unset-value@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559"
-  integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=
-  dependencies:
-    has-value "^0.3.1"
-    isobject "^3.0.0"
-
-untildify@^2.0.0, untildify@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/untildify/-/untildify-2.1.0.tgz#17eb2807987f76952e9c0485fc311d06a826a2e0"
-  integrity sha1-F+soB5h/dpUunASF/DEdBqgmouA=
-  dependencies:
-    os-homedir "^1.0.0"
-
-untildify@^3.0.3:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.3.tgz#1e7b42b140bcfd922b22e70ca1265bfe3634c7c9"
-  integrity sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==
-
-unzip-response@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe"
-  integrity sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=
-
-unzip-response@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97"
-  integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=
-
-update-notifier@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-1.0.3.tgz#8f92c515482bd6831b7c93013e70f87552c7cf5a"
-  integrity sha1-j5LFFUgr1oMbfJMBPnD4dVLHz1o=
-  dependencies:
-    boxen "^0.6.0"
-    chalk "^1.0.0"
-    configstore "^2.0.0"
-    is-npm "^1.0.0"
-    latest-version "^2.0.0"
-    lazy-req "^1.1.0"
-    semver-diff "^2.0.0"
-    xdg-basedir "^2.0.0"
-
-update-notifier@^2.2.0, update-notifier@^2.3.0:
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6"
-  integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==
-  dependencies:
-    boxen "^1.2.1"
-    chalk "^2.0.1"
-    configstore "^3.0.0"
-    import-lazy "^2.1.0"
-    is-ci "^1.0.10"
-    is-installed-globally "^0.1.0"
-    is-npm "^1.0.0"
-    latest-version "^3.0.0"
-    semver-diff "^2.0.0"
-    xdg-basedir "^3.0.0"
-
 update-notifier@^5.0.0:
   version "5.1.0"
-  resolved "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9"
   integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==
   dependencies:
     boxen "^5.0.0"
@@ -10296,284 +2859,41 @@
     semver-diff "^3.1.1"
     xdg-basedir "^4.0.0"
 
-upper-case@^1.1.1:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598"
-  integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=
-
 uri-js@^4.2.2:
   version "4.4.1"
-  resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz"
+  resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
   integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
   dependencies:
     punycode "^2.1.0"
 
-urijs@^1.16.1:
-  version "1.19.6"
-  resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.6.tgz#51f8cb17ca16faefb20b9a31ac60f84aa2b7c870"
-  integrity sha512-eSXsXZ2jLvGWeLYlQA3Gh36BcjF+0amo92+wHPyN1mdR8Nxf75fuEuYTd9c0a+m/vhCjRK0ESlE9YNLW+E1VEw==
-
-urix@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
-  integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
-
-url-parse-lax@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"
-  integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=
-  dependencies:
-    prepend-http "^1.0.1"
-
 url-parse-lax@^3.0.0:
   version "3.0.0"
-  resolved "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
   integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=
   dependencies:
     prepend-http "^2.0.0"
 
-url-to-options@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
-  integrity sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=
-
-use@^3.1.0:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
-  integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
-
-util-deprecate@^1.0.1, util-deprecate@~1.0.1:
+util-deprecate@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
 
-utils-merge@1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
-  integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
-
-uuid@^2.0.1:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
-  integrity sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=
-
-uuid@^3.2.1, uuid@^3.3.2:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
-  integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
-
 v8-compile-cache@^2.0.3:
   version "2.3.0"
-  resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz"
+  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
   integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
 
-vali-date@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/vali-date/-/vali-date-1.0.0.tgz#1b904a59609fb328ef078138420934f6b86709a6"
-  integrity sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=
-
-validate-element-name@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/validate-element-name/-/validate-element-name-2.1.1.tgz#8ff75f7da69f73e7c510588362130508b7ac644e"
-  integrity sha1-j/dffaafc+fFEFiDYhMFCLesZE4=
-  dependencies:
-    is-potential-custom-element-name "^1.0.0"
-    log-symbols "^1.0.0"
-    meow "^3.7.0"
-
 validate-npm-package-license@^3.0.1:
   version "3.0.4"
-  resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz"
+  resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
   integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
   dependencies:
     spdx-correct "^3.0.0"
     spdx-expression-parse "^3.0.0"
 
-vargs@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/vargs/-/vargs-0.1.0.tgz#6b6184da6520cc3204ce1b407cac26d92609ebff"
-  integrity sha1-a2GE2mUgzDIEzhtAfKwm2SYJ6/8=
-
-vary@^1, vary@~1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
-  integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
-
-verror@1.10.0:
-  version "1.10.0"
-  resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
-  integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=
-  dependencies:
-    assert-plus "^1.0.0"
-    core-util-is "1.0.2"
-    extsprintf "^1.2.0"
-
-vinyl-file@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/vinyl-file/-/vinyl-file-3.0.0.tgz#b104d9e4409ffa325faadd520642d0a3b488b365"
-  integrity sha1-sQTZ5ECf+jJfqt1SBkLQo7SIs2U=
-  dependencies:
-    graceful-fs "^4.1.2"
-    pify "^2.3.0"
-    strip-bom-buf "^1.0.0"
-    strip-bom-stream "^2.0.0"
-    vinyl "^2.0.1"
-
-vinyl-fs@^2.4.3, vinyl-fs@^2.4.4:
-  version "2.4.4"
-  resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-2.4.4.tgz#be6ff3270cb55dfd7d3063640de81f25d7532239"
-  integrity sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=
-  dependencies:
-    duplexify "^3.2.0"
-    glob-stream "^5.3.2"
-    graceful-fs "^4.0.0"
-    gulp-sourcemaps "1.6.0"
-    is-valid-glob "^0.3.0"
-    lazystream "^1.0.0"
-    lodash.isequal "^4.0.0"
-    merge-stream "^1.0.0"
-    mkdirp "^0.5.0"
-    object-assign "^4.0.0"
-    readable-stream "^2.0.4"
-    strip-bom "^2.0.0"
-    strip-bom-stream "^1.0.0"
-    through2 "^2.0.0"
-    through2-filter "^2.0.0"
-    vali-date "^1.0.0"
-    vinyl "^1.0.0"
-
-vinyl@^1.0.0, vinyl@^1.1.1, vinyl@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884"
-  integrity sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=
-  dependencies:
-    clone "^1.0.0"
-    clone-stats "^0.0.1"
-    replace-ext "0.0.1"
-
-vinyl@^2.0.1, vinyl@^2.2.0, vinyl@^2.2.1:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.1.tgz#23cfb8bbab5ece3803aa2c0a1eb28af7cbba1974"
-  integrity sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==
-  dependencies:
-    clone "^2.1.1"
-    clone-buffer "^1.0.0"
-    clone-stats "^1.0.0"
-    cloneable-readable "^1.0.0"
-    remove-trailing-separator "^1.0.1"
-    replace-ext "^1.0.0"
-
-vlq@^0.2.2:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26"
-  integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==
-
-vscode-uri@=1.0.6:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.6.tgz#6b8f141b0bbc44ad7b07e94f82f168ac7608ad4d"
-  integrity sha512-sLI2L0uGov3wKVb9EB+vIQBl9tVP90nqRvxSoJ35vI3NjxE8jfsE5DSOhWgSunHSZmKS4OCi2jrtfxK7uyp2ww==
-
-wbuf@^1.1.0, wbuf@^1.7.2:
-  version "1.7.3"
-  resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df"
-  integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==
-  dependencies:
-    minimalistic-assert "^1.0.0"
-
-wct-local@^2.1.1:
-  version "2.1.5"
-  resolved "https://registry.yarnpkg.com/wct-local/-/wct-local-2.1.5.tgz#f7986753e3ad9a35d39178a9989350523561fff1"
-  integrity sha512-eqoZhjGy4Xq2tY0uB46Grkw/ztq+/rC0ImbYKl62unFHXtOgal+kkvnxR3SLRFNM8ty9+ItgycPeH0IpTqVL+w==
-  dependencies:
-    "@types/express" "^4.0.30"
-    "@types/freeport" "^1.0.19"
-    "@types/launchpad" "^0.6.0"
-    "@types/which" "^1.3.1"
-    chalk "^2.3.0"
-    cleankill "^2.0.0"
-    freeport "^1.0.4"
-    launchpad "^0.7.0"
-    selenium-standalone "^6.7.0"
-    which "^1.0.8"
-
-wct-sauce@^2.0.2:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/wct-sauce/-/wct-sauce-2.1.0.tgz#67d0be346aabbbc28384e8d143b8d3ca7ba774c0"
-  integrity sha512-c3R4PJcbpS7Gxv2vZ4HDAqpXV6cT9peslAWMU7hHH9PMhKDPbn8RNa6E4DVL0tOmZznB+3cRmtZ6+vJ/aDwu1A==
-  dependencies:
-    chalk "^2.4.1"
-    cleankill "^2.0.0"
-    lodash "^4.17.10"
-    request "^2.85.0"
-    sauce-connect-launcher "^1.0.0"
-    temp "^0.8.1"
-    uuid "^3.2.1"
-
-wd@^1.2.0:
-  version "1.14.0"
-  resolved "https://registry.yarnpkg.com/wd/-/wd-1.14.0.tgz#1fe6450b5baef37caa135e7755292c6998ca8a90"
-  integrity sha512-X7ZfGHHYlQ5zYpRlgP16LUsvYti+Al/6fz3T/ClVyivVCpCZQpESTDdz6zbK910O5OIvujO23Ym2DBBo3XsQlA==
-  dependencies:
-    archiver "^3.0.0"
-    async "^2.0.0"
-    lodash "^4.0.0"
-    mkdirp "^0.5.1"
-    q "^1.5.1"
-    request "2.88.0"
-    vargs "^0.1.0"
-
-web-component-tester@^6.9.0:
-  version "6.9.2"
-  resolved "https://registry.yarnpkg.com/web-component-tester/-/web-component-tester-6.9.2.tgz#40a7b824f2cf3cbc4305552bdfc3357977ded48a"
-  integrity sha512-s2kB/+IE8XWcnxY1fqSpqTiiHEGHWgUWariAbiRlxmAvWSuvaCVNALHYebsZrLCNCLHKcJR8/sGv/bw0MWMvjw==
-  dependencies:
-    "@polymer/sinonjs" "^1.14.1"
-    "@polymer/test-fixture" "^0.0.3"
-    "@webcomponents/webcomponentsjs" "^1.0.7"
-    accessibility-developer-tools "^2.12.0"
-    async "^2.4.1"
-    body-parser "^1.17.2"
-    bower-config "^1.4.0"
-    chalk "^1.1.3"
-    cleankill "^2.0.0"
-    express "^4.15.3"
-    findup-sync "^2.0.0"
-    glob "^7.1.2"
-    lodash "^3.10.1"
-    multer "^1.3.0"
-    nomnom "^1.8.1"
-    polyserve "^0.27.13"
-    resolve "^1.5.0"
-    semver "^5.3.0"
-    send "^0.16.1"
-    server-destroy "^1.0.1"
-    sinon "^2.3.5"
-    sinon-chai "^2.10.0"
-    socket.io "^2.0.3"
-    stacky "^1.3.1"
-    wd "^1.2.0"
-  optionalDependencies:
-    update-notifier "^2.2.0"
-    wct-local "^2.1.1"
-    wct-sauce "^2.0.2"
-
-webidl-conversions@^4.0.2:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
-  integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
-
-whatwg-url@^6.4.0:
-  version "6.5.0"
-  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"
-  integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==
-  dependencies:
-    lodash.sortby "^4.7.0"
-    tr46 "^1.0.1"
-    webidl-conversions "^4.0.2"
-
 which-boxed-primitive@^1.0.2:
   version "1.0.2"
-  resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz"
+  resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
   integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==
   dependencies:
     is-bigint "^1.0.1"
@@ -10582,101 +2902,28 @@
     is-string "^1.0.5"
     is-symbol "^1.0.3"
 
-which@^1.0.8, which@^1.2.12, which@^1.2.14, which@^1.2.9:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
-  integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
-  dependencies:
-    isexe "^2.0.0"
-
-which@^2.0.1, which@^2.0.2:
+which@^2.0.1:
   version "2.0.2"
-  resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
+  resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
   integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
   dependencies:
     isexe "^2.0.0"
 
-widest-line@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-1.0.0.tgz#0c09c85c2a94683d0d7eaf8ee097d564bf0e105c"
-  integrity sha1-DAnIXCqUaD0Nfq+O4JfVZL8OEFw=
-  dependencies:
-    string-width "^1.0.1"
-
-widest-line@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc"
-  integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==
-  dependencies:
-    string-width "^2.1.1"
-
 widest-line@^3.1.0:
   version "3.1.0"
-  resolved "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz"
+  resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca"
   integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==
   dependencies:
     string-width "^4.0.0"
 
-windows-release@^3.1.0:
-  version "3.3.3"
-  resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.3.3.tgz#1c10027c7225743eec6b89df160d64c2e0293999"
-  integrity sha512-OSOGH1QYiW5yVor9TtmXKQvt2vjQqbYS+DqmsZw+r7xDwLXEeT3JGW0ZppFmHx4diyXmxt238KFR3N9jzevBRg==
-  dependencies:
-    execa "^1.0.0"
-
-winston-transport@^4.2.0, winston-transport@^4.4.0:
-  version "4.4.0"
-  resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.4.0.tgz#17af518daa690d5b2ecccaa7acf7b20ca7925e59"
-  integrity sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==
-  dependencies:
-    readable-stream "^2.3.7"
-    triple-beam "^1.2.0"
-
-winston@^3.0.0:
-  version "3.3.3"
-  resolved "https://registry.yarnpkg.com/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170"
-  integrity sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==
-  dependencies:
-    "@dabh/diagnostics" "^2.0.2"
-    async "^3.1.0"
-    is-stream "^2.0.0"
-    logform "^2.2.0"
-    one-time "^1.0.0"
-    readable-stream "^3.4.0"
-    stack-trace "0.0.x"
-    triple-beam "^1.3.0"
-    winston-transport "^4.4.0"
-
-with-open-file@^0.1.6:
-  version "0.1.7"
-  resolved "https://registry.yarnpkg.com/with-open-file/-/with-open-file-0.1.7.tgz#e2de8d974e8a8ae6e58886be4fe8e7465b58a729"
-  integrity sha512-ecJS2/oHtESJ1t3ZfMI3B7KIDKyfN0O16miWxdn30zdh66Yd3LsRFebXZXq6GU4xfxLf6nVxp9kIqElb5fqczA==
-  dependencies:
-    p-finally "^1.0.0"
-    p-try "^2.1.0"
-    pify "^4.0.1"
-
 word-wrap@^1.2.3:
   version "1.2.3"
-  resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz"
+  resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
   integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
 
-wordwrap@^0.0.3:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
-  integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
-
-wordwrapjs@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-3.0.0.tgz#c94c372894cadc6feb1a66bff64e1d9af92c5d1e"
-  integrity sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==
-  dependencies:
-    reduce-flatten "^1.0.1"
-    typical "^2.6.1"
-
 wrap-ansi@^7.0.0:
   version "7.0.0"
-  resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
   integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
   dependencies:
     ansi-styles "^4.0.0"
@@ -10685,30 +2932,12 @@
 
 wrappy@1:
   version "1.0.2"
-  resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
+  resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
   integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
 
-write-file-atomic@^1.1.2:
-  version "1.3.4"
-  resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.4.tgz#f807a4f0b1d9e913ae7a48112e6cc3af1991b45f"
-  integrity sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=
-  dependencies:
-    graceful-fs "^4.1.11"
-    imurmurhash "^0.1.4"
-    slide "^1.1.5"
-
-write-file-atomic@^2.0.0:
-  version "2.4.3"
-  resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481"
-  integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==
-  dependencies:
-    graceful-fs "^4.1.11"
-    imurmurhash "^0.1.4"
-    signal-exit "^3.0.2"
-
 write-file-atomic@^3.0.0, write-file-atomic@^3.0.3:
   version "3.0.3"
-  resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz"
+  resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8"
   integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==
   dependencies:
     imurmurhash "^0.1.4"
@@ -10716,189 +2945,17 @@
     signal-exit "^3.0.2"
     typedarray-to-buffer "^3.1.5"
 
-ws@~7.4.2:
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59"
-  integrity sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==
-
-xdg-basedir@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-2.0.0.tgz#edbc903cc385fc04523d966a335504b5504d1bd2"
-  integrity sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I=
-  dependencies:
-    os-homedir "^1.0.0"
-
-xdg-basedir@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
-  integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=
-
 xdg-basedir@^4.0.0:
   version "4.0.0"
-  resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
   integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
 
-xmlbuilder@8.2.2:
-  version "8.2.2"
-  resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-8.2.2.tgz#69248673410b4ba42e1a6136551d2922335aa773"
-  integrity sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=
-
-xmldom@0.1.x:
-  version "0.1.31"
-  resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff"
-  integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==
-
-xmlhttprequest-ssl@~1.5.4:
-  version "1.5.5"
-  resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
-  integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=
-
-"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
-  integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
-
-yallist@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
-  integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
-
 yallist@^4.0.0:
   version "4.0.0"
-  resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"
+  resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
   integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
 
 yargs-parser@^20.2.3:
-  version "20.2.7"
-  resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz"
-  integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==
-
-yauzl@^2.10.0:
-  version "2.10.0"
-  resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
-  integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
-  dependencies:
-    buffer-crc32 "~0.2.3"
-    fd-slicer "~1.1.0"
-
-yeast@0.1.2:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
-  integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk=
-
-yeoman-environment@^1.5.2:
-  version "1.6.6"
-  resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-1.6.6.tgz#cd85fa67d156060e440d7807d7ef7cf0d2d1d671"
-  integrity sha1-zYX6Z9FWBg5EDXgH1+988NLR1nE=
-  dependencies:
-    chalk "^1.0.0"
-    debug "^2.0.0"
-    diff "^2.1.2"
-    escape-string-regexp "^1.0.2"
-    globby "^4.0.0"
-    grouped-queue "^0.3.0"
-    inquirer "^1.0.2"
-    lodash "^4.11.1"
-    log-symbols "^1.0.1"
-    mem-fs "^1.1.0"
-    text-table "^0.2.0"
-    untildify "^2.0.0"
-
-yeoman-environment@^2.0.5, yeoman-environment@^2.9.5:
-  version "2.10.3"
-  resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-2.10.3.tgz#9d8f42b77317414434cc0e51fb006a4bdd54688e"
-  integrity sha512-pLIhhU9z/G+kjOXmJ2bPFm3nejfbH+f1fjYRSOteEXDBrv1EoJE/e+kuHixSXfCYfTkxjYsvRaDX+1QykLCnpQ==
-  dependencies:
-    chalk "^2.4.1"
-    debug "^3.1.0"
-    diff "^3.5.0"
-    escape-string-regexp "^1.0.2"
-    execa "^4.0.0"
-    globby "^8.0.1"
-    grouped-queue "^1.1.0"
-    inquirer "^7.1.0"
-    is-scoped "^1.0.0"
-    lodash "^4.17.10"
-    log-symbols "^2.2.0"
-    mem-fs "^1.1.0"
-    mem-fs-editor "^6.0.0"
-    npm-api "^1.0.0"
-    semver "^7.1.3"
-    strip-ansi "^4.0.0"
-    text-table "^0.2.0"
-    untildify "^3.0.3"
-    yeoman-generator "^4.8.2"
-
-yeoman-generator@^3.1.1:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/yeoman-generator/-/yeoman-generator-3.2.0.tgz#02077d2d7ff28fedc1ed7dad7f9967fd7c3604cc"
-  integrity sha512-iR/qb2je3GdXtSfxgvOXxUW0Cp8+C6LaZaNlK2BAICzFNzwHtM10t/QBwz5Ea9nk6xVDQNj4Q889TjCXGuIv8w==
-  dependencies:
-    async "^2.6.0"
-    chalk "^2.3.0"
-    cli-table "^0.3.1"
-    cross-spawn "^6.0.5"
-    dargs "^6.0.0"
-    dateformat "^3.0.3"
-    debug "^4.1.0"
-    detect-conflict "^1.0.0"
-    error "^7.0.2"
-    find-up "^3.0.0"
-    github-username "^4.0.0"
-    istextorbinary "^2.2.1"
-    lodash "^4.17.10"
-    make-dir "^1.1.0"
-    mem-fs-editor "^5.0.0"
-    minimist "^1.2.0"
-    pretty-bytes "^5.1.0"
-    read-chunk "^3.0.0"
-    read-pkg-up "^4.0.0"
-    rimraf "^2.6.2"
-    run-async "^2.0.0"
-    shelljs "^0.8.0"
-    text-table "^0.2.0"
-    through2 "^3.0.0"
-    yeoman-environment "^2.0.5"
-
-yeoman-generator@^4.8.2:
-  version "4.13.0"
-  resolved "https://registry.yarnpkg.com/yeoman-generator/-/yeoman-generator-4.13.0.tgz#a6caeed8491fceea1f84f53e31795f25888b4672"
-  integrity sha512-f2/5N5IR3M2Ozm+QocvZQudlQITv2DwI6Mcxfy7R7gTTzaKgvUpgo/pQMJ+WQKm0KN0YMWCFOZpj0xFGxevc1w==
-  dependencies:
-    async "^2.6.2"
-    chalk "^2.4.2"
-    cli-table "^0.3.1"
-    cross-spawn "^6.0.5"
-    dargs "^6.1.0"
-    dateformat "^3.0.3"
-    debug "^4.1.1"
-    diff "^4.0.1"
-    error "^7.0.2"
-    find-up "^3.0.0"
-    github-username "^3.0.0"
-    istextorbinary "^2.5.1"
-    lodash "^4.17.11"
-    make-dir "^3.0.0"
-    mem-fs-editor "^7.0.1"
-    minimist "^1.2.5"
-    pretty-bytes "^5.2.0"
-    read-chunk "^3.2.0"
-    read-pkg-up "^5.0.0"
-    rimraf "^2.6.3"
-    run-async "^2.0.0"
-    semver "^7.2.1"
-    shelljs "^0.8.4"
-    text-table "^0.2.0"
-    through2 "^3.0.1"
-  optionalDependencies:
-    grouped-queue "^1.1.0"
-    yeoman-environment "^2.9.5"
-
-zip-stream@^2.1.2:
-  version "2.1.3"
-  resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-2.1.3.tgz#26cc4bdb93641a8590dd07112e1f77af1758865b"
-  integrity sha512-EkXc2JGcKhO5N5aZ7TmuNo45budRaFGHOmz24wtJR7znbNqDPmdZtUauKX6et8KAVseAMBOyWJqEpXcHTBsh7Q==
-  dependencies:
-    archiver-utils "^2.1.0"
-    compress-commons "^2.1.1"
-    readable-stream "^3.4.0"
+  version "20.2.9"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
+  integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==