diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml
index fa2a322..54dfbab 100644
--- a/.github/workflows/dart.yml
+++ b/.github/workflows/dart.yml
@@ -14,22 +14,22 @@
 
 jobs:
   job_001:
-    name: "analyze_and_format; linux; Dart 2.12.0; PKG: pkgs/shelf_packages_handler; `dart analyze --fatal-infos .`"
+    name: "analyze_and_format; linux; Dart 2.14.0; PKGS: pkgs/shelf_packages_handler, pkgs/shelf_proxy, pkgs/shelf_router, pkgs/shelf_router_generator, pkgs/shelf_static, pkgs/shelf_test_handler, pkgs/shelf_web_socket; `dart analyze --fatal-infos .`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
         uses: actions/cache@v3
         with:
           path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:2.12.0;packages:pkgs/shelf_packages_handler;commands:analyze"
+          key: "os:ubuntu-latest;pub-cache-hosted;sdk:2.14.0;packages:pkgs/shelf_packages_handler-pkgs/shelf_proxy-pkgs/shelf_router-pkgs/shelf_router_generator-pkgs/shelf_static-pkgs/shelf_test_handler-pkgs/shelf_web_socket;commands:analyze"
           restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:2.12.0;packages:pkgs/shelf_packages_handler
-            os:ubuntu-latest;pub-cache-hosted;sdk:2.12.0
+            os:ubuntu-latest;pub-cache-hosted;sdk:2.14.0;packages:pkgs/shelf_packages_handler-pkgs/shelf_proxy-pkgs/shelf_router-pkgs/shelf_router_generator-pkgs/shelf_static-pkgs/shelf_test_handler-pkgs/shelf_web_socket
+            os:ubuntu-latest;pub-cache-hosted;sdk:2.14.0
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
       - uses: dart-lang/setup-dart@v1.3
         with:
-          sdk: "2.12.0"
+          sdk: "2.14.0"
       - id: checkout
         uses: actions/checkout@v3
       - id: pkgs_shelf_packages_handler_pub_upgrade
@@ -41,53 +41,6 @@
         if: "always() && steps.pkgs_shelf_packages_handler_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf_packages_handler
         run: dart analyze --fatal-infos .
-  job_002:
-    name: "analyze_and_format; linux; Dart 2.12.0; PKG: pkgs/shelf_static; `dart analyze --fatal-infos .`"
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cache Pub hosted dependencies
-        uses: actions/cache@v3
-        with:
-          path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:2.12.0;packages:pkgs/shelf_static;commands:analyze"
-          restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:2.12.0;packages:pkgs/shelf_static
-            os:ubuntu-latest;pub-cache-hosted;sdk:2.12.0
-            os:ubuntu-latest;pub-cache-hosted
-            os:ubuntu-latest
-      - uses: dart-lang/setup-dart@v1.3
-        with:
-          sdk: "2.12.0"
-      - id: checkout
-        uses: actions/checkout@v3
-      - id: pkgs_shelf_static_pub_upgrade
-        name: pkgs/shelf_static; dart pub upgrade
-        if: "always() && steps.checkout.conclusion == 'success'"
-        working-directory: pkgs/shelf_static
-        run: dart pub upgrade
-      - name: "pkgs/shelf_static; dart analyze --fatal-infos ."
-        if: "always() && steps.pkgs_shelf_static_pub_upgrade.conclusion == 'success'"
-        working-directory: pkgs/shelf_static
-        run: dart analyze --fatal-infos .
-  job_003:
-    name: "analyze_and_format; linux; Dart 2.14.0; PKG: pkgs/shelf_proxy; `dart analyze --fatal-infos .`"
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cache Pub hosted dependencies
-        uses: actions/cache@v3
-        with:
-          path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:2.14.0;packages:pkgs/shelf_proxy;commands:analyze"
-          restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:2.14.0;packages:pkgs/shelf_proxy
-            os:ubuntu-latest;pub-cache-hosted;sdk:2.14.0
-            os:ubuntu-latest;pub-cache-hosted
-            os:ubuntu-latest
-      - uses: dart-lang/setup-dart@v1.3
-        with:
-          sdk: "2.14.0"
-      - id: checkout
-        uses: actions/checkout@v3
       - id: pkgs_shelf_proxy_pub_upgrade
         name: pkgs/shelf_proxy; dart pub upgrade
         if: "always() && steps.checkout.conclusion == 'success'"
@@ -97,7 +50,52 @@
         if: "always() && steps.pkgs_shelf_proxy_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf_proxy
         run: dart analyze --fatal-infos .
-  job_004:
+      - id: pkgs_shelf_router_pub_upgrade
+        name: pkgs/shelf_router; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_router
+        run: dart pub upgrade
+      - name: "pkgs/shelf_router; dart analyze --fatal-infos ."
+        if: "always() && steps.pkgs_shelf_router_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_router
+        run: dart analyze --fatal-infos .
+      - id: pkgs_shelf_router_generator_pub_upgrade
+        name: pkgs/shelf_router_generator; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_router_generator
+        run: dart pub upgrade
+      - name: "pkgs/shelf_router_generator; dart analyze --fatal-infos ."
+        if: "always() && steps.pkgs_shelf_router_generator_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_router_generator
+        run: dart analyze --fatal-infos .
+      - id: pkgs_shelf_static_pub_upgrade
+        name: pkgs/shelf_static; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_static
+        run: dart pub upgrade
+      - name: "pkgs/shelf_static; dart analyze --fatal-infos ."
+        if: "always() && steps.pkgs_shelf_static_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_static
+        run: dart analyze --fatal-infos .
+      - id: pkgs_shelf_test_handler_pub_upgrade
+        name: pkgs/shelf_test_handler; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: dart pub upgrade
+      - name: "pkgs/shelf_test_handler; dart analyze --fatal-infos ."
+        if: "always() && steps.pkgs_shelf_test_handler_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: dart analyze --fatal-infos .
+      - id: pkgs_shelf_web_socket_pub_upgrade
+        name: pkgs/shelf_web_socket; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_web_socket
+        run: dart pub upgrade
+      - name: "pkgs/shelf_web_socket; dart analyze --fatal-infos ."
+        if: "always() && steps.pkgs_shelf_web_socket_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_web_socket
+        run: dart analyze --fatal-infos .
+  job_002:
     name: "analyze_and_format; linux; Dart 2.16.0; PKG: pkgs/shelf; `dart analyze --fatal-infos .`"
     runs-on: ubuntu-latest
     steps:
@@ -125,17 +123,17 @@
         if: "always() && steps.pkgs_shelf_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf
         run: dart analyze --fatal-infos .
-  job_005:
-    name: "analyze_and_format; linux; Dart dev; PKG: pkgs/shelf; `dart analyze --fatal-infos .`"
+  job_003:
+    name: "analyze_and_format; linux; Dart dev; PKGS: pkgs/shelf, pkgs/shelf_packages_handler, pkgs/shelf_proxy, pkgs/shelf_router, pkgs/shelf_router_generator, pkgs/shelf_static, pkgs/shelf_test_handler, pkgs/shelf_web_socket; `dart analyze --fatal-infos .`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
         uses: actions/cache@v3
         with:
           path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf;commands:analyze"
+          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf-pkgs/shelf_packages_handler-pkgs/shelf_proxy-pkgs/shelf_router-pkgs/shelf_router_generator-pkgs/shelf_static-pkgs/shelf_test_handler-pkgs/shelf_web_socket;commands:analyze"
           restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf
+            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf-pkgs/shelf_packages_handler-pkgs/shelf_proxy-pkgs/shelf_router-pkgs/shelf_router_generator-pkgs/shelf_static-pkgs/shelf_test_handler-pkgs/shelf_web_socket
             os:ubuntu-latest;pub-cache-hosted;sdk:dev
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
@@ -153,25 +151,6 @@
         if: "always() && steps.pkgs_shelf_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf
         run: dart analyze --fatal-infos .
-  job_006:
-    name: "analyze_and_format; linux; Dart dev; PKG: pkgs/shelf_packages_handler; `dart analyze --fatal-infos .`"
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cache Pub hosted dependencies
-        uses: actions/cache@v3
-        with:
-          path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_packages_handler;commands:analyze"
-          restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_packages_handler
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev
-            os:ubuntu-latest;pub-cache-hosted
-            os:ubuntu-latest
-      - uses: dart-lang/setup-dart@v1.3
-        with:
-          sdk: dev
-      - id: checkout
-        uses: actions/checkout@v3
       - id: pkgs_shelf_packages_handler_pub_upgrade
         name: pkgs/shelf_packages_handler; dart pub upgrade
         if: "always() && steps.checkout.conclusion == 'success'"
@@ -181,25 +160,6 @@
         if: "always() && steps.pkgs_shelf_packages_handler_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf_packages_handler
         run: dart analyze --fatal-infos .
-  job_007:
-    name: "analyze_and_format; linux; Dart dev; PKG: pkgs/shelf_proxy; `dart analyze --fatal-infos .`"
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cache Pub hosted dependencies
-        uses: actions/cache@v3
-        with:
-          path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_proxy;commands:analyze"
-          restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_proxy
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev
-            os:ubuntu-latest;pub-cache-hosted
-            os:ubuntu-latest
-      - uses: dart-lang/setup-dart@v1.3
-        with:
-          sdk: dev
-      - id: checkout
-        uses: actions/checkout@v3
       - id: pkgs_shelf_proxy_pub_upgrade
         name: pkgs/shelf_proxy; dart pub upgrade
         if: "always() && steps.checkout.conclusion == 'success'"
@@ -209,25 +169,24 @@
         if: "always() && steps.pkgs_shelf_proxy_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf_proxy
         run: dart analyze --fatal-infos .
-  job_008:
-    name: "analyze_and_format; linux; Dart dev; PKG: pkgs/shelf_static; `dart analyze --fatal-infos .`"
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cache Pub hosted dependencies
-        uses: actions/cache@v3
-        with:
-          path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_static;commands:analyze"
-          restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_static
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev
-            os:ubuntu-latest;pub-cache-hosted
-            os:ubuntu-latest
-      - uses: dart-lang/setup-dart@v1.3
-        with:
-          sdk: dev
-      - id: checkout
-        uses: actions/checkout@v3
+      - id: pkgs_shelf_router_pub_upgrade
+        name: pkgs/shelf_router; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_router
+        run: dart pub upgrade
+      - name: "pkgs/shelf_router; dart analyze --fatal-infos ."
+        if: "always() && steps.pkgs_shelf_router_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_router
+        run: dart analyze --fatal-infos .
+      - id: pkgs_shelf_router_generator_pub_upgrade
+        name: pkgs/shelf_router_generator; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_router_generator
+        run: dart pub upgrade
+      - name: "pkgs/shelf_router_generator; dart analyze --fatal-infos ."
+        if: "always() && steps.pkgs_shelf_router_generator_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_router_generator
+        run: dart analyze --fatal-infos .
       - id: pkgs_shelf_static_pub_upgrade
         name: pkgs/shelf_static; dart pub upgrade
         if: "always() && steps.checkout.conclusion == 'success'"
@@ -237,17 +196,35 @@
         if: "always() && steps.pkgs_shelf_static_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf_static
         run: dart analyze --fatal-infos .
-  job_009:
-    name: "analyze_and_format; linux; Dart dev; PKG: pkgs/shelf; `dart format --output=none --set-exit-if-changed .`"
+      - id: pkgs_shelf_test_handler_pub_upgrade
+        name: pkgs/shelf_test_handler; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: dart pub upgrade
+      - name: "pkgs/shelf_test_handler; dart analyze --fatal-infos ."
+        if: "always() && steps.pkgs_shelf_test_handler_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: dart analyze --fatal-infos .
+      - id: pkgs_shelf_web_socket_pub_upgrade
+        name: pkgs/shelf_web_socket; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_web_socket
+        run: dart pub upgrade
+      - name: "pkgs/shelf_web_socket; dart analyze --fatal-infos ."
+        if: "always() && steps.pkgs_shelf_web_socket_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_web_socket
+        run: dart analyze --fatal-infos .
+  job_004:
+    name: "analyze_and_format; linux; Dart dev; PKGS: pkgs/shelf, pkgs/shelf_packages_handler, pkgs/shelf_proxy, pkgs/shelf_router, pkgs/shelf_router_generator, pkgs/shelf_static, pkgs/shelf_test_handler, pkgs/shelf_web_socket; `dart format --output=none --set-exit-if-changed .`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
         uses: actions/cache@v3
         with:
           path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf;commands:format"
+          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf-pkgs/shelf_packages_handler-pkgs/shelf_proxy-pkgs/shelf_router-pkgs/shelf_router_generator-pkgs/shelf_static-pkgs/shelf_test_handler-pkgs/shelf_web_socket;commands:format"
           restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf
+            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf-pkgs/shelf_packages_handler-pkgs/shelf_proxy-pkgs/shelf_router-pkgs/shelf_router_generator-pkgs/shelf_static-pkgs/shelf_test_handler-pkgs/shelf_web_socket
             os:ubuntu-latest;pub-cache-hosted;sdk:dev
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
@@ -265,25 +242,6 @@
         if: "always() && steps.pkgs_shelf_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf
         run: "dart format --output=none --set-exit-if-changed ."
-  job_010:
-    name: "analyze_and_format; linux; Dart dev; PKG: pkgs/shelf_packages_handler; `dart format --output=none --set-exit-if-changed .`"
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cache Pub hosted dependencies
-        uses: actions/cache@v3
-        with:
-          path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_packages_handler;commands:format"
-          restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_packages_handler
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev
-            os:ubuntu-latest;pub-cache-hosted
-            os:ubuntu-latest
-      - uses: dart-lang/setup-dart@v1.3
-        with:
-          sdk: dev
-      - id: checkout
-        uses: actions/checkout@v3
       - id: pkgs_shelf_packages_handler_pub_upgrade
         name: pkgs/shelf_packages_handler; dart pub upgrade
         if: "always() && steps.checkout.conclusion == 'success'"
@@ -293,25 +251,6 @@
         if: "always() && steps.pkgs_shelf_packages_handler_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf_packages_handler
         run: "dart format --output=none --set-exit-if-changed ."
-  job_011:
-    name: "analyze_and_format; linux; Dart dev; PKG: pkgs/shelf_proxy; `dart format --output=none --set-exit-if-changed .`"
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cache Pub hosted dependencies
-        uses: actions/cache@v3
-        with:
-          path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_proxy;commands:format"
-          restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_proxy
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev
-            os:ubuntu-latest;pub-cache-hosted
-            os:ubuntu-latest
-      - uses: dart-lang/setup-dart@v1.3
-        with:
-          sdk: dev
-      - id: checkout
-        uses: actions/checkout@v3
       - id: pkgs_shelf_proxy_pub_upgrade
         name: pkgs/shelf_proxy; dart pub upgrade
         if: "always() && steps.checkout.conclusion == 'success'"
@@ -321,25 +260,24 @@
         if: "always() && steps.pkgs_shelf_proxy_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf_proxy
         run: "dart format --output=none --set-exit-if-changed ."
-  job_012:
-    name: "analyze_and_format; linux; Dart dev; PKG: pkgs/shelf_static; `dart format --output=none --set-exit-if-changed .`"
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cache Pub hosted dependencies
-        uses: actions/cache@v3
-        with:
-          path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_static;commands:format"
-          restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_static
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev
-            os:ubuntu-latest;pub-cache-hosted
-            os:ubuntu-latest
-      - uses: dart-lang/setup-dart@v1.3
-        with:
-          sdk: dev
-      - id: checkout
-        uses: actions/checkout@v3
+      - id: pkgs_shelf_router_pub_upgrade
+        name: pkgs/shelf_router; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_router
+        run: dart pub upgrade
+      - name: "pkgs/shelf_router; dart format --output=none --set-exit-if-changed ."
+        if: "always() && steps.pkgs_shelf_router_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_router
+        run: "dart format --output=none --set-exit-if-changed ."
+      - id: pkgs_shelf_router_generator_pub_upgrade
+        name: pkgs/shelf_router_generator; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_router_generator
+        run: dart pub upgrade
+      - name: "pkgs/shelf_router_generator; dart format --output=none --set-exit-if-changed ."
+        if: "always() && steps.pkgs_shelf_router_generator_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_router_generator
+        run: "dart format --output=none --set-exit-if-changed ."
       - id: pkgs_shelf_static_pub_upgrade
         name: pkgs/shelf_static; dart pub upgrade
         if: "always() && steps.checkout.conclusion == 'success'"
@@ -349,23 +287,41 @@
         if: "always() && steps.pkgs_shelf_static_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf_static
         run: "dart format --output=none --set-exit-if-changed ."
-  job_013:
-    name: "unit_test; linux; Dart 2.12.0; PKG: pkgs/shelf_packages_handler; `dart test --test-randomize-ordering-seed=random`"
+      - id: pkgs_shelf_test_handler_pub_upgrade
+        name: pkgs/shelf_test_handler; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: dart pub upgrade
+      - name: "pkgs/shelf_test_handler; dart format --output=none --set-exit-if-changed ."
+        if: "always() && steps.pkgs_shelf_test_handler_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: "dart format --output=none --set-exit-if-changed ."
+      - id: pkgs_shelf_web_socket_pub_upgrade
+        name: pkgs/shelf_web_socket; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_web_socket
+        run: dart pub upgrade
+      - name: "pkgs/shelf_web_socket; dart format --output=none --set-exit-if-changed ."
+        if: "always() && steps.pkgs_shelf_web_socket_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_web_socket
+        run: "dart format --output=none --set-exit-if-changed ."
+  job_005:
+    name: "unit_test; linux; Dart 2.14.0; PKGS: pkgs/shelf_packages_handler, pkgs/shelf_proxy, pkgs/shelf_router, pkgs/shelf_router_generator, pkgs/shelf_static, pkgs/shelf_test_handler, pkgs/shelf_web_socket; `dart test --test-randomize-ordering-seed=random`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
         uses: actions/cache@v3
         with:
           path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:2.12.0;packages:pkgs/shelf_packages_handler;commands:test_0"
+          key: "os:ubuntu-latest;pub-cache-hosted;sdk:2.14.0;packages:pkgs/shelf_packages_handler-pkgs/shelf_proxy-pkgs/shelf_router-pkgs/shelf_router_generator-pkgs/shelf_static-pkgs/shelf_test_handler-pkgs/shelf_web_socket;commands:test_0"
           restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:2.12.0;packages:pkgs/shelf_packages_handler
-            os:ubuntu-latest;pub-cache-hosted;sdk:2.12.0
+            os:ubuntu-latest;pub-cache-hosted;sdk:2.14.0;packages:pkgs/shelf_packages_handler-pkgs/shelf_proxy-pkgs/shelf_router-pkgs/shelf_router_generator-pkgs/shelf_static-pkgs/shelf_test_handler-pkgs/shelf_web_socket
+            os:ubuntu-latest;pub-cache-hosted;sdk:2.14.0
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
       - uses: dart-lang/setup-dart@v1.3
         with:
-          sdk: "2.12.0"
+          sdk: "2.14.0"
       - id: checkout
         uses: actions/checkout@v3
       - id: pkgs_shelf_packages_handler_pub_upgrade
@@ -377,79 +333,6 @@
         if: "always() && steps.pkgs_shelf_packages_handler_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf_packages_handler
         run: "dart test --test-randomize-ordering-seed=random"
-    needs:
-      - job_001
-      - job_002
-      - job_003
-      - job_004
-      - job_005
-      - job_006
-      - job_007
-      - job_008
-      - job_009
-      - job_010
-      - job_011
-      - job_012
-  job_014:
-    name: "unit_test; linux; Dart 2.12.0; PKG: pkgs/shelf_static; `dart test --test-randomize-ordering-seed=random`"
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cache Pub hosted dependencies
-        uses: actions/cache@v3
-        with:
-          path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:2.12.0;packages:pkgs/shelf_static;commands:test_0"
-          restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:2.12.0;packages:pkgs/shelf_static
-            os:ubuntu-latest;pub-cache-hosted;sdk:2.12.0
-            os:ubuntu-latest;pub-cache-hosted
-            os:ubuntu-latest
-      - uses: dart-lang/setup-dart@v1.3
-        with:
-          sdk: "2.12.0"
-      - id: checkout
-        uses: actions/checkout@v3
-      - id: pkgs_shelf_static_pub_upgrade
-        name: pkgs/shelf_static; dart pub upgrade
-        if: "always() && steps.checkout.conclusion == 'success'"
-        working-directory: pkgs/shelf_static
-        run: dart pub upgrade
-      - name: "pkgs/shelf_static; dart test --test-randomize-ordering-seed=random"
-        if: "always() && steps.pkgs_shelf_static_pub_upgrade.conclusion == 'success'"
-        working-directory: pkgs/shelf_static
-        run: "dart test --test-randomize-ordering-seed=random"
-    needs:
-      - job_001
-      - job_002
-      - job_003
-      - job_004
-      - job_005
-      - job_006
-      - job_007
-      - job_008
-      - job_009
-      - job_010
-      - job_011
-      - job_012
-  job_015:
-    name: "unit_test; linux; Dart 2.14.0; PKG: pkgs/shelf_proxy; `dart test --test-randomize-ordering-seed=random`"
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cache Pub hosted dependencies
-        uses: actions/cache@v3
-        with:
-          path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:2.14.0;packages:pkgs/shelf_proxy;commands:test_0"
-          restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:2.14.0;packages:pkgs/shelf_proxy
-            os:ubuntu-latest;pub-cache-hosted;sdk:2.14.0
-            os:ubuntu-latest;pub-cache-hosted
-            os:ubuntu-latest
-      - uses: dart-lang/setup-dart@v1.3
-        with:
-          sdk: "2.14.0"
-      - id: checkout
-        uses: actions/checkout@v3
       - id: pkgs_shelf_proxy_pub_upgrade
         name: pkgs/shelf_proxy; dart pub upgrade
         if: "always() && steps.checkout.conclusion == 'success'"
@@ -459,20 +342,90 @@
         if: "always() && steps.pkgs_shelf_proxy_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf_proxy
         run: "dart test --test-randomize-ordering-seed=random"
+      - id: pkgs_shelf_router_pub_upgrade
+        name: pkgs/shelf_router; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_router
+        run: dart pub upgrade
+      - name: "pkgs/shelf_router; dart test --test-randomize-ordering-seed=random"
+        if: "always() && steps.pkgs_shelf_router_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_router
+        run: "dart test --test-randomize-ordering-seed=random"
+      - id: pkgs_shelf_router_generator_pub_upgrade
+        name: pkgs/shelf_router_generator; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_router_generator
+        run: dart pub upgrade
+      - name: "pkgs/shelf_router_generator; dart test --test-randomize-ordering-seed=random"
+        if: "always() && steps.pkgs_shelf_router_generator_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_router_generator
+        run: "dart test --test-randomize-ordering-seed=random"
+      - id: pkgs_shelf_static_pub_upgrade
+        name: pkgs/shelf_static; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_static
+        run: dart pub upgrade
+      - name: "pkgs/shelf_static; dart test --test-randomize-ordering-seed=random"
+        if: "always() && steps.pkgs_shelf_static_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_static
+        run: "dart test --test-randomize-ordering-seed=random"
+      - id: pkgs_shelf_test_handler_pub_upgrade
+        name: pkgs/shelf_test_handler; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: dart pub upgrade
+      - name: "pkgs/shelf_test_handler; dart test --test-randomize-ordering-seed=random"
+        if: "always() && steps.pkgs_shelf_test_handler_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: "dart test --test-randomize-ordering-seed=random"
+      - id: pkgs_shelf_web_socket_pub_upgrade
+        name: pkgs/shelf_web_socket; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_web_socket
+        run: dart pub upgrade
+      - name: "pkgs/shelf_web_socket; dart test --test-randomize-ordering-seed=random"
+        if: "always() && steps.pkgs_shelf_web_socket_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_web_socket
+        run: "dart test --test-randomize-ordering-seed=random"
     needs:
       - job_001
       - job_002
       - job_003
       - job_004
-      - job_005
-      - job_006
-      - job_007
-      - job_008
-      - job_009
-      - job_010
-      - job_011
-      - job_012
-  job_016:
+  job_006:
+    name: "unit_test; linux; Dart 2.14.0; PKG: pkgs/shelf_test_handler; `dart test --test-randomize-ordering-seed=random -p chrome`"
+    runs-on: ubuntu-latest
+    steps:
+      - name: Cache Pub hosted dependencies
+        uses: actions/cache@v3
+        with:
+          path: "~/.pub-cache/hosted"
+          key: "os:ubuntu-latest;pub-cache-hosted;sdk:2.14.0;packages:pkgs/shelf_test_handler;commands:test_1"
+          restore-keys: |
+            os:ubuntu-latest;pub-cache-hosted;sdk:2.14.0;packages:pkgs/shelf_test_handler
+            os:ubuntu-latest;pub-cache-hosted;sdk:2.14.0
+            os:ubuntu-latest;pub-cache-hosted
+            os:ubuntu-latest
+      - uses: dart-lang/setup-dart@v1.3
+        with:
+          sdk: "2.14.0"
+      - id: checkout
+        uses: actions/checkout@v3
+      - id: pkgs_shelf_test_handler_pub_upgrade
+        name: pkgs/shelf_test_handler; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: dart pub upgrade
+      - name: "pkgs/shelf_test_handler; dart test --test-randomize-ordering-seed=random -p chrome"
+        if: "always() && steps.pkgs_shelf_test_handler_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: "dart test --test-randomize-ordering-seed=random -p chrome"
+    needs:
+      - job_001
+      - job_002
+      - job_003
+      - job_004
+  job_007:
     name: "unit_test; linux; Dart 2.16.0; PKG: pkgs/shelf; `dart test --test-randomize-ordering-seed=random -p chrome`"
     runs-on: ubuntu-latest
     steps:
@@ -505,15 +458,7 @@
       - job_002
       - job_003
       - job_004
-      - job_005
-      - job_006
-      - job_007
-      - job_008
-      - job_009
-      - job_010
-      - job_011
-      - job_012
-  job_017:
+  job_008:
     name: "unit_test; linux; Dart 2.16.0; PKG: pkgs/shelf; `dart test --test-randomize-ordering-seed=random`"
     runs-on: ubuntu-latest
     steps:
@@ -546,25 +491,17 @@
       - job_002
       - job_003
       - job_004
-      - job_005
-      - job_006
-      - job_007
-      - job_008
-      - job_009
-      - job_010
-      - job_011
-      - job_012
-  job_018:
-    name: "unit_test; linux; Dart dev; PKG: pkgs/shelf; `dart test --test-randomize-ordering-seed=random -p chrome`"
+  job_009:
+    name: "unit_test; linux; Dart dev; PKGS: pkgs/shelf, pkgs/shelf_test_handler; `dart test --test-randomize-ordering-seed=random -p chrome`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
         uses: actions/cache@v3
         with:
           path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf;commands:test_1"
+          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf-pkgs/shelf_test_handler;commands:test_1"
           restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf
+            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf-pkgs/shelf_test_handler
             os:ubuntu-latest;pub-cache-hosted;sdk:dev
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
@@ -582,30 +519,31 @@
         if: "always() && steps.pkgs_shelf_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf
         run: "dart test --test-randomize-ordering-seed=random -p chrome"
+      - id: pkgs_shelf_test_handler_pub_upgrade
+        name: pkgs/shelf_test_handler; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: dart pub upgrade
+      - name: "pkgs/shelf_test_handler; dart test --test-randomize-ordering-seed=random -p chrome"
+        if: "always() && steps.pkgs_shelf_test_handler_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: "dart test --test-randomize-ordering-seed=random -p chrome"
     needs:
       - job_001
       - job_002
       - job_003
       - job_004
-      - job_005
-      - job_006
-      - job_007
-      - job_008
-      - job_009
-      - job_010
-      - job_011
-      - job_012
-  job_019:
-    name: "unit_test; linux; Dart dev; PKG: pkgs/shelf; `dart test --test-randomize-ordering-seed=random`"
+  job_010:
+    name: "unit_test; linux; Dart dev; PKGS: pkgs/shelf, pkgs/shelf_packages_handler, pkgs/shelf_proxy, pkgs/shelf_router, pkgs/shelf_router_generator, pkgs/shelf_static, pkgs/shelf_test_handler, pkgs/shelf_web_socket; `dart test --test-randomize-ordering-seed=random`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
         uses: actions/cache@v3
         with:
           path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf;commands:test_0"
+          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf-pkgs/shelf_packages_handler-pkgs/shelf_proxy-pkgs/shelf_router-pkgs/shelf_router_generator-pkgs/shelf_static-pkgs/shelf_test_handler-pkgs/shelf_web_socket;commands:test_0"
           restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf
+            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf-pkgs/shelf_packages_handler-pkgs/shelf_proxy-pkgs/shelf_router-pkgs/shelf_router_generator-pkgs/shelf_static-pkgs/shelf_test_handler-pkgs/shelf_web_socket
             os:ubuntu-latest;pub-cache-hosted;sdk:dev
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
@@ -623,38 +561,6 @@
         if: "always() && steps.pkgs_shelf_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf
         run: "dart test --test-randomize-ordering-seed=random"
-    needs:
-      - job_001
-      - job_002
-      - job_003
-      - job_004
-      - job_005
-      - job_006
-      - job_007
-      - job_008
-      - job_009
-      - job_010
-      - job_011
-      - job_012
-  job_020:
-    name: "unit_test; linux; Dart dev; PKG: pkgs/shelf_packages_handler; `dart test --test-randomize-ordering-seed=random`"
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cache Pub hosted dependencies
-        uses: actions/cache@v3
-        with:
-          path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_packages_handler;commands:test_0"
-          restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_packages_handler
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev
-            os:ubuntu-latest;pub-cache-hosted
-            os:ubuntu-latest
-      - uses: dart-lang/setup-dart@v1.3
-        with:
-          sdk: dev
-      - id: checkout
-        uses: actions/checkout@v3
       - id: pkgs_shelf_packages_handler_pub_upgrade
         name: pkgs/shelf_packages_handler; dart pub upgrade
         if: "always() && steps.checkout.conclusion == 'success'"
@@ -664,38 +570,6 @@
         if: "always() && steps.pkgs_shelf_packages_handler_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf_packages_handler
         run: "dart test --test-randomize-ordering-seed=random"
-    needs:
-      - job_001
-      - job_002
-      - job_003
-      - job_004
-      - job_005
-      - job_006
-      - job_007
-      - job_008
-      - job_009
-      - job_010
-      - job_011
-      - job_012
-  job_021:
-    name: "unit_test; linux; Dart dev; PKG: pkgs/shelf_proxy; `dart test --test-randomize-ordering-seed=random`"
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cache Pub hosted dependencies
-        uses: actions/cache@v3
-        with:
-          path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_proxy;commands:test_0"
-          restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_proxy
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev
-            os:ubuntu-latest;pub-cache-hosted
-            os:ubuntu-latest
-      - uses: dart-lang/setup-dart@v1.3
-        with:
-          sdk: dev
-      - id: checkout
-        uses: actions/checkout@v3
       - id: pkgs_shelf_proxy_pub_upgrade
         name: pkgs/shelf_proxy; dart pub upgrade
         if: "always() && steps.checkout.conclusion == 'success'"
@@ -705,38 +579,24 @@
         if: "always() && steps.pkgs_shelf_proxy_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf_proxy
         run: "dart test --test-randomize-ordering-seed=random"
-    needs:
-      - job_001
-      - job_002
-      - job_003
-      - job_004
-      - job_005
-      - job_006
-      - job_007
-      - job_008
-      - job_009
-      - job_010
-      - job_011
-      - job_012
-  job_022:
-    name: "unit_test; linux; Dart dev; PKG: pkgs/shelf_static; `dart test --test-randomize-ordering-seed=random`"
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cache Pub hosted dependencies
-        uses: actions/cache@v3
-        with:
-          path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_static;commands:test_0"
-          restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/shelf_static
-            os:ubuntu-latest;pub-cache-hosted;sdk:dev
-            os:ubuntu-latest;pub-cache-hosted
-            os:ubuntu-latest
-      - uses: dart-lang/setup-dart@v1.3
-        with:
-          sdk: dev
-      - id: checkout
-        uses: actions/checkout@v3
+      - id: pkgs_shelf_router_pub_upgrade
+        name: pkgs/shelf_router; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_router
+        run: dart pub upgrade
+      - name: "pkgs/shelf_router; dart test --test-randomize-ordering-seed=random"
+        if: "always() && steps.pkgs_shelf_router_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_router
+        run: "dart test --test-randomize-ordering-seed=random"
+      - id: pkgs_shelf_router_generator_pub_upgrade
+        name: pkgs/shelf_router_generator; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_router_generator
+        run: dart pub upgrade
+      - name: "pkgs/shelf_router_generator; dart test --test-randomize-ordering-seed=random"
+        if: "always() && steps.pkgs_shelf_router_generator_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_router_generator
+        run: "dart test --test-randomize-ordering-seed=random"
       - id: pkgs_shelf_static_pub_upgrade
         name: pkgs/shelf_static; dart pub upgrade
         if: "always() && steps.checkout.conclusion == 'success'"
@@ -746,26 +606,36 @@
         if: "always() && steps.pkgs_shelf_static_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf_static
         run: "dart test --test-randomize-ordering-seed=random"
+      - id: pkgs_shelf_test_handler_pub_upgrade
+        name: pkgs/shelf_test_handler; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: dart pub upgrade
+      - name: "pkgs/shelf_test_handler; dart test --test-randomize-ordering-seed=random"
+        if: "always() && steps.pkgs_shelf_test_handler_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: "dart test --test-randomize-ordering-seed=random"
+      - id: pkgs_shelf_web_socket_pub_upgrade
+        name: pkgs/shelf_web_socket; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_web_socket
+        run: dart pub upgrade
+      - name: "pkgs/shelf_web_socket; dart test --test-randomize-ordering-seed=random"
+        if: "always() && steps.pkgs_shelf_web_socket_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_web_socket
+        run: "dart test --test-randomize-ordering-seed=random"
     needs:
       - job_001
       - job_002
       - job_003
       - job_004
-      - job_005
-      - job_006
-      - job_007
-      - job_008
-      - job_009
-      - job_010
-      - job_011
-      - job_012
-  job_023:
-    name: "unit_test; windows; Dart 2.12.0; PKG: pkgs/shelf_packages_handler; `dart test --test-randomize-ordering-seed=random`"
+  job_011:
+    name: "unit_test; windows; Dart 2.14.0; PKGS: pkgs/shelf_packages_handler, pkgs/shelf_static, pkgs/shelf_test_handler, pkgs/shelf_web_socket; `dart test --test-randomize-ordering-seed=random`"
     runs-on: windows-latest
     steps:
       - uses: dart-lang/setup-dart@v1.3
         with:
-          sdk: "2.12.0"
+          sdk: "2.14.0"
       - id: checkout
         uses: actions/checkout@v3
       - id: pkgs_shelf_packages_handler_pub_upgrade
@@ -777,28 +647,6 @@
         if: "always() && steps.pkgs_shelf_packages_handler_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf_packages_handler
         run: "dart test --test-randomize-ordering-seed=random"
-    needs:
-      - job_001
-      - job_002
-      - job_003
-      - job_004
-      - job_005
-      - job_006
-      - job_007
-      - job_008
-      - job_009
-      - job_010
-      - job_011
-      - job_012
-  job_024:
-    name: "unit_test; windows; Dart 2.12.0; PKG: pkgs/shelf_static; `dart test --test-randomize-ordering-seed=random`"
-    runs-on: windows-latest
-    steps:
-      - uses: dart-lang/setup-dart@v1.3
-        with:
-          sdk: "2.12.0"
-      - id: checkout
-        uses: actions/checkout@v3
       - id: pkgs_shelf_static_pub_upgrade
         name: pkgs/shelf_static; dart pub upgrade
         if: "always() && steps.checkout.conclusion == 'success'"
@@ -808,20 +656,53 @@
         if: "always() && steps.pkgs_shelf_static_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf_static
         run: "dart test --test-randomize-ordering-seed=random"
+      - id: pkgs_shelf_test_handler_pub_upgrade
+        name: pkgs/shelf_test_handler; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: dart pub upgrade
+      - name: "pkgs/shelf_test_handler; dart test --test-randomize-ordering-seed=random"
+        if: "always() && steps.pkgs_shelf_test_handler_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: "dart test --test-randomize-ordering-seed=random"
+      - id: pkgs_shelf_web_socket_pub_upgrade
+        name: pkgs/shelf_web_socket; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_web_socket
+        run: dart pub upgrade
+      - name: "pkgs/shelf_web_socket; dart test --test-randomize-ordering-seed=random"
+        if: "always() && steps.pkgs_shelf_web_socket_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_web_socket
+        run: "dart test --test-randomize-ordering-seed=random"
     needs:
       - job_001
       - job_002
       - job_003
       - job_004
-      - job_005
-      - job_006
-      - job_007
-      - job_008
-      - job_009
-      - job_010
-      - job_011
-      - job_012
-  job_025:
+  job_012:
+    name: "unit_test; windows; Dart 2.14.0; PKG: pkgs/shelf_test_handler; `dart test --test-randomize-ordering-seed=random -p chrome`"
+    runs-on: windows-latest
+    steps:
+      - uses: dart-lang/setup-dart@v1.3
+        with:
+          sdk: "2.14.0"
+      - id: checkout
+        uses: actions/checkout@v3
+      - id: pkgs_shelf_test_handler_pub_upgrade
+        name: pkgs/shelf_test_handler; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: dart pub upgrade
+      - name: "pkgs/shelf_test_handler; dart test --test-randomize-ordering-seed=random -p chrome"
+        if: "always() && steps.pkgs_shelf_test_handler_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: "dart test --test-randomize-ordering-seed=random -p chrome"
+    needs:
+      - job_001
+      - job_002
+      - job_003
+      - job_004
+  job_013:
     name: "unit_test; windows; Dart 2.16.0; PKG: pkgs/shelf; `dart test --test-randomize-ordering-seed=random -p chrome`"
     runs-on: windows-latest
     steps:
@@ -844,16 +725,8 @@
       - job_002
       - job_003
       - job_004
-      - job_005
-      - job_006
-      - job_007
-      - job_008
-      - job_009
-      - job_010
-      - job_011
-      - job_012
-  job_026:
-    name: "unit_test; windows; Dart dev; PKG: pkgs/shelf; `dart test --test-randomize-ordering-seed=random -p chrome`"
+  job_014:
+    name: "unit_test; windows; Dart dev; PKGS: pkgs/shelf, pkgs/shelf_test_handler; `dart test --test-randomize-ordering-seed=random -p chrome`"
     runs-on: windows-latest
     steps:
       - uses: dart-lang/setup-dart@v1.3
@@ -870,21 +743,22 @@
         if: "always() && steps.pkgs_shelf_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf
         run: "dart test --test-randomize-ordering-seed=random -p chrome"
+      - id: pkgs_shelf_test_handler_pub_upgrade
+        name: pkgs/shelf_test_handler; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: dart pub upgrade
+      - name: "pkgs/shelf_test_handler; dart test --test-randomize-ordering-seed=random -p chrome"
+        if: "always() && steps.pkgs_shelf_test_handler_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: "dart test --test-randomize-ordering-seed=random -p chrome"
     needs:
       - job_001
       - job_002
       - job_003
       - job_004
-      - job_005
-      - job_006
-      - job_007
-      - job_008
-      - job_009
-      - job_010
-      - job_011
-      - job_012
-  job_027:
-    name: "unit_test; windows; Dart dev; PKG: pkgs/shelf_packages_handler; `dart test --test-randomize-ordering-seed=random`"
+  job_015:
+    name: "unit_test; windows; Dart dev; PKGS: pkgs/shelf_packages_handler, pkgs/shelf_static, pkgs/shelf_test_handler, pkgs/shelf_web_socket; `dart test --test-randomize-ordering-seed=random`"
     runs-on: windows-latest
     steps:
       - uses: dart-lang/setup-dart@v1.3
@@ -901,28 +775,6 @@
         if: "always() && steps.pkgs_shelf_packages_handler_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf_packages_handler
         run: "dart test --test-randomize-ordering-seed=random"
-    needs:
-      - job_001
-      - job_002
-      - job_003
-      - job_004
-      - job_005
-      - job_006
-      - job_007
-      - job_008
-      - job_009
-      - job_010
-      - job_011
-      - job_012
-  job_028:
-    name: "unit_test; windows; Dart dev; PKG: pkgs/shelf_static; `dart test --test-randomize-ordering-seed=random`"
-    runs-on: windows-latest
-    steps:
-      - uses: dart-lang/setup-dart@v1.3
-        with:
-          sdk: dev
-      - id: checkout
-        uses: actions/checkout@v3
       - id: pkgs_shelf_static_pub_upgrade
         name: pkgs/shelf_static; dart pub upgrade
         if: "always() && steps.checkout.conclusion == 'success'"
@@ -932,16 +784,26 @@
         if: "always() && steps.pkgs_shelf_static_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/shelf_static
         run: "dart test --test-randomize-ordering-seed=random"
+      - id: pkgs_shelf_test_handler_pub_upgrade
+        name: pkgs/shelf_test_handler; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: dart pub upgrade
+      - name: "pkgs/shelf_test_handler; dart test --test-randomize-ordering-seed=random"
+        if: "always() && steps.pkgs_shelf_test_handler_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_test_handler
+        run: "dart test --test-randomize-ordering-seed=random"
+      - id: pkgs_shelf_web_socket_pub_upgrade
+        name: pkgs/shelf_web_socket; dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/shelf_web_socket
+        run: dart pub upgrade
+      - name: "pkgs/shelf_web_socket; dart test --test-randomize-ordering-seed=random"
+        if: "always() && steps.pkgs_shelf_web_socket_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/shelf_web_socket
+        run: "dart test --test-randomize-ordering-seed=random"
     needs:
       - job_001
       - job_002
       - job_003
       - job_004
-      - job_005
-      - job_006
-      - job_007
-      - job_008
-      - job_009
-      - job_010
-      - job_011
-      - job_012
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..93ca1ee
--- /dev/null
+++ b/README.md
@@ -0,0 +1,41 @@
+[![Build Status](https://github.com/dart-lang/shelf/workflows/Dart%20CI/badge.svg)](https://github.com/dart-lang/shelf/actions?query=workflow%3A"Dart+CI"+branch%3Amaster)
+
+## shelf [![Pub Package](https://img.shields.io/pub/v/shelf.svg)](https://pub.dev/packages/shelf)
+
+- Package: <https://pub.dev/packages/shelf>
+- [Source code](pkgs/shelf)
+
+## shelf_packages_handler [![Pub Package](https://img.shields.io/pub/v/shelf_packages_handler.svg)](https://pub.dev/packages/shelf_packages_handler)
+
+- Package: <https://pub.dev/packages/shelf_packages_handler>
+- [Source code](pkgs/shelf_packages_handler)
+
+## shelf_proxy [![Pub Package](https://img.shields.io/pub/v/shelf_proxy.svg)](https://pub.dev/packages/shelf_proxy)
+
+- Package: <https://pub.dev/packages/shelf_proxy>
+- [Source code](pkgs/shelf_proxy)
+
+## shelf_router [![Pub Package](https://img.shields.io/pub/v/shelf_router.svg)](https://pub.dev/packages/shelf_router)
+
+- Package: <https://pub.dev/packages/shelf_router>
+- [Source code](pkgs/shelf_router)
+
+## shelf_router_generator [![Pub Package](https://img.shields.io/pub/v/shelf_router_generator.svg)](https://pub.dev/packages/shelf_router_generator)
+
+- Package: <https://pub.dev/packages/shelf_router_generator>
+- [Source code](pkgs/shelf_router_generator)
+
+## shelf_static [![Pub Package](https://img.shields.io/pub/v/shelf_static.svg)](https://pub.dev/packages/shelf_static)
+
+- Package: <https://pub.dev/packages/shelf_static>
+- [Source code](pkgs/shelf_static)
+
+## shelf_test_handler [![Pub Package](https://img.shields.io/pub/v/shelf_test_handler.svg)](https://pub.dev/packages/shelf_test_handler)
+
+- Package: <https://pub.dev/packages/shelf_test_handler>
+- [Source code](pkgs/shelf_test_handler)
+
+## shelf_web_socket [![Pub Package](https://img.shields.io/pub/v/shelf_web_socket.svg)](https://pub.dev/packages/shelf_web_socket)
+
+- Package: <https://pub.dev/packages/shelf_web_socket>
+- [Source code](pkgs/shelf_web_socket)
diff --git a/pkgs/shelf/analysis_options.yaml b/analysis_options.yaml
similarity index 100%
rename from pkgs/shelf/analysis_options.yaml
rename to analysis_options.yaml
diff --git a/mono_repo.yaml b/mono_repo.yaml
new file mode 100644
index 0000000..7dc1d9e
--- /dev/null
+++ b/mono_repo.yaml
@@ -0,0 +1,3 @@
+merge_stages:
+  - analyze_and_format
+  - unit_test
diff --git a/pkgs/shelf/test/shelf_io_test.dart b/pkgs/shelf/test/shelf_io_test.dart
index 9ae3ad2..417f8cb 100644
--- a/pkgs/shelf/test/shelf_io_test.dart
+++ b/pkgs/shelf/test/shelf_io_test.dart
@@ -521,13 +521,13 @@
 
     var sslClient = HttpClient(context: securityContext);
 
-    Future<HttpClientRequest> _scheduleSecureGet() =>
+    Future<HttpClientRequest> scheduleSecureGet() =>
         sslClient.getUrl(Uri.https('localhost:${_server!.port}', ''));
 
     test('secure sync handler returns a value to the client', () async {
       await _scheduleServer(syncHandler, securityContext: securityContext);
 
-      var req = await _scheduleSecureGet();
+      var req = await scheduleSecureGet();
 
       var response = await req.close();
       expect(response.statusCode, HttpStatus.ok);
@@ -538,7 +538,7 @@
     test('secure async handler returns a value to the client', () async {
       await _scheduleServer(asyncHandler, securityContext: securityContext);
 
-      var req = await _scheduleSecureGet();
+      var req = await scheduleSecureGet();
       var response = await req.close();
       expect(response.statusCode, HttpStatus.ok);
       expect(await response.cast<List<int>>().transform(utf8.decoder).single,
diff --git a/pkgs/shelf_packages_handler/CHANGELOG.md b/pkgs/shelf_packages_handler/CHANGELOG.md
index 4c7dfaf..6e72d91 100644
--- a/pkgs/shelf_packages_handler/CHANGELOG.md
+++ b/pkgs/shelf_packages_handler/CHANGELOG.md
@@ -1,5 +1,7 @@
 ## 3.0.1-dev
 
+- Require Dart `2.14`.
+
 ## 3.0.0
 
 * Migrate to null safety.
@@ -13,9 +15,9 @@
 ### Breaking changes
 
 * Dropped the dependency on `package_resolver`.
-  * All `PackageResolver` apis now take a `Map<String, Uri>` of package name
-    to the base uri for resolving `package:` uris for that package.
-  * Named arguments have been renamed from `resolver` to `packageMap`.
+    * All `PackageResolver` apis now take a `Map<String, Uri>` of package name to the base uri for resolving `package:`
+      uris for that package.
+    * Named arguments have been renamed from `resolver` to `packageMap`.
 
 ## 1.0.4
 
diff --git a/pkgs/shelf_packages_handler/analysis_options.yaml b/pkgs/shelf_packages_handler/analysis_options.yaml
deleted file mode 100644
index d183bf1..0000000
--- a/pkgs/shelf_packages_handler/analysis_options.yaml
+++ /dev/null
@@ -1,80 +0,0 @@
-include: package:pedantic/analysis_options.yaml
-
-analyzer:
-  strong-mode:
-    implicit-casts: false
-
-linter:
-  rules:
-    - avoid_bool_literals_in_conditional_expressions
-    - avoid_catching_errors
-    - avoid_classes_with_only_static_members
-    - avoid_function_literals_in_foreach_calls
-    - avoid_private_typedef_functions
-    - avoid_redundant_argument_values
-    - avoid_renaming_method_parameters
-    - avoid_returning_null
-    - avoid_returning_null_for_future
-    - avoid_returning_null_for_void
-    - avoid_returning_this
-    - avoid_single_cascade_in_expression_statements
-    - avoid_unused_constructor_parameters
-    - avoid_void_async
-    - await_only_futures
-    - camel_case_types
-    - cancel_subscriptions
-    - cascade_invocations
-    - comment_references
-    - constant_identifier_names
-    - control_flow_in_finally
-    - directives_ordering
-    - empty_statements
-    - file_names
-    - hash_and_equals
-    - implementation_imports
-    - invariant_booleans
-    - iterable_contains_unrelated_type
-    - join_return_with_assignment
-    - lines_longer_than_80_chars
-    - list_remove_unrelated_type
-    - literal_only_boolean_expressions
-    - missing_whitespace_between_adjacent_strings
-    - no_adjacent_strings_in_list
-    - no_runtimeType_toString
-    - non_constant_identifier_names
-    - only_throw_errors
-    - overridden_fields
-    - package_api_docs
-    - package_names
-    - package_prefixed_library_names
-    - prefer_asserts_in_initializer_lists
-    - prefer_const_constructors
-    - prefer_const_declarations
-    - prefer_expression_function_bodies
-    - prefer_final_locals
-    - prefer_function_declarations_over_variables
-    - prefer_initializing_formals
-    - prefer_inlined_adds
-    - prefer_interpolation_to_compose_strings
-    - prefer_is_not_operator
-    - prefer_null_aware_operators
-    - prefer_relative_imports
-    - prefer_typing_uninitialized_variables
-    - prefer_void_to_null
-    - provide_deprecation_message
-    - sort_pub_dependencies
-    - test_types_in_equals
-    - throw_in_finally
-    - type_annotate_public_apis
-    - unnecessary_await_in_return
-    - unnecessary_brace_in_string_interps
-    - unnecessary_getters_setters
-    - unnecessary_lambdas
-    - unnecessary_null_aware_assignments
-    - unnecessary_overrides
-    - unnecessary_parenthesis
-    - unnecessary_statements
-    - unnecessary_string_interpolations
-    - use_is_even_rather_than_modulo
-    - use_string_buffers
-    - void_checks
diff --git a/pkgs/shelf_packages_handler/mono_pkg.yaml b/pkgs/shelf_packages_handler/mono_pkg.yaml
index 75b1a04..80a16a3 100644
--- a/pkgs/shelf_packages_handler/mono_pkg.yaml
+++ b/pkgs/shelf_packages_handler/mono_pkg.yaml
@@ -1,5 +1,5 @@
 sdk:
-- 2.12.0
+- 2.14.0
 - dev
 
 stages:
diff --git a/pkgs/shelf_packages_handler/pubspec.yaml b/pkgs/shelf_packages_handler/pubspec.yaml
index 652a99a..26836b1 100644
--- a/pkgs/shelf_packages_handler/pubspec.yaml
+++ b/pkgs/shelf_packages_handler/pubspec.yaml
@@ -5,7 +5,7 @@
 repository: https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_packages_handler
 
 environment:
-  sdk: '>=2.12.0-0 <3.0.0'
+  sdk: '>=2.14.0 <3.0.0'
 
 dependencies:
   path: ^1.8.0
@@ -13,7 +13,7 @@
   shelf_static: ^1.0.0
 
 dev_dependencies:
-  pedantic: ^1.10.0
+  lints: ^1.0.0
   test: ^1.16.0
 
 dependency_overrides:
diff --git a/pkgs/shelf_proxy/lib/shelf_proxy.dart b/pkgs/shelf_proxy/lib/shelf_proxy.dart
index 1bd7dc3..7bd3c86 100644
--- a/pkgs/shelf_proxy/lib/shelf_proxy.dart
+++ b/pkgs/shelf_proxy/lib/shelf_proxy.dart
@@ -22,7 +22,7 @@
 ///
 /// [proxyName] is used in headers to identify this proxy. It should be a valid
 /// HTTP token or a hostname. It defaults to `shelf_proxy`.
-Handler proxyHandler(url, {http.Client? client, String? proxyName}) {
+Handler proxyHandler(Object url, {http.Client? client, String? proxyName}) {
   Uri uri;
   if (url is String) {
     uri = Uri.parse(url);
diff --git a/pkgs/shelf_proxy/test/shelf_proxy_test.dart b/pkgs/shelf_proxy/test/shelf_proxy_test.dart
index b1ec3fd..822d262 100644
--- a/pkgs/shelf_proxy/test/shelf_proxy_test.dart
+++ b/pkgs/shelf_proxy/test/shelf_proxy_test.dart
@@ -173,7 +173,7 @@
 ///
 /// [targetPath] is the root-relative path on the target server to proxy to. It
 /// defaults to `/`.
-Future createProxy(shelf.Handler handler, {String? targetPath}) async {
+Future<void> createProxy(shelf.Handler handler, {String? targetPath}) async {
   handler = expectAsync1(handler, reason: 'target server handler');
   final targetServer = await shelf_io.serve(handler, 'localhost', 0);
   targetUri = Uri.parse('http://localhost:${targetServer.port}');
diff --git a/pkgs/shelf_router/CHANGELOG.md b/pkgs/shelf_router/CHANGELOG.md
new file mode 100644
index 0000000..a8c0eb0
--- /dev/null
+++ b/pkgs/shelf_router/CHANGELOG.md
@@ -0,0 +1,66 @@
+## v1.1.2
+
+  * Remove trailing slash requirement when using `mount`.
+
+## v1.1.1
+
+ * Fix `Router.routeNotFound` to enable multiple `read()` calls on it.
+
+## v1.1.0
+ * `params` is deprecated in favor of `Request.params` adding using an extension
+   on `Request`.
+ * The default `notFoundHandler` now returns a sentinel `routeNotFound` response
+   object which causes 404 with the message 'Route not found'.
+ * __Minor breaking__: Handlers and sub-routers that return the sentinel
+   `routeNotFound` response object will be ignored and pattern matching will
+   continue on additional routes/handlers.
+
+Changing the router to continue pattern matching additional routes if a matched
+_handler_ or _nested router_ returns the sentinel `routeNotFound` response
+object is technically a _breaking change_. However, it only affects scenarios
+where the request matches a _mounted sub-router_, but does not match any route
+on this sub-router. In this case, `shelf_router` version `1.0.0` would
+immediately respond 404, without attempting to match further routes. With this
+release, the behavior changes to matching additional routes until one returns
+a custom 404 response object, or all routes have been matched.
+
+This behavior is more in line with how `shelf_router` version `0.7.x` worked,
+and since many affected users consider the behavior from `1.0.0` a defect,
+we decided to remedy the situation.
+
+## v1.0.0
+
+ * Migrate package to null-safety
+ * Since handlers are not allowed to return `null` in `shelf` 1.0.0, a router
+   will return a default 404 response instead.
+   This behavior can be overridden with the `notFoundHandler` constructor
+   parameter.
+ * __Breaking__: Remove deprecated `Router.handler` getter.
+   The router itself is a handler.
+
+## v0.7.4
+
+ * Update `Router.mount` parameter to accept a `Handler`.
+ * Make `Router` to be considered a `Handler`.
+ * Deprecate the `Router.handler` getter.
+
+## v0.7.3
+
+ * Added `@sealed` annotation to `Router` and `Route`.
+
+## v0.7.2
+
+ * Always register a `HEAD` handler whenever a `GET` handler is registered.
+   Defaulting to calling the `GET` handler and throwing away the body.
+
+## v0.7.1
+
+ * Use `Function` instead of `dynamic` in `RouterEntry` to improve typing.
+
+## v0.7.0+1
+
+ * Fixed description to fit size recommendations.
+
+## v0.7.0
+
+ * Initial release
diff --git a/pkgs/shelf_router/LICENSE b/pkgs/shelf_router/LICENSE
new file mode 100644
index 0000000..7a4a3ea
--- /dev/null
+++ b/pkgs/shelf_router/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
\ No newline at end of file
diff --git a/pkgs/shelf_router/README.md b/pkgs/shelf_router/README.md
new file mode 100644
index 0000000..7558cdc
--- /dev/null
+++ b/pkgs/shelf_router/README.md
@@ -0,0 +1,45 @@
+# Web Request Router for Shelf
+
+[Shelf][shelf] makes it easy to build web
+applications in Dart by composing request handlers. This package offers a
+request router for Shelf, matching request to handlers using route patterns.
+
+Also see the [`shelf_router_generator`][shelf_router_generator] package
+for how to automatically generate
+a `Route` using the `Route` annotation in this package.
+
+## Example
+
+```dart
+import 'package:shelf_router/shelf_router.dart';
+import 'package:shelf/shelf.dart';
+import 'package:shelf/shelf_io.dart' as io;
+
+var app = Router();
+
+app.get('/hello', (Request request) {
+  return Response.ok('hello-world');
+});
+
+app.get('/user/<user>', (Request request, String user) {
+  return Response.ok('hello $user');
+});
+
+var server = await io.serve(app, 'localhost', 8080);
+```
+
+See reference documentation of `Router` class for more information.
+
+## See also
+ * Package [`shelf`][shelf] for which this package can create routers.
+ * Package [`shelf_router_generator`][shelf_router_generator] which can generate
+   a router using source code annotations.
+ * Third-party tutorial by [creativebracket.com]:
+   * Video: [Build RESTful Web APIs with shelf_router][1]
+   * Sample: [repository for tutorial][2]
+
+[shelf]: https://pub.dev/packages/shelf
+[shelf_router_generator]: https://pub.dev/packages/shelf_router_generator
+[creativebracket.com]: https://creativebracket.com/
+[1]: https://www.youtube.com/watch?v=v7FhaV9e3yY
+[2]: https://github.com/graphicbeacon/shelf_router_api_tutorial
diff --git a/pkgs/shelf_router/analysis_options.yaml b/pkgs/shelf_router/analysis_options.yaml
new file mode 100644
index 0000000..572dd23
--- /dev/null
+++ b/pkgs/shelf_router/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:lints/recommended.yaml
diff --git a/pkgs/shelf_router/example/main.dart b/pkgs/shelf_router/example/main.dart
new file mode 100644
index 0000000..ebd7dfd
--- /dev/null
+++ b/pkgs/shelf_router/example/main.dart
@@ -0,0 +1,85 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     https://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 'dart:async' show Future;
+import 'package:shelf_router/shelf_router.dart';
+import 'package:shelf/shelf.dart';
+import 'package:shelf/shelf_io.dart' as shelf_io;
+
+class Service {
+  // The [Router] can be used to create a handler, which can be used with
+  // [shelf_io.serve].
+  Handler get handler {
+    final router = Router();
+
+    // Handlers can be added with `router.<verb>('<route>', handler)`, the
+    // '<route>' may embed URL-parameters, and these may be taken as parameters
+    // by the handler (but either all URL parameters or no URL parameters, must
+    // be taken parameters by the handler).
+    router.get('/say-hi/<name>', (Request request, String name) {
+      return Response.ok('hi $name');
+    });
+
+    // Embedded URL parameters may also be associated with a regular-expression
+    // that the pattern must match.
+    router.get('/user/<userId|[0-9]+>', (Request request, String userId) {
+      return Response.ok('User has the user-number: $userId');
+    });
+
+    // Handlers can be asynchronous (returning `FutureOr` is also allowed).
+    router.get('/wave', (Request request) async {
+      await Future.delayed(Duration(milliseconds: 100));
+      return Response.ok('_o/');
+    });
+
+    // Other routers can be mounted...
+    router.mount('/api/', Api().router);
+
+    // You can catch all verbs and use a URL-parameter with a regular expression
+    // that matches everything to catch app.
+    router.all('/<ignored|.*>', (Request request) {
+      return Response.notFound('Page not found');
+    });
+
+    return router;
+  }
+}
+
+class Api {
+  Future<Response> _messages(Request request) async {
+    return Response.ok('[]');
+  }
+
+  // By exposing a [Router] for an object, it can be mounted in other routers.
+  Router get router {
+    final router = Router();
+
+    // A handler can have more that one route.
+    router.get('/messages', _messages);
+    router.get('/messages/', _messages);
+
+    // This nested catch-all, will only catch /api/.* when mounted above.
+    // Notice that ordering if annotated handlers and mounts is significant.
+    router.all('/<ignored|.*>', (Request request) => Response.notFound('null'));
+
+    return router;
+  }
+}
+
+// Run shelf server and host a [Service] instance on port 8080.
+void main() async {
+  final service = Service();
+  final server = await shelf_io.serve(service.handler, 'localhost', 8080);
+  print('Server running on localhost:${server.port}');
+}
diff --git a/pkgs/shelf_router/lib/shelf_router.dart b/pkgs/shelf_router/lib/shelf_router.dart
new file mode 100644
index 0000000..40b2e35
--- /dev/null
+++ b/pkgs/shelf_router/lib/shelf_router.dart
@@ -0,0 +1,85 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     https://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.
+
+/// A request routing library for shelf.
+///
+/// When writing a shelf web server it is often desirable to route requests to
+/// different handlers based on HTTP method and path patterns. The following
+/// example demonstrates how to do this using [Router].
+///
+/// **Example**
+/// ```dart
+/// import 'package:shelf_router/shelf_router.dart';
+/// import 'package:shelf/shelf.dart' show Request, Response;
+/// import 'package:shelf/shelf_io.dart' as io;
+///
+/// void main() async {
+///   // Create a router
+///   final router = Router();
+///
+///   // Handle GET requests with a path matching ^/say-hello/[^\]*$
+///   router.get('/say-hello/<name>', (Request request, String name) async {
+///     return Response.ok('hello $name');
+///   });
+///
+///   // Listen for requests on port localhost:8080
+///   await io.serve(router, 'localhost', 8080);
+/// }
+/// ```
+///
+/// As it is often useful to organize request handlers in classes, methods can
+/// be annotated with the [Route] annotation, allowing the
+/// `shelf_router_generator` package to generated a method for creating a
+/// [Router] wrapping the class.
+///
+/// To automatically generate add the `shelf_router_generator` and
+/// `build_runner` packages to `dev_dependencies`. The follow the example
+/// below and generate code using `pub run build_runner build`.
+///
+/// **Example**, assume file name is `hello.dart`.
+/// ```dart
+/// import 'package:shelf_router/shelf_router.dart';
+/// import 'package:shelf/shelf.dart' show Request, Response;
+/// import 'package:shelf/shelf_io.dart' as io;
+///
+/// // include the generated part, assumes current file is 'hello.dart'.
+/// part 'hello.g.dart';
+///
+/// class HelloService {
+///   // Annotate a handler with the `Route` annotation.
+///   @Route.get('/say-hello/<name>')
+///   Future<Response> _sayHello(Request request, String name) async {
+///     return Response.ok('hello $name');
+///   }
+///
+///   // Use the generated function `_$<ClassName>Router(<ClassName> instance)`
+///   // to create a getter returning a `Router` for this instance of
+///   // `HelloService`
+///   Router get router => _$HelloServiceRouter(this);
+/// }
+///
+/// void main() async {
+///   // Create a `HelloService` instance
+///   final service = HelloService();
+///
+///   await io.serve(service.router.handler, 'localhost', 8080);
+/// }
+/// ```
+///
+library shelf_router;
+
+import 'package:shelf_router/src/router.dart';
+import 'package:shelf_router/src/route.dart';
+export 'package:shelf_router/src/router.dart';
+export 'package:shelf_router/src/route.dart';
diff --git a/pkgs/shelf_router/lib/src/route.dart b/pkgs/shelf_router/lib/src/route.dart
new file mode 100644
index 0000000..62da18f
--- /dev/null
+++ b/pkgs/shelf_router/lib/src/route.dart
@@ -0,0 +1,92 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     https://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 'package:meta/meta.dart' show sealed;
+import 'package:shelf_router/src/router.dart';
+
+/// Annotation for handler methods that requests should be routed when using
+/// package `shelf_router_generator`.
+///
+/// The `shelf_router_generator` packages makes it easy to generate a function
+/// that wraps your class and returns a [Router] that forwards requests to
+/// annotated methods. Simply add the `shelf_router_generator` and
+/// `build_runner` packages to `dev_dependencies`, write as illustrated in the
+/// following example and run `pub run build_runner build` to generate code.
+///
+/// **Example**
+/// ```dart
+/// // Always import 'shelf_router' without 'show' or 'as'.
+/// import 'package:shelf_router/shelf_router.dart';
+/// import 'package:shelf/shelf.dart' show Request, Response;
+///
+/// // Include generated code, this assumes current file is 'my_service.dart'.
+/// part 'my_service.g.dart';
+///
+/// class MyService {
+///   @Route.get('/say-hello/<name>')
+///   Future<Response> _sayHello(Request request, String name) async {
+///     return Response.ok('hello $name');
+///   }
+///
+///   /// Get a router for this service.
+///   Router get router => _$MyServiceRouter(this);
+/// }
+/// ```
+///
+/// It is also permitted to annotate public members, the only requirement is
+/// that the member has a signature accepted by [Router] as `handler`.
+@sealed
+class Route {
+  /// HTTP verb for requests routed to the annotated method.
+  final String verb;
+
+  /// HTTP route for request routed to the annotated method.
+  final String route;
+
+  /// Create an annotation that routes requests matching [verb] and [route] to
+  /// the annotated method.
+  const Route(this.verb, this.route);
+
+  /// Route all requests matching [route] to annotated method.
+  const Route.all(this.route) : verb = r'$all';
+
+  /// Route `GET` requests matching [route] to annotated method.
+  const Route.get(this.route) : verb = 'GET';
+
+  /// Route `HEAD` requests matching [route] to annotated method.
+  const Route.head(this.route) : verb = 'HEAD';
+
+  /// Route `POST` requests matching [route] to annotated method.
+  const Route.post(this.route) : verb = 'POST';
+
+  /// Route `PUT` requests matching [route] to annotated method.
+  const Route.put(this.route) : verb = 'PUT';
+
+  /// Route `DELETE` requests matching [route] to annotated method.
+  const Route.delete(this.route) : verb = 'DELETE';
+
+  /// Route `CONNECT` requests matching [route] to annotated method.
+  const Route.connect(this.route) : verb = 'CONNECT';
+
+  /// Route `OPTIONS` requests matching [route] to annotated method.
+  const Route.options(this.route) : verb = 'OPTIONS';
+
+  /// Route `TRACE` requests matching [route] to annotated method.
+  const Route.trace(this.route) : verb = 'TRACE';
+
+  /// Route `MOUNT` requests matching [route] to annotated method.
+  const Route.mount(String prefix)
+      : verb = r'$mount',
+        route = prefix;
+}
diff --git a/pkgs/shelf_router/lib/src/router.dart b/pkgs/shelf_router/lib/src/router.dart
new file mode 100644
index 0000000..9460788
--- /dev/null
+++ b/pkgs/shelf_router/lib/src/router.dart
@@ -0,0 +1,305 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     https://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 'dart:collection' show UnmodifiableMapView;
+import 'dart:convert';
+
+import 'package:http_methods/http_methods.dart';
+import 'package:meta/meta.dart' show sealed;
+import 'package:shelf/shelf.dart';
+import 'package:shelf_router/src/router_entry.dart' show RouterEntry;
+
+/// Get a URL parameter captured by the [Router].
+@Deprecated('Use Request.params instead')
+String params(Request request, String name) {
+  final value = request.params[name];
+  if (value == null) {
+    throw Exception('no such parameter $name');
+  }
+  return value;
+}
+
+final _emptyParams = UnmodifiableMapView(<String, String>{});
+
+extension RouterParams on Request {
+  /// Get URL parameters captured by the [Router].
+  ///
+  /// **Example**
+  /// ```dart
+  /// final app = Router();
+  ///
+  /// app.get('/hello/<name>', (Request request) {
+  ///   final name = request.params['name'];
+  ///   return Response.ok('Hello $name');
+  /// });
+  /// ```
+  ///
+  /// If no parameters are captured this returns an empty map.
+  ///
+  /// The returned map is unmodifiable.
+  Map<String, String> get params {
+    final p = context['shelf_router/params'];
+    if (p is Map<String, String>) {
+      return UnmodifiableMapView(p);
+    }
+    return _emptyParams;
+  }
+}
+
+/// Middleware to remove body from request.
+final _removeBody = createMiddleware(responseHandler: (r) {
+  if (r.headers.containsKey('content-length')) {
+    r = r.change(headers: {'content-length': '0'});
+  }
+  return r.change(body: <int>[]);
+});
+
+/// A shelf [Router] routes requests to handlers based on HTTP verb and route
+/// pattern.
+///
+/// ```dart
+/// import 'package:shelf_router/shelf_router.dart';
+/// import 'package:shelf/shelf.dart';
+/// import 'package:shelf/shelf_io.dart' as io;
+///
+/// var app = Router();
+///
+/// // Route pattern parameters can be specified <paramName>
+/// app.get('/users/<userName>/whoami', (Request request) async {
+///   // The matched values can be read with params(request, param)
+///   var userName = request.params['userName'];
+///   return Response.ok('You are ${userName}');
+/// });
+///
+/// // The matched value can also be taken as parameter, if the handler given
+/// // doesn't implement Handler, it's assumed to take all parameters in the
+/// // order they appear in the route pattern.
+/// app.get('/users/<userName>/say-hello', (Request request, String userName) async {
+///   assert(userName == request.params['userName']);
+///   return Response.ok('Hello ${userName}');
+/// });
+///
+/// // It is possible to have multiple parameters, and if desired a custom
+/// // regular expression can be specified with <paramName|REGEXP>, where
+/// // REGEXP is a regular expression (leaving out ^ and $).
+/// // If no regular expression is specified `[^/]+` will be used.
+/// app.get('/users/<userName>/messages/<msgId|\d+>', (Request request) async {
+///   var msgId = int.parse(request.params['msgId']!);
+///   return Response.ok(message.getById(msgId));
+/// });
+///
+/// var server = await io.serve(app, 'localhost', 8080);
+/// ```
+///
+/// If multiple routes match the same request, the handler for the first
+/// route is called.
+/// If no route matches a request, a [Response.notFound] will be returned
+/// instead. The default matcher can be overridden with the `notFoundHandler`
+/// constructor parameter.
+@sealed
+class Router {
+  final List<RouterEntry> _routes = [];
+  final Handler _notFoundHandler;
+
+  /// Creates a new [Router] routing requests to handlers.
+  ///
+  /// The [notFoundHandler] will be invoked for requests where no matching route
+  /// was found. By default, a simple [Response.notFound] will be used instead.
+  Router({Handler notFoundHandler = _defaultNotFound})
+      : _notFoundHandler = notFoundHandler;
+
+  /// Add [handler] for [verb] requests to [route].
+  ///
+  /// If [verb] is `GET` the [handler] will also be called for `HEAD` requests
+  /// matching [route]. This is because handling `GET` requests without handling
+  /// `HEAD` is always wrong. To explicitely implement a `HEAD` handler it must
+  /// be registered before the `GET` handler.
+  void add(String verb, String route, Function handler) {
+    if (!isHttpMethod(verb)) {
+      throw ArgumentError.value(verb, 'verb', 'expected a valid HTTP method');
+    }
+    verb = verb.toUpperCase();
+
+    if (verb == 'GET') {
+      // Handling in a 'GET' request without handling a 'HEAD' request is always
+      // wrong, thus, we add a default implementation that discards the body.
+      _routes.add(RouterEntry('HEAD', route, handler, middleware: _removeBody));
+    }
+    _routes.add(RouterEntry(verb, route, handler));
+  }
+
+  /// Handle all request to [route] using [handler].
+  void all(String route, Function handler) {
+    _routes.add(RouterEntry('ALL', route, handler));
+  }
+
+  /// Mount a handler below a prefix.
+  ///
+  /// In this case prefix may not contain any parameters, nor
+  void mount(String prefix, Handler handler) {
+    if (!prefix.startsWith('/')) {
+      throw ArgumentError.value(prefix, 'prefix', 'must start with a slash');
+    }
+
+    // first slash is always in request.handlerPath
+    final path = prefix.substring(1);
+    if (prefix.endsWith('/')) {
+      all(prefix + '<path|[^]*>', (Request request) {
+        return handler(request.change(path: path));
+      });
+    } else {
+      all(prefix, (Request request) {
+        return handler(request.change(path: path));
+      });
+      all(prefix + '/<path|[^]*>', (Request request) {
+        return handler(request.change(path: path + '/'));
+      });
+    }
+  }
+
+  /// Route incoming requests to registered handlers.
+  ///
+  /// This method allows a Router instance to be a [Handler].
+  Future<Response> call(Request request) async {
+    // Note: this is a great place to optimize the implementation by building
+    //       a trie for faster matching... left as an exercise for the reader :)
+    for (var route in _routes) {
+      if (route.verb != request.method.toUpperCase() && route.verb != 'ALL') {
+        continue;
+      }
+      var params = route.match('/' + request.url.path);
+      if (params != null) {
+        final response = await route.invoke(request, params);
+        if (response != routeNotFound) {
+          return response;
+        }
+      }
+    }
+    return _notFoundHandler(request);
+  }
+
+  // Handlers for all methods
+
+  /// Handle `GET` request to [route] using [handler].
+  ///
+  /// If no matching handler for `HEAD` requests is registered, such requests
+  /// will also be routed to the [handler] registered here.
+  void get(String route, Function handler) => add('GET', route, handler);
+
+  /// Handle `HEAD` request to [route] using [handler].
+  void head(String route, Function handler) => add('HEAD', route, handler);
+
+  /// Handle `POST` request to [route] using [handler].
+  void post(String route, Function handler) => add('POST', route, handler);
+
+  /// Handle `PUT` request to [route] using [handler].
+  void put(String route, Function handler) => add('PUT', route, handler);
+
+  /// Handle `DELETE` request to [route] using [handler].
+  void delete(String route, Function handler) => add('DELETE', route, handler);
+
+  /// Handle `CONNECT` request to [route] using [handler].
+  void connect(String route, Function handler) =>
+      add('CONNECT', route, handler);
+
+  /// Handle `OPTIONS` request to [route] using [handler].
+  void options(String route, Function handler) =>
+      add('OPTIONS', route, handler);
+
+  /// Handle `TRACE` request to [route] using [handler].
+  void trace(String route, Function handler) => add('TRACE', route, handler);
+
+  /// Handle `PATCH` request to [route] using [handler].
+  void patch(String route, Function handler) => add('PATCH', route, handler);
+
+  static Response _defaultNotFound(Request request) => routeNotFound;
+
+  /// Sentinel [Response] object indicating that no matching route was found.
+  ///
+  /// This is the default response value from a [Router] created without a
+  /// `notFoundHandler`, when no routes matches the incoming request.
+  ///
+  /// If the [routeNotFound] object is returned from a [Handler] the [Router]
+  /// will consider the route _not matched_, and attempt to match other routes.
+  /// This is useful when mounting nested routers, or when matching a route
+  /// is conditioned on properties beyond the path of the URL.
+  ///
+  /// **Example**
+  /// ```dart
+  /// final app = Router();
+  ///
+  /// // The pattern for this route will match '/search' and '/search?q=...',
+  /// // but if request does not have `?q=...', then the handler will return
+  /// // [Router.routeNotFound] causing the router to attempt further routes.
+  /// app.get('/search', (Request request) async {
+  ///   if (!request.uri.queryParameters.containsKey('q')) {
+  ///     return Router.routeNotFound;
+  ///   }
+  ///   return Response.ok('TODO: make search results');
+  /// });
+  ///
+  /// // Same pattern as above
+  /// app.get('/search', (Request request) async {
+  ///   return Response.ok('TODO: return search form');
+  /// });
+  ///
+  /// // Create a single nested router we can mount for handling API requests.
+  /// final api = Router();
+  ///
+  /// api.get('/version', (Request request) => Response.ok('1'));
+  ///
+  /// // Mounting router under '/api'
+  /// app.mount('/api', api);
+  ///
+  /// // If a request matches `/api/...` then the routes in the [api] router
+  /// // will be attempted. However, for a request like `/api/hello` there is
+  /// // no matching route in the [api] router. Thus, the router will return
+  /// // [Router.routeNotFound], which will cause matching to continue.
+  /// // Hence, the catch-all route below will be matched, causing a custom 404
+  /// // response with message 'nothing found'.
+  ///
+  /// // In the pattern below `<anything|.*>` is on the form `<name|regex>`,
+  /// // thus, this simply creates a URL parameter called `anything` which
+  /// // matches anything.
+  /// app.all('/<anything|.*>', (Request request) {
+  ///   return Response.notFound('nothing found');
+  /// });
+  /// ```
+  static final Response routeNotFound = _RouteNotFoundResponse();
+}
+
+/// Extends [Response] to allow it to be used multiple times in the
+/// actual content being served.
+class _RouteNotFoundResponse extends Response {
+  static const _message = 'Route not found';
+  static final _messageBytes = utf8.encode(_message);
+
+  _RouteNotFoundResponse() : super.notFound(_message);
+
+  @override
+  Stream<List<int>> read() => Stream<List<int>>.value(_messageBytes);
+
+  @override
+  Response change({
+    Map<String, /* String | List<String> */ Object?>? headers,
+    Map<String, Object?>? context,
+    body,
+  }) {
+    return super.change(
+      headers: headers,
+      context: context,
+      body: body ?? _message,
+    );
+  }
+}
diff --git a/pkgs/shelf_router/lib/src/router_entry.dart b/pkgs/shelf_router/lib/src/router_entry.dart
new file mode 100644
index 0000000..4860ef3
--- /dev/null
+++ b/pkgs/shelf_router/lib/src/router_entry.dart
@@ -0,0 +1,113 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     https://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 'dart:async';
+import 'package:shelf/shelf.dart';
+
+/// Check if the [regexp] is non-capturing.
+bool _isNoCapture(String regexp) {
+  // Construct a new regular expression matching anything containing regexp,
+  // then match with empty-string and count number of groups.
+  return RegExp('^(?:$regexp)|.*\$').firstMatch('')!.groupCount == 0;
+}
+
+/// Entry in the router.
+///
+/// This class implements the logic for matching the path pattern.
+class RouterEntry {
+  /// Pattern for parsing the route pattern
+  static final RegExp _parser = RegExp(r'([^<]*)(?:<([^>|]+)(?:\|([^>]*))?>)?');
+
+  final String verb, route;
+  final Function _handler;
+  final Middleware _middleware;
+
+  /// Expression that the request path must match.
+  ///
+  /// This also captures any parameters in the route pattern.
+  final RegExp _routePattern;
+
+  /// Names for the parameters in the route pattern.
+  final List<String> _params;
+
+  /// List of parameter names in the route pattern.
+  List<String> get params => _params.toList(); // exposed for using generator.
+
+  RouterEntry._(this.verb, this.route, this._handler, this._middleware,
+      this._routePattern, this._params);
+
+  factory RouterEntry(
+    String verb,
+    String route,
+    Function handler, {
+    Middleware? middleware,
+  }) {
+    middleware = middleware ?? ((Handler fn) => fn);
+
+    if (!route.startsWith('/')) {
+      throw ArgumentError.value(
+          route, 'route', 'expected route to start with a slash');
+    }
+
+    final params = <String>[];
+    var pattern = '';
+    for (var m in _parser.allMatches(route)) {
+      pattern += RegExp.escape(m[1]!);
+      if (m[2] != null) {
+        params.add(m[2]!);
+        if (m[3] != null && !_isNoCapture(m[3]!)) {
+          throw ArgumentError.value(
+              route, 'route', 'expression for "${m[2]}" is capturing');
+        }
+        pattern += '(${m[3] ?? r'[^/]+'})';
+      }
+    }
+    final routePattern = RegExp('^$pattern\$');
+
+    return RouterEntry._(
+        verb, route, handler, middleware, routePattern, params);
+  }
+
+  /// Returns a map from parameter name to value, if the path matches the
+  /// route pattern. Otherwise returns null.
+  Map<String, String>? match(String path) {
+    // Check if path matches the route pattern
+    var m = _routePattern.firstMatch(path);
+    if (m == null) {
+      return null;
+    }
+    // Construct map from parameter name to matched value
+    var params = <String, String>{};
+    for (var i = 0; i < _params.length; i++) {
+      // first group is always the full match, we ignore this group.
+      params[_params[i]] = m[i + 1]!;
+    }
+    return params;
+  }
+
+  // invoke handler with given request and params
+  Future<Response> invoke(Request request, Map<String, String> params) async {
+    request = request.change(context: {'shelf_router/params': params});
+
+    return await _middleware((request) async {
+      if (_handler is Handler || _params.isEmpty) {
+        return await _handler(request);
+      }
+      return await Function.apply(_handler, [
+        request,
+        ..._params.map((n) => params[n]),
+      ]);
+    })(request);
+  }
+}
diff --git a/pkgs/shelf_web_socket/mono_pkg.yml b/pkgs/shelf_router/mono_pkg.yaml
similarity index 78%
copy from pkgs/shelf_web_socket/mono_pkg.yml
copy to pkgs/shelf_router/mono_pkg.yaml
index 75b1a04..84c7b48 100644
--- a/pkgs/shelf_web_socket/mono_pkg.yml
+++ b/pkgs/shelf_router/mono_pkg.yaml
@@ -1,5 +1,5 @@
 sdk:
-- 2.12.0
+- 2.14.0
 - dev
 
 stages:
@@ -10,6 +10,3 @@
     - dev
 - unit_test:
   - test: --test-randomize-ordering-seed=random
-    os:
-    - linux
-    - windows
diff --git a/pkgs/shelf_router/pubspec.yaml b/pkgs/shelf_router/pubspec.yaml
new file mode 100644
index 0000000..99a1e46
--- /dev/null
+++ b/pkgs/shelf_router/pubspec.yaml
@@ -0,0 +1,18 @@
+name: shelf_router
+version: 1.1.2
+description: |
+  A convinent request router for the shelf web-framework, with support for
+  URL-parameters, nested routers and routers generated from source annotations.
+homepage: https://github.com/google/dart-neats/tree/master/shelf_router
+repository: https://github.com/google/dart-neats.git
+issue_tracker: https://github.com/google/dart-neats/labels/pkg:shelf_router
+dependencies:
+  meta: ^1.3.0
+  shelf: ^1.0.0
+  http_methods: ^1.1.0
+dev_dependencies:
+  test: ^1.16.0
+  lints: ^1.0.0
+  http: ^0.13.0
+environment:
+  sdk: '>=2.12.0 <3.0.0'
diff --git a/pkgs/shelf_router/test/route_entry_test.dart b/pkgs/shelf_router/test/route_entry_test.dart
new file mode 100644
index 0000000..cb6a514
--- /dev/null
+++ b/pkgs/shelf_router/test/route_entry_test.dart
@@ -0,0 +1,73 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     https://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 'package:shelf_router/src/router_entry.dart' show RouterEntry;
+import 'package:test/test.dart';
+
+void main() {
+  void testPattern(
+    String pattern, {
+    Map<String, Map<String, String>> match = const {},
+    List<String> notMatch = const [],
+  }) {
+    group('RouterEntry: "$pattern"', () {
+      final r = RouterEntry('GET', pattern, () => null);
+      for (final e in match.entries) {
+        test('Matches "${e.key}"', () {
+          expect(r.match(e.key), equals(e.value));
+        });
+      }
+      for (final v in notMatch) {
+        test('NotMatch "$v"', () {
+          expect(r.match(v), isNull);
+        });
+      }
+    });
+  }
+
+  testPattern('/hello', match: {
+    '/hello': {},
+  }, notMatch: [
+    '/not-hello',
+    '/',
+  ]);
+
+  testPattern(r'/user/<user>/groups/<group|\d+>', match: {
+    '/user/jonasfj/groups/42': {
+      'user': 'jonasfj',
+      'group': '42',
+    },
+    '/user/jonasfj/groups/0': {
+      'user': 'jonasfj',
+      'group': '0',
+    },
+    '/user/123/groups/101': {
+      'user': '123',
+      'group': '101',
+    },
+  }, notMatch: [
+    '/user/',
+    '/user/jonasfj/groups/5-3',
+    '/user/jonasfj/test/groups/5',
+    '/user/jonasfjtest/groups/4/',
+    '/user/jonasfj/groups/',
+    '/not-hello',
+    '/',
+  ]);
+
+  test('non-capture regex only', () {
+    expect(() => RouterEntry('GET', '/users/<user|([^]*)>/info', () {}),
+        throwsA(anything));
+  });
+}
diff --git a/pkgs/shelf_router/test/router_test.dart b/pkgs/shelf_router/test/router_test.dart
new file mode 100644
index 0000000..f664f56
--- /dev/null
+++ b/pkgs/shelf_router/test/router_test.dart
@@ -0,0 +1,205 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     https://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.
+
+@TestOn('vm')
+import 'dart:async';
+import 'dart:io';
+
+import 'package:http/http.dart' as http;
+import 'package:shelf/shelf.dart';
+import 'package:shelf/shelf_io.dart' as io;
+import 'package:shelf_router/shelf_router.dart';
+import 'package:test/test.dart';
+
+void main() {
+  // Create a server that listens on localhost for testing
+  late io.IOServer server;
+
+  setUp(() async {
+    try {
+      server = await io.IOServer.bind(InternetAddress.loopbackIPv6, 0);
+    } on SocketException catch (_) {
+      server = await io.IOServer.bind(InternetAddress.loopbackIPv4, 0);
+    }
+  });
+
+  tearDown(() => server.close());
+
+  Future<String> get(String path) =>
+      http.read(Uri.parse(server.url.toString() + path));
+  Future<int> head(String path) async =>
+      (await http.head(Uri.parse(server.url.toString() + path))).statusCode;
+
+  test('get sync/async handler', () async {
+    var app = Router();
+
+    app.get('/sync-hello', (Request request) {
+      return Response.ok('hello-world');
+    });
+
+    app.get('/async-hello', (Request request) async {
+      return Future.microtask(() {
+        return Response.ok('hello-world');
+      });
+    });
+
+    // check that catch-alls work
+    app.all('/<path|[^]*>', (Request request) {
+      return Response.ok('not-found');
+    });
+
+    server.mount(app);
+
+    expect(await get('/sync-hello'), 'hello-world');
+    expect(await get('/async-hello'), 'hello-world');
+    expect(await get('/wrong-path'), 'not-found');
+
+    expect(await head('/sync-hello'), 200);
+    expect(await head('/async-hello'), 200);
+    expect(await head('/wrong-path'), 200);
+  });
+
+  test('params', () async {
+    var app = Router();
+
+    app.get(r'/user/<user>/groups/<group|\d+>', (Request request) {
+      final user = request.params['user'];
+      final group = request.params['group'];
+      return Response.ok('$user / $group');
+    });
+
+    server.mount(app);
+
+    expect(await get('/user/jonasfj/groups/42'), 'jonasfj / 42');
+  });
+
+  test('params by arguments', () async {
+    var app = Router();
+
+    app.get(r'/user/<user>/groups/<group|\d+>',
+        (Request request, String user, String group) {
+      return Response.ok('$user / $group');
+    });
+
+    server.mount(app);
+
+    expect(await get('/user/jonasfj/groups/42'), 'jonasfj / 42');
+  });
+
+  test('mount(Router)', () async {
+    var api = Router();
+    api.get('/user/<user>/info', (Request request, String user) {
+      return Response.ok('Hello $user');
+    });
+
+    var app = Router();
+    app.get('/hello', (Request request) {
+      return Response.ok('hello-world');
+    });
+
+    app.mount('/api/', api);
+
+    app.all('/<_|[^]*>', (Request request) {
+      return Response.ok('catch-all-handler');
+    });
+
+    server.mount(app);
+
+    expect(await get('/hello'), 'hello-world');
+    expect(await get('/api/user/jonasfj/info'), 'Hello jonasfj');
+    expect(await get('/api/user/jonasfj/info-wrong'), 'catch-all-handler');
+  });
+
+  test('mount(Handler) with middleware', () async {
+    var api = Router();
+    api.get('/hello', (Request request) {
+      return Response.ok('Hello');
+    });
+
+    final middleware = createMiddleware(
+      requestHandler: (request) {
+        if (request.url.queryParameters.containsKey('ok')) {
+          return Response.ok('middleware');
+        }
+        return null;
+      },
+    );
+
+    var app = Router();
+    app.mount(
+      '/api/',
+      Pipeline().addMiddleware(middleware).addHandler(api),
+    );
+
+    server.mount(app);
+
+    expect(await get('/api/hello'), 'Hello');
+    expect(await get('/api/hello?ok'), 'middleware');
+  });
+
+  test('mount(Router) does not require a trailing slash', () async {
+    var api = Router();
+    api.get('/', (Request request) {
+      return Response.ok('Hello World!');
+    });
+
+    api.get('/user/<user>/info', (Request request, String user) {
+      return Response.ok('Hello $user');
+    });
+
+    var app = Router();
+    app.get('/hello', (Request request) {
+      return Response.ok('hello-world');
+    });
+
+    app.mount('/api', api);
+
+    app.all('/<_|[^]*>', (Request request) {
+      return Response.ok('catch-all-handler');
+    });
+
+    server.mount(app);
+
+    expect(await get('/hello'), 'hello-world');
+    expect(await get('/api'), 'Hello World!');
+    expect(await get('/api/'), 'Hello World!');
+    expect(await get('/api/user/jonasfj/info'), 'Hello jonasfj');
+    expect(await get('/api/user/jonasfj/info-wrong'), 'catch-all-handler');
+  });
+
+  test('responds with 404 if no handler matches', () {
+    var api = Router()..get('/hello', (request) => Response.ok('Hello'));
+    server.mount(api);
+
+    expect(
+        get('/hi'),
+        throwsA(isA<http.ClientException>()
+            .having((e) => e.message, 'message', contains('404: Not Found.'))));
+  });
+
+  test('can invoke custom handler if no route matches', () {
+    var api = Router(notFoundHandler: (req) => Response.ok('Not found, but ok'))
+      ..get('/hello', (request) => Response.ok('Hello'));
+    server.mount(api);
+
+    expect(get('/hi'), completion('Not found, but ok'));
+  });
+
+  test('can call Router.routeNotFound.read multiple times', () async {
+    final b1 = await Router.routeNotFound.readAsString();
+    expect(b1, 'Route not found');
+    final b2 = await Router.routeNotFound.readAsString();
+    expect(b2, b1);
+  });
+}
diff --git a/pkgs/shelf_router_generator/CHANGELOG.md b/pkgs/shelf_router_generator/CHANGELOG.md
new file mode 100644
index 0000000..a5ff54d
--- /dev/null
+++ b/pkgs/shelf_router_generator/CHANGELOG.md
@@ -0,0 +1,48 @@
+## v1.0.2
+
+* Support update-to-date dependencies.
+
+## v1.0.1
+
+* Support update-to-date dependencies.
+
+## v1.0.0
+
+ * Support update-to-date dependencies.
+ * Migrate implementation to null safety.
+ * Generate null-safe code.
+
+## v0.7.2+4
+
+ * Relax dependency constraint on `analyzer`.
+
+## v0.7.2+3
+
+ * Relax dependency constraint on `analyzer`.
+
+## v0.7.2+2
+
+ * Relax dependency constraint on `analyzer`.
+
+## v0.7.2+1
+
+ * Relax dependency constraint on `analyzer`.
+
+## v0.7.2
+
+ * Use `literalString('...', raw: true)` from `package:code_builder` to ensure
+   that generated strings are properly escaped (fixing [#10][issue-10]).
+
+[issue-10]: https://github.com/google/dart-neats/issues/10
+
+## v0.7.1
+
+ * Bumped dependencies.
+
+## v0.7.0+1
+
+ * Updated `README.md` with references to package `build_runner`.
+
+## v0.7.0
+
+ * Initial release
diff --git a/pkgs/shelf_router_generator/LICENSE b/pkgs/shelf_router_generator/LICENSE
new file mode 100644
index 0000000..7a4a3ea
--- /dev/null
+++ b/pkgs/shelf_router_generator/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
\ No newline at end of file
diff --git a/pkgs/shelf_router_generator/README.md b/pkgs/shelf_router_generator/README.md
new file mode 100644
index 0000000..75eb84d
--- /dev/null
+++ b/pkgs/shelf_router_generator/README.md
@@ -0,0 +1,70 @@
+Shelf Router Generator
+======================
+
+[Shelf](https://pub.dartlang.org/packages/shelf) makes it easy to build web
+applications in Dart by composing request handlers. The `shelf_router` package
+offers a request router for Shelf. this package enables generating a
+`shelf_route.Router` from annotations in code.
+
+This package should be a _development dependency_ along with
+[package `build_runner`](https://pub.dartlang.org/packages/build_runner), and
+used with [package `shelf`](https://pub.dartlang.org/packages/shelf) and
+[package `shelf_router`](https://pub.dartlang.org/packages/shelf_router) as
+dependencies.
+
+```yaml
+dependencies:
+  shelf: ^0.7.5
+  shelf_router: ^0.7.0+1
+dev_dependencies:
+  shelf_router_generator: ^0.7.0+1
+  build_runner: ^1.3.1
+```
+
+Once your code have been annotated as illustrated in the example below the
+generated part can be created with `pub run build_runner build`.
+
+## Example
+
+```dart
+import 'package:shelf/shelf.dart';
+import 'package:shelf_router/shelf_router.dart';
+
+part 'userservice.g.dart'; // generated with 'pub run build_runner build'
+
+class UserService {
+  final DatabaseConnection connection;
+  UserService(this.connection);
+
+  @Route.get('/users/')
+  Future<Response> listUsers(Request request) async {
+    return Response.ok('["user1"]');
+  }
+
+  @Route.get('/users/<userId>')
+  Future<Response> fetchUser(Request request, String userId) async {
+    if (userId == 'user1') {
+      return Response.ok('user1');
+    }
+    return Response.notFound('no such user');
+  }
+
+  // Create router using the generate function defined in 'userservice.g.dart'.
+  Router get router => _$UserServiceRouter(this);
+}
+
+void main() async {
+  // You can setup context, database connections, cache connections, email
+  // services, before you create an instance of your service.
+  var connection = await DatabaseConnection.connect('localhost:1234');
+
+  // Create an instance of your service, usine one of the constructors you've
+  // defined.
+  var service = UserService(connection);
+  // Service request using the router, note the router can also be mounted.
+  var router = service.router;
+  var server = await io.serve(router.handler, 'localhost', 8080);
+}
+```
+
+
diff --git a/pkgs/shelf_proxy/analysis_options.yaml b/pkgs/shelf_router_generator/analysis_options.yaml
similarity index 75%
rename from pkgs/shelf_proxy/analysis_options.yaml
rename to pkgs/shelf_router_generator/analysis_options.yaml
index 5467db8..8cc7cd1 100644
--- a/pkgs/shelf_proxy/analysis_options.yaml
+++ b/pkgs/shelf_router_generator/analysis_options.yaml
@@ -6,45 +6,30 @@
 
 linter:
   rules:
-    - avoid_bool_literals_in_conditional_expressions
-    - avoid_catching_errors
-    - avoid_classes_with_only_static_members
     - avoid_dynamic_calls
-    - avoid_empty_else
+    - sort_pub_dependencies
     - avoid_function_literals_in_foreach_calls
     - avoid_private_typedef_functions
     - avoid_redundant_argument_values
     - avoid_renaming_method_parameters
-    - avoid_returning_null
-    - avoid_returning_null_for_future
     - avoid_returning_null_for_void
-    - avoid_returning_this
     - avoid_unused_constructor_parameters
-    - avoid_void_async
+    - await_only_futures
     - camel_case_types
-    - cancel_subscriptions
-    - cascade_invocations
-    - comment_references
     - constant_identifier_names
-    - control_flow_in_finally
     - directives_ordering
     - empty_statements
     - file_names
-    - hash_and_equals
     - implementation_imports
-    - invariant_booleans
     - iterable_contains_unrelated_type
     - join_return_with_assignment
     - lines_longer_than_80_chars
     - list_remove_unrelated_type
-    - literal_only_boolean_expressions
     - missing_whitespace_between_adjacent_strings
-    - no_adjacent_strings_in_list
     - no_runtimeType_toString
     - non_constant_identifier_names
     - only_throw_errors
     - overridden_fields
-    - package_api_docs
     - package_names
     - package_prefixed_library_names
     - prefer_asserts_in_initializer_lists
@@ -53,7 +38,9 @@
     - prefer_expression_function_bodies
     - prefer_final_locals
     - prefer_function_declarations_over_variables
+    - prefer_if_null_operators
     - prefer_initializing_formals
+    - prefer_inlined_adds
     - prefer_interpolation_to_compose_strings
     - prefer_is_not_operator
     - prefer_null_aware_operators
@@ -61,15 +48,16 @@
     - prefer_typing_uninitialized_variables
     - prefer_void_to_null
     - provide_deprecation_message
-    - sort_pub_dependencies
     - test_types_in_equals
     - throw_in_finally
-    - unnecessary_await_in_return
+    - type_annotate_public_apis
+    - unnecessary_brace_in_string_interps
     - unnecessary_lambdas
     - unnecessary_null_aware_assignments
     - unnecessary_overrides
     - unnecessary_parenthesis
     - unnecessary_statements
     - unnecessary_string_interpolations
+    - use_is_even_rather_than_modulo
     - use_string_buffers
     - void_checks
diff --git a/pkgs/shelf_router_generator/build.yaml b/pkgs/shelf_router_generator/build.yaml
new file mode 100644
index 0000000..d6561c0
--- /dev/null
+++ b/pkgs/shelf_router_generator/build.yaml
@@ -0,0 +1,14 @@
+targets:
+  $default:
+    builders:
+      shelf_router_generator|shelf_router:
+        enabled: true
+
+builders:
+  shelf_router:
+    import: "package:shelf_router_generator/builder.dart"
+    builder_factories: ["shelfRouter"]
+    build_extensions: {".dart": [".shelf_router.g.part"]}
+    auto_apply: dependents
+    build_to: cache
+    applies_builders: ["source_gen|combining_builder"]
diff --git a/pkgs/shelf_router_generator/example/main.dart b/pkgs/shelf_router_generator/example/main.dart
new file mode 100644
index 0000000..ca68090
--- /dev/null
+++ b/pkgs/shelf_router_generator/example/main.dart
@@ -0,0 +1,82 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     https://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.
+
+// @dart=2.12
+
+import 'dart:async' show Future;
+
+import 'package:shelf/shelf.dart';
+import 'package:shelf/shelf_io.dart' as shelf_io;
+import 'package:shelf_router/shelf_router.dart';
+
+// Generated code will be written to 'main.g.dart'
+part 'main.g.dart';
+
+class Service {
+  // A handler is annotated with @Route.<verb>('<route>'), the '<route>' may
+  // embed URL-parameters, and these may be taken as parameters by the handler.
+  // But either all URL-parameters or none of the URL parameters must be taken
+  // as parameters by the handler.
+  @Route.get('/say-hi/<name>')
+  Response _hi(Request request, String name) => Response.ok('hi $name');
+
+  // Embedded URL parameters may also be associated with a regular-expression
+  // that the pattern must match.
+  @Route.get('/user/<userId|[0-9]+>')
+  Response _user(Request request, String userId) =>
+      Response.ok('User has the user-number: $userId');
+
+  // Handlers can be asynchronous (returning `FutureOr` is also allowed).
+  @Route.get('/wave')
+  Future<Response> _wave(Request request) async {
+    await Future.delayed(const Duration(milliseconds: 100));
+    return Response.ok('_o/');
+  }
+
+  // Other routers can be mounted...
+  @Route.mount('/api/')
+  Router get _api => Api().router;
+
+  // You can catch all verbs and use a URL-parameter with a regular expression
+  // that matches everything to catch app.
+  @Route.all('/<ignored|.*>')
+  Response _notFound(Request request) => Response.notFound('Page not found');
+
+  // The generated function _$ServiceRouter can be used to get a [Handler]
+  // for this object. This can be used with [shelf_io.serve].
+  Handler get handler => _$ServiceRouter(this);
+}
+
+class Api {
+  // A handler can have more that one route :)
+  @Route.get('/messages')
+  @Route.get('/messages/')
+  Future<Response> _messages(Request request) async => Response.ok('[]');
+
+  // This nested catch-all, will only catch /api/.* when mounted above.
+  // Notice that ordering if annotated handlers and mounts is significant.
+  @Route.all('/<ignored|.*>')
+  Response _notFound(Request request) => Response.notFound('null');
+
+  // The generated function _$ApiRouter can be used to expose a [Router] for
+  // this object.
+  Router get router => _$ApiRouter(this);
+}
+
+// Run shelf server and host a [Service] instance on port 8080.
+void main() async {
+  final service = Service();
+  final server = await shelf_io.serve(service.handler, 'localhost', 8080);
+  print('Server running on localhost:${server.port}');
+}
diff --git a/pkgs/shelf_router_generator/example/main.g.dart b/pkgs/shelf_router_generator/example/main.g.dart
new file mode 100644
index 0000000..16d9a47
--- /dev/null
+++ b/pkgs/shelf_router_generator/example/main.g.dart
@@ -0,0 +1,26 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// @dart=2.12
+
+part of 'main.dart';
+
+// **************************************************************************
+// ShelfRouterGenerator
+// **************************************************************************
+
+Router _$ServiceRouter(Service service) {
+  final router = Router();
+  router.add('GET', r'/say-hi/<name>', service._hi);
+  router.add('GET', r'/user/<userId|[0-9]+>', service._user);
+  router.add('GET', r'/wave', service._wave);
+  router.mount(r'/api/', service._api);
+  router.all(r'/<ignored|.*>', service._notFound);
+  return router;
+}
+
+Router _$ApiRouter(Api service) {
+  final router = Router();
+  router.add('GET', r'/messages', service._messages);
+  router.add('GET', r'/messages/', service._messages);
+  router.all(r'/<ignored|.*>', service._notFound);
+  return router;
+}
diff --git a/pkgs/shelf_router_generator/lib/builder.dart b/pkgs/shelf_router_generator/lib/builder.dart
new file mode 100644
index 0000000..4430571
--- /dev/null
+++ b/pkgs/shelf_router_generator/lib/builder.dart
@@ -0,0 +1,35 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     https://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.
+
+/// This library provides a [Builder] for generating functions that can create
+/// a [shelf_router.Router] based on annotated members.
+///
+/// This is **not intended** for consumption, this library should be used by
+/// running `pub run build_runner build`. Using this library through other means
+/// is not supported and may break arbitrarily.
+library builder;
+
+import 'package:build/build.dart';
+import 'package:shelf_router/shelf_router.dart' as shelf_router;
+import 'package:source_gen/source_gen.dart';
+
+import 'src/shelf_router_generator.dart';
+
+/// A [Builder] that generates a `_$<className>Router(<className> service)`
+/// function for each class `<className>` containing a member annotated with
+/// [shelf_router.Route].
+Builder shelfRouter(BuilderOptions _) => SharedPartBuilder(
+      [ShelfRouterGenerator()],
+      'shelf_router',
+    );
diff --git a/pkgs/shelf_router_generator/lib/src/shelf_router_generator.dart b/pkgs/shelf_router_generator/lib/src/shelf_router_generator.dart
new file mode 100644
index 0000000..3050ed9
--- /dev/null
+++ b/pkgs/shelf_router_generator/lib/src/shelf_router_generator.dart
@@ -0,0 +1,302 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     https://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 'dart:async' show Future;
+
+import 'package:analyzer/dart/element/element.dart'
+    show ClassElement, ElementKind, ExecutableElement;
+import 'package:analyzer/dart/element/type.dart' show ParameterizedType;
+import 'package:build/build.dart' show BuildStep, log;
+import 'package:code_builder/code_builder.dart' as code;
+import 'package:http_methods/http_methods.dart' show isHttpMethod;
+import 'package:shelf/shelf.dart' as shelf;
+import 'package:shelf_router/shelf_router.dart' as shelf_router;
+
+// ignore: implementation_imports
+import 'package:shelf_router/src/router_entry.dart' show RouterEntry;
+import 'package:source_gen/source_gen.dart' as g;
+
+// Type checkers that we need later
+const _routeType = g.TypeChecker.fromRuntime(shelf_router.Route);
+const _routerType = g.TypeChecker.fromRuntime(shelf_router.Router);
+const _responseType = g.TypeChecker.fromRuntime(shelf.Response);
+const _requestType = g.TypeChecker.fromRuntime(shelf.Request);
+const _stringType = g.TypeChecker.fromRuntime(String);
+
+/// A representation of a handler that was annotated with [Route].
+class _Handler {
+  final String verb, route;
+  final ExecutableElement element;
+
+  _Handler(this.verb, this.route, this.element);
+}
+
+/// Find members of a class annotated with [shelf_router.Route].
+List<ExecutableElement> getAnnotatedElementsOrderBySourceOffset(
+        ClassElement cls) =>
+    <ExecutableElement>[
+      ...cls.methods.where(_routeType.hasAnnotationOfExact),
+      ...cls.accessors.where(_routeType.hasAnnotationOfExact)
+    ]..sort((a, b) => (a.nameOffset).compareTo(b.nameOffset));
+
+/// Generate a `_$<className>Router(<className> service)` method that returns a
+/// [shelf_router.Router] configured based on annotated handlers.
+code.Method _buildRouterMethod({
+  required ClassElement classElement,
+  required List<_Handler> handlers,
+}) =>
+    code.Method(
+      (b) => b
+        ..name = '_\$${classElement.name}Router'
+        ..requiredParameters.add(
+          code.Parameter((b) => b
+            ..name = 'service'
+            ..type = code.refer(classElement.name)),
+        )
+        ..returns = code.refer('Router')
+        ..body = code.Block(
+          (b) => b
+            ..addExpression(
+                code.refer('Router').newInstance([]).assignFinal('router'))
+            ..statements.addAll(handlers.map((h) => _buildAddHandlerCode(
+                  router: code.refer('router'),
+                  service: code.refer('service'),
+                  handler: h,
+                )))
+            ..addExpression(code.refer('router').returned),
+        ),
+    );
+
+/// Generate the code statement that adds [handler] from [service] to [router].
+code.Code _buildAddHandlerCode({
+  required code.Reference router,
+  required code.Reference service,
+  required _Handler handler,
+}) {
+  switch (handler.verb) {
+    case r'$mount':
+      return router.property('mount').call([
+        code.literalString(handler.route, raw: true),
+        service.property(handler.element.name),
+      ]).statement;
+    case r'$all':
+      return router.property('all').call([
+        code.literalString(handler.route, raw: true),
+        service.property(handler.element.name),
+      ]).statement;
+    default:
+      return router.property('add').call([
+        code.literalString(handler.verb.toUpperCase()),
+        code.literalString(handler.route, raw: true),
+        service.property(handler.element.name),
+      ]).statement;
+  }
+}
+
+class ShelfRouterGenerator extends g.Generator {
+  @override
+  Future<String?> generate(g.LibraryReader library, BuildStep buildStep) async {
+    // Create a map from ClassElement to list of annotated elements sorted by
+    // offset in source code, this is not type checked yet.
+    final classes = <ClassElement, List<_Handler>>{};
+    for (final cls in library.classes) {
+      final elements = getAnnotatedElementsOrderBySourceOffset(cls);
+      if (elements.isEmpty) {
+        continue;
+      }
+      log.info('found shelf_router.Route annotations in ${cls.name}');
+
+      classes[cls] = elements
+          .map((e) => _routeType.annotationsOfExact(e).map((a) => _Handler(
+                a.getField('verb')!.toStringValue()!,
+                a.getField('route')!.toStringValue()!,
+                e,
+              )))
+          .expand((i) => i)
+          .toList();
+    }
+    if (classes.isEmpty) {
+      return null; // nothing to do if nothing was annotated
+    }
+
+    // Run type check to ensure method and getters have the right signatures.
+    for (final handler in classes.values.expand((i) => i)) {
+      // If the verb is $mount, then it's not a handler, but a mount.
+      if (handler.verb.toLowerCase() == r'$mount') {
+        _typeCheckMount(handler);
+      } else {
+        _typeCheckHandler(handler);
+      }
+    }
+
+    // Build library and emit code with all generate methods.
+    final methods = classes.entries.map((e) => _buildRouterMethod(
+          classElement: e.key,
+          handlers: e.value,
+        ));
+    return code.Library((b) => b.body.addAll(methods))
+        .accept(code.DartEmitter())
+        .toString();
+  }
+}
+
+/// Type checks for the case where [shelf_router.Route] is used to annotate
+/// shelf request handler.
+void _typeCheckHandler(_Handler h) {
+  if (h.element.isStatic) {
+    throw g.InvalidGenerationSourceError(
+        'The shelf_router.Route annotation cannot be used on static members',
+        element: h.element);
+  }
+
+  // Check the verb, note that $all is a special value for handling all verbs.
+  if (!isHttpMethod(h.verb) && h.verb != r'$all') {
+    throw g.InvalidGenerationSourceError(
+        'The verb "${h.verb}" used in shelf_router.Route annotation must be '
+        'a valid HTTP method',
+        element: h.element);
+  }
+
+  // Check that this shouldn't have been annotated with Route.mount
+  if (h.element.kind == ElementKind.GETTER) {
+    throw g.InvalidGenerationSourceError(
+        'Only the shelf_router.Route.mount annotation can only be used on a '
+        'getter, and only if it returns a shelf_router.Router',
+        element: h.element);
+  }
+
+  // Check that this is indeed a method
+  if (h.element.kind != ElementKind.METHOD) {
+    throw g.InvalidGenerationSourceError(
+        'The shelf_router.Route annotation can only be used on request '
+        'handling methods',
+        element: h.element);
+  }
+
+  // Check the route can parse
+  List<String> params;
+  try {
+    params = RouterEntry(h.verb, h.route, () => null).params;
+  } on ArgumentError catch (e) {
+    throw g.InvalidGenerationSourceError(
+      e.toString(),
+      element: h.element,
+    );
+  }
+
+  // Ensure that the first parameter is shelf.Request
+  if (h.element.parameters.isEmpty) {
+    throw g.InvalidGenerationSourceError(
+        'The shelf_router.Route annotation can only be used on shelf request '
+        'handlers accept a shelf.Request parameter',
+        element: h.element);
+  }
+  for (final p in h.element.parameters) {
+    if (p.isOptional) {
+      throw g.InvalidGenerationSourceError(
+          'The shelf_router.Route annotation can only be used on shelf '
+          'request handlers accept a shelf.Request parameter and/or a '
+          'shelf.Request parameter and all string parameters in the route, '
+          'optional parameters are not permitted',
+          element: p);
+    }
+  }
+  if (!_requestType.isExactlyType(h.element.parameters.first.type)) {
+    throw g.InvalidGenerationSourceError(
+        'The shelf_router.Route annotation can only be used on shelf request '
+        'handlers accept a shelf.Request parameter as first parameter',
+        element: h.element);
+  }
+  if (h.element.parameters.length > 1) {
+    if (h.element.parameters.length != params.length + 1) {
+      throw g.InvalidGenerationSourceError(
+          'The shelf_router.Route annotation can only be used on shelf '
+          'request handlers accept a shelf.Request parameter and/or a '
+          'shelf.Request parameter and all string parameters in the route',
+          element: h.element);
+    }
+    for (var i = 0; i < params.length; i++) {
+      final p = h.element.parameters[i + 1];
+      if (p.name != params[i]) {
+        throw g.InvalidGenerationSourceError(
+            'The shelf_router.Route annotation can only be used on shelf '
+            'request handlers accept a shelf.Request parameter and/or a '
+            'shelf.Request parameter and all string parameters in the route, '
+            'the "${p.name}" parameter should be named "${params[i]}"',
+            element: p);
+      }
+      if (!_stringType.isExactlyType(p.type)) {
+        throw g.InvalidGenerationSourceError(
+            'The shelf_router.Route annotation can only be used on shelf '
+            'request handlers accept a shelf.Request parameter and/or a '
+            'shelf.Request parameter and all string parameters in the route, '
+            'the "${p.name}" parameter is not of type string',
+            element: p);
+      }
+    }
+  }
+
+  // Check the return value of the method.
+  var returnType = h.element.returnType;
+  // Unpack Future<T> and FutureOr<T> wrapping of responseType
+  if (returnType.isDartAsyncFuture || returnType.isDartAsyncFutureOr) {
+    returnType = (returnType as ParameterizedType).typeArguments.first;
+  }
+  if (!_responseType.isAssignableFromType(returnType)) {
+    throw g.InvalidGenerationSourceError(
+        'The shelf_router.Route annotation can only be used on shelf request '
+        'handlers that return shelf.Response, Future<shelf.Response> or '
+        'FutureOr<shelf.Response>, and not "${h.element.returnType}"',
+        element: h.element);
+  }
+}
+
+/// Type checks for the case where [shelf_router.Route.mount] is used to
+/// annotate a getter that returns a [shelf_router.Router].
+void _typeCheckMount(_Handler h) {
+  if (h.element.isStatic) {
+    throw g.InvalidGenerationSourceError(
+        'The shelf_router.Route annotation cannot be used on static members',
+        element: h.element);
+  }
+
+  // Check that this should have been annotated with Route.mount
+  if (h.element.kind != ElementKind.GETTER) {
+    throw g.InvalidGenerationSourceError(
+        'The shelf_router.Route.mount annotation can only be used on a '
+        'getter that returns shelf_router.Router',
+        element: h.element);
+  }
+
+  // Sanity checks for the prefix
+  if (!h.route.startsWith('/') || !h.route.endsWith('/')) {
+    throw g.InvalidGenerationSourceError(
+        'The prefix "${h.route}" in shelf_router.Route.mount(prefix) '
+        'annotation must begin and end with a slash',
+        element: h.element);
+  }
+  if (h.route.contains('<')) {
+    throw g.InvalidGenerationSourceError(
+        'The prefix "${h.route}" in shelf_router.Route.mount(prefix) '
+        'annotation cannot contain <',
+        element: h.element);
+  }
+
+  if (!_routerType.isAssignableFromType(h.element.returnType)) {
+    throw g.InvalidGenerationSourceError(
+        'The shelf_router.Route.mount annotation can only be used on a '
+        'getter that returns shelf_router.Router',
+        element: h.element);
+  }
+}
diff --git a/pkgs/shelf_web_socket/mono_pkg.yml b/pkgs/shelf_router_generator/mono_pkg.yaml
similarity index 78%
copy from pkgs/shelf_web_socket/mono_pkg.yml
copy to pkgs/shelf_router_generator/mono_pkg.yaml
index 75b1a04..84c7b48 100644
--- a/pkgs/shelf_web_socket/mono_pkg.yml
+++ b/pkgs/shelf_router_generator/mono_pkg.yaml
@@ -1,5 +1,5 @@
 sdk:
-- 2.12.0
+- 2.14.0
 - dev
 
 stages:
@@ -10,6 +10,3 @@
     - dev
 - unit_test:
   - test: --test-randomize-ordering-seed=random
-    os:
-    - linux
-    - windows
diff --git a/pkgs/shelf_router_generator/pubspec.yaml b/pkgs/shelf_router_generator/pubspec.yaml
new file mode 100644
index 0000000..9489f3c
--- /dev/null
+++ b/pkgs/shelf_router_generator/pubspec.yaml
@@ -0,0 +1,28 @@
+name: shelf_router_generator
+version: 1.0.2
+description: >-
+  A package:build compatible builder for generating request routers for the
+  shelf web-framework based on source annotations.
+homepage: https://github.com/google/dart-neats/tree/master/shelf_router_generator
+repository: https://github.com/google/dart-neats.git
+issue_tracker: https://github.com/google/dart-neats/labels/pkg:shelf_router_generator
+
+dependencies:
+  analyzer: '>=2.0.0 <4.0.0'
+  build: ^2.0.0
+  build_config: ^1.0.0
+  code_builder: ^4.0.0
+  http_methods: ^1.0.0
+  shelf: ^1.1.0
+  shelf_router: ^1.0.0
+  source_gen: ^1.0.0
+
+dev_dependencies:
+  build_runner: ^2.0.0
+  build_verify: ^3.0.0
+  http: ^0.13.0
+  lints: ^1.0.0
+  test: ^1.5.3
+
+environment:
+  sdk: '>=2.12.0 <3.0.0'
diff --git a/pkgs/shelf_router_generator/test/ensure_build_test.dart b/pkgs/shelf_router_generator/test/ensure_build_test.dart
new file mode 100644
index 0000000..55a246d
--- /dev/null
+++ b/pkgs/shelf_router_generator/test/ensure_build_test.dart
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     https://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 'package:build_verify/build_verify.dart';
+// import 'package:test/test.dart';
+
+void main() {
+  // TODO: Run this test when the build_runner bug is fixed
+  // test('ensure_build', () {
+  //   expectBuildClean(packageRelativeDirectory: 'shelf_router_generator');
+  // });
+}
diff --git a/pkgs/shelf_router_generator/test/server/api.dart b/pkgs/shelf_router_generator/test/server/api.dart
new file mode 100644
index 0000000..f27323a
--- /dev/null
+++ b/pkgs/shelf_router_generator/test/server/api.dart
@@ -0,0 +1,36 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     https://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.
+
+// @dart=2.12
+
+import 'dart:async' show Future;
+import 'package:shelf/shelf.dart';
+import 'package:shelf_router/shelf_router.dart';
+
+part 'api.g.dart';
+
+class Api {
+  @Route.get('/time')
+  Response _time(Request request) => Response.ok('it is about now');
+
+  @Route.get('/to-uppercase/<word|.*>')
+  Future<Response> _toUpperCase(Request request, String word) async =>
+      Response.ok(word.toUpperCase());
+
+  @Route.get(r'/$string-escape')
+  Response _stringEscapingWorks(Request request) =>
+      Response.ok('Just testing string escaping');
+
+  Router get router => _$ApiRouter(this);
+}
diff --git a/pkgs/shelf_router_generator/test/server/api.g.dart b/pkgs/shelf_router_generator/test/server/api.g.dart
new file mode 100644
index 0000000..7c834d1
--- /dev/null
+++ b/pkgs/shelf_router_generator/test/server/api.g.dart
@@ -0,0 +1,16 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// @dart=2.12
+
+part of 'api.dart';
+
+// **************************************************************************
+// ShelfRouterGenerator
+// **************************************************************************
+
+Router _$ApiRouter(Api service) {
+  final router = Router();
+  router.add('GET', r'/time', service._time);
+  router.add('GET', r'/to-uppercase/<word|.*>', service._toUpperCase);
+  router.add('GET', r'/$string-escape', service._stringEscapingWorks);
+  return router;
+}
diff --git a/pkgs/shelf_router_generator/test/server/server.dart b/pkgs/shelf_router_generator/test/server/server.dart
new file mode 100644
index 0000000..b882bdf
--- /dev/null
+++ b/pkgs/shelf_router_generator/test/server/server.dart
@@ -0,0 +1,39 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     https://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.
+
+// @dart=2.12
+
+import 'dart:async' show Future;
+import 'dart:io' show HttpServer;
+
+import 'package:shelf/shelf_io.dart' as shelf_io;
+
+import 'service.dart';
+
+class Server {
+  final _service = Service();
+  late HttpServer _server;
+
+  Future<void> start() async {
+    _server = await shelf_io.serve(_service.router, 'localhost', 0);
+  }
+
+  Future<void> stop() => _server.close();
+
+  Uri get uri => Uri(
+        scheme: 'http',
+        host: 'localhost',
+        port: _server.port,
+      );
+}
diff --git a/pkgs/shelf_router_generator/test/server/service.dart b/pkgs/shelf_router_generator/test/server/service.dart
new file mode 100644
index 0000000..7fec7f6
--- /dev/null
+++ b/pkgs/shelf_router_generator/test/server/service.dart
@@ -0,0 +1,61 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     https://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.
+
+// @dart=2.12
+
+import 'dart:async' show Future, FutureOr;
+
+import 'package:shelf/shelf.dart';
+import 'package:shelf_router/shelf_router.dart';
+
+import 'api.dart';
+import 'unrelatedannotation.dart';
+
+part 'service.g.dart';
+
+class Service {
+  @Route.get('/say-hello')
+  @Route.get('/say-hello/')
+  Response _sayHello(Request request) => Response.ok('hello world');
+
+  @Route.get('/wave')
+  FutureOr<Response> _wave(Request request) async {
+    await Future.delayed(const Duration(milliseconds: 50));
+    return Response.ok('_o/');
+  }
+
+  @Route.get('/greet/<user>')
+  Future<Response> _greet(Request request, String user) async =>
+      Response.ok('Greetings, $user');
+
+  @Route.get('/hi/<user>')
+  Future<Response> _hi(Request request) async {
+    final name = request.params['user'];
+    return Response.ok('hi $name');
+  }
+
+  @Route.mount('/api/')
+  Router get _api => Api().router;
+
+  @Route.all('/<_|.*>')
+  Response _index(Request request) => Response.ok('nothing-here');
+
+  Router get router => _$ServiceRouter(this);
+}
+
+class UnrelatedThing {
+  @EndPoint.put('/api/test')
+  Future<Response> unrelatedMethod(Request request) async =>
+      Response.ok('hello world');
+}
diff --git a/pkgs/shelf_router_generator/test/server/service.g.dart b/pkgs/shelf_router_generator/test/server/service.g.dart
new file mode 100644
index 0000000..762773a
--- /dev/null
+++ b/pkgs/shelf_router_generator/test/server/service.g.dart
@@ -0,0 +1,20 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// @dart=2.12
+
+part of 'service.dart';
+
+// **************************************************************************
+// ShelfRouterGenerator
+// **************************************************************************
+
+Router _$ServiceRouter(Service service) {
+  final router = Router();
+  router.add('GET', r'/say-hello', service._sayHello);
+  router.add('GET', r'/say-hello/', service._sayHello);
+  router.add('GET', r'/wave', service._wave);
+  router.add('GET', r'/greet/<user>', service._greet);
+  router.add('GET', r'/hi/<user>', service._hi);
+  router.mount(r'/api/', service._api);
+  router.all(r'/<_|.*>', service._index);
+  return router;
+}
diff --git a/pkgs/shelf_router_generator/test/server/unrelatedannotation.dart b/pkgs/shelf_router_generator/test/server/unrelatedannotation.dart
new file mode 100644
index 0000000..505e199
--- /dev/null
+++ b/pkgs/shelf_router_generator/test/server/unrelatedannotation.dart
@@ -0,0 +1,38 @@
+// @dart=2.12
+
+/// Annotation for an API end-point.
+class EndPoint {
+  /// HTTP verb for requests routed to the annotated method.
+  final String verb;
+
+  /// HTTP route for request routed to the annotated method.
+  final String route;
+
+  /// Create an annotation that routes requests matching [verb] and [route] to
+  /// the annotated method.
+  const EndPoint(this.verb, this.route);
+
+  /// Route `GET` requests matching [route] to annotated method.
+  const EndPoint.get(this.route) : verb = 'GET';
+
+  /// Route `HEAD` requests matching [route] to annotated method.
+  const EndPoint.head(this.route) : verb = 'HEAD';
+
+  /// Route `POST` requests matching [route] to annotated method.
+  const EndPoint.post(this.route) : verb = 'POST';
+
+  /// Route `PUT` requests matching [route] to annotated method.
+  const EndPoint.put(this.route) : verb = 'PUT';
+
+  /// Route `DELETE` requests matching [route] to annotated method.
+  const EndPoint.delete(this.route) : verb = 'DELETE';
+
+  /// Route `CONNECT` requests matching [route] to annotated method.
+  const EndPoint.connect(this.route) : verb = 'CONNECT';
+
+  /// Route `OPTIONS` requests matching [route] to annotated method.
+  const EndPoint.options(this.route) : verb = 'OPTIONS';
+
+  /// Route `TRACE` requests matching [route] to annotated method.
+  const EndPoint.trace(this.route) : verb = 'TRACE';
+}
diff --git a/pkgs/shelf_router_generator/test/server_test.dart b/pkgs/shelf_router_generator/test/server_test.dart
new file mode 100644
index 0000000..28199fc
--- /dev/null
+++ b/pkgs/shelf_router_generator/test/server_test.dart
@@ -0,0 +1,58 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     https://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.
+
+// @dart=2.12
+
+import 'package:http/http.dart' as http;
+import 'package:test/test.dart';
+
+import 'server/server.dart';
+
+void main() {
+  final server = Server();
+  setUpAll(server.start);
+  tearDownAll(server.stop);
+
+  void testGet({
+    required String path,
+    required String result,
+  }) =>
+      test('GET $path', () async {
+        final result = await http.get(server.uri.resolve(path));
+        expect(result, equals(result));
+      });
+
+  // Test simple handlers
+  testGet(path: '/say-hello', result: 'hello world');
+  testGet(path: '/say-hello/', result: 'hello world');
+  testGet(path: '/wave', result: '_o/');
+  testGet(path: '/greet/jonasfj', result: 'Greetings, jonasfj');
+  testGet(path: '/greet/sigurdm', result: 'Greetings, sigurdm');
+  testGet(path: '/hi/jonasfj', result: 'hi jonasfj');
+  testGet(path: '/hi/sigurdm', result: 'hi sigurdm');
+
+  // Test /api/
+  testGet(path: '/api/time', result: 'it is about now');
+  testGet(path: '/api/to-uppercase/wEiRd%20Word', result: 'WEIRD WORD');
+  testGet(path: '/api/to-uppercase/wEiRd Word', result: 'WEIRD WORD');
+
+  // Test the catch all handler
+  testGet(path: '/', result: 'nothing-here');
+  testGet(path: '/wrong-path', result: 'nothing-here');
+  testGet(path: '/hi/sigurdm/ups', result: 'nothing-here');
+  testGet(path: '/api/to-uppercase/too/many/slashs', result: 'nothing-here');
+  testGet(path: '/api/', result: 'nothing-here');
+  testGet(path: '/api/time/', result: 'nothing-here'); // notice the extra slash
+  testGet(path: '/api/tim', result: 'nothing-here');
+}
diff --git a/pkgs/shelf_static/CHANGELOG.md b/pkgs/shelf_static/CHANGELOG.md
index 8be7a56..b471192 100644
--- a/pkgs/shelf_static/CHANGELOG.md
+++ b/pkgs/shelf_static/CHANGELOG.md
@@ -1,5 +1,7 @@
 ## 1.1.1-dev
 
+- Require Dart `2.14`.
+
 ## 1.1.0
 
 * Correctly handle `HEAD` requests.
@@ -22,8 +24,8 @@
 * Update SDK constraint to `>=2.3.0 <3.0.0`.
 * Allow `3.x` versions of `package:convert`.
 * Allow `4.x` versions of `package:http_parser`.
-* Use file `modified` dates instead of `changed` for `304 Not Modified` checks
-  as `changed` returns creation dates on Windows.
+* Use file `modified` dates instead of `changed` for `304 Not Modified` checks as `changed` returns creation dates on
+  Windows.
 
 ## 0.2.8
 
diff --git a/pkgs/shelf_static/analysis_options.yaml b/pkgs/shelf_static/analysis_options.yaml
deleted file mode 100644
index bb10866..0000000
--- a/pkgs/shelf_static/analysis_options.yaml
+++ /dev/null
@@ -1,80 +0,0 @@
-include: package:lints/recommended.yaml
-
-analyzer:
-  strong-mode:
-    implicit-casts: false
-
-linter:
-  rules:
-  - avoid_bool_literals_in_conditional_expressions
-  - avoid_catching_errors
-  - avoid_classes_with_only_static_members
-  - avoid_function_literals_in_foreach_calls
-  - avoid_private_typedef_functions
-  - avoid_redundant_argument_values
-  - avoid_renaming_method_parameters
-  - avoid_returning_null
-  - avoid_returning_null_for_future
-  - avoid_returning_null_for_void
-  - avoid_returning_this
-  - avoid_single_cascade_in_expression_statements
-  - avoid_unused_constructor_parameters
-  - avoid_void_async
-  - await_only_futures
-  - camel_case_types
-  - cancel_subscriptions
-  - cascade_invocations
-  - comment_references
-  - constant_identifier_names
-  - control_flow_in_finally
-  - directives_ordering
-  - empty_statements
-  - file_names
-  - hash_and_equals
-  - implementation_imports
-  - invariant_booleans
-  - iterable_contains_unrelated_type
-  - join_return_with_assignment
-  - lines_longer_than_80_chars
-  - list_remove_unrelated_type
-  - literal_only_boolean_expressions
-  - missing_whitespace_between_adjacent_strings
-  - no_adjacent_strings_in_list
-  - no_runtimeType_toString
-  - non_constant_identifier_names
-  - only_throw_errors
-  - overridden_fields
-  - package_api_docs
-  - package_names
-  - package_prefixed_library_names
-  - prefer_asserts_in_initializer_lists
-  - prefer_const_constructors
-  - prefer_const_declarations
-  - prefer_expression_function_bodies
-  - prefer_final_locals
-  - prefer_function_declarations_over_variables
-  - prefer_initializing_formals
-  - prefer_inlined_adds
-  - prefer_interpolation_to_compose_strings
-  - prefer_is_not_operator
-  - prefer_null_aware_operators
-  - prefer_relative_imports
-  - prefer_typing_uninitialized_variables
-  - prefer_void_to_null
-  - provide_deprecation_message
-  - sort_pub_dependencies
-  - test_types_in_equals
-  - throw_in_finally
-  - type_annotate_public_apis
-  - unnecessary_await_in_return
-  - unnecessary_brace_in_string_interps
-  - unnecessary_getters_setters
-  - unnecessary_lambdas
-  - unnecessary_null_aware_assignments
-  - unnecessary_overrides
-  - unnecessary_parenthesis
-  - unnecessary_statements
-  - unnecessary_string_interpolations
-  - use_is_even_rather_than_modulo
-  - use_string_buffers
-  - void_checks
diff --git a/pkgs/shelf_static/lib/src/static_handler.dart b/pkgs/shelf_static/lib/src/static_handler.dart
index 463c731..f5d7006 100644
--- a/pkgs/shelf_static/lib/src/static_handler.dart
+++ b/pkgs/shelf_static/lib/src/static_handler.dart
@@ -110,7 +110,7 @@
 
         final byteSink = ByteAccumulatorSink();
 
-        await file.openRead(0, length).listen(byteSink.add).asFuture();
+        await file.openRead(0, length).listen(byteSink.add).asFuture<void>();
 
         return mimeResolver.lookup(file.path, headerBytes: byteSink.bytes);
       } else {
diff --git a/pkgs/shelf_static/mono_pkg.yaml b/pkgs/shelf_static/mono_pkg.yaml
index 75b1a04..80a16a3 100644
--- a/pkgs/shelf_static/mono_pkg.yaml
+++ b/pkgs/shelf_static/mono_pkg.yaml
@@ -1,5 +1,5 @@
 sdk:
-- 2.12.0
+- 2.14.0
 - dev
 
 stages:
diff --git a/pkgs/shelf_static/pubspec.yaml b/pkgs/shelf_static/pubspec.yaml
index 5a395c4..1677c06 100644
--- a/pkgs/shelf_static/pubspec.yaml
+++ b/pkgs/shelf_static/pubspec.yaml
@@ -4,7 +4,7 @@
 repository: https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_static
 
 environment:
-  sdk: '>=2.12.0 <3.0.0'
+  sdk: '>=2.14.0 <3.0.0'
 
 dependencies:
   convert: ^3.0.0
diff --git a/pkgs/shelf_static/test/sample_test.dart b/pkgs/shelf_static/test/sample_test.dart
index ecae3d7..0a0249a 100644
--- a/pkgs/shelf_static/test/sample_test.dart
+++ b/pkgs/shelf_static/test/sample_test.dart
@@ -52,7 +52,7 @@
   return _request(Request('GET', uri));
 }
 
-Future _testFileContents(String filename) async {
+Future<void> _testFileContents(String filename) async {
   final filePath = p.join(_samplePath, filename);
   final file = File(filePath);
   final fileContents = file.readAsBytesSync();
@@ -64,7 +64,7 @@
   await _expectCompletesWithBytes(response, fileContents);
 }
 
-Future _expectCompletesWithBytes(
+Future<void> _expectCompletesWithBytes(
     Response response, List<int> expectedBytes) async {
   final bytes = await response.read().toList();
   final flatBytes = bytes.expand((e) => e);
diff --git a/pkgs/shelf_static/test/test_util.dart b/pkgs/shelf_static/test/test_util.dart
index 5c628d8..81d5ead 100644
--- a/pkgs/shelf_static/test/test_util.dart
+++ b/pkgs/shelf_static/test/test_util.dart
@@ -55,7 +55,7 @@
       : _target = toSecondResolution(target);
 
   @override
-  bool matches(dynamic item, Map matchState) {
+  bool matches(dynamic item, Map<dynamic, dynamic> matchState) {
     if (item is! DateTime) return false;
 
     return _datesEqualToSecond(_target, item);
diff --git a/pkgs/shelf_test_handler/CHANGELOG.md b/pkgs/shelf_test_handler/CHANGELOG.md
index d450af6..4ca8a96 100644
--- a/pkgs/shelf_test_handler/CHANGELOG.md
+++ b/pkgs/shelf_test_handler/CHANGELOG.md
@@ -1,5 +1,7 @@
 ## 2.0.1-dev
 
+- Require Dart `2.14`.
+
 ## 2.0.0
 
 * Migration to null safety.
diff --git a/pkgs/shelf_test_handler/analysis_options.yaml b/pkgs/shelf_test_handler/analysis_options.yaml
deleted file mode 100644
index 0711aca..0000000
--- a/pkgs/shelf_test_handler/analysis_options.yaml
+++ /dev/null
@@ -1,43 +0,0 @@
-include: package:pedantic/analysis_options.yaml
-analyzer:
-  strong-mode:
-    implicit-casts: false
-linter:
-  rules:
-    - avoid_empty_else
-    - avoid_init_to_null
-    - avoid_null_checks_in_equality_operators
-    - avoid_unused_constructor_parameters
-    - await_only_futures
-    - camel_case_types
-    - cancel_subscriptions
-    - constant_identifier_names
-    - control_flow_in_finally
-    - directives_ordering
-    - empty_catches
-    - empty_constructor_bodies
-    - empty_statements
-    - hash_and_equals
-    - implementation_imports
-    - iterable_contains_unrelated_type
-    - library_names
-    - library_prefixes
-    - list_remove_unrelated_type
-    - non_constant_identifier_names
-    - overridden_fields
-    - package_api_docs
-    - package_names
-    - package_prefixed_library_names
-    - prefer_equal_for_default_values
-    - prefer_final_fields
-    - prefer_generic_function_type_aliases
-    - prefer_is_not_empty
-    - slash_for_doc_comments
-    - test_types_in_equals
-    - throw_in_finally
-    - type_init_formals
-    - unnecessary_brace_in_string_interps
-    - unnecessary_const
-    - unnecessary_new
-    - unrelated_type_equality_checks
-    - valid_regexps
diff --git a/pkgs/shelf_test_handler/lib/src/handler.dart b/pkgs/shelf_test_handler/lib/src/handler.dart
index 0d3eaa7..391066b 100644
--- a/pkgs/shelf_test_handler/lib/src/handler.dart
+++ b/pkgs/shelf_test_handler/lib/src/handler.dart
@@ -10,7 +10,8 @@
 
 import 'expectation.dart';
 
-/// A [Handler] that handles requests as specified by [expect] and [expectAnything].
+/// A [Handler] that handles requests as specified by [expect] and
+/// [expectAnything].
 class ShelfTestHandler {
   /// The description used in debugging output for this handler.
   final String description;
diff --git a/pkgs/shelf_test_handler/lib/src/server.dart b/pkgs/shelf_test_handler/lib/src/server.dart
index 5bfb269..540649e 100644
--- a/pkgs/shelf_test_handler/lib/src/server.dart
+++ b/pkgs/shelf_test_handler/lib/src/server.dart
@@ -44,5 +44,5 @@
   /// Closes the server.
   ///
   /// If [force] is `true`, all active connections will be closed immediately.
-  Future close({bool force = false}) => _server.close(force: force);
+  Future<void> close({bool force = false}) => _server.close(force: force);
 }
diff --git a/pkgs/shelf_test_handler/mono_pkg.yml b/pkgs/shelf_test_handler/mono_pkg.yaml
similarity index 96%
rename from pkgs/shelf_test_handler/mono_pkg.yml
rename to pkgs/shelf_test_handler/mono_pkg.yaml
index d49105b..c7bde78 100644
--- a/pkgs/shelf_test_handler/mono_pkg.yml
+++ b/pkgs/shelf_test_handler/mono_pkg.yaml
@@ -1,5 +1,5 @@
 sdk:
-- 2.12.0
+- 2.14.0
 - dev
 
 stages:
diff --git a/pkgs/shelf_test_handler/pubspec.yaml b/pkgs/shelf_test_handler/pubspec.yaml
index 1267771..f57ad93 100644
--- a/pkgs/shelf_test_handler/pubspec.yaml
+++ b/pkgs/shelf_test_handler/pubspec.yaml
@@ -4,7 +4,7 @@
 repository: https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_test_handler
 
 environment:
-  sdk: '>=2.12.0 <3.0.0'
+  sdk: '>=2.14.0 <3.0.0'
 
 dependencies:
   http_multi_server: ^3.0.0
@@ -13,10 +13,6 @@
 
 dev_dependencies:
   http: ^0.13.0
-  pedantic: ^1.10.0
+  lints: ^1.0.0
   shelf_web_socket: ^1.0.0
-
-dependency_overrides:
-  http_multi_server: ^3.0.0
-  shelf_web_socket: ^1.0.0
-  test: ^1.16.0
+  web_socket_channel: ^2.0.0
diff --git a/pkgs/shelf_test_handler/test/handler_test.dart b/pkgs/shelf_test_handler/test/handler_test.dart
index a7b0e77..af33450 100644
--- a/pkgs/shelf_test_handler/test/handler_test.dart
+++ b/pkgs/shelf_test_handler/test/handler_test.dart
@@ -112,7 +112,7 @@
   });
 }
 
-void _expectZoneFailure(Future Function() callback) {
+void _expectZoneFailure(Future<void> Function() callback) {
   runZonedGuarded(callback, expectAsync2((error, stack) {
     expect(error, TypeMatcher<TestFailure>());
   }));
diff --git a/pkgs/shelf_test_handler/test/server_test.dart b/pkgs/shelf_test_handler/test/server_test.dart
index 2390b60..29208d9 100644
--- a/pkgs/shelf_test_handler/test/server_test.dart
+++ b/pkgs/shelf_test_handler/test/server_test.dart
@@ -10,6 +10,7 @@
 import 'package:shelf_test_handler/shelf_test_handler.dart';
 import 'package:shelf_web_socket/shelf_web_socket.dart';
 import 'package:test/test.dart';
+import 'package:web_socket_channel/web_socket_channel.dart';
 
 void main() {
   test('serves a ShelfTestHandler', () async {
@@ -25,7 +26,8 @@
     var server = await ShelfTestServer.create();
     addTearDown(server.close);
 
-    server.handler.expect('GET', '/', webSocketHandler((webSocket) {
+    server.handler.expect('GET', '/',
+        webSocketHandler((WebSocketChannel webSocket) {
       webSocket.sink.add('hello!');
       webSocket.sink.close();
     }));
diff --git a/pkgs/shelf_web_socket/CHANGELOG.md b/pkgs/shelf_web_socket/CHANGELOG.md
index aa5f4e0..b4b67be 100644
--- a/pkgs/shelf_web_socket/CHANGELOG.md
+++ b/pkgs/shelf_web_socket/CHANGELOG.md
@@ -1,5 +1,7 @@
 ## 1.0.2-dev
 
+- Require Dart `2.14`.
+
 ## 1.0.1
 
 * Require the latest shelf, remove dead code.
@@ -16,13 +18,11 @@
 
 * Support the latest shelf release (`1.x.x`).
 * Require at least Dart 2.1
-* Allow omitting `protocols` argument even if the `onConnection` callback takes
-  a second argument.
+* Allow omitting `protocols` argument even if the `onConnection` callback takes a second argument.
 
 ## 0.2.3
 
-* Add `pingInterval` argument to `webSocketHandler`, to be passed through
-  to the created channel.
+* Add `pingInterval` argument to `webSocketHandler`, to be passed through to the created channel.
 
 ## 0.2.2+5
 
@@ -30,8 +30,7 @@
 
 ## 0.2.2+4
 
-* Fix the check for `onConnection` to check the number of arguments  and not
-  that the arguments are `dynamic`.
+* Fix the check for `onConnection` to check the number of arguments and not that the arguments are `dynamic`.
 
 ## 0.2.2+3
 
@@ -64,8 +63,8 @@
 
 ## 0.1.0
 
-* **Breaking change**: `webSocketHandler()` now passes a `WebSocketChannel` to
-  the `onConnection()` callback, rather than a deprecated `CompatibleWebSocket`.
+* **Breaking change**: `webSocketHandler()` now passes a `WebSocketChannel` to the `onConnection()` callback, rather
+  than a deprecated `CompatibleWebSocket`.
 
 ## 0.0.1+5
 
@@ -85,5 +84,4 @@
 
 ## 0.0.1+1
 
-* Properly parse the `Connection` header. This fixes an issue where Firefox was
-  unable to connect.
+* Properly parse the `Connection` header. This fixes an issue where Firefox was unable to connect.
diff --git a/pkgs/shelf_web_socket/analysis_options.yaml b/pkgs/shelf_web_socket/analysis_options.yaml
deleted file mode 100644
index d183bf1..0000000
--- a/pkgs/shelf_web_socket/analysis_options.yaml
+++ /dev/null
@@ -1,80 +0,0 @@
-include: package:pedantic/analysis_options.yaml
-
-analyzer:
-  strong-mode:
-    implicit-casts: false
-
-linter:
-  rules:
-    - avoid_bool_literals_in_conditional_expressions
-    - avoid_catching_errors
-    - avoid_classes_with_only_static_members
-    - avoid_function_literals_in_foreach_calls
-    - avoid_private_typedef_functions
-    - avoid_redundant_argument_values
-    - avoid_renaming_method_parameters
-    - avoid_returning_null
-    - avoid_returning_null_for_future
-    - avoid_returning_null_for_void
-    - avoid_returning_this
-    - avoid_single_cascade_in_expression_statements
-    - avoid_unused_constructor_parameters
-    - avoid_void_async
-    - await_only_futures
-    - camel_case_types
-    - cancel_subscriptions
-    - cascade_invocations
-    - comment_references
-    - constant_identifier_names
-    - control_flow_in_finally
-    - directives_ordering
-    - empty_statements
-    - file_names
-    - hash_and_equals
-    - implementation_imports
-    - invariant_booleans
-    - iterable_contains_unrelated_type
-    - join_return_with_assignment
-    - lines_longer_than_80_chars
-    - list_remove_unrelated_type
-    - literal_only_boolean_expressions
-    - missing_whitespace_between_adjacent_strings
-    - no_adjacent_strings_in_list
-    - no_runtimeType_toString
-    - non_constant_identifier_names
-    - only_throw_errors
-    - overridden_fields
-    - package_api_docs
-    - package_names
-    - package_prefixed_library_names
-    - prefer_asserts_in_initializer_lists
-    - prefer_const_constructors
-    - prefer_const_declarations
-    - prefer_expression_function_bodies
-    - prefer_final_locals
-    - prefer_function_declarations_over_variables
-    - prefer_initializing_formals
-    - prefer_inlined_adds
-    - prefer_interpolation_to_compose_strings
-    - prefer_is_not_operator
-    - prefer_null_aware_operators
-    - prefer_relative_imports
-    - prefer_typing_uninitialized_variables
-    - prefer_void_to_null
-    - provide_deprecation_message
-    - sort_pub_dependencies
-    - test_types_in_equals
-    - throw_in_finally
-    - type_annotate_public_apis
-    - unnecessary_await_in_return
-    - unnecessary_brace_in_string_interps
-    - unnecessary_getters_setters
-    - unnecessary_lambdas
-    - unnecessary_null_aware_assignments
-    - unnecessary_overrides
-    - unnecessary_parenthesis
-    - unnecessary_statements
-    - unnecessary_string_interpolations
-    - use_is_even_rather_than_modulo
-    - use_string_buffers
-    - void_checks
diff --git a/pkgs/shelf_web_socket/lib/shelf_web_socket.dart b/pkgs/shelf_web_socket/lib/shelf_web_socket.dart
index 420b1bf..61ae886 100644
--- a/pkgs/shelf_web_socket/lib/shelf_web_socket.dart
+++ b/pkgs/shelf_web_socket/lib/shelf_web_socket.dart
@@ -45,6 +45,7 @@
     Duration? pingInterval}) {
   if (onConnection is! void Function(Null, Null)) {
     final innerOnConnection = onConnection;
+    // ignore: inference_failure_on_untyped_parameter
     onConnection = (webSocket, _) => innerOnConnection(webSocket);
   }
 
diff --git a/pkgs/shelf_web_socket/mono_pkg.yml b/pkgs/shelf_web_socket/mono_pkg.yaml
similarity index 95%
rename from pkgs/shelf_web_socket/mono_pkg.yml
rename to pkgs/shelf_web_socket/mono_pkg.yaml
index 75b1a04..80a16a3 100644
--- a/pkgs/shelf_web_socket/mono_pkg.yml
+++ b/pkgs/shelf_web_socket/mono_pkg.yaml
@@ -1,5 +1,5 @@
 sdk:
-- 2.12.0
+- 2.14.0
 - dev
 
 stages:
diff --git a/pkgs/shelf_web_socket/pubspec.yaml b/pkgs/shelf_web_socket/pubspec.yaml
index c4c5828..c3397c2 100644
--- a/pkgs/shelf_web_socket/pubspec.yaml
+++ b/pkgs/shelf_web_socket/pubspec.yaml
@@ -3,10 +3,10 @@
 
 description: >-
   A shelf handler that wires up a listener for every connection.
-repository: https://github.com/dart-lang/shelf/tree/master/shelf_web_socket
+repository: https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_web_socket
 
 environment:
-  sdk: ">=2.12.0 <3.0.0"
+  sdk: ">=2.14.0 <3.0.0"
 
 dependencies:
   shelf: ^1.1.0
@@ -15,5 +15,5 @@
 
 dev_dependencies:
   http: ^0.13.0
-  pedantic: ^1.10.0
+  lints: ^1.0.0
   test: ^1.16.0
diff --git a/pkgs/shelf_web_socket/test/web_socket_test.dart b/pkgs/shelf_web_socket/test/web_socket_test.dart
index d2b6452..5f6b1d8 100644
--- a/pkgs/shelf_web_socket/test/web_socket_test.dart
+++ b/pkgs/shelf_web_socket/test/web_socket_test.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+// ignore_for_file: inference_failure_on_untyped_parameter
+
 import 'dart:io';
 
 import 'package:http/http.dart' as http;
@@ -43,7 +45,7 @@
           fail('Only expected two messages.');
         }
         n++;
-      }).asFuture();
+      }).asFuture<void>();
     } finally {
       await server.close();
     }
diff --git a/tool/readme.dart b/tool/readme.dart
new file mode 100644
index 0000000..92b8179
--- /dev/null
+++ b/tool/readme.dart
@@ -0,0 +1,22 @@
+import 'dart:io';
+
+const _pkgsDir = 'pkgs';
+
+void main() {
+  final dirs = Directory(_pkgsDir).listSync().whereType<Directory>();
+
+  final pkgs = dirs.map((e) => e.uri.pathSegments[1]).toList()..sort();
+
+  for (var pkg in pkgs) {
+    _printPkg(pkg);
+  }
+}
+
+void _printPkg(String pkgName) {
+  print('''
+## $pkgName [![Pub Package](https://img.shields.io/pub/v/$pkgName.svg)](https://pub.dev/packages/$pkgName)
+
+- Package: <https://pub.dev/packages/$pkgName>
+- [Source code]($_pkgsDir/$pkgName)
+''');
+}