diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8d6003b33317..9ba305f15d93 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,19 @@ Code v99.99.999
 
 ## Unreleased
 
+## [4.97.2](https://github.com/coder/code-server/releases/tag/v4.96.4) - 2025-02-18
+
+Code v1.97.2
+
+### Added
+
+- Added back macOS amd64 builds.
+
+### Changed
+
+- Update to Code 1.97.2.
+- Softened dark mode login page colors.
+
 ## [4.96.4](https://github.com/coder/code-server/releases/tag/v4.96.4) - 2025-01-20
 
 Code v1.96.4
diff --git a/ci/build/build-release.sh b/ci/build/build-release.sh
index 780e84bf18b5..0d226fd83a25 100755
--- a/ci/build/build-release.sh
+++ b/ci/build/build-release.sh
@@ -44,10 +44,6 @@ bundle_code_server() {
   rsync src/browser/pages/*.css "$RELEASE_PATH/src/browser/pages"
   rsync src/browser/robots.txt "$RELEASE_PATH/src/browser"
 
-  # Add typings for plugins
-  mkdir -p "$RELEASE_PATH/typings"
-  rsync typings/pluginapi.d.ts "$RELEASE_PATH/typings"
-
   # Adds the commit to package.json
   jq --slurp '(.[0] | del(.scripts,.jest,.devDependencies)) * .[1]' package.json <(
     cat << EOF
diff --git a/ci/dev/test-integration.sh b/ci/dev/test-integration.sh
index 7f8fd5b3e826..380502c230ba 100755
--- a/ci/dev/test-integration.sh
+++ b/ci/dev/test-integration.sh
@@ -33,7 +33,7 @@ main() {
     exit 1
   fi
 
-  CODE_SERVER_PATH="$path" CS_DISABLE_PLUGINS=true ./test/node_modules/.bin/jest "$@" --coverage=false --testRegex "./test/integration" --testPathIgnorePatterns "./test/integration/fixtures"
+  CODE_SERVER_PATH="$path" ./test/node_modules/.bin/jest "$@" --coverage=false --testRegex "./test/integration" --testPathIgnorePatterns "./test/integration/fixtures"
 }
 
 main "$@"
diff --git a/ci/dev/test-unit.sh b/ci/dev/test-unit.sh
index e312c073e4ef..15fd2030ea59 100755
--- a/ci/dev/test-unit.sh
+++ b/ci/dev/test-unit.sh
@@ -6,15 +6,10 @@ main() {
 
   source ./ci/lib.sh
 
-  echo "Building test plugin"
-  pushd test/unit/node/test-plugin
-  make -s out/index.js
-  popd
-
   # We must keep jest in a sub-directory. See ../../test/package.json for more
   # information. We must also run it from the root otherwise coverage will not
   # include our source files.
-  CS_DISABLE_PLUGINS=true ./test/node_modules/.bin/jest "$@" --testRegex "./test/unit/.*ts" --testPathIgnorePatterns "./test/unit/node/test-plugin"
+  ./test/node_modules/.bin/jest "$@" --testRegex "./test/unit/.*ts"
 }
 
 main "$@"
diff --git a/package-lock.json b/package-lock.json
index 9f300cad98b6..ae176eb012fc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,7 +15,7 @@
         "compression": "^1.7.4",
         "cookie-parser": "^1.4.6",
         "env-paths": "^2.2.1",
-        "express": "5.0.0-beta.3",
+        "express": "^5.0.1",
         "http-proxy": "^1.18.1",
         "httpolyglot": "^0.1.2",
         "i18next": "^23.5.1",
@@ -42,7 +42,7 @@
         "@types/compression": "^1.7.3",
         "@types/cookie-parser": "^1.4.4",
         "@types/eslint__js": "^8.42.3",
-        "@types/express": "^4.17.17",
+        "@types/express": "^5.0.0",
         "@types/http-proxy": "1.17.7",
         "@types/js-yaml": "^4.0.6",
         "@types/node": "20.x",
@@ -219,18 +219,32 @@
       }
     },
     "node_modules/@eslint/plugin-kit": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz",
-      "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==",
+      "version": "0.2.7",
+      "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz",
+      "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==",
       "dev": true,
       "license": "Apache-2.0",
       "dependencies": {
+        "@eslint/core": "^0.12.0",
         "levn": "^0.4.1"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       }
     },
+    "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz",
+      "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@types/json-schema": "^7.0.15"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
     "node_modules/@humanfs/core": {
       "version": "0.19.0",
       "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz",
@@ -520,22 +534,24 @@
       "license": "MIT"
     },
     "node_modules/@types/express": {
-      "version": "4.17.21",
-      "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
-      "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz",
+      "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "@types/body-parser": "*",
-        "@types/express-serve-static-core": "^4.17.33",
+        "@types/express-serve-static-core": "^5.0.0",
         "@types/qs": "*",
         "@types/serve-static": "*"
       }
     },
     "node_modules/@types/express-serve-static-core": {
-      "version": "4.19.6",
-      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz",
-      "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==",
+      "version": "5.0.6",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz",
+      "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "@types/node": "*",
         "@types/qs": "*",
@@ -621,16 +637,18 @@
       }
     },
     "node_modules/@types/qs": {
-      "version": "6.9.16",
-      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz",
-      "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==",
-      "dev": true
+      "version": "6.9.18",
+      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz",
+      "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==",
+      "dev": true,
+      "license": "MIT"
     },
     "node_modules/@types/range-parser": {
       "version": "1.2.7",
       "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
       "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
-      "dev": true
+      "dev": true,
+      "license": "MIT"
     },
     "node_modules/@types/safe-compare": {
       "version": "1.1.2",
@@ -898,12 +916,13 @@
       "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
     },
     "node_modules/accepts": {
-      "version": "1.3.8",
-      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
-      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+      "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+      "license": "MIT",
       "dependencies": {
-        "mime-types": "~2.1.34",
-        "negotiator": "0.6.3"
+        "mime-types": "^3.0.0",
+        "negotiator": "^1.0.0"
       },
       "engines": {
         "node": ">= 0.6"
@@ -1062,11 +1081,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/array-flatten": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz",
-      "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA=="
-    },
     "node_modules/array-includes": {
       "version": "3.1.8",
       "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz",
@@ -1216,45 +1230,32 @@
       }
     },
     "node_modules/body-parser": {
-      "version": "2.0.0-beta.2",
-      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.0.0-beta.2.tgz",
-      "integrity": "sha512-oxdqeGYQcO5ovwwkC1A89R0Mf0v3+7smTVh0chGfzDeiK37bg5bYNtXDy3Nmzn6CShoIYk5+nHTyBoSZIWwnCA==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.1.0.tgz",
+      "integrity": "sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==",
+      "license": "MIT",
       "dependencies": {
-        "bytes": "3.1.2",
-        "content-type": "~1.0.5",
-        "debug": "3.1.0",
-        "destroy": "1.2.0",
-        "http-errors": "2.0.0",
-        "iconv-lite": "0.5.2",
-        "on-finished": "2.4.1",
-        "qs": "6.11.0",
-        "raw-body": "3.0.0-beta.1",
-        "type-is": "~1.6.18",
-        "unpipe": "1.0.0"
+        "bytes": "^3.1.2",
+        "content-type": "^1.0.5",
+        "debug": "^4.4.0",
+        "http-errors": "^2.0.0",
+        "iconv-lite": "^0.5.2",
+        "on-finished": "^2.4.1",
+        "qs": "^6.14.0",
+        "raw-body": "^3.0.0",
+        "type-is": "^2.0.0"
       },
       "engines": {
-        "node": ">= 0.10"
-      }
-    },
-    "node_modules/body-parser/node_modules/debug": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-      "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
-      "dependencies": {
-        "ms": "2.0.0"
+        "node": ">=18"
       }
     },
-    "node_modules/body-parser/node_modules/ms": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
-    },
     "node_modules/body-parser/node_modules/qs": {
-      "version": "6.11.0",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
-      "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+      "version": "6.14.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+      "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+      "license": "BSD-3-Clause",
       "dependencies": {
-        "side-channel": "^1.0.4"
+        "side-channel": "^1.1.0"
       },
       "engines": {
         "node": ">=0.6"
@@ -1317,6 +1318,7 @@
       "version": "1.0.7",
       "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
       "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+      "dev": true,
       "dependencies": {
         "es-define-property": "^1.0.0",
         "es-errors": "^1.3.0",
@@ -1331,6 +1333,35 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/call-bound": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "get-intrinsic": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/callsites": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -1502,9 +1533,10 @@
       "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
     },
     "node_modules/content-disposition": {
-      "version": "0.5.4",
-      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
-      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
+      "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
+      "license": "MIT",
       "dependencies": {
         "safe-buffer": "5.2.1"
       },
@@ -1516,24 +1548,27 @@
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
       "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+      "license": "MIT",
       "engines": {
         "node": ">= 0.6"
       }
     },
     "node_modules/cookie": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
-      "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+      "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+      "license": "MIT",
       "engines": {
         "node": ">= 0.6"
       }
     },
     "node_modules/cookie-parser": {
-      "version": "1.4.6",
-      "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
-      "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
+      "version": "1.4.7",
+      "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
+      "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
+      "license": "MIT",
       "dependencies": {
-        "cookie": "0.4.1",
+        "cookie": "0.7.2",
         "cookie-signature": "1.0.6"
       },
       "engines": {
@@ -1552,10 +1587,11 @@
       "dev": true
     },
     "node_modules/cross-spawn": {
-      "version": "7.0.3",
-      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
-      "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+      "version": "7.0.6",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+      "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "path-key": "^3.1.0",
         "shebang-command": "^2.0.0",
@@ -1633,9 +1669,10 @@
       }
     },
     "node_modules/debug": {
-      "version": "4.3.7",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
-      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+      "license": "MIT",
       "dependencies": {
         "ms": "^2.1.3"
       },
@@ -1658,6 +1695,7 @@
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
       "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+      "dev": true,
       "dependencies": {
         "es-define-property": "^1.0.0",
         "es-errors": "^1.3.0",
@@ -1709,6 +1747,7 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
       "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "license": "MIT",
       "engines": {
         "node": ">= 0.8"
       }
@@ -1717,6 +1756,7 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
       "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+      "license": "MIT",
       "engines": {
         "node": ">= 0.8",
         "npm": "1.2.8000 || >= 1.4.16"
@@ -1820,10 +1860,25 @@
         "url": "https://github.com/fb55/domutils?sponsor=1"
       }
     },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
-      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+      "license": "MIT"
     },
     "node_modules/emoji-regex": {
       "version": "10.1.0",
@@ -1832,9 +1887,10 @@
       "dev": true
     },
     "node_modules/encodeurl": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
-      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+      "license": "MIT",
       "engines": {
         "node": ">= 0.8"
       }
@@ -1933,12 +1989,10 @@
       }
     },
     "node_modules/es-define-property": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
-      "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
-      "dependencies": {
-        "get-intrinsic": "^1.2.4"
-      },
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "license": "MIT",
       "engines": {
         "node": ">= 0.4"
       }
@@ -1952,10 +2006,10 @@
       }
     },
     "node_modules/es-object-atoms": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
-      "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
-      "dev": true,
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "license": "MIT",
       "dependencies": {
         "es-errors": "^1.3.0"
       },
@@ -2014,7 +2068,8 @@
     "node_modules/escape-html": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
-      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "license": "MIT"
     },
     "node_modules/escape-string-regexp": {
       "version": "4.0.0",
@@ -2424,6 +2479,7 @@
       "version": "1.8.1",
       "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
       "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+      "license": "MIT",
       "engines": {
         "node": ">= 0.6"
       }
@@ -2434,83 +2490,89 @@
       "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
     },
     "node_modules/express": {
-      "version": "5.0.0-beta.3",
-      "resolved": "https://registry.npmjs.org/express/-/express-5.0.0-beta.3.tgz",
-      "integrity": "sha512-e7Qizw4gMBVe1Ky2oNi5C1h6oS8aWDcY2yYxvRMy5aMc6t2aqobuHpQRfR3LRC9NAW/c6081SeGWMGBorLXePg==",
-      "dependencies": {
-        "accepts": "~1.3.8",
-        "array-flatten": "3.0.0",
-        "body-parser": "2.0.0-beta.2",
-        "content-disposition": "0.5.4",
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz",
+      "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==",
+      "license": "MIT",
+      "dependencies": {
+        "accepts": "^2.0.0",
+        "body-parser": "^2.0.1",
+        "content-disposition": "^1.0.0",
         "content-type": "~1.0.4",
-        "cookie": "0.6.0",
-        "cookie-signature": "1.0.6",
-        "debug": "3.1.0",
+        "cookie": "0.7.1",
+        "cookie-signature": "^1.2.1",
+        "debug": "4.3.6",
         "depd": "2.0.0",
-        "encodeurl": "~1.0.2",
+        "encodeurl": "~2.0.0",
         "escape-html": "~1.0.3",
         "etag": "~1.8.1",
-        "finalhandler": "1.2.0",
-        "fresh": "0.5.2",
+        "finalhandler": "^2.0.0",
+        "fresh": "2.0.0",
         "http-errors": "2.0.0",
-        "merge-descriptors": "1.0.1",
+        "merge-descriptors": "^2.0.0",
         "methods": "~1.1.2",
-        "mime-types": "~2.1.34",
+        "mime-types": "^3.0.0",
         "on-finished": "2.4.1",
+        "once": "1.4.0",
         "parseurl": "~1.3.3",
-        "path-is-absolute": "1.0.1",
         "proxy-addr": "~2.0.7",
-        "qs": "6.11.0",
+        "qs": "6.13.0",
         "range-parser": "~1.2.1",
-        "router": "2.0.0-beta.2",
+        "router": "^2.0.0",
         "safe-buffer": "5.2.1",
-        "send": "1.0.0-beta.2",
-        "serve-static": "2.0.0-beta.2",
+        "send": "^1.1.0",
+        "serve-static": "^2.1.0",
         "setprototypeof": "1.2.0",
         "statuses": "2.0.1",
-        "type-is": "~1.6.18",
+        "type-is": "^2.0.0",
         "utils-merge": "1.0.1",
         "vary": "~1.1.2"
       },
       "engines": {
-        "node": ">= 4"
+        "node": ">= 18"
       }
     },
     "node_modules/express/node_modules/cookie": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
-      "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+      "version": "0.7.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+      "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+      "license": "MIT",
       "engines": {
         "node": ">= 0.6"
       }
     },
-    "node_modules/express/node_modules/debug": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-      "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
-      "dependencies": {
-        "ms": "2.0.0"
+    "node_modules/express/node_modules/cookie-signature": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+      "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.6.0"
       }
     },
-    "node_modules/express/node_modules/ms": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
-    },
-    "node_modules/express/node_modules/qs": {
-      "version": "6.11.0",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
-      "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+    "node_modules/express/node_modules/debug": {
+      "version": "4.3.6",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
+      "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
+      "license": "MIT",
       "dependencies": {
-        "side-channel": "^1.0.4"
+        "ms": "2.1.2"
       },
       "engines": {
-        "node": ">=0.6"
+        "node": ">=6.0"
       },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
       }
     },
+    "node_modules/express/node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "license": "MIT"
+    },
     "node_modules/extend": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -2619,35 +2681,22 @@
       }
     },
     "node_modules/finalhandler": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
-      "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
+      "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
+      "license": "MIT",
       "dependencies": {
-        "debug": "2.6.9",
-        "encodeurl": "~1.0.2",
-        "escape-html": "~1.0.3",
-        "on-finished": "2.4.1",
-        "parseurl": "~1.3.3",
-        "statuses": "2.0.1",
-        "unpipe": "~1.0.0"
+        "debug": "^4.4.0",
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "on-finished": "^2.4.1",
+        "parseurl": "^1.3.3",
+        "statuses": "^2.0.1"
       },
       "engines": {
         "node": ">= 0.8"
       }
     },
-    "node_modules/finalhandler/node_modules/debug": {
-      "version": "2.6.9",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-      "dependencies": {
-        "ms": "2.0.0"
-      }
-    },
-    "node_modules/finalhandler/node_modules/ms": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
-    },
     "node_modules/find-up": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -2731,11 +2780,12 @@
       }
     },
     "node_modules/fresh": {
-      "version": "0.5.2",
-      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
-      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+      "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+      "license": "MIT",
       "engines": {
-        "node": ">= 0.6"
+        "node": ">= 0.8"
       }
     },
     "node_modules/fs-extra": {
@@ -2834,15 +2884,21 @@
       }
     },
     "node_modules/get-intrinsic": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
-      "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "license": "MIT",
       "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
         "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
         "function-bind": "^1.1.2",
-        "has-proto": "^1.0.1",
-        "has-symbols": "^1.0.3",
-        "hasown": "^2.0.0"
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
       },
       "engines": {
         "node": ">= 0.4"
@@ -2851,6 +2907,19 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "license": "MIT",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/get-symbol-description": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz",
@@ -2956,11 +3025,12 @@
       }
     },
     "node_modules/gopd": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
-      "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
-      "dependencies": {
-        "get-intrinsic": "^1.1.3"
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
@@ -2999,6 +3069,7 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
       "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+      "dev": true,
       "dependencies": {
         "es-define-property": "^1.0.0"
       },
@@ -3010,6 +3081,7 @@
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
       "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+      "dev": true,
       "engines": {
         "node": ">= 0.4"
       },
@@ -3018,9 +3090,10 @@
       }
     },
     "node_modules/has-symbols": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
-      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "license": "MIT",
       "engines": {
         "node": ">= 0.4"
       },
@@ -3082,6 +3155,7 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
       "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+      "license": "MIT",
       "dependencies": {
         "depd": "2.0.0",
         "inherits": "2.0.4",
@@ -3175,6 +3249,7 @@
       "version": "0.5.2",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz",
       "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==",
+      "license": "MIT",
       "dependencies": {
         "safer-buffer": ">= 2.1.2 < 3"
       },
@@ -3503,7 +3578,8 @@
     "node_modules/is-promise": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
-      "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
+      "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+      "license": "MIT"
     },
     "node_modules/is-regex": {
       "version": "1.1.4",
@@ -3779,6 +3855,15 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/md5": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
@@ -3949,17 +4034,25 @@
       }
     },
     "node_modules/media-typer": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
-      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+      "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+      "license": "MIT",
       "engines": {
-        "node": ">= 0.6"
+        "node": ">= 0.8"
       }
     },
     "node_modules/merge-descriptors": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
-      "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+      "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
     },
     "node_modules/merge2": {
       "version": "1.4.1",
@@ -3974,6 +4067,7 @@
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
       "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+      "license": "MIT",
       "engines": {
         "node": ">= 0.6"
       }
@@ -4126,24 +4220,17 @@
       }
     },
     "node_modules/mime-types": {
-      "version": "2.1.35",
-      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
-      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz",
+      "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==",
+      "license": "MIT",
       "dependencies": {
-        "mime-db": "1.52.0"
+        "mime-db": "^1.53.0"
       },
       "engines": {
         "node": ">= 0.6"
       }
     },
-    "node_modules/mime-types/node_modules/mime-db": {
-      "version": "1.52.0",
-      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
-      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
     "node_modules/minimatch": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -4225,9 +4312,10 @@
       "dev": true
     },
     "node_modules/negotiator": {
-      "version": "0.6.3",
-      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
-      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+      "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+      "license": "MIT",
       "engines": {
         "node": ">= 0.6"
       }
@@ -4299,9 +4387,10 @@
       }
     },
     "node_modules/object-inspect": {
-      "version": "1.13.2",
-      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
-      "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
+      "version": "1.13.4",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+      "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+      "license": "MIT",
       "engines": {
         "node": ">= 0.4"
       },
@@ -4389,6 +4478,7 @@
       "version": "2.4.1",
       "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
       "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+      "license": "MIT",
       "dependencies": {
         "ee-first": "1.1.1"
       },
@@ -4555,6 +4645,7 @@
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
       "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "license": "MIT",
       "engines": {
         "node": ">= 0.8"
       }
@@ -4592,9 +4683,13 @@
       "dev": true
     },
     "node_modules/path-to-regexp": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz",
-      "integrity": "sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA=="
+      "version": "8.2.0",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
+      "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=16"
+      }
     },
     "node_modules/pem": {
       "version": "1.14.8",
@@ -4793,24 +4888,38 @@
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
       "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+      "license": "MIT",
       "engines": {
         "node": ">= 0.6"
       }
     },
     "node_modules/raw-body": {
-      "version": "3.0.0-beta.1",
-      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0-beta.1.tgz",
-      "integrity": "sha512-XlSTHr67bCjSo5aOfAnN3x507zGvi3unF65BW57limYkc2ws/XB0mLUtJvvP7JGFeSPsYrlCv1ZrPGh0cwDxPQ==",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
+      "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
+      "license": "MIT",
       "dependencies": {
         "bytes": "3.1.2",
         "http-errors": "2.0.0",
-        "iconv-lite": "0.5.2",
+        "iconv-lite": "0.6.3",
         "unpipe": "1.0.0"
       },
       "engines": {
         "node": ">= 0.8"
       }
     },
+    "node_modules/raw-body/node_modules/iconv-lite": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/readable-stream": {
       "version": "3.6.2",
       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
@@ -4990,20 +5099,17 @@
       }
     },
     "node_modules/router": {
-      "version": "2.0.0-beta.2",
-      "resolved": "https://registry.npmjs.org/router/-/router-2.0.0-beta.2.tgz",
-      "integrity": "sha512-ascmzrv4IAB64SpWzFwYOA+jz6PaUbrzHLPsQrPjQ3uQTL2qlhwY9S2sRvvBMgUISQptQG457jcWWcWqtwrbag==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/router/-/router-2.1.0.tgz",
+      "integrity": "sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==",
+      "license": "MIT",
       "dependencies": {
-        "array-flatten": "3.0.0",
-        "is-promise": "4.0.0",
-        "methods": "~1.1.2",
-        "parseurl": "~1.3.3",
-        "path-to-regexp": "3.2.0",
-        "setprototypeof": "1.2.0",
-        "utils-merge": "1.0.1"
+        "is-promise": "^4.0.0",
+        "parseurl": "^1.3.3",
+        "path-to-regexp": "^8.0.0"
       },
       "engines": {
-        "node": ">= 0.10"
+        "node": ">= 18"
       }
     },
     "node_modules/run-parallel": {
@@ -5094,7 +5200,8 @@
     "node_modules/safer-buffer": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
-      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "license": "MIT"
     },
     "node_modules/semver": {
       "version": "7.7.1",
@@ -5109,52 +5216,71 @@
       }
     },
     "node_modules/send": {
-      "version": "1.0.0-beta.2",
-      "resolved": "https://registry.npmjs.org/send/-/send-1.0.0-beta.2.tgz",
-      "integrity": "sha512-k1yHu/FNK745PULKdsGpQ+bVSXYNwSk+bWnYzbxGZbt5obZc0JKDVANsCRuJD1X/EG15JtP9eZpwxkhUxIYEcg==",
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz",
+      "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==",
+      "license": "MIT",
       "dependencies": {
-        "debug": "3.1.0",
-        "destroy": "1.2.0",
-        "encodeurl": "~1.0.2",
-        "escape-html": "~1.0.3",
-        "etag": "~1.8.1",
-        "fresh": "0.5.2",
-        "http-errors": "2.0.0",
-        "mime-types": "~2.1.34",
-        "ms": "2.1.3",
-        "on-finished": "2.4.1",
-        "range-parser": "~1.2.1",
-        "statuses": "2.0.1"
+        "debug": "^4.3.5",
+        "destroy": "^1.2.0",
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "etag": "^1.8.1",
+        "fresh": "^0.5.2",
+        "http-errors": "^2.0.0",
+        "mime-types": "^2.1.35",
+        "ms": "^2.1.3",
+        "on-finished": "^2.4.1",
+        "range-parser": "^1.2.1",
+        "statuses": "^2.0.1"
       },
       "engines": {
-        "node": ">= 0.10"
+        "node": ">= 18"
       }
     },
-    "node_modules/send/node_modules/debug": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-      "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
-      "dependencies": {
-        "ms": "2.0.0"
+    "node_modules/send/node_modules/fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
       }
     },
-    "node_modules/send/node_modules/debug/node_modules/ms": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+    "node_modules/send/node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/send/node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
     },
     "node_modules/serve-static": {
-      "version": "2.0.0-beta.2",
-      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.0.0-beta.2.tgz",
-      "integrity": "sha512-Ge718g4UJjzYoXFEGLY/VLSuTHp0kQcUV65QA98J8d3XREsVIHu53GBh9NWjDy4u2xwsSwRzu9nu7Q+b4o6Xyw==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz",
+      "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==",
+      "license": "MIT",
       "dependencies": {
-        "encodeurl": "~1.0.2",
-        "escape-html": "~1.0.3",
-        "parseurl": "~1.3.3",
-        "send": "^1.0.0-beta.2"
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "parseurl": "^1.3.3",
+        "send": "^1.0.0"
       },
       "engines": {
-        "node": ">= 0.10"
+        "node": ">= 18"
       }
     },
     "node_modules/set-blocking": {
@@ -5166,6 +5292,7 @@
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
       "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+      "dev": true,
       "dependencies": {
         "define-data-property": "^1.1.4",
         "es-errors": "^1.3.0",
@@ -5196,7 +5323,8 @@
     "node_modules/setprototypeof": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
-      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+      "license": "ISC"
     },
     "node_modules/sh-syntax": {
       "version": "0.4.2",
@@ -5235,14 +5363,69 @@
       }
     },
     "node_modules/side-channel": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
-      "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+      "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+      "license": "MIT",
       "dependencies": {
-        "call-bind": "^1.0.7",
         "es-errors": "^1.3.0",
-        "get-intrinsic": "^1.2.4",
-        "object-inspect": "^1.13.1"
+        "object-inspect": "^1.13.3",
+        "side-channel-list": "^1.0.0",
+        "side-channel-map": "^1.0.1",
+        "side-channel-weakmap": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-list": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+      "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-map": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+      "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-weakmap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+      "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3",
+        "side-channel-map": "^1.0.1"
       },
       "engines": {
         "node": ">= 0.4"
@@ -5327,6 +5510,7 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
       "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+      "license": "MIT",
       "engines": {
         "node": ">= 0.8"
       }
@@ -5571,6 +5755,7 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
       "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "license": "MIT",
       "engines": {
         "node": ">=0.6"
       }
@@ -5692,12 +5877,14 @@
       }
     },
     "node_modules/type-is": {
-      "version": "1.6.18",
-      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
-      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz",
+      "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==",
+      "license": "MIT",
       "dependencies": {
-        "media-typer": "0.3.0",
-        "mime-types": "~2.1.24"
+        "content-type": "^1.0.5",
+        "media-typer": "^1.1.0",
+        "mime-types": "^3.0.0"
       },
       "engines": {
         "node": ">= 0.6"
@@ -5950,6 +6137,7 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
       "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "license": "MIT",
       "engines": {
         "node": ">= 0.8"
       }
@@ -5979,6 +6167,7 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
       "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+      "license": "MIT",
       "engines": {
         "node": ">= 0.4.0"
       }
diff --git a/package.json b/package.json
index e91feacc035e..ffdfccafdede 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
     "publish:docker": "./ci/steps/docker-buildx-push.sh",
     "fmt": "npm run prettier && ./ci/dev/doctoc.sh",
     "lint:scripts": "./ci/dev/lint-scripts.sh",
-    "lint:ts": "eslint --max-warnings=0 --fix $(git ls-files '*.ts' '*.js' | grep -v 'lib/vscode' | grep -v test-plugin)",
+    "lint:ts": "eslint --max-warnings=0 --fix $(git ls-files '*.ts' '*.js' | grep -v 'lib/vscode')",
     "test": "echo 'Run npm run test:unit or npm run test:e2e' && exit 1",
     "watch": "VSCODE_DEV=1 VSCODE_IPC_HOOK_CLI= NODE_OPTIONS='--max_old_space_size=32384 --trace-warnings' ts-node ./ci/dev/watch.ts",
     "icons": "./ci/dev/gen_icons.sh"
@@ -44,7 +44,7 @@
     "@types/compression": "^1.7.3",
     "@types/cookie-parser": "^1.4.4",
     "@types/eslint__js": "^8.42.3",
-    "@types/express": "^4.17.17",
+    "@types/express": "^5.0.0",
     "@types/http-proxy": "1.17.7",
     "@types/js-yaml": "^4.0.6",
     "@types/node": "20.x",
@@ -73,7 +73,7 @@
     "compression": "^1.7.4",
     "cookie-parser": "^1.4.6",
     "env-paths": "^2.2.1",
-    "express": "5.0.0-beta.3",
+    "express": "^5.0.1",
     "http-proxy": "^1.18.1",
     "httpolyglot": "^0.1.2",
     "i18next": "^23.5.1",
diff --git a/src/node/plugin.ts b/src/node/plugin.ts
deleted file mode 100644
index 85c588c9ab7b..000000000000
--- a/src/node/plugin.ts
+++ /dev/null
@@ -1,302 +0,0 @@
-import { field, Level, Logger } from "@coder/logger"
-import * as express from "express"
-import * as fs from "fs"
-import * as path from "path"
-import * as semver from "semver"
-import * as pluginapi from "../../typings/pluginapi"
-import { HttpCode, HttpError } from "../common/http"
-import { version } from "./constants"
-import { authenticated, ensureAuthenticated, replaceTemplates } from "./http"
-import { proxy } from "./proxy"
-import * as util from "./util"
-import { Router as WsRouter, WebsocketRouter, wss } from "./wsRouter"
-const fsp = fs.promises
-
-// Represents a required module which could be anything.
-type Module = any
-
-/**
- * Inject code-server when `require`d. This is required because the API provides
- * more than just types so these need to be provided at run-time.
- */
-const originalLoad = require("module")._load
-require("module")._load = function (request: string, parent: object, isMain: boolean): Module {
-  return request === "code-server" ? codeServer : originalLoad.apply(this, [request, parent, isMain])
-}
-
-/**
- * The module you get when importing "code-server".
- */
-export const codeServer = {
-  HttpCode,
-  HttpError,
-  Level,
-  authenticated,
-  ensureAuthenticated,
-  express,
-  field,
-  proxy,
-  replaceTemplates,
-  WsRouter,
-  wss,
-}
-
-interface Plugin extends pluginapi.Plugin {
-  /**
-   * These fields are populated from the plugin's package.json
-   * and now guaranteed to exist.
-   */
-  name: string
-  version: string
-
-  /**
-   * path to the node module on the disk.
-   */
-  modulePath: string
-}
-
-interface Application extends pluginapi.Application {
-  /*
-   * Clone of the above without functions.
-   */
-  plugin: Omit<Plugin, "init" | "deinit" | "router" | "applications">
-}
-
-/**
- * PluginAPI implements the plugin API described in typings/pluginapi.d.ts
- * Please see that file for details.
- */
-export class PluginAPI {
-  private readonly plugins = new Map<string, Plugin>()
-  private readonly logger: Logger
-
-  public constructor(
-    logger: Logger,
-    /**
-     * These correspond to $CS_PLUGIN_PATH and $CS_PLUGIN respectively.
-     */
-    private readonly csPlugin = "",
-    private readonly csPluginPath = `${path.join(util.paths.data, "plugins")}:/usr/share/code-server/plugins`,
-    private readonly workingDirectory: string | undefined = undefined,
-  ) {
-    this.logger = logger.named("pluginapi")
-  }
-
-  /**
-   * applications grabs the full list of applications from
-   * all loaded plugins.
-   */
-  public async applications(): Promise<Application[]> {
-    const apps = new Array<Application>()
-    for (const [, p] of this.plugins) {
-      if (!p.applications) {
-        continue
-      }
-      const pluginApps = await p.applications()
-
-      // Add plugin key to each app.
-      apps.push(
-        ...pluginApps.map((app) => {
-          app = { ...app, path: path.join(p.routerPath, app.path || "") }
-          app = { ...app, iconPath: path.join(app.path || "", app.iconPath) }
-          return {
-            ...app,
-            plugin: {
-              name: p.name,
-              version: p.version,
-              modulePath: p.modulePath,
-
-              displayName: p.displayName,
-              description: p.description,
-              routerPath: p.routerPath,
-              homepageURL: p.homepageURL,
-            },
-          }
-        }),
-      )
-    }
-    return apps
-  }
-
-  /**
-   * mount mounts all plugin routers onto r and websocket routers onto wr.
-   */
-  public mount(r: express.Router, wr: express.Router): void {
-    for (const [, p] of this.plugins) {
-      if (p.router) {
-        r.use(`${p.routerPath}`, p.router())
-      }
-      if (p.wsRouter) {
-        wr.use(`${p.routerPath}`, (p.wsRouter() as WebsocketRouter).router)
-      }
-    }
-  }
-
-  /**
-   * loadPlugins loads all plugins based on this.csPlugin,
-   * this.csPluginPath and the built in plugins.
-   */
-  public async loadPlugins(loadBuiltin = true): Promise<void> {
-    for (const dir of this.csPlugin.split(":")) {
-      if (!dir) {
-        continue
-      }
-      await this.loadPlugin(dir)
-    }
-
-    for (const dir of this.csPluginPath.split(":")) {
-      if (!dir) {
-        continue
-      }
-      await this._loadPlugins(dir)
-    }
-
-    if (loadBuiltin) {
-      await this._loadPlugins(path.join(__dirname, "../../plugins"))
-    }
-  }
-
-  /**
-   * _loadPlugins is the counterpart to loadPlugins.
-   *
-   * It differs in that it loads all plugins in a single
-   * directory whereas loadPlugins uses all available directories
-   * as documented.
-   */
-  private async _loadPlugins(dir: string): Promise<void> {
-    try {
-      const entries = await fsp.readdir(dir, { withFileTypes: true })
-      for (const ent of entries) {
-        if (!ent.isDirectory()) {
-          continue
-        }
-        await this.loadPlugin(path.join(dir, ent.name))
-      }
-    } catch (error: any) {
-      if (error.code !== "ENOENT") {
-        this.logger.warn(`failed to load plugins from ${q(dir)}: ${error.message}`)
-      }
-    }
-  }
-
-  private async loadPlugin(dir: string): Promise<void> {
-    try {
-      const str = await fsp.readFile(path.join(dir, "package.json"), {
-        encoding: "utf8",
-      })
-      const packageJSON: PackageJSON = JSON.parse(str)
-      for (const [, p] of this.plugins) {
-        if (p.name === packageJSON.name) {
-          this.logger.warn(
-            `ignoring duplicate plugin ${q(p.name)} at ${q(dir)}, using previously loaded ${q(p.modulePath)}`,
-          )
-          return
-        }
-      }
-      const p = this._loadPlugin(dir, packageJSON)
-      this.plugins.set(p.name, p)
-    } catch (error: any) {
-      if (error.code !== "ENOENT") {
-        this.logger.warn(`failed to load plugin: ${error.stack}`)
-      }
-    }
-  }
-
-  /**
-   * _loadPlugin is the counterpart to loadPlugin and actually
-   * loads the plugin now that we know there is no duplicate
-   * and that the package.json has been read.
-   */
-  private _loadPlugin(dir: string, packageJSON: PackageJSON): Plugin {
-    dir = path.resolve(dir)
-
-    const logger = this.logger.named(packageJSON.name)
-    logger.debug("loading plugin", field("plugin_dir", dir), field("package_json", packageJSON))
-
-    if (!packageJSON.name) {
-      throw new Error("plugin package.json missing name")
-    }
-    if (!packageJSON.version) {
-      throw new Error("plugin package.json missing version")
-    }
-    if (!packageJSON.engines || !packageJSON.engines["code-server"]) {
-      throw new Error(`plugin package.json missing code-server range like:
-  "engines": {
-    "code-server": "^3.7.0"
-   }
-`)
-    }
-    if (!semver.satisfies(version, packageJSON.engines["code-server"])) {
-      this.logger.warn(
-        `plugin range ${q(packageJSON.engines["code-server"])} incompatible` + ` with code-server version ${version}`,
-      )
-    }
-
-    const pluginModule = require(dir)
-    if (!pluginModule.plugin) {
-      throw new Error("plugin module does not export a plugin")
-    }
-
-    const p = {
-      name: packageJSON.name,
-      version: packageJSON.version,
-      modulePath: dir,
-      ...pluginModule.plugin,
-    } as Plugin
-
-    if (!p.displayName) {
-      throw new Error("plugin missing displayName")
-    }
-    if (!p.description) {
-      throw new Error("plugin missing description")
-    }
-    if (!p.routerPath) {
-      throw new Error("plugin missing router path")
-    }
-    if (!p.routerPath.startsWith("/")) {
-      throw new Error(`plugin router path ${q(p.routerPath)}: invalid`)
-    }
-    if (!p.homepageURL) {
-      throw new Error("plugin missing homepage")
-    }
-
-    p.init({
-      logger: logger,
-      workingDirectory: this.workingDirectory,
-    })
-
-    logger.debug("loaded")
-
-    return p
-  }
-
-  public async dispose(): Promise<void> {
-    await Promise.all(
-      Array.from(this.plugins.values()).map(async (p) => {
-        if (!p.deinit) {
-          return
-        }
-        try {
-          await p.deinit()
-        } catch (error: any) {
-          this.logger.error("plugin failed to deinit", field("name", p.name), field("error", error.message))
-        }
-      }),
-    )
-  }
-}
-
-interface PackageJSON {
-  name: string
-  version: string
-  engines: {
-    "code-server": string
-  }
-}
-
-function q(s: string | undefined): string {
-  if (s === undefined) {
-    s = "undefined"
-  }
-  return JSON.stringify(s)
-}
diff --git a/src/node/routes/apps.ts b/src/node/routes/apps.ts
deleted file mode 100644
index 5c8541fc9ad4..000000000000
--- a/src/node/routes/apps.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import * as express from "express"
-import { PluginAPI } from "../plugin"
-
-/**
- * Implements the /api/applications endpoint
- *
- * See typings/pluginapi.d.ts for details.
- */
-export function router(papi: PluginAPI): express.Router {
-  const router = express.Router()
-
-  router.get("/", async (req, res) => {
-    res.json(await papi.applications())
-  })
-
-  return router
-}
diff --git a/src/node/routes/errors.ts b/src/node/routes/errors.ts
index 743699c847ef..1f1475e9beee 100644
--- a/src/node/routes/errors.ts
+++ b/src/node/routes/errors.ts
@@ -2,8 +2,8 @@ import { logger } from "@coder/logger"
 import express from "express"
 import { promises as fs } from "fs"
 import path from "path"
-import { WebsocketRequest } from "../../../typings/pluginapi"
 import { HttpCode } from "../../common/http"
+import type { WebsocketRequest } from "../wsRouter"
 import { rootPath } from "../constants"
 import { replaceTemplates } from "../http"
 import { escapeHtml, getMediaMime } from "../util"
diff --git a/src/node/routes/index.ts b/src/node/routes/index.ts
index e61cbd65795c..36cf76b4a7ca 100644
--- a/src/node/routes/index.ts
+++ b/src/node/routes/index.ts
@@ -4,7 +4,6 @@ import * as express from "express"
 import { promises as fs } from "fs"
 import * as path from "path"
 import * as tls from "tls"
-import * as pluginapi from "../../../typings/pluginapi"
 import { Disposable } from "../../common/emitter"
 import { HttpCode, HttpError } from "../../common/http"
 import { plural } from "../../common/util"
@@ -12,12 +11,11 @@ import { App } from "../app"
 import { AuthType, DefaultedArgs } from "../cli"
 import { commit, rootPath } from "../constants"
 import { Heart } from "../heart"
-import { ensureAuthenticated, redirect } from "../http"
-import { PluginAPI } from "../plugin"
+import { redirect } from "../http"
 import { CoderSettings, SettingsProvider } from "../settings"
 import { UpdateProvider } from "../update"
+import type { WebsocketRequest } from "../wsRouter"
 import { getMediaMime, paths } from "../util"
-import * as apps from "./apps"
 import * as domainProxy from "./domainProxy"
 import { errorHandler, wsErrorHandler } from "./errors"
 import * as health from "./health"
@@ -81,65 +79,53 @@ export const register = async (app: App, args: DefaultedArgs): Promise<Disposabl
   app.router.use(common)
   app.wsRouter.use(common)
 
-  app.router.use(async (req, res, next) => {
+  app.router.use(/.*/, async (req, res, next) => {
     // If we're handling TLS ensure all requests are redirected to HTTPS.
     // TODO: This does *NOT* work if you have a base path since to specify the
     // protocol we need to specify the whole path.
     if (args.cert && !(req.connection as tls.TLSSocket).encrypted) {
       return res.redirect(`https://${req.headers.host}${req.originalUrl}`)
     }
+    next()
+  })
 
-    // Return security.txt.
-    if (req.originalUrl === "/security.txt" || req.originalUrl === "/.well-known/security.txt") {
-      const resourcePath = path.resolve(rootPath, "src/browser/security.txt")
-      res.set("Content-Type", getMediaMime(resourcePath))
-      return res.send(await fs.readFile(resourcePath))
-    }
-
-    // Return robots.txt.
-    if (req.originalUrl === "/robots.txt") {
-      const resourcePath = path.resolve(rootPath, "src/browser/robots.txt")
-      res.set("Content-Type", getMediaMime(resourcePath))
-      return res.send(await fs.readFile(resourcePath))
-    }
+  app.router.get(["/security.txt", "/.well-known/security.txt"], async (_, res) => {
+    const resourcePath = path.resolve(rootPath, "src/browser/security.txt")
+    res.set("Content-Type", getMediaMime(resourcePath))
+    res.send(await fs.readFile(resourcePath))
+  })
 
-    next()
+  app.router.get("/robots.txt", async (_, res) => {
+    const resourcePath = path.resolve(rootPath, "src/browser/robots.txt")
+    res.set("Content-Type", getMediaMime(resourcePath))
+    res.send(await fs.readFile(resourcePath))
   })
 
   app.router.use("/", domainProxy.router)
   app.wsRouter.use("/", domainProxy.wsRouter.router)
 
-  app.router.all("/proxy/:port/:path(.*)?", async (req, res) => {
+  app.router.all("/proxy/:port{/*path}", async (req, res) => {
     await pathProxy.proxy(req, res)
   })
-  app.wsRouter.get("/proxy/:port/:path(.*)?", async (req) => {
-    await pathProxy.wsProxy(req as pluginapi.WebsocketRequest)
+  app.wsRouter.get("/proxy/:port{/*path}", async (req) => {
+    await pathProxy.wsProxy(req as unknown as WebsocketRequest)
   })
   // These two routes pass through the path directly.
   // So the proxied app must be aware it is running
   // under /absproxy/<someport>/
-  app.router.all("/absproxy/:port/:path(.*)?", async (req, res) => {
+  app.router.all("/absproxy/:port{/*path}", async (req, res) => {
     await pathProxy.proxy(req, res, {
       passthroughPath: true,
       proxyBasePath: args["abs-proxy-base-path"],
     })
   })
-  app.wsRouter.get("/absproxy/:port/:path(.*)?", async (req) => {
-    await pathProxy.wsProxy(req as pluginapi.WebsocketRequest, {
+  app.wsRouter.get("/absproxy/:port{/*path}", async (req) => {
+    await pathProxy.wsProxy(req as unknown as WebsocketRequest, {
       passthroughPath: true,
       proxyBasePath: args["abs-proxy-base-path"],
     })
   })
 
-  let pluginApi: PluginAPI
-  if (!process.env.CS_DISABLE_PLUGINS) {
-    const workingDir = args._ && args._.length > 0 ? path.resolve(args._[args._.length - 1]) : undefined
-    pluginApi = new PluginAPI(logger, process.env.CS_PLUGIN, process.env.CS_PLUGIN_PATH, workingDir)
-    await pluginApi.loadPlugins()
-    pluginApi.mount(app.router, app.wsRouter)
-    app.router.use("/api/applications", ensureAuthenticated, apps.router(pluginApi))
-  }
-
   app.router.use(express.json())
   app.router.use(express.urlencoded({ extended: true }))
 
@@ -172,7 +158,9 @@ export const register = async (app: App, args: DefaultedArgs): Promise<Disposabl
 
   app.router.use("/update", update.router)
 
-  // Note that the root route is replaced in Coder Enterprise by the plugin API.
+  // For historic reasons we also load at /vscode because the root was replaced
+  // by a plugin in v1 of Coder.  The plugin system (which was for internal use
+  // only) has been removed, but leave the additional route for now.
   for (const routePrefix of ["/vscode", "/"]) {
     app.router.use(routePrefix, vscode.router)
     app.wsRouter.use(routePrefix, vscode.wsRouter.router)
@@ -187,7 +175,6 @@ export const register = async (app: App, args: DefaultedArgs): Promise<Disposabl
 
   return () => {
     heart.dispose()
-    pluginApi?.dispose()
     vscode.dispose()
   }
 }
diff --git a/src/node/routes/pathProxy.ts b/src/node/routes/pathProxy.ts
index ccfb0cc824a0..bb8efd40d832 100644
--- a/src/node/routes/pathProxy.ts
+++ b/src/node/routes/pathProxy.ts
@@ -1,9 +1,9 @@
 import { Request, Response } from "express"
 import * as path from "path"
-import * as pluginapi from "../../../typings/pluginapi"
 import { HttpCode, HttpError } from "../../common/http"
 import { ensureProxyEnabled, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
 import { proxy as _proxy } from "../proxy"
+import type { WebsocketRequest } from "../wsRouter"
 
 const getProxyTarget = (
   req: Request,
@@ -49,7 +49,7 @@ export async function proxy(
 }
 
 export async function wsProxy(
-  req: pluginapi.WebsocketRequest,
+  req: WebsocketRequest,
   opts?: {
     passthroughPath?: boolean
     proxyBasePath?: string
diff --git a/src/node/routes/vscode.ts b/src/node/routes/vscode.ts
index 637a30352ceb..4efb32993115 100644
--- a/src/node/routes/vscode.ts
+++ b/src/node/routes/vscode.ts
@@ -6,14 +6,13 @@ import * as http from "http"
 import * as net from "net"
 import * as path from "path"
 import * as os from "os"
-import { WebsocketRequest } from "../../../typings/pluginapi"
 import { logError } from "../../common/util"
 import { CodeArgs, toCodeArgs } from "../cli"
 import { isDevMode, vsRootPath } from "../constants"
 import { authenticated, ensureAuthenticated, ensureOrigin, redirect, replaceTemplates, self } from "../http"
 import { SocketProxyProvider } from "../socket"
 import { isFile } from "../util"
-import { Router as WsRouter } from "../wsRouter"
+import { type WebsocketRequest, Router as WsRouter } from "../wsRouter"
 
 export const router = express.Router()
 
@@ -176,7 +175,7 @@ router.get("/manifest.json", async (req, res) => {
   const appName = req.args["app-name"] || "code-server"
   res.writeHead(200, { "Content-Type": "application/manifest+json" })
 
-  return res.end(
+  res.end(
     replaceTemplates(
       req,
       JSON.stringify(
diff --git a/src/node/wsRouter.ts b/src/node/wsRouter.ts
index 3f15ba9928bc..0f901bb8ad7f 100644
--- a/src/node/wsRouter.ts
+++ b/src/node/wsRouter.ts
@@ -1,8 +1,17 @@
 import * as express from "express"
 import * as expressCore from "express-serve-static-core"
 import * as http from "http"
+import * as stream from "stream"
 import Websocket from "ws"
-import * as pluginapi from "../../typings/pluginapi"
+
+export interface WebsocketRequest extends express.Request {
+  ws: stream.Duplex
+  head: Buffer
+}
+
+interface InternalWebsocketRequest extends WebsocketRequest {
+  _ws_handled: boolean
+}
 
 export const handleUpgrade = (app: express.Express, server: http.Server): void => {
   server.on("upgrade", (req, socket, head) => {
@@ -22,9 +31,11 @@ export const handleUpgrade = (app: express.Express, server: http.Server): void =
   })
 }
 
-interface InternalWebsocketRequest extends pluginapi.WebsocketRequest {
-  _ws_handled: boolean
-}
+export type WebSocketHandler = (
+  req: WebsocketRequest,
+  res: express.Response,
+  next: express.NextFunction,
+) => void | Promise<void>
 
 export class WebsocketRouter {
   public readonly router = express.Router()
@@ -36,13 +47,13 @@ export class WebsocketRouter {
    * If the origin header exists it must match the host or the connection will
    * be prevented.
    */
-  public ws(route: expressCore.PathParams, ...handlers: pluginapi.WebSocketHandler[]): void {
+  public ws(route: expressCore.PathParams, ...handlers: WebSocketHandler[]): void {
     this.router.get(
       route,
       ...handlers.map((handler) => {
         const wrapped: express.Handler = (req, res, next) => {
           ;(req as InternalWebsocketRequest)._ws_handled = true
-          return handler(req as pluginapi.WebsocketRequest, res, next)
+          return handler(req as WebsocketRequest, res, next)
         }
         return wrapped
       }),
diff --git a/test/tsconfig.json b/test/tsconfig.json
index 3e568e887dd6..5197ce2769f4 100644
--- a/test/tsconfig.json
+++ b/test/tsconfig.json
@@ -1,5 +1,4 @@
 {
   "extends": "../tsconfig.json",
-  "include": ["./**/*.ts"],
-  "exclude": ["./unit/node/test-plugin"]
+  "include": ["./**/*.ts"]
 }
diff --git a/test/unit/node/plugin.test.ts b/test/unit/node/plugin.test.ts
deleted file mode 100644
index 8237b1177d53..000000000000
--- a/test/unit/node/plugin.test.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import { logger } from "@coder/logger"
-import * as express from "express"
-import * as fs from "fs"
-import * as path from "path"
-import { HttpCode } from "../../../src/common/http"
-import { AuthType } from "../../../src/node/cli"
-import { codeServer, PluginAPI } from "../../../src/node/plugin"
-import * as apps from "../../../src/node/routes/apps"
-import * as httpserver from "../../utils/httpserver"
-const fsp = fs.promises
-
-// Jest overrides `require` so our usual override doesn't work.
-jest.mock("code-server", () => codeServer, { virtual: true })
-
-/**
- * Use $LOG_LEVEL=debug to see debug logs.
- */
-describe("plugin", () => {
-  let papi: PluginAPI
-  let s: httpserver.HttpServer
-
-  beforeAll(async () => {
-    // Only include the test plugin to avoid contaminating results with other
-    // plugins that might be on the filesystem.
-    papi = new PluginAPI(logger, `${path.resolve(__dirname, "test-plugin")}:meow`, "")
-    await papi.loadPlugins(false)
-
-    const app = express.default()
-    const wsApp = express.default()
-
-    const common: express.RequestHandler = (req, _, next) => {
-      // Routes might use these arguments.
-      req.args = {
-        _: [],
-        auth: AuthType.None,
-        host: "localhost",
-        port: 8080,
-        "proxy-domain": [],
-        config: "~/.config/code-server/config.yaml",
-        verbose: false,
-        "disable-file-downloads": false,
-        usingEnvPassword: false,
-        usingEnvHashedPassword: false,
-        "extensions-dir": "",
-        "user-data-dir": "",
-        "session-socket": "",
-      }
-      next()
-    }
-
-    app.use(common)
-    wsApp.use(common)
-
-    papi.mount(app, wsApp)
-    app.use("/api/applications", apps.router(papi))
-
-    s = new httpserver.HttpServer()
-    await s.listen(app)
-    s.listenUpgrade(wsApp)
-  })
-
-  afterAll(async () => {
-    await s.dispose()
-  })
-
-  it("/api/applications", async () => {
-    const resp = await s.fetch("/api/applications")
-    expect(resp.status).toBe(200)
-    const body = await resp.json()
-    logger.debug(`${JSON.stringify(body)}`)
-    expect(body).toStrictEqual([
-      {
-        name: "Test App",
-        version: "4.0.1",
-
-        description: "This app does XYZ.",
-        iconPath: "/test-plugin/test-app/icon.svg",
-        homepageURL: "https://example.com",
-        path: "/test-plugin/test-app",
-
-        plugin: {
-          name: "test-plugin",
-          version: "1.0.0",
-          modulePath: path.join(__dirname, "test-plugin"),
-
-          displayName: "Test Plugin",
-          description: "Plugin used in code-server tests.",
-          routerPath: "/test-plugin",
-          homepageURL: "https://example.com",
-        },
-      },
-    ])
-  })
-
-  it("/test-plugin/test-app", async () => {
-    const indexHTML = await fsp.readFile(path.join(__dirname, "test-plugin/public/index.html"), {
-      encoding: "utf8",
-    })
-    const resp = await s.fetch("/test-plugin/test-app")
-    expect(resp.status).toBe(200)
-    const body = await resp.text()
-    expect(body).toBe(indexHTML)
-  })
-
-  it("/test-plugin/test-app (websocket)", async () => {
-    const ws = s.ws("/test-plugin/test-app")
-    const message = await new Promise((resolve) => {
-      ws.once("message", (message) => resolve(message))
-    })
-    ws.terminate()
-    expect(message).toBe("hello")
-  })
-
-  it("/test-plugin/error", async () => {
-    const resp = await s.fetch("/test-plugin/error")
-    expect(resp.status).toBe(HttpCode.LargePayload)
-  })
-})
diff --git a/test/unit/node/test-plugin/.eslintrc.js b/test/unit/node/test-plugin/.eslintrc.js
deleted file mode 100644
index 3999419107b0..000000000000
--- a/test/unit/node/test-plugin/.eslintrc.js
+++ /dev/null
@@ -1,9 +0,0 @@
-module.exports = {
-  settings: {
-    "import/resolver": {
-      typescript: {
-        project: __dirname,
-      },
-    },
-  },
-}
diff --git a/test/unit/node/test-plugin/.gitignore b/test/unit/node/test-plugin/.gitignore
deleted file mode 100644
index 1fcb1529f8e5..000000000000
--- a/test/unit/node/test-plugin/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-out
diff --git a/test/unit/node/test-plugin/Makefile b/test/unit/node/test-plugin/Makefile
deleted file mode 100644
index 397424629bb5..000000000000
--- a/test/unit/node/test-plugin/Makefile
+++ /dev/null
@@ -1,6 +0,0 @@
-out/index.js: src/index.ts
-	# Typescript always emits, even on errors.
-	npm run build || rm out/index.js
-
-node_modules: package.json package-lock.json
-	npm install
diff --git a/test/unit/node/test-plugin/package-lock.json b/test/unit/node/test-plugin/package-lock.json
deleted file mode 100644
index 0bcd9b40eebc..000000000000
--- a/test/unit/node/test-plugin/package-lock.json
+++ /dev/null
@@ -1,90 +0,0 @@
-{
-  "name": "test-plugin",
-  "version": "1.0.0",
-  "lockfileVersion": 1,
-  "requires": true,
-  "dependencies": {
-    "@types/body-parser": {
-      "version": "1.19.0",
-      "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz",
-      "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==",
-      "requires": {
-        "@types/connect": "*",
-        "@types/node": "*"
-      },
-      "dev": true
-    },
-    "@types/connect": {
-      "version": "3.4.33",
-      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz",
-      "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==",
-      "requires": {
-        "@types/node": "*"
-      },
-      "dev": true
-    },
-    "@types/express": {
-      "version": "4.17.8",
-      "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.8.tgz",
-      "integrity": "sha512-wLhcKh3PMlyA2cNAB9sjM1BntnhPMiM0JOBwPBqttjHev2428MLEB4AYVN+d8s2iyCVZac+o41Pflm/ZH5vLXQ==",
-      "requires": {
-        "@types/body-parser": "*",
-        "@types/express-serve-static-core": "*",
-        "@types/qs": "*",
-        "@types/serve-static": "*"
-      },
-      "dev": true
-    },
-    "@types/express-serve-static-core": {
-      "version": "4.17.13",
-      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.13.tgz",
-      "integrity": "sha512-RgDi5a4nuzam073lRGKTUIaL3eF2+H7LJvJ8eUnCI0wA6SNjXc44DCmWNiTLs/AZ7QlsFWZiw/gTG3nSQGL0fA==",
-      "requires": {
-        "@types/node": "*",
-        "@types/qs": "*",
-        "@types/range-parser": "*"
-      },
-      "dev": true
-    },
-    "@types/mime": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz",
-      "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==",
-      "dev": true
-    },
-    "@types/node": {
-      "version": "14.14.6",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.6.tgz",
-      "integrity": "sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw==",
-      "dev": true
-    },
-    "@types/qs": {
-      "version": "6.9.5",
-      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz",
-      "integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==",
-      "dev": true
-    },
-    "@types/range-parser": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
-      "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
-      "dev": true
-    },
-    "@types/serve-static": {
-      "version": "1.13.6",
-      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.6.tgz",
-      "integrity": "sha512-nuRJmv7jW7VmCVTn+IgYDkkbbDGyIINOeu/G0d74X3lm6E5KfMeQPJhxIt1ayQeQB3cSxvYs1RA/wipYoFB4EA==",
-      "requires": {
-        "@types/mime": "*",
-        "@types/node": "*"
-      },
-      "dev": true
-    },
-    "typescript": {
-      "version": "4.0.5",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz",
-      "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==",
-      "dev": true
-    }
-  }
-}
\ No newline at end of file
diff --git a/test/unit/node/test-plugin/package.json b/test/unit/node/test-plugin/package.json
deleted file mode 100644
index 0247d60beefb..000000000000
--- a/test/unit/node/test-plugin/package.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "private": true,
-  "name": "test-plugin",
-  "version": "1.0.0",
-  "engines": {
-    "code-server": "*"
-  },
-  "main": "out/index.js",
-  "devDependencies": {
-    "@types/express": "^4.17.8",
-    "typescript": "^4.0.5"
-  },
-  "scripts": {
-    "build": "tsc"
-  }
-}
diff --git a/test/unit/node/test-plugin/public/icon.svg b/test/unit/node/test-plugin/public/icon.svg
deleted file mode 100644
index 25b9cf0474cf..000000000000
--- a/test/unit/node/test-plugin/public/icon.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg width="121" height="131" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient x1="9.612%" y1="66.482%" x2="89.899%" y2="33.523%" id="a"><stop stop-color="#FCEE39" offset="0%"/><stop stop-color="#F37B3D" offset="100%"/></linearGradient><linearGradient x1="8.601%" y1="15.03%" x2="99.641%" y2="89.058%" id="b"><stop stop-color="#EF5A6B" offset="0%"/><stop stop-color="#F26F4E" offset="57%"/><stop stop-color="#F37B3D" offset="100%"/></linearGradient><linearGradient x1="90.118%" y1="69.931%" x2="17.938%" y2="38.628%" id="c"><stop stop-color="#7C59A4" offset="0%"/><stop stop-color="#AF4C92" offset="38.52%"/><stop stop-color="#DC4183" offset="76.54%"/><stop stop-color="#ED3D7D" offset="95.7%"/></linearGradient><linearGradient x1="91.376%" y1="19.144%" x2="18.895%" y2="70.21%" id="d"><stop stop-color="#EF5A6B" offset="0%"/><stop stop-color="#EE4E72" offset="36.4%"/><stop stop-color="#ED3D7D" offset="100%"/></linearGradient></defs><g fill="none"><path d="M118.623 71.8c.9-.8 1.4-1.9 1.5-3.2.1-2.6-1.8-4.7-4.4-4.9-1.2-.1-2.4.4-3.3 1.1l-83.8 45.9c-1.9.8-3.6 2.2-4.7 4.1-2.9 4.8-1.3 11 3.6 13.9 3.4 2 7.5 1.8 10.7-.2.2-.2.5-.3.7-.5l78-54.8c.4-.3 1.5-1.1 1.7-1.4z" fill="url(#a)" transform="translate(-.023)"/><path d="M118.823 65.1l-63.8-62.6c-1.4-1.5-3.4-2.5-5.7-2.5-4.3 0-7.7 3.5-7.7 7.7 0 2.1.8 3.9 2.1 5.3.4.4.8.7 1.2 1l67.4 57.7c.8.7 1.8 1.2 3 1.3 2.6.1 4.7-1.8 4.9-4.4 0-1.3-.5-2.6-1.4-3.5z" fill="url(#b)" transform="translate(-.023)"/><path d="M57.123 59.5c-.1 0-39.4-31-40.2-31.5l-1.8-.9c-5.8-2.2-12.2.8-14.4 6.6-1.9 5.1.2 10.7 4.6 13.4.7.4 1.3.7 2 .9.4.2 45.4 18.8 45.4 18.8 1.8.8 3.9.3 5.1-1.2 1.5-1.9 1.2-4.6-.7-6.1z" fill="url(#c)" transform="translate(-.023)"/><path d="M49.323 0c-1.7 0-3.3.6-4.6 1.5l-39.8 26.8c-.1.1-.2.1-.2.2h-.1c-1.7 1.2-3.1 3-3.9 5.1-2.2 5.8.8 12.3 6.6 14.4 3.6 1.4 7.5.7 10.4-1.4.7-.5 1.3-1 1.8-1.6l34.6-31.2c1.8-1.4 3-3.6 3-6.1 0-4.2-3.5-7.7-7.8-7.7z" fill="url(#d)" transform="translate(-.023)"/><path fill="#000" d="M34.6 37.4h51v51h-51z"/><path fill="#FFF" d="M39 78.8h19.1V82H39zm-.2-28l1.5-1.4c.4.5.8.8 1.3.8.6 0 .9-.4.9-1.2v-5.3h2.3V49c0 1-.3 1.8-.8 2.3-.5.5-1.3.8-2.3.8-1.5.1-2.3-.5-2.9-1.3zm6.5-7H52v1.9h-4.4V47h4v1.8h-4v1.3h4.5v2h-6.7zm9.7 2h-2.5v-2h7.3v2h-2.5v6.3H55zM39 54h4.3c1 0 1.8.3 2.3.7.3.3.5.8.5 1.4 0 1-.5 1.5-1.3 1.9 1 .3 1.6.9 1.6 2 0 1.4-1.2 2.3-3.1 2.3H39V54zm4.8 2.6c0-.5-.4-.7-1-.7h-1.5v1.5h1.4c.7-.1 1.1-.3 1.1-.8zM43 59h-1.8v1.5H43c.7 0 1.1-.3 1.1-.8s-.4-.7-1.1-.7zm3.8-5h3.9c1.3 0 2.1.3 2.7.9.5.5.7 1.1.7 1.9 0 1.3-.7 2.1-1.7 2.6l2 2.9h-2.6l-1.7-2.5h-1v2.5h-2.3V54zm3.8 4c.8 0 1.2-.4 1.2-1 0-.7-.5-1-1.2-1h-1.5v2h1.5z"/><path d="M56.8 54H59l3.5 8.4H60l-.6-1.5h-3.2l-.6 1.5h-2.4l3.6-8.4zm2 5l-.9-2.3L57 59h1.8zm4-5h2.3v8.3h-2.3zm2.9 0h2.1l3.4 4.4V54h2.3v8.3h-2L68 57.8v4.6h-2.3zm8 7.1l1.3-1.5c.8.7 1.7 1 2.7 1 .6 0 1-.2 1-.6 0-.4-.3-.5-1.4-.8-1.8-.4-3.1-.9-3.1-2.6 0-1.5 1.2-2.7 3.2-2.7 1.4 0 2.5.4 3.4 1.1l-1.2 1.6c-.8-.5-1.6-.8-2.3-.8-.6 0-.8.2-.8.5 0 .4.3.5 1.4.8 1.9.4 3.1 1 3.1 2.6 0 1.7-1.3 2.7-3.4 2.7-1.5.1-2.9-.4-3.9-1.3z" fill="#FFF"/></g></svg>
\ No newline at end of file
diff --git a/test/unit/node/test-plugin/public/index.html b/test/unit/node/test-plugin/public/index.html
deleted file mode 100644
index e3f70cab0c8e..000000000000
--- a/test/unit/node/test-plugin/public/index.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<!doctype html>
-<html lang="en">
-  <head>
-    <meta charset="UTF-8" />
-    <title>Test Plugin</title>
-  </head>
-  <body>
-    <p>Welcome to the test plugin!</p>
-  </body>
-</html>
diff --git a/test/unit/node/test-plugin/src/index.ts b/test/unit/node/test-plugin/src/index.ts
deleted file mode 100644
index 22ef723bac1a..000000000000
--- a/test/unit/node/test-plugin/src/index.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import * as cs from "code-server"
-import * as fspath from "path"
-
-export const plugin: cs.Plugin = {
-  displayName: "Test Plugin",
-  routerPath: "/test-plugin",
-  homepageURL: "https://example.com",
-  description: "Plugin used in code-server tests.",
-
-  init(config) {
-    config.logger.debug("test-plugin loaded!")
-  },
-
-  router() {
-    const r = cs.express.Router()
-    r.get("/test-app", (_, res) => {
-      res.sendFile(fspath.resolve(__dirname, "../public/index.html"))
-    })
-    r.get("/goland/icon.svg", (_, res) => {
-      res.sendFile(fspath.resolve(__dirname, "../public/icon.svg"))
-    })
-    r.get("/error", () => {
-      throw new cs.HttpError("error", cs.HttpCode.LargePayload)
-    })
-    return r
-  },
-
-  wsRouter() {
-    const wr = cs.WsRouter()
-    wr.ws("/test-app", (req) => {
-      cs.wss.handleUpgrade(req, req.ws, req.head, (ws) => {
-        req.ws.resume()
-        ws.send("hello")
-      })
-    })
-    return wr
-  },
-
-  applications() {
-    return [
-      {
-        name: "Test App",
-        version: "4.0.1",
-        iconPath: "/icon.svg",
-        path: "/test-app",
-
-        description: "This app does XYZ.",
-        homepageURL: "https://example.com",
-      },
-    ]
-  },
-}
diff --git a/test/unit/node/test-plugin/tsconfig.json b/test/unit/node/test-plugin/tsconfig.json
deleted file mode 100644
index 194af1418df9..000000000000
--- a/test/unit/node/test-plugin/tsconfig.json
+++ /dev/null
@@ -1,71 +0,0 @@
-{
-  "compilerOptions": {
-    /* Visit https://aka.ms/tsconfig.json to read more about this file */
-
-    /* Basic Options */
-    // "incremental": true,                   /* Enable incremental compilation */
-    "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
-    "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
-    // "lib": [],                             /* Specify library files to be included in the compilation. */
-    // "allowJs": true,                       /* Allow javascript files to be compiled. */
-    // "checkJs": true,                       /* Report errors in .js files. */
-    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
-    // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
-    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
-    // "sourceMap": true,                     /* Generates corresponding '.map' file. */
-    // "outFile": "./",                       /* Concatenate and emit output to single file. */
-    "outDir": "./out" /* Redirect output structure to the directory. */,
-    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
-    // "composite": true,                     /* Enable project compilation */
-    // "tsBuildInfoFile": "./",               /* Specify file to store incremental compilation information */
-    // "removeComments": true,                /* Do not emit comments to output. */
-    // "noEmit": true,                        /* Do not emit outputs. */
-    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
-    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
-    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
-
-    /* Strict Type-Checking Options */
-    "strict": true /* Enable all strict type-checking options. */,
-    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
-    // "strictNullChecks": true,              /* Enable strict null checks. */
-    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
-    // "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
-    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
-    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
-    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */
-
-    /* Additional Checks */
-    // "noUnusedLocals": true,                /* Report errors on unused locals. */
-    // "noUnusedParameters": true,            /* Report errors on unused parameters. */
-    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
-    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */
-
-    /* Module Resolution Options */
-    // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
-    "baseUrl": "./" /* Base directory to resolve non-absolute module names. */,
-    "paths": {
-      "code-server": ["../../../../typings/pluginapi"]
-    } /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */,
-    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
-    // "typeRoots": [],                       /* List of folders to include type definitions from. */
-    // "types": [],                           /* Type declaration files to be included in compilation. */
-    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
-    "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
-    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */
-    // "allowUmdGlobalAccess": true,          /* Allow accessing UMD globals from modules. */
-
-    /* Source Map Options */
-    // "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */
-    // "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. */
-    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
-    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
-
-    /* Experimental Options */
-    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
-    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */
-
-    /* Advanced Options */
-    "skipLibCheck": true /* Skip type checking of declaration files. */,
-    "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
-  }
-}
diff --git a/typings/pluginapi.d.ts b/typings/pluginapi.d.ts
deleted file mode 100644
index 829709b2d163..000000000000
--- a/typings/pluginapi.d.ts
+++ /dev/null
@@ -1,297 +0,0 @@
-/**
- * This file describes the code-server plugin API for adding new applications.
- */
-import { field, Level, Logger } from "@coder/logger"
-import * as express from "express"
-import * as expressCore from "express-serve-static-core"
-import ProxyServer from "http-proxy"
-import * as stream from "stream"
-import Websocket from "ws"
-
-/**
- * Overlay
- *
- * The homepage of code-server will launch into VS Code. However, there will be an overlay
- * button that when clicked, will show all available applications with their names,
- * icons and provider plugins. When one clicks on an app's icon, they will be directed
- * to <code-server-root>/<plugin-path>/<app-path> to access the application.
- */
-
-/**
- * Plugins
- *
- * Plugins are just node modules that contain a top level export "plugin" that implements
- * the Plugin interface.
- *
- * 1. code-server uses $CS_PLUGIN to find plugins.
- *
- * e.g. CS_PLUGIN=/tmp/will:/tmp/teffen will cause code-server to load
- * /tmp/will and /tmp/teffen as plugins.
- *
- * 2. code-server uses $CS_PLUGIN_PATH to find plugins. Each subdirectory in
- * $CS_PLUGIN_PATH with a package.json where the engine is code-server is
- * a valid plugin.
- *
- * e.g. CS_PLUGIN_PATH=/tmp/nhooyr:/tmp/ash will cause code-server to search
- * /tmp/nhooyr and then /tmp/ash for plugins.
- *
- * CS_PLUGIN_PATH defaults to
- * ~/.local/share/code-server/plugins:/usr/share/code-server/plugins
- * if unset.
- *
- *
- * 3. Built in plugins are loaded from __dirname/../plugins
- *
- * Plugins are required as soon as they are found and then initialized.
- * See the Plugin interface for details.
- *
- * If two plugins are found with the exact same name, then code-server will
- * use the first one and emit a warning.
- *
- */
-
-/**
- * Programmability
- *
- * There is also a /api/applications endpoint to allow programmatic access to all
- * available applications. It could be used to create a custom application dashboard
- * for example. An important difference with the API is that all application paths
- * will be absolute (i.e have the plugin path prepended) so that they may be used
- * directly.
- *
- * Example output:
- *
- * [
- *   {
- *     "name": "Test App",
- *     "version": "4.0.1",
- *     "iconPath": "/test-plugin/test-app/icon.svg",
- *     "path": "/test-plugin/test-app",
- *     "description": "This app does XYZ.",
- *     "homepageURL": "https://example.com",
- *     "plugin": {
- *       "name": "test-plugin",
- *       "version": "1.0.0",
- *       "modulePath": "/Users/nhooyr/src/coder/code-server/test/test-plugin",
- *       "displayName": "Test Plugin",
- *       "description": "Plugin used in code-server tests.",
- *       "routerPath": "/test-plugin",
- *       "homepageURL": "https://example.com"
- *     }
- *   }
- * ]
- */
-
-export enum HttpCode {
-  Ok = 200,
-  Redirect = 302,
-  NotFound = 404,
-  BadRequest = 400,
-  Unauthorized = 401,
-  LargePayload = 413,
-  ServerError = 500,
-}
-
-export declare class HttpError extends Error {
-  constructor(message: string, status: HttpCode, details?: object)
-}
-
-export interface WebsocketRequest extends express.Request {
-  ws: stream.Duplex
-  head: Buffer
-}
-
-export type WebSocketHandler = (
-  req: WebsocketRequest,
-  res: express.Response,
-  next: express.NextFunction,
-) => void | Promise<void>
-
-export interface WebsocketRouter {
-  readonly router: express.Router
-  ws(route: expressCore.PathParams, ...handlers: WebSocketHandler[]): void
-}
-
-/**
- * Create a router for websocket routes.
- */
-export function WsRouter(): WebsocketRouter
-
-/**
- * The websocket server used by code-server.
- */
-export const wss: Websocket.Server
-
-/**
- * The Express import used by code-server.
- *
- * Re-exported so plugins don't have to import duplicate copies of Express and
- * to avoid potential version differences or issues caused by running separate
- * instances.
- */
-export { express }
-/**
- * Use to add a field to a log.
- *
- * Re-exported so plugins don't have to import duplicate copies of the logger.
- */
-export { field, Level, Logger }
-
-/**
- * code-server's proxy server.
- */
-export const proxy: ProxyServer
-
-/**
- * Middleware to ensure the user is authenticated. Throws if they are not.
- */
-export function ensureAuthenticated(
-  req: express.Request,
-  res?: express.Response,
-  next?: express.NextFunction,
-): Promise<void>
-
-/**
- * Returns true if the user is authenticated.
- */
-export function authenticated(req: express.Request): Promise<void>
-
-/**
- * Replace variables in HTML: TO, BASE, CS_STATIC_BASE, and OPTIONS.
- */
-export function replaceTemplates<T extends object>(
-  req: express.Request,
-  content: string,
-  extraOpts?: Omit<T, "base" | "csStaticBase" | "logLevel">,
-): string
-
-/**
- * Your plugin module must have a top level export "plugin" that implements this interface.
- *
- * The plugin's router will be mounted at <code-sever-root>/<plugin-path>
- */
-export interface Plugin {
-  /**
-   * name is used as the plugin's unique identifier.
-   * No two plugins may share the same name.
-   *
-   * Fetched from package.json.
-   */
-  readonly name?: string
-
-  /**
-   * The version for the plugin in the overlay.
-   *
-   * Fetched from package.json.
-   */
-  readonly version?: string
-
-  /**
-   * Name used in the overlay.
-   */
-  readonly displayName: string
-
-  /**
-   * Used in overlay.
-   * Should be a full sentence describing the plugin.
-   */
-  readonly description: string
-
-  /**
-   * The path at which the plugin router is to be registered.
-   */
-  readonly routerPath: string
-
-  /**
-   * Link to plugin homepage.
-   */
-  readonly homepageURL: string
-
-  /**
-   * init is called so that the plugin may initialize itself with the config.
-   */
-  init(config: PluginConfig): void
-
-  /**
-   * Called when the plugin should dispose/shutdown everything.
-   */
-  deinit?(): Promise<void>
-
-  /**
-   * Returns the plugin's router.
-   *
-   * Mounted at <code-sever-root>/<plugin-path>
-   *
-   * If not present, the plugin provides no routes.
-   */
-  router?(): express.Router
-
-  /**
-   * Returns the plugin's websocket router.
-   *
-   * Mounted at <code-sever-root>/<plugin-path>
-   *
-   * If not present, the plugin provides no websockets.
-   */
-  wsRouter?(): WebsocketRouter
-
-  /**
-   * code-server uses this to collect the list of applications that
-   * the plugin can currently provide.
-   * It is called when /api/applications is hit or the overlay needs to
-   * refresh the list of applications
-   *
-   * Ensure this is as fast as possible.
-   *
-   * If not present, the plugin provides no applications.
-   */
-  applications?(): Application[] | Promise<Application[]>
-}
-
-/**
- * PluginConfig contains the configuration required for initializing
- * a plugin.
- */
-export interface PluginConfig {
-  /**
-   * All plugin logs should be logged via this logger.
-   */
-  readonly logger: Logger
-
-  /**
-   * This can be specified by the user on the command line. Plugins should
-   * default to this directory when applicable. For example, the Jupyter plugin
-   * uses this to launch in this directory.
-   */
-  readonly workingDirectory?: string
-}
-
-/**
- * Application represents a user accessible application.
- */
-export interface Application {
-  readonly name: string
-  readonly version: string
-
-  /**
-   * When the user clicks on the icon in the overlay, they will be
-   * redirected to <code-server-root>/<plugin-path>/<app-path>
-   * where the application should be accessible.
-   *
-   * If undefined, then <code-server-root>/<plugin-path> is used.
-   */
-  readonly path?: string
-
-  readonly description?: string
-
-  /**
-   * The path at which the icon for this application can be accessed.
-   * <code-server-root>/<plugin-path>/<app-path>/<icon-path>
-   */
-  readonly iconPath: string
-
-  /**
-   * Link to application homepage.
-   */
-  readonly homepageURL: string
-}