From d62af432cc30288c68179ee1a2f3260013782cf8 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Tue, 13 Aug 2024 11:50:04 +0100 Subject: [PATCH] Use WebWorker --- package-lock.json | 690 ++++++++++++++++++--------- package.json | 3 +- src/buffered_stdin.ts | 198 ++++++++ src/builtin/alias_command.ts | 4 +- src/builtin/clear_command.ts | 2 +- src/builtin/history_command.ts | 4 +- src/builtin/options.ts | 4 +- src/callback.ts | 2 +- src/defs.ts | 57 +++ src/environment.ts | 10 +- src/history.ts | 2 +- src/index.ts | 3 +- src/io/buffered_output.ts | 2 +- src/io/console_output.ts | 2 +- src/io/output.ts | 2 +- src/io/redirect_output.ts | 2 +- src/io/terminal_output.ts | 6 +- src/shell.ts | 438 ++--------------- src/shell_impl.ts | 416 ++++++++++++++++ src/shell_worker.ts | 59 +++ test/package.json | 1 + test/rspack.config.js | 4 + test/serve/index.ts | 13 +- test/serve/input_setup.ts | 20 - test/serve/output_setup.ts | 2 +- test/serve/shell_setup.ts | 52 +- test/tests/aliases.test.ts | 2 + test/tests/command/alias.test.ts | 14 +- test/tests/command/cat.test.ts | 4 +- test/tests/command/cd.test.ts | 52 +- test/tests/command/echo.test.ts | 4 +- test/tests/command/env.test.ts | 26 +- test/tests/command/export.test.ts | 17 +- test/tests/command/grep.test.ts | 33 +- test/tests/command/ls.test.ts | 58 +-- test/tests/command/stty.test.ts | 8 +- test/tests/command/tty.test.ts | 4 +- test/tests/command/wc.test.ts | 10 +- test/tests/command_registry.test.ts | 2 + test/tests/history.test.ts | 52 +- test/tests/io/file_input.test.ts | 2 + test/tests/io/terminal_input.test.ts | 2 + test/tests/shell.test.ts | 189 ++++---- test/tests/utils.ts | 90 ++-- tsconfig.json | 4 +- 45 files changed, 1565 insertions(+), 1006 deletions(-) create mode 100644 src/buffered_stdin.ts create mode 100644 src/defs.ts create mode 100644 src/shell_impl.ts create mode 100644 src/shell_worker.ts delete mode 100644 test/serve/input_setup.ts diff --git a/package-lock.json b/package-lock.json index 1d17e31..2676ca1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "license": "BSD-3-Clause", "dependencies": { "@jupyterlab/services": "^7.2.4", - "@jupyterlite/contents": "^0.4.0" + "@jupyterlite/contents": "^0.4.0", + "comlink": "^4.4.1" }, "devDependencies": { "@types/json-schema": "^7.0.15", @@ -27,8 +28,9 @@ }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, - "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -41,16 +43,18 @@ }, "node_modules/@eslint-community/regexpp": { "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "dev": true, - "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -71,8 +75,9 @@ }, "node_modules/@eslint/eslintrc/node_modules/ajv": { "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -86,8 +91,9 @@ }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -95,13 +101,15 @@ }, "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -111,16 +119,19 @@ }, "node_modules/@eslint/js": { "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, - "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, - "license": "Apache-2.0", "dependencies": { "@humanwhocodes/object-schema": "^2.0.2", "debug": "^4.3.1", @@ -132,8 +143,9 @@ }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -141,8 +153,9 @@ }, "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -152,8 +165,9 @@ }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -164,12 +178,15 @@ }, "node_modules/@humanwhocodes/object-schema": { "version": "2.0.3", - "dev": true, - "license": "BSD-3-Clause" + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true }, "node_modules/@jupyter/ydoc": { "version": "2.1.1", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/@jupyter/ydoc/-/ydoc-2.1.1.tgz", + "integrity": "sha512-NeEwqXQ2j1OyLq4uezeQmsMiI+Qo5k7dYIMqNByOM7dJp6sHeP0jQ96w7BEc9E4SmrxwcOT4cLvcJWJE8Xun4g==", "dependencies": { "@jupyterlab/nbformat": "^3.0.0 || ^4.0.0-alpha.21 || ^4.0.0", "@lumino/coreutils": "^1.11.0 || ^2.0.0", @@ -181,7 +198,8 @@ }, "node_modules/@jupyterlab/coreutils": { "version": "6.2.4", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-6.2.4.tgz", + "integrity": "sha512-A3yHM3UgMYKKJU8JtcPEMGUWUHMlol3TFCdFQbeJnqj+uJmxV0ejkLvDlGk+mefjpcVm/z3cXaDrwoUVtY2FDw==", "dependencies": { "@lumino/coreutils": "^2.1.2", "@lumino/disposable": "^2.1.2", @@ -193,14 +211,16 @@ }, "node_modules/@jupyterlab/nbformat": { "version": "4.2.4", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/@jupyterlab/nbformat/-/nbformat-4.2.4.tgz", + "integrity": "sha512-xR4qqvgjMSBeUQ7nUiRQAP6YDOZcqSHxNzIkds81dDL4wk5pj10oKpTg9lAqfwMImubjcGxTM4Msl8+N3cdpsw==", "dependencies": { "@lumino/coreutils": "^2.1.2" } }, "node_modules/@jupyterlab/services": { "version": "7.2.4", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/@jupyterlab/services/-/services-7.2.4.tgz", + "integrity": "sha512-9a9945owJsuAExroT/afecPSCLlQ/dhhQuttRBpycZFRBEGdSXcXY3/1q9rGUDb9wEk0A4ySk7LTZIWZTNP2rg==", "dependencies": { "@jupyter/ydoc": "^2.0.1", "@jupyterlab/coreutils": "^6.2.4", @@ -217,7 +237,8 @@ }, "node_modules/@jupyterlab/settingregistry": { "version": "4.2.4", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/@jupyterlab/settingregistry/-/settingregistry-4.2.4.tgz", + "integrity": "sha512-95eLd8uiufWE0pso/hlHKcF760NZKlQoubazG8uIphBuYCbSVhd621JoMViAIH6LqDwWi1LfdwYRMJp9hzTlfA==", "dependencies": { "@jupyterlab/nbformat": "^4.2.4", "@jupyterlab/statedb": "^4.2.4", @@ -235,7 +256,8 @@ }, "node_modules/@jupyterlab/statedb": { "version": "4.2.4", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/@jupyterlab/statedb/-/statedb-4.2.4.tgz", + "integrity": "sha512-MzNCYEPZcQ55G4M9jExM0zOjC3Bldj63kVM5ontVUpykQgs+sbMjeGcHY7mcMi9gY6hiPPSSvpL3Rf0eZArqkQ==", "dependencies": { "@lumino/commands": "^2.3.0", "@lumino/coreutils": "^2.1.2", @@ -246,7 +268,8 @@ }, "node_modules/@jupyterlite/contents": { "version": "0.4.0", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/@jupyterlite/contents/-/contents-0.4.0.tgz", + "integrity": "sha512-4KijNc6i66xO0zyHKaoEjBfHfWgncvutK+vB9UH6+bY4n687aB5Rp/xMXFbH4tzyLXG2c3LvSdUOyVzqtdcZaw==", "dependencies": { "@jupyterlab/nbformat": "~4.2.4", "@jupyterlab/services": "~7.2.4", @@ -259,7 +282,8 @@ }, "node_modules/@jupyterlite/localforage": { "version": "0.4.0", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/@jupyterlite/localforage/-/localforage-0.4.0.tgz", + "integrity": "sha512-5CAKrZa+t33fouSNc8526kh6ywbDtUEBPHAXQ+WVLKMNqLeEtvlis7U4tZ3gIqcRDvw5H6R7Bb3Eb1W5sRAHOQ==", "dependencies": { "@jupyterlab/coreutils": "~6.2.4", "@lumino/coreutils": "^2.1.2", @@ -269,11 +293,13 @@ }, "node_modules/@lumino/algorithm": { "version": "2.0.2", - "license": "BSD-3-Clause" + "resolved": "https://registry.npmjs.org/@lumino/algorithm/-/algorithm-2.0.2.tgz", + "integrity": "sha512-cI8yJ2+QK1yM5ZRU3Kuaw9fJ/64JEDZEwWWp7+U0cd/mvcZ44BGdJJ29w+tIet1QXxPAvnsUleWyQ5qm4qUouA==" }, "node_modules/@lumino/commands": { "version": "2.3.1", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/@lumino/commands/-/commands-2.3.1.tgz", + "integrity": "sha512-DpX1kkE4PhILpvK1T4ZnaFb6UP4+YTkdZifvN3nbiomD64O2CTd+wcWIBpZMgy6MMgbVgrE8dzHxHk1EsKxNxw==", "dependencies": { "@lumino/algorithm": "^2.0.2", "@lumino/coreutils": "^2.2.0", @@ -286,29 +312,34 @@ }, "node_modules/@lumino/coreutils": { "version": "2.2.0", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/@lumino/coreutils/-/coreutils-2.2.0.tgz", + "integrity": "sha512-x5wnQ/GjWBayJ6vXVaUi6+Q6ETDdcUiH9eSfpRZFbgMQyyM6pi6baKqJBK2CHkCc/YbAEl6ipApTgm3KOJ/I3g==", "dependencies": { "@lumino/algorithm": "^2.0.2" } }, "node_modules/@lumino/disposable": { "version": "2.1.3", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/@lumino/disposable/-/disposable-2.1.3.tgz", + "integrity": "sha512-k5KXy/+T3UItiWHY4WwQawnsJnGo3aNtP5CTRKqo4+tbTNuhc3rTSvygJlNKIbEfIZXW2EWYnwfFDozkYx95eA==", "dependencies": { "@lumino/signaling": "^2.1.3" } }, "node_modules/@lumino/domutils": { "version": "2.0.2", - "license": "BSD-3-Clause" + "resolved": "https://registry.npmjs.org/@lumino/domutils/-/domutils-2.0.2.tgz", + "integrity": "sha512-2Kp6YHaMNI1rKB0PrALvOsZBHPy2EvVVAvJLWjlCm8MpWOVETjFp0MA9QpMubT9I76aKbaI5s1o1NJyZ8Y99pQ==" }, "node_modules/@lumino/keyboard": { "version": "2.0.2", - "license": "BSD-3-Clause" + "resolved": "https://registry.npmjs.org/@lumino/keyboard/-/keyboard-2.0.2.tgz", + "integrity": "sha512-icRUpvswDaFjqmAJNbQRb/aTu6Iugo6Y2oC08TiIwhQtLS9W+Ee9VofdqvbPSvCm6DkyP+DCWMuA3KXZ4V4g4g==" }, "node_modules/@lumino/polling": { "version": "2.1.3", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/@lumino/polling/-/polling-2.1.3.tgz", + "integrity": "sha512-WEZk96ddK6eHEhdDkFUAAA40EOLit86QVbqQqnbPmhdGwFogek26Kq9b1U273LJeirv95zXCATOJAkjRyb7D+w==", "dependencies": { "@lumino/coreutils": "^2.2.0", "@lumino/disposable": "^2.1.3", @@ -317,11 +348,13 @@ }, "node_modules/@lumino/properties": { "version": "2.0.2", - "license": "BSD-3-Clause" + "resolved": "https://registry.npmjs.org/@lumino/properties/-/properties-2.0.2.tgz", + "integrity": "sha512-b312oA3Bh97WFK8efXejYmC3DVJmvzJk72LQB7H3fXhfqS5jUWvL7MSnNmgcQvGzl9fIhDWDWjhtSTi0KGYYBg==" }, "node_modules/@lumino/signaling": { "version": "2.1.3", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/@lumino/signaling/-/signaling-2.1.3.tgz", + "integrity": "sha512-9Wd4iMk8F1i6pYjy65bqKuPlzQMicyL9xy1/ccS20kovPcfD074waneL/7BVe+3M8i+fGa3x2qjbWrBzOdTdNw==", "dependencies": { "@lumino/algorithm": "^2.0.2", "@lumino/coreutils": "^2.2.0" @@ -329,15 +362,17 @@ }, "node_modules/@lumino/virtualdom": { "version": "2.0.2", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/@lumino/virtualdom/-/virtualdom-2.0.2.tgz", + "integrity": "sha512-HYZThOtZSoknjdXA102xpy5CiXtTFCVz45EXdWeYLx3NhuEwuAIX93QBBIhupalmtFlRg1yhdDNV40HxJ4kcXg==", "dependencies": { "@lumino/algorithm": "^2.0.2" } }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -348,16 +383,18 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -368,8 +405,9 @@ }, "node_modules/@pkgr/core": { "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", "dev": true, - "license": "MIT", "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -378,8 +416,9 @@ } }, "node_modules/@rjsf/utils": { - "version": "5.19.4", - "license": "Apache-2.0", + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-5.20.0.tgz", + "integrity": "sha512-1hICYfz4DI+kgyITJgKcOAX2WoTMHLVwb199dz9ZnTH90YzbpbaQeXoEs06/i5vy0HNYLJ6uKd4Ety5eN4Uf+w==", "dependencies": { "json-schema-merge-allof": "^0.8.1", "jsonpointer": "^5.0.1", @@ -396,30 +435,35 @@ }, "node_modules/@types/emscripten": { "version": "1.39.13", - "license": "MIT" + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.13.tgz", + "integrity": "sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw==" }, "node_modules/@types/json-schema": { "version": "7.0.15", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true }, "node_modules/@types/node": { - "version": "20.14.13", + "version": "20.14.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.15.tgz", + "integrity": "sha512-Fz1xDMCF/B00/tYSVMlmK7hVeLh7jE5f3B7X1/hmV0MJBwE27KlS7EvD/Yp+z1lm8mVhwV5w+n8jOZG8AfTlKw==", "dev": true, - "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/prop-types": { "version": "15.7.12", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", + "dev": true }, "node_modules/@types/react": { "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", "dev": true, - "license": "MIT", "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -427,8 +471,9 @@ }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "7.18.0", @@ -459,8 +504,9 @@ }, "node_modules/@typescript-eslint/parser": { "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", @@ -486,8 +532,9 @@ }, "node_modules/@typescript-eslint/scope-manager": { "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", "dev": true, - "license": "MIT", "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0" @@ -502,8 +549,9 @@ }, "node_modules/@typescript-eslint/type-utils": { "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", "dev": true, - "license": "MIT", "dependencies": { "@typescript-eslint/typescript-estree": "7.18.0", "@typescript-eslint/utils": "7.18.0", @@ -528,8 +576,9 @@ }, "node_modules/@typescript-eslint/types": { "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || >=20.0.0" }, @@ -540,8 +589,9 @@ }, "node_modules/@typescript-eslint/typescript-estree": { "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", @@ -567,8 +617,9 @@ }, "node_modules/@typescript-eslint/utils": { "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "7.18.0", @@ -588,8 +639,9 @@ }, "node_modules/@typescript-eslint/visitor-keys": { "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", "dev": true, - "license": "MIT", "dependencies": { "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" @@ -604,13 +656,15 @@ }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true }, "node_modules/acorn": { "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, - "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -620,15 +674,17 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/ajv": { "version": "8.17.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -642,16 +698,18 @@ }, "node_modules/ansi-regex": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -664,34 +722,39 @@ }, "node_modules/argparse": { "version": "2.0.1", - "dev": true, - "license": "Python-2.0" + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/array-union": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/balanced-match": { "version": "1.0.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/brace-expansion": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/braces": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -701,16 +764,18 @@ }, "node_modules/callsites": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/chalk": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -724,8 +789,9 @@ }, "node_modules/color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -735,11 +801,19 @@ }, "node_modules/color-name": { "version": "1.1.4", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/comlink": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.1.tgz", + "integrity": "sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q==" }, "node_modules/compute-gcd": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz", + "integrity": "sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg==", "dependencies": { "validate.io-array": "^1.0.3", "validate.io-function": "^1.0.2", @@ -748,6 +822,8 @@ }, "node_modules/compute-lcm": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.2.tgz", + "integrity": "sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ==", "dependencies": { "compute-gcd": "^1.2.1", "validate.io-array": "^1.0.3", @@ -757,13 +833,15 @@ }, "node_modules/concat-map": { "version": "0.0.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "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==", "dev": true, - "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -775,13 +853,15 @@ }, "node_modules/csstype": { "version": "3.1.3", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true }, "node_modules/debug": { "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dev": true, - "license": "MIT", "dependencies": { "ms": "2.1.2" }, @@ -796,13 +876,15 @@ }, "node_modules/deep-is": { "version": "0.1.4", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true }, "node_modules/dir-glob": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, - "license": "MIT", "dependencies": { "path-type": "^4.0.0" }, @@ -812,8 +894,9 @@ }, "node_modules/doctrine": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, - "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -823,8 +906,9 @@ }, "node_modules/escape-string-regexp": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" }, @@ -834,8 +918,9 @@ }, "node_modules/eslint": { "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -888,8 +973,9 @@ }, "node_modules/eslint-config-prettier": { "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, - "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -899,8 +985,9 @@ }, "node_modules/eslint-plugin-prettier": { "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", "dev": true, - "license": "MIT", "dependencies": { "prettier-linter-helpers": "^1.0.0", "synckit": "^0.9.1" @@ -928,8 +1015,9 @@ }, "node_modules/eslint-scope": { "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -943,8 +1031,9 @@ }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -954,8 +1043,9 @@ }, "node_modules/eslint/node_modules/ajv": { "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -969,8 +1059,9 @@ }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -978,13 +1069,15 @@ }, "node_modules/eslint/node_modules/json-schema-traverse": { "version": "0.4.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -994,8 +1087,9 @@ }, "node_modules/espree": { "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -1010,8 +1104,9 @@ }, "node_modules/esquery": { "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -1021,8 +1116,9 @@ }, "node_modules/esrecurse": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -1032,33 +1128,38 @@ }, "node_modules/estraverse": { "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, "node_modules/esutils": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "license": "MIT" + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-diff": { "version": "1.3.0", - "dev": true, - "license": "Apache-2.0" + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true }, "node_modules/fast-glob": { "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -1072,8 +1173,9 @@ }, "node_modules/fast-glob/node_modules/glob-parent": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -1083,30 +1185,35 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true }, "node_modules/fast-uri": { "version": "3.0.1", - "license": "MIT" + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==" }, "node_modules/fastq": { "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, - "license": "ISC", "dependencies": { "reusify": "^1.0.4" } }, "node_modules/file-entry-cache": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, - "license": "MIT", "dependencies": { "flat-cache": "^3.0.4" }, @@ -1116,8 +1223,9 @@ }, "node_modules/fill-range": { "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, - "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1127,8 +1235,9 @@ }, "node_modules/find-up": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -1142,8 +1251,9 @@ }, "node_modules/flat-cache": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, - "license": "MIT", "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -1155,18 +1265,22 @@ }, "node_modules/flatted": { "version": "3.3.1", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true }, "node_modules/fs.realpath": { "version": "1.0.0", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/glob": { "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1184,8 +1298,9 @@ }, "node_modules/glob-parent": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -1195,8 +1310,9 @@ }, "node_modules/glob/node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1204,8 +1320,9 @@ }, "node_modules/glob/node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1215,8 +1332,9 @@ }, "node_modules/globals": { "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, - "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -1229,8 +1347,9 @@ }, "node_modules/globby": { "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, - "license": "MIT", "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -1248,33 +1367,38 @@ }, "node_modules/graphemer": { "version": "1.4.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true }, "node_modules/has-flag": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/ignore": { - "version": "5.3.1", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/immediate": { "version": "3.0.6", - "license": "MIT" + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, "node_modules/import-fresh": { "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, - "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -1288,16 +1412,19 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.8.19" } }, "node_modules/inflight": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -1305,21 +1432,24 @@ }, "node_modules/inherits": { "version": "2.0.4", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/is-extglob": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/is-glob": { "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -1329,28 +1459,32 @@ }, "node_modules/is-number": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.12.0" } }, "node_modules/is-path-inside": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/isexe": { "version": "2.0.0", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "node_modules/isomorphic.js": { "version": "0.2.5", - "license": "MIT", + "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", + "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", "funding": { "type": "GitHub Sponsors ❤", "url": "https://github.com/sponsors/dmonad" @@ -1358,13 +1492,15 @@ }, "node_modules/js-tokens": { "version": "4.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "peer": true }, "node_modules/js-yaml": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -1374,19 +1510,22 @@ }, "node_modules/json-buffer": { "version": "3.0.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true }, "node_modules/json-schema-compare": { "version": "0.2.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz", + "integrity": "sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==", "dependencies": { "lodash": "^4.17.4" } }, "node_modules/json-schema-merge-allof": { "version": "0.8.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz", + "integrity": "sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w==", "dependencies": { "compute-lcm": "^1.1.2", "json-schema-compare": "^0.2.2", @@ -1398,16 +1537,19 @@ }, "node_modules/json-schema-traverse": { "version": "1.0.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true }, "node_modules/json5": { "version": "2.2.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "bin": { "json5": "lib/cli.js" }, @@ -1417,23 +1559,26 @@ }, "node_modules/jsonpointer": { "version": "5.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", "engines": { "node": ">=0.10.0" } }, "node_modules/keyv": { "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, - "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } }, "node_modules/levn": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -1443,8 +1588,9 @@ } }, "node_modules/lib0": { - "version": "0.2.95", - "license": "MIT", + "version": "0.2.97", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.97.tgz", + "integrity": "sha512-Q4d1ekgvufi9FiHkkL46AhecfNjznSL9MRNoJRQ76gBHS9OqU2ArfQK0FvBpuxgWeJeNI0LVgAYMIpsGeX4gYg==", "dependencies": { "isomorphic.js": "^0.2.4" }, @@ -1463,28 +1609,33 @@ }, "node_modules/lie": { "version": "3.1.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", "dependencies": { "immediate": "~3.0.5" } }, "node_modules/localforage": { "version": "1.10.0", - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", "dependencies": { "lie": "3.1.1" } }, "node_modules/localforage-memoryStorageDriver": { "version": "0.9.2", + "resolved": "https://registry.npmjs.org/localforage-memoryStorageDriver/-/localforage-memoryStorageDriver-0.9.2.tgz", + "integrity": "sha512-DRB4BkkW9o5HIetbsuvtcg98GP7J1JBRDyDMJK13hfr9QsNpnMW6UUWmU9c6bcRg99akR1mGZ/ubUV1Ek0fbpg==", "dependencies": { "localforage": ">=1.4.0" } }, "node_modules/locate-path": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -1497,20 +1648,24 @@ }, "node_modules/lodash": { "version": "4.17.21", - "license": "MIT" + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash-es": { "version": "4.17.21", - "license": "MIT" + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, "node_modules/lodash.merge": { "version": "4.6.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true }, "node_modules/loose-envify": { "version": "1.4.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -1521,16 +1676,18 @@ }, "node_modules/merge2": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/micromatch": { "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, - "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -1541,7 +1698,8 @@ }, "node_modules/mime": { "version": "3.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", "bin": { "mime": "cli.js" }, @@ -1551,8 +1709,9 @@ }, "node_modules/minimatch": { "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -1565,33 +1724,38 @@ }, "node_modules/minimist": { "version": "1.2.8", - "license": "MIT", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/ms": { "version": "2.1.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/natural-compare": { "version": "1.4.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true }, "node_modules/once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, - "license": "ISC", "dependencies": { "wrappy": "1" } }, "node_modules/optionator": { "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, - "license": "MIT", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -1606,8 +1770,9 @@ }, "node_modules/p-limit": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -1620,8 +1785,9 @@ }, "node_modules/p-locate": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -1634,8 +1800,9 @@ }, "node_modules/parent-module": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -1645,44 +1812,50 @@ }, "node_modules/path-browserify": { "version": "1.0.1", - "license": "MIT" + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" }, "node_modules/path-exists": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/path-is-absolute": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/path-key": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/path-type": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/picomatch": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, - "license": "MIT", "engines": { "node": ">=8.6" }, @@ -1692,16 +1865,18 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/prettier": { "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, - "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -1714,8 +1889,9 @@ }, "node_modules/prettier-linter-helpers": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, - "license": "MIT", "dependencies": { "fast-diff": "^1.1.2" }, @@ -1725,18 +1901,22 @@ }, "node_modules/punycode": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/querystringify": { "version": "2.2.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, "node_modules/queue-microtask": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, "funding": [ { @@ -1751,12 +1931,12 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/react": { "version": "18.3.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "peer": true, "dependencies": { "loose-envify": "^1.1.0" @@ -1767,31 +1947,36 @@ }, "node_modules/react-is": { "version": "18.3.1", - "license": "MIT" + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "node_modules/require-from-string": { "version": "2.0.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "engines": { "node": ">=0.10.0" } }, "node_modules/requires-port": { "version": "1.0.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/resolve-from": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/reusify": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, - "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -1799,8 +1984,10 @@ }, "node_modules/rimraf": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, - "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -1813,6 +2000,8 @@ }, "node_modules/run-parallel": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { @@ -1828,15 +2017,15 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } }, "node_modules/semver": { "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -1846,8 +2035,9 @@ }, "node_modules/shebang-command": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -1857,24 +2047,27 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/slash": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/strip-ansi": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -1884,8 +2077,9 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" }, @@ -1895,8 +2089,9 @@ }, "node_modules/supports-color": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -1906,8 +2101,9 @@ }, "node_modules/synckit": { "version": "0.9.1", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", "dev": true, - "license": "MIT", "dependencies": { "@pkgr/core": "^0.1.0", "tslib": "^2.6.2" @@ -1921,13 +2117,15 @@ }, "node_modules/text-table": { "version": "0.2.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true }, "node_modules/to-regex-range": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -1937,8 +2135,9 @@ }, "node_modules/ts-api-utils": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=16" }, @@ -1948,13 +2147,15 @@ }, "node_modules/tslib": { "version": "2.6.3", - "dev": true, - "license": "0BSD" + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true }, "node_modules/type-check": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -1964,8 +2165,9 @@ }, "node_modules/type-fest": { "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -1975,8 +2177,9 @@ }, "node_modules/typescript": { "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, - "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -1987,20 +2190,23 @@ }, "node_modules/undici-types": { "version": "5.26.5", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true }, "node_modules/uri-js": { "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, "node_modules/url-parse": { "version": "1.5.10", - "license": "MIT", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -2008,31 +2214,41 @@ }, "node_modules/validate.io-array": { "version": "1.0.6", - "license": "MIT" + "resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz", + "integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==" }, "node_modules/validate.io-function": { - "version": "1.0.2" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz", + "integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==" }, "node_modules/validate.io-integer": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/validate.io-integer/-/validate.io-integer-1.0.5.tgz", + "integrity": "sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ==", "dependencies": { "validate.io-number": "^1.0.3" } }, "node_modules/validate.io-integer-array": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz", + "integrity": "sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA==", "dependencies": { "validate.io-array": "^1.0.3", "validate.io-integer": "^1.0.4" } }, "node_modules/validate.io-number": { - "version": "1.0.3" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz", + "integrity": "sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg==" }, "node_modules/which": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -2045,20 +2261,23 @@ }, "node_modules/word-wrap": { "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/wrappy": { "version": "1.0.2", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "node_modules/ws": { "version": "8.18.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "engines": { "node": ">=10.0.0" }, @@ -2077,7 +2296,8 @@ }, "node_modules/y-protocols": { "version": "1.0.6", - "license": "MIT", + "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.6.tgz", + "integrity": "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==", "dependencies": { "lib0": "^0.2.85" }, @@ -2095,7 +2315,8 @@ }, "node_modules/yjs": { "version": "13.6.18", - "license": "MIT", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.18.tgz", + "integrity": "sha512-GBTjO4QCmv2HFKFkYIJl7U77hIB1o22vSCSQD1Ge8ZxWbIbn8AltI4gyXbtL+g5/GJep67HCMq3Y5AmNwDSyEg==", "dependencies": { "lib0": "^0.2.86" }, @@ -2110,8 +2331,9 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index 3f4f4e4..a19fe9b 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ }, "dependencies": { "@jupyterlab/services": "^7.2.4", - "@jupyterlite/contents": "^0.4.0" + "@jupyterlite/contents": "^0.4.0", + "comlink": "^4.4.1" }, "devDependencies": { "@types/json-schema": "^7.0.15", diff --git a/src/buffered_stdin.ts b/src/buffered_stdin.ts new file mode 100644 index 0000000..cf56c7e --- /dev/null +++ b/src/buffered_stdin.ts @@ -0,0 +1,198 @@ +/** + * Classes to deal with buffered stdin. Both main and webworkers have access to the same + * SharedArrayBuffer and use that to pass stdin characters from the UI (main worker) to the shell + * (webworker). This is necessary when the shell is running a WASM command that is synchronous and + * blocking, as the usual async message passing from main to webworker does not work as the received + * messages would only be processed when the command has finished. + */ + +// Indexes into SharedArrayBuffer. +const MAIN = 0; +const WORKER = 1; +const LENGTH = 2; +const START_CHAR = 3; + +abstract class BufferedStdin { + constructor(sharedArrayBuffer?: SharedArrayBuffer) { + if (sharedArrayBuffer === undefined) { + const length = (this._maxChars + 3) * Int32Array.BYTES_PER_ELEMENT; + this._sharedArrayBuffer = new SharedArrayBuffer(length); + } else { + this._sharedArrayBuffer = sharedArrayBuffer; + } + + this._intArray = new Int32Array(this._sharedArrayBuffer); + if (sharedArrayBuffer === undefined) { + this._intArray[MAIN] = 0; + this._intArray[WORKER] = 0; + } + } + + async disable(): Promise { + this._enabled = false; + this._clear(); + } + + async enable(): Promise { + this._enabled = true; + } + + get enabled(): boolean { + return this._enabled; + } + + protected _clear() { + this._intArray[MAIN] = 0; + this._intArray[WORKER] = 0; + this._readCount = 0; + } + + /** + * Load the character from the shared array buffer and return it. + */ + protected _loadFromSharedArrayBuffer(): number[] { + const len = Atomics.load(this._intArray, LENGTH); + const ret: number[] = []; + for (let i = 0; i < len; i++) { + ret.push(Atomics.load(this._intArray, START_CHAR + i)); + } + return ret; + } + + protected _enabled: boolean = false; + protected _maxChars: number = 8; // Max number of actual characters in a token. + protected _sharedArrayBuffer: SharedArrayBuffer; + protected _intArray: Int32Array; + protected _readCount: number = 0; +} + +export namespace MainBufferedStdin { + export interface ISendStdinNow { + (output: string): Promise; + } +} + +/** + * Main worker buffers characters locally, and stores just one character at a time in the + * SharedArrayBuffer so that the web worker can read it. + */ +export class MainBufferedStdin extends BufferedStdin { + constructor() { + super(); + } + + override async disable(): Promise { + // Send all remaining buffered characters as soon as possible via the supplied sendFunction. + this._disabling = true; + if (this._storedCount !== this._readCount) { + const codes = this._loadFromSharedArrayBuffer(); + let text = ''; + for (const code of codes) { + text += String.fromCharCode(code); + } + await this._sendStdinNow!(text); + } + while (this._buffer.length > 0) { + await this._sendStdinNow!(this._buffer.shift()!); + } + this._disabling = false; + + super.disable(); + } + + get sharedArrayBuffer(): SharedArrayBuffer { + return this._sharedArrayBuffer; + } + + /** + * Push a character to the buffer. + * It may or may not be stored in the SharedArrayBuffer immediately. + */ + async push(char: string) { + // May be multiple characters if ANSI control sequence. + this._buffer.push(char); + this._bufferCount++; + + if (char.length > this._maxChars) { + // Too big, log this and do not pass it on? + console.log(`String '${char}' is too long to buffer`); + } + + if (!this._disabling && this._readCount === this._storedCount) { + this._storeInSharedArrayBuffer(); + } + } + + registerSendStdinNow(sendStdinNow: MainBufferedStdin.ISendStdinNow) { + this._sendStdinNow = sendStdinNow; + } + + /** + * After a successful read by the worker, main checks if another character can be stored in the + * SharedArrayBuffer. + */ + private _afterRead() { + this._readCount = Atomics.load(this._intArray, 1); + if (this._readCount !== this._storedCount) { + throw new Error('Should not happen'); + } + + if (this._bufferCount > this._storedCount) { + this._storeInSharedArrayBuffer(); + } + } + + protected override _clear() { + super._clear(); + this._buffer = []; + this._bufferCount = 0; + this._storedCount = 0; + } + + private _storeInSharedArrayBuffer() { + const char: string = this._buffer.shift()!; + this._storedCount++; + + // Store character in SharedArrayBuffer. + const len = char.length; + Atomics.store(this._intArray, LENGTH, len); + for (let i = 0; i < len; i++) { + Atomics.store(this._intArray, START_CHAR + i, char.charCodeAt(i)); + } + + // Notify web worker that a new character is available. + Atomics.store(this._intArray, MAIN, this._storedCount); + Atomics.notify(this._intArray, MAIN, 1); + + // Async wait for web worker to read this character. + const { async, value } = Atomics.waitAsync(this._intArray, WORKER, this._readCount); + if (async) { + value.then(() => this._afterRead()); + } + } + + private _buffer: string[] = []; + private _bufferCount: number = 0; + private _disabling: boolean = false; + private _storedCount: number = 0; + private _sendStdinNow?: MainBufferedStdin.ISendStdinNow; +} + +export class WorkerBufferedStdin extends BufferedStdin { + constructor(sharedArrayBuffer: SharedArrayBuffer) { + super(sharedArrayBuffer); + } + + get(): number[] { + // Wait for main worker to store a new character. + Atomics.wait(this._intArray, MAIN, this._readCount); + const ret = this._loadFromSharedArrayBuffer(); + this._readCount++; + + // Notify main worker that character has been read and a new one can be stored. + Atomics.store(this._intArray, WORKER, this._readCount); + Atomics.notify(this._intArray, WORKER, 1); + + return ret; + } +} diff --git a/src/builtin/alias_command.ts b/src/builtin/alias_command.ts index 630a544..5f6d5d3 100644 --- a/src/builtin/alias_command.ts +++ b/src/builtin/alias_command.ts @@ -22,7 +22,7 @@ export class AliasCommand extends BuiltinCommand { const index = name.indexOf('='); if (index === -1) { // Print alias. - await stdout.write(`${name}='${aliases.get(name)}'\n`); + stdout.write(`${name}='${aliases.get(name)}'\n`); } else { // Set alias. aliases.set(name.slice(0, index), name.slice(index + 1)); @@ -31,7 +31,7 @@ export class AliasCommand extends BuiltinCommand { } else { // Write all aliases. for (const [key, value] of aliases.entries()) { - await stdout.write(`${key}='${value}'\n`); + stdout.write(`${key}='${value}'\n`); } } return ExitCode.SUCCESS; diff --git a/src/builtin/clear_command.ts b/src/builtin/clear_command.ts index 324e7d9..f66a81f 100644 --- a/src/builtin/clear_command.ts +++ b/src/builtin/clear_command.ts @@ -11,7 +11,7 @@ export class ClearCommand extends BuiltinCommand { protected async _run(context: Context): Promise { const { stdout } = context; if (stdout.supportsAnsiEscapes()) { - await stdout.write(ansi.eraseScreen + ansi.eraseSavedLines + ansi.cursorHome); + stdout.write(ansi.eraseScreen + ansi.eraseSavedLines + ansi.cursorHome); } return ExitCode.SUCCESS; } diff --git a/src/builtin/history_command.ts b/src/builtin/history_command.ts index f26bfb7..ad875e8 100644 --- a/src/builtin/history_command.ts +++ b/src/builtin/history_command.ts @@ -19,11 +19,11 @@ export class HistoryCommand extends BuiltinCommand { const options = Options.fromArgs(args, HistoryOptions); if (options.help.isSet) { - await options.writeHelp(stdout); + options.writeHelp(stdout); } else if (options.clear.isSet) { history.clear(); } else { - await history.write(stdout); + history.write(stdout); } return ExitCode.SUCCESS; } diff --git a/src/builtin/options.ts b/src/builtin/options.ts index f971ff8..b6af555 100644 --- a/src/builtin/options.ts +++ b/src/builtin/options.ts @@ -39,9 +39,9 @@ export abstract class Options { return options; } - async writeHelp(output: IOutput): Promise { + writeHelp(output: IOutput): void { for (const line of this._help()) { - await output.write(`${line}\n`); + output.write(`${line}\n`); } } diff --git a/src/callback.ts b/src/callback.ts index 9646eb7..c475955 100644 --- a/src/callback.ts +++ b/src/callback.ts @@ -10,7 +10,7 @@ export interface IOutputCallback { } /** - * Enable/disable buffered stdin in the terminal. + * Enable/disable buffered stdin. */ export interface IEnableBufferedStdinCallback { (enable: boolean): void; diff --git a/src/defs.ts b/src/defs.ts new file mode 100644 index 0000000..a136b48 --- /dev/null +++ b/src/defs.ts @@ -0,0 +1,57 @@ +import { IEnableBufferedStdinCallback, IOutputCallback, IStdinCallback } from './callback'; + +import { ProxyMarked, Remote } from 'comlink'; + +interface IOptionsCommon { + color?: boolean; + mountpoint?: string; + driveFsBaseUrl?: string; + // Initial directories and files to create, for testing purposes. + initialDirectories?: string[]; + initialFiles?: IShell.IFiles; +} + +export interface IShell { + input(char: string): Promise; + setSize(rows: number, columns: number): Promise; + start(): Promise; +} + +export namespace IShell { + export interface IOptions extends IOptionsCommon { + outputCallback: IOutputCallback; + } + + export type IFiles = { [key: string]: string }; +} + +export interface IShellWorker extends IShell { + // Handle any lazy initialization activities. + // Callback proxies need to be separate arguments, they cannot be in IOptions. + initialize( + options: IShellWorker.IOptions, + outputCallback: IShellWorker.IProxyOutputCallback, + enableBufferedStdinCallback: IShellWorker.IProxyEnableBufferedStdinCallback + ): void; +} + +export namespace IShellWorker { + export interface IProxyOutputCallback extends IOutputCallback, ProxyMarked {} + export interface IProxyEnableBufferedStdinCallback + extends IEnableBufferedStdinCallback, + ProxyMarked {} + + export interface IOptions extends IOptionsCommon { + sharedArrayBuffer: SharedArrayBuffer; + } +} + +export type IRemoteShell = Remote; + +export namespace IShellImpl { + export interface IOptions extends IOptionsCommon { + outputCallback: IOutputCallback; + enableBufferedStdinCallback: IEnableBufferedStdinCallback; + stdinCallback: IStdinCallback; + } +} diff --git a/src/environment.ts b/src/environment.ts index de3f416..3ac0a1b 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -5,10 +5,14 @@ import { ansi } from './ansi'; * commands. */ export class Environment extends Map { - constructor() { + constructor(color: boolean) { super(); - this.set('PS1', ansi.styleBoldGreen + 'js-shell:' + ansi.styleReset + ' '); - this.set('TERM', 'xterm-256color'); + if (color) { + this.set('PS1', ansi.styleBoldGreen + 'js-shell:' + ansi.styleReset + ' '); + this.set('TERM', 'xterm-256color'); + } else { + this.set('PS1', 'js-shell: '); + } } /** diff --git a/src/history.ts b/src/history.ts index 8424cdc..e90e183 100644 --- a/src/history.ts +++ b/src/history.ts @@ -61,7 +61,7 @@ export class History { async write(output: IOutput): Promise { for (let i = 0; i < this._history.length; i++) { const index = String(i).padStart(5, ' '); - await output.write(`${index} ${this._history[i]}\n`); + output.write(`${index} ${this._history[i]}\n`); } } diff --git a/src/index.ts b/src/index.ts index 59a4edc..028f455 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,9 +2,10 @@ export { Aliases } from './aliases'; export { IOutputCallback, IEnableBufferedStdinCallback, IStdinCallback } from './callback'; export { CommandRegistry } from './command_registry'; export { Context } from './context'; +export { IShell } from './defs'; export * from './exit_code'; export { IFileSystem } from './file_system'; export * from './io'; export { parse } from './parse'; -export * from './shell'; +export { Shell } from './shell'; export { tokenize, Token } from './tokenize'; diff --git a/src/io/buffered_output.ts b/src/io/buffered_output.ts index f5d1d91..8d02eff 100644 --- a/src/io/buffered_output.ts +++ b/src/io/buffered_output.ts @@ -15,7 +15,7 @@ export abstract class BufferedOutput implements IOutput { return false; } - async write(text: string): Promise { + write(text: string): void { this.data.push(text); } diff --git a/src/io/console_output.ts b/src/io/console_output.ts index 51145b9..e77a8d8 100644 --- a/src/io/console_output.ts +++ b/src/io/console_output.ts @@ -7,7 +7,7 @@ export class ConsoleOutput implements IOutput { return false; } - async write(text: string): Promise { + write(text: string): void { console.log(text); } } diff --git a/src/io/output.ts b/src/io/output.ts index 88c717c..2c9d63d 100644 --- a/src/io/output.ts +++ b/src/io/output.ts @@ -1,5 +1,5 @@ export interface IOutput { flush(): Promise; supportsAnsiEscapes(): boolean; - write(text: string): Promise; + write(text: string): void; } diff --git a/src/io/redirect_output.ts b/src/io/redirect_output.ts index 246cdd7..1eb5bef 100644 --- a/src/io/redirect_output.ts +++ b/src/io/redirect_output.ts @@ -8,7 +8,7 @@ export class RedirectOutput extends BufferedOutput { } override async flush(): Promise { - this.data.forEach(async line => await this.target.write(line)); + this.data.forEach(async line => this.target.write(line)); this.clear(); await this.target.flush(); } diff --git a/src/io/terminal_output.ts b/src/io/terminal_output.ts index e6c3e54..e841bd4 100644 --- a/src/io/terminal_output.ts +++ b/src/io/terminal_output.ts @@ -2,8 +2,6 @@ import { BufferedOutput } from './buffered_output'; import { IOutputCallback } from '../callback'; export class TerminalOutput extends BufferedOutput { - // Needs to know if supports terminal escape codes. - constructor( readonly outputCallback: IOutputCallback, readonly prefix: string | null = null, @@ -21,7 +19,7 @@ export class TerminalOutput extends BufferedOutput { return true; } - override async write(text: string): Promise { + override write(text: string): void { if (text.endsWith('\n')) { text = text.slice(0, -1) + '\r\n'; } @@ -31,6 +29,6 @@ export class TerminalOutput extends BufferedOutput { if (this.suffix !== null) { text = text + this.suffix; } - await super.write(text); + super.write(text); } } diff --git a/src/shell.ts b/src/shell.ts index 4e412f4..dca2a22 100644 --- a/src/shell.ts +++ b/src/shell.ts @@ -1,413 +1,59 @@ -import { Aliases } from './aliases'; -import { ansi } from './ansi'; -import { IOutputCallback, IEnableBufferedStdinCallback, IStdinCallback } from './callback'; -import { CommandRegistry } from './command_registry'; -import { Context } from './context'; -import { Environment } from './environment'; -import { ErrorExitCode, FindCommandError, GeneralError } from './error_exit_code'; -import { ExitCode } from './exit_code'; -import { IFileSystem } from './file_system'; -import { History } from './history'; -import { FileInput, FileOutput, IInput, IOutput, Pipe, TerminalInput, TerminalOutput } from './io'; -import { CommandNode, PipeNode, parse } from './parse'; -import { longestStartsWith, toColumns } from './utils'; -import * as FsModule from './wasm/fs'; - -export namespace IShell { - export interface IOptions { - mountpoint?: string; - outputCallback: IOutputCallback; - enableBufferedStdinCallback?: IEnableBufferedStdinCallback; - stdinCallback?: IStdinCallback; - } -} - -export class Shell { - constructor(options: IShell.IOptions) { - this._outputCallback = options.outputCallback; - this._mountpoint = options.mountpoint ?? '/drive'; - this._enableBufferedStdinCallback = options.enableBufferedStdinCallback; - this._stdinCallback = options.stdinCallback; - this._currentLine = ''; - this._cursorIndex = 0; - this._aliases = new Aliases(); - this._environment = new Environment(); - this._history = new History(); - } - - get aliases(): Aliases { - return this._aliases; - } - - get environment(): Environment { - return this._environment; - } +import { proxy, wrap } from 'comlink'; + +import { MainBufferedStdin } from './buffered_stdin'; +import { IRemoteShell, IShell } from './defs'; + +/** + * External-facing Shell class that external libraries use. It communicates with the real shell + * that runs in a WebWorker. + */ +export class Shell implements IShell { + constructor(readonly options: IShell.IOptions) { + this._bufferedStdin = new MainBufferedStdin(); + this._initWorker(options); + } + + private async _initWorker(options: IShell.IOptions) { + this._worker = new Worker(new URL('./shell_worker.js', import.meta.url), { type: 'module' }); + + this._remote = wrap(this._worker); + const { color, mountpoint, driveFsBaseUrl, initialDirectories, initialFiles } = options; + const { sharedArrayBuffer } = this._bufferedStdin; + await this._remote.initialize( + { color, mountpoint, driveFsBaseUrl, sharedArrayBuffer, initialDirectories, initialFiles }, + proxy(options.outputCallback), + proxy(this.enableBufferedStdinCallback.bind(this)) + ); - get history(): History { - return this._history; + // Register sendStdinNow callback only after this._remote has been initialized. + this._bufferedStdin.registerSendStdinNow(this._remote.input); } - async input(char: string): Promise { - // Might be a multi-char string if begins with escape code. - const code = char.charCodeAt(0); - //console.log("CODE", code) - if (code === 13) { - // \r - await this.output('\r\n'); - const cmdText = this._currentLine.trimStart(); - this._currentLine = ''; - this._cursorIndex = 0; - await this._runCommands(cmdText); - await this.output(this._environment.getPrompt()); - } else if (code === 127) { - // Backspace - if (this._cursorIndex > 0) { - const suffix = this._currentLine.slice(this._cursorIndex); - this._currentLine = this._currentLine.slice(0, this._cursorIndex - 1) + suffix; - this._cursorIndex--; - await this.output( - ansi.cursorLeft(1) + suffix + ansi.eraseEndLine + ansi.cursorLeft(suffix.length) - ); - } - } else if (code === 9) { - // Tab \t - await this._tabComplete(); - } else if (code === 27) { - // Escape following by 1+ more characters - const remainder = char.slice(1); - console.log('Escape code', char); - if ( - remainder === '[A' || // Up arrow - remainder === '[1A' || - remainder === '[B' || // Down arrow - remainder === '[1B' - ) { - const cmdText = this._history.scrollCurrent(remainder.endsWith('B')); - this._currentLine = cmdText !== null ? cmdText : ''; - this._cursorIndex = this._currentLine.length; - // Re-output whole line. - this.output(ansi.eraseStartLine + `\r${this._environment.getPrompt()}${this._currentLine}`); - } else if (remainder === '[D' || remainder === '[1D') { - // Left arrow - if (this._cursorIndex > 0) { - this._cursorIndex--; - await this.output(ansi.cursorLeft()); - } - } else if (remainder === '[C' || remainder === '[1C') { - // Right arrow - if (this._cursorIndex < this._currentLine.length) { - this._cursorIndex++; - await this.output(ansi.cursorRight()); - } - } else if (remainder === '[3~') { - // Delete - if (this._cursorIndex < this._currentLine.length) { - const suffix = this._currentLine.slice(this._cursorIndex + 1); - this._currentLine = this._currentLine.slice(0, this._cursorIndex) + suffix; - await this.output(ansi.eraseEndLine + suffix + ansi.cursorLeft(suffix.length)); - } - } else if (remainder === '[H' || remainder === '[1;2H') { - // Home - if (this._cursorIndex > 0) { - await this.output(ansi.cursorLeft(this._cursorIndex)); - this._cursorIndex = 0; - } - } else if (remainder === '[F' || remainder === '[1;2F') { - // End - const { length } = this._currentLine; - if (this._cursorIndex < length) { - await this.output(ansi.cursorRight(length - this._cursorIndex)); - this._cursorIndex = length; - } - } else if (remainder === '[1;2D' || remainder === '[1;5D') { - // Start of previous word - if (this._cursorIndex > 0) { - const index = - this._currentLine.slice(0, this._cursorIndex).trimEnd().lastIndexOf(' ') + 1; - this.output(ansi.cursorLeft(this._cursorIndex - index)); - this._cursorIndex = index; - } - } else if (remainder === '[1;2C' || remainder === '[1;5C') { - // End of next word - const { length } = this._currentLine; - if (this._cursorIndex < length - 1) { - const end = this._currentLine.slice(this._cursorIndex); - const trimmed = end.trimStart(); - const i = trimmed.indexOf(' '); - const index = i < 0 ? length : this._cursorIndex + end.length - trimmed.length + i; - this.output(ansi.cursorRight(index - this._cursorIndex)); - this._cursorIndex = index; - } - } - } else if (code === 4) { - // EOT, usually = Ctrl-D + private async enableBufferedStdinCallback(enable: boolean) { + if (enable) { + await this._bufferedStdin.enable(); } else { - // Add char to command line at cursor position. - if (this._cursorIndex === this._currentLine.length) { - // Append char. - this._currentLine += char; - await this.output(char); - } else { - // Insert char. - const suffix = this._currentLine.slice(this._cursorIndex); - this._currentLine = this._currentLine.slice(0, this._cursorIndex) + char + suffix; - await this.output(ansi.eraseEndLine + char + suffix + ansi.cursorLeft(suffix.length)); - } - this._cursorIndex++; + await this._bufferedStdin.disable(); } } - async initFilesystem(): Promise { - this._fsModule = await FsModule.default(); - const { FS, PATH, ERRNO_CODES, PROXYFS } = this._fsModule; - FS.mkdir(this._mountpoint, 0o777); - FS.chdir(this._mountpoint); - this._environment.set('PWD', FS.cwd()); - this._fileSystem = { FS, PATH, ERRNO_CODES, PROXYFS }; - return this._fileSystem; - } - - async inputs(chars: string[]): Promise { - // Might be best to not have this as it implies each input fron frontend in a single char when - // it can be multiple chars for escape sequences. - for (let i = 0; i < chars.length; ++i) { - await this.input(chars[i]); + async input(char: string): Promise { + if (this._bufferedStdin.enabled) { + await this._bufferedStdin.push(char); + } else { + await this._remote!.input(char); } } - async output(text: string): Promise { - await this._outputCallback(text); - } - async setSize(rows: number, columns: number): Promise { - const { environment } = this; - - if (rows >= 1) { - environment.set('LINES', rows.toString()); - } else { - environment.delete('LINES'); - } - - if (columns >= 1) { - environment.set('COLUMNS', columns.toString()); - } else { - environment.delete('COLUMNS'); - } + await this._remote!.setSize(rows, columns); } async start(): Promise { - await this.output(this._environment.getPrompt()); + await this._remote!.start(); } - // Keeping this public for tests. - async _runCommands(cmdText: string): Promise { - if (this._enableBufferedStdinCallback) { - this._enableBufferedStdinCallback(true); - } - - if (cmdText.startsWith('!')) { - // Get command from history and run that. - const index = parseInt(cmdText.slice(1)); - const possibleCmd = this._history.at(index); - if (possibleCmd === null) { - // Does not set exit code. - await this.output( - ansi.styleBoldRed + '!' + index + ': event not found' + ansi.styleReset + '\r\n' - ); - await this.output(this._environment.getPrompt()); - return; - } - cmdText = possibleCmd; - } - - this._history.add(cmdText); - - let exitCode!: number; - const stdin = new TerminalInput(this._stdinCallback); - const stdout = new TerminalOutput(this._outputCallback); - const stderr = new TerminalOutput(this._outputCallback, ansi.styleBoldRed, ansi.styleReset); - try { - const nodes = parse(cmdText, true, this._aliases); - - for (const node of nodes) { - if (node instanceof CommandNode) { - exitCode = await this._runCommand(node, stdin, stdout, stderr); - } else if (node instanceof PipeNode) { - const { commands } = node; - const n = commands.length; - let prevPipe: Pipe; - for (let i = 0; i < n; i++) { - const input = i === 0 ? stdin : prevPipe!.input; - const output = i < n - 1 ? (prevPipe = new Pipe()) : stdout; - await this._runCommand(commands[i], input, output, stderr); - } - } else { - // This should not occur. - throw new GeneralError(`Expected CommandNode or PipeNode not ${node}`); - } - } - } catch (error: any) { - if (error instanceof ErrorExitCode) { - exitCode = error.exitCode; - } - stderr.write(error + '\r\n'); - await stderr.flush(); - } finally { - exitCode = exitCode ?? ExitCode.GENERAL_ERROR; - this.environment.set('?', `${exitCode}`); - - if (this._enableBufferedStdinCallback) { - this._enableBufferedStdinCallback(false); - } - } - } - - private async _runCommand( - commandNode: CommandNode, - input: IInput, - output: IOutput, - error: IOutput - ): Promise { - const name = commandNode.name.value; - const runner = CommandRegistry.instance().get(name); - if (runner === null) { - // Give location of command in input? - throw new FindCommandError(name); - } - - if (commandNode.redirects) { - // Support single redirect only, write or append to file. - if (commandNode.redirects.length > 1) { - throw new GeneralError('Only implemented a single redirect per command'); - } - const redirect = commandNode.redirects[0]; - const redirectChars = redirect.token.value; - const path = redirect.target.value; - if (redirectChars === '>' || redirectChars === '>>') { - output = new FileOutput(this._fileSystem!, path, redirectChars === '>>'); - } else if (redirectChars === '<') { - input = new FileInput(this._fileSystem!, path); - } else { - throw new GeneralError('Unrecognised redirect ' + redirectChars); - } - } - - const args = commandNode.suffix.map(token => token.value); - const context = new Context( - args, - this._fileSystem!, - this._mountpoint, - this._aliases, - this._environment, - this._history, - input, - output, - error - ); - const exitCode = await runner.run(name, context); - - await context.flush(); - return exitCode; - } - - private async _tabComplete(): Promise { - const text = this._currentLine.slice(0, this._cursorIndex); - if (text.endsWith(' ') && text.trim().length > 0) { - return; - } - - const suffix = this._currentLine.slice(this._cursorIndex); - const parsed = parse(text, false); - const [lastToken, isCommand] = - parsed.length > 0 ? parsed[parsed.length - 1].lastToken() : [null, true]; - let lookup = lastToken?.value ?? ''; - - let possibles: string[] = []; - if (isCommand) { - const commandMatches = CommandRegistry.instance().match(lookup); - const aliasMatches = this._aliases.match(lookup); - // Combine, removing duplicates, and sort. - possibles = [...new Set([...commandMatches, ...aliasMatches])].sort(); - } else { - // Is filename. - const { FS } = this._fileSystem!; - const analyze = FS.analyzePath(lookup, false); - if (!analyze.parentExists) { - return; - } - - const initialLookup = lookup; - lookup = analyze.name; - const { exists } = analyze; - if (exists && !FS.isDir(FS.stat(analyze.path).mode)) { - // Exactly matches a filename. - possibles = [lookup]; - } else { - const lookupPath = exists ? analyze.path : analyze.parentPath; - possibles = FS.readdir(lookupPath); - - if (exists) { - const wantDot = - initialLookup === '.' || - initialLookup === '..' || - initialLookup.endsWith('/.') || - initialLookup.endsWith('/..'); - if (wantDot) { - possibles = possibles.filter((path: string) => path.startsWith('.')); - } else { - possibles = possibles.filter((path: string) => !path.startsWith('.')); - if (!initialLookup.endsWith('/')) { - this._currentLine += '/'; - } - } - } else { - possibles = possibles.filter((path: string) => path.startsWith(lookup)); - } - - // Directories are displayed with appended / - possibles = possibles.map((path: string) => - FS.isDir(FS.stat(lookupPath + '/' + path).mode) ? path + '/' : path - ); - } - } - - if (possibles.length === 1) { - let extra = possibles[0].slice(lookup.length); - if (!extra.endsWith('/')) { - extra += ' '; - } - this._currentLine = this._currentLine.slice(0, this._cursorIndex) + extra + suffix; - this._cursorIndex += extra.length; - await this.output(extra + suffix + ansi.cursorLeft(suffix.length)); - } else if (possibles.length > 1) { - // Multiple possibles. - const startsWith = longestStartsWith(possibles, lookup.length); - if (startsWith.length > lookup.length) { - // Complete up to the longest common startsWith. - const extra = startsWith.slice(lookup.length); - this._currentLine = this._currentLine.slice(0, this._cursorIndex) + extra + suffix; - this._cursorIndex += extra.length; - await this.output(extra + suffix + ansi.cursorLeft(suffix.length)); - } else { - // Write all the possibles in columns across the terminal. - const lines = toColumns(possibles, this._environment.getNumber('COLUMNS') ?? 0); - const output = `\r\n${lines.join('\r\n')}\r\n${this._environment.getPrompt()}${this._currentLine}`; - await this.output(output + ansi.cursorLeft(suffix.length)); - } - } - } - - private readonly _outputCallback: IOutputCallback; - private readonly _enableBufferedStdinCallback?: IEnableBufferedStdinCallback; - private readonly _stdinCallback?: IStdinCallback; - - private _currentLine: string; - private _cursorIndex: number; - private _aliases: Aliases; - private _environment: Environment; - private _history: History; - - private _fsModule: any; - private _fileSystem?: IFileSystem; - private _mountpoint: string; + private _worker?: Worker; + private _remote?: IRemoteShell; + private _bufferedStdin: MainBufferedStdin; } diff --git a/src/shell_impl.ts b/src/shell_impl.ts new file mode 100644 index 0000000..8959fe2 --- /dev/null +++ b/src/shell_impl.ts @@ -0,0 +1,416 @@ +import { DriveFS } from '@jupyterlite/contents'; + +import { Aliases } from './aliases'; +import { ansi } from './ansi'; +import { CommandRegistry } from './command_registry'; +import { Context } from './context'; +import { IShell, IShellImpl } from './defs'; +import { Environment } from './environment'; +import { ErrorExitCode, FindCommandError, GeneralError } from './error_exit_code'; +import { ExitCode } from './exit_code'; +import { IFileSystem } from './file_system'; +import { History } from './history'; +import { FileInput, FileOutput, IInput, IOutput, Pipe, TerminalInput, TerminalOutput } from './io'; +import { CommandNode, PipeNode, parse } from './parse'; +import { longestStartsWith, toColumns } from './utils'; +import * as FsModule from './wasm/fs'; + +/** + * Shell implementation. + */ +export class ShellImpl implements IShell { + constructor(readonly options: IShellImpl.IOptions) { + this._environment = new Environment(options.color ?? true); + } + + get aliases(): Aliases { + return this._aliases; + } + + get environment(): Environment { + return this._environment; + } + + get history(): History { + return this._history; + } + + async initialize() { + await this._initFilesystem(); + } + + async input(char: string): Promise { + // Might be a multi-char string if begins with escape code. + const code = char.charCodeAt(0); + //console.log("CODE", code) + if (code === 13) { + // \r + await this.output('\r\n'); + const cmdText = this._currentLine; + this._currentLine = ''; + this._cursorIndex = 0; + await this._runCommands(cmdText); + await this.output(this._environment.getPrompt()); + } else if (code === 127) { + // Backspace + if (this._cursorIndex > 0) { + const suffix = this._currentLine.slice(this._cursorIndex); + this._currentLine = this._currentLine.slice(0, this._cursorIndex - 1) + suffix; + this._cursorIndex--; + await this.output( + ansi.cursorLeft(1) + suffix + ansi.eraseEndLine + ansi.cursorLeft(suffix.length) + ); + } + } else if (code === 9) { + // Tab \t + await this._tabComplete(); + } else if (code === 27) { + // Escape following by 1+ more characters + const remainder = char.slice(1); + console.log('Escape code', char); + if ( + remainder === '[A' || // Up arrow + remainder === '[1A' || + remainder === '[B' || // Down arrow + remainder === '[1B' + ) { + const cmdText = this._history.scrollCurrent(remainder.endsWith('B')); + this._currentLine = cmdText !== null ? cmdText : ''; + this._cursorIndex = this._currentLine.length; + // Re-output whole line. + this.output(ansi.eraseStartLine + `\r${this._environment.getPrompt()}${this._currentLine}`); + } else if (remainder === '[D' || remainder === '[1D') { + // Left arrow + if (this._cursorIndex > 0) { + this._cursorIndex--; + await this.output(ansi.cursorLeft()); + } + } else if (remainder === '[C' || remainder === '[1C') { + // Right arrow + if (this._cursorIndex < this._currentLine.length) { + this._cursorIndex++; + await this.output(ansi.cursorRight()); + } + } else if (remainder === '[3~') { + // Delete + if (this._cursorIndex < this._currentLine.length) { + const suffix = this._currentLine.slice(this._cursorIndex + 1); + this._currentLine = this._currentLine.slice(0, this._cursorIndex) + suffix; + await this.output(ansi.eraseEndLine + suffix + ansi.cursorLeft(suffix.length)); + } + } else if (remainder === '[H' || remainder === '[1;2H') { + // Home + if (this._cursorIndex > 0) { + await this.output(ansi.cursorLeft(this._cursorIndex)); + this._cursorIndex = 0; + } + } else if (remainder === '[F' || remainder === '[1;2F') { + // End + const { length } = this._currentLine; + if (this._cursorIndex < length) { + await this.output(ansi.cursorRight(length - this._cursorIndex)); + this._cursorIndex = length; + } + } else if (remainder === '[1;2D' || remainder === '[1;5D') { + // Start of previous word + if (this._cursorIndex > 0) { + const index = + this._currentLine.slice(0, this._cursorIndex).trimEnd().lastIndexOf(' ') + 1; + this.output(ansi.cursorLeft(this._cursorIndex - index)); + this._cursorIndex = index; + } + } else if (remainder === '[1;2C' || remainder === '[1;5C') { + // End of next word + const { length } = this._currentLine; + if (this._cursorIndex < length - 1) { + const end = this._currentLine.slice(this._cursorIndex); + const trimmed = end.trimStart(); + const i = trimmed.indexOf(' '); + const index = i < 0 ? length : this._cursorIndex + end.length - trimmed.length + i; + this.output(ansi.cursorRight(index - this._cursorIndex)); + this._cursorIndex = index; + } + } + } else if (code === 4) { + // EOT, usually = Ctrl-D + } else { + // Add char to command line at cursor position. + if (this._cursorIndex === this._currentLine.length) { + // Append char. + this._currentLine += char; + await this.output(char); + } else { + // Insert char. + const suffix = this._currentLine.slice(this._cursorIndex); + this._currentLine = this._currentLine.slice(0, this._cursorIndex) + char + suffix; + await this.output(ansi.eraseEndLine + char + suffix + ansi.cursorLeft(suffix.length)); + } + this._cursorIndex++; + } + } + + get mountpoint(): string { + return this.options.mountpoint ?? '/drive'; + } + + async output(text: string): Promise { + await this.options.outputCallback(text); + } + + async setSize(rows: number, columns: number): Promise { + const { environment } = this; + + if (rows >= 1) { + environment.set('LINES', rows.toString()); + } else { + environment.delete('LINES'); + } + + if (columns >= 1) { + environment.set('COLUMNS', columns.toString()); + } else { + environment.delete('COLUMNS'); + } + } + + async start(): Promise { + await this.output(this._environment.getPrompt()); + } + + private async _initFilesystem(): Promise { + this._fsModule = await FsModule.default(); + const { FS, PATH, ERRNO_CODES, PROXYFS } = this._fsModule; + const { mountpoint } = this; + FS.mkdir(mountpoint, 0o777); + this._fileSystem = { FS, PATH, ERRNO_CODES, PROXYFS }; + + const { driveFsBaseUrl, initialDirectories, initialFiles } = this.options; + if (driveFsBaseUrl) { + this._driveFS = new DriveFS({ + FS, + PATH, + ERRNO_CODES, + baseUrl: driveFsBaseUrl, + driveName: '', + mountpoint: mountpoint + }); + FS.mount(this._driveFS, {}, mountpoint); + } + + FS.chdir(mountpoint); + this._environment.set('PWD', FS.cwd()); + + if (initialDirectories) { + initialDirectories.forEach((directory: string) => FS.mkdir(directory, 0o775)); + } + + if (initialFiles) { + Object.entries(initialFiles).forEach(([filename, contents]) => + FS.writeFile(filename, contents, { mode: 0o664 }) + ); + } + } + + private async _runCommands(cmdText: string): Promise { + this.options.enableBufferedStdinCallback(true); + + if (cmdText.startsWith('!')) { + // Get command from history and run that. + const index = parseInt(cmdText.slice(1)); + const possibleCmd = this._history.at(index); + if (possibleCmd === null) { + // Does not set exit code. + await this.output( + ansi.styleBoldRed + '!' + index + ': event not found' + ansi.styleReset + '\r\n' + ); + await this.output(this._environment.getPrompt()); + return; + } + cmdText = possibleCmd; + } + + this._history.add(cmdText); + + let exitCode!: number; + const stdin = new TerminalInput(this.options.stdinCallback); + const stdout = new TerminalOutput(this.output.bind(this)); + const stderr = new TerminalOutput(this.output.bind(this), ansi.styleBoldRed, ansi.styleReset); + try { + const nodes = parse(cmdText, true, this._aliases); + + for (const node of nodes) { + if (node instanceof CommandNode) { + exitCode = await this._runCommand(node, stdin, stdout, stderr); + } else if (node instanceof PipeNode) { + const { commands } = node; + const n = commands.length; + let prevPipe: Pipe; + for (let i = 0; i < n; i++) { + const input = i === 0 ? stdin : prevPipe!.input; + const output = i < n - 1 ? (prevPipe = new Pipe()) : stdout; + await this._runCommand(commands[i], input, output, stderr); + } + } else { + // This should not occur. + throw new GeneralError(`Expected CommandNode or PipeNode not ${node}`); + } + } + } catch (error: any) { + if (error instanceof ErrorExitCode) { + exitCode = error.exitCode; + } + stderr.write(error + '\r\n'); + await stderr.flush(); + } finally { + exitCode = exitCode ?? ExitCode.GENERAL_ERROR; + this.environment.set('?', `${exitCode}`); + + this.options.enableBufferedStdinCallback(false); + } + } + + private async _runCommand( + commandNode: CommandNode, + input: IInput, + output: IOutput, + error: IOutput + ): Promise { + const name = commandNode.name.value; + const runner = CommandRegistry.instance().get(name); + if (runner === null) { + // Give location of command in input? + throw new FindCommandError(name); + } + + if (commandNode.redirects) { + // Support single redirect only, write or append to file. + if (commandNode.redirects.length > 1) { + throw new GeneralError('Only implemented a single redirect per command'); + } + const redirect = commandNode.redirects[0]; + const redirectChars = redirect.token.value; + const path = redirect.target.value; + if (redirectChars === '>' || redirectChars === '>>') { + output = new FileOutput(this._fileSystem!, path, redirectChars === '>>'); + } else if (redirectChars === '<') { + input = new FileInput(this._fileSystem!, path); + } else { + throw new GeneralError('Unrecognised redirect ' + redirectChars); + } + } + + const args = commandNode.suffix.map(token => token.value); + const context = new Context( + args, + this._fileSystem!, + this.mountpoint, + this._aliases, + this._environment, + this._history, + input, + output, + error + ); + const exitCode = await runner.run(name, context); + + await context.flush(); + return exitCode; + } + + private async _tabComplete(): Promise { + const text = this._currentLine.slice(0, this._cursorIndex); + if (text.endsWith(' ') && text.trim().length > 0) { + return; + } + + const suffix = this._currentLine.slice(this._cursorIndex); + const parsed = parse(text, false); + const [lastToken, isCommand] = + parsed.length > 0 ? parsed[parsed.length - 1].lastToken() : [null, true]; + let lookup = lastToken?.value ?? ''; + + let possibles: string[] = []; + if (isCommand) { + const commandMatches = CommandRegistry.instance().match(lookup); + const aliasMatches = this._aliases.match(lookup); + // Combine, removing duplicates, and sort. + possibles = [...new Set([...commandMatches, ...aliasMatches])].sort(); + } else { + // Is filename. + const { FS } = this._fileSystem!; + const analyze = FS.analyzePath(lookup, false); + if (!analyze.parentExists) { + return; + } + + const initialLookup = lookup; + lookup = analyze.name; + const { exists } = analyze; + if (exists && !FS.isDir(FS.stat(analyze.path).mode)) { + // Exactly matches a filename. + possibles = [lookup]; + } else { + const lookupPath = exists ? analyze.path : analyze.parentPath; + possibles = FS.readdir(lookupPath); + + if (exists) { + const wantDot = + initialLookup === '.' || + initialLookup === '..' || + initialLookup.endsWith('/.') || + initialLookup.endsWith('/..'); + if (wantDot) { + possibles = possibles.filter((path: string) => path.startsWith('.')); + } else { + possibles = possibles.filter((path: string) => !path.startsWith('.')); + if (!initialLookup.endsWith('/')) { + this._currentLine += '/'; + } + } + } else { + possibles = possibles.filter((path: string) => path.startsWith(lookup)); + } + + // Directories are displayed with appended / + possibles = possibles.map((path: string) => + FS.isDir(FS.stat(lookupPath + '/' + path).mode) ? path + '/' : path + ); + } + } + + if (possibles.length === 1) { + let extra = possibles[0].slice(lookup.length); + if (!extra.endsWith('/')) { + extra += ' '; + } + this._currentLine = this._currentLine.slice(0, this._cursorIndex) + extra + suffix; + this._cursorIndex += extra.length; + await this.output(extra + suffix + ansi.cursorLeft(suffix.length)); + } else if (possibles.length > 1) { + // Multiple possibles. + const startsWith = longestStartsWith(possibles, lookup.length); + if (startsWith.length > lookup.length) { + // Complete up to the longest common startsWith. + const extra = startsWith.slice(lookup.length); + this._currentLine = this._currentLine.slice(0, this._cursorIndex) + extra + suffix; + this._cursorIndex += extra.length; + await this.output(extra + suffix + ansi.cursorLeft(suffix.length)); + } else { + // Write all the possibles in columns across the terminal. + const lines = toColumns(possibles, this._environment.getNumber('COLUMNS') ?? 0); + const output = `\r\n${lines.join('\r\n')}\r\n${this._environment.getPrompt()}${this._currentLine}`; + await this.output(output + ansi.cursorLeft(suffix.length)); + } + } + } + + private _currentLine: string = ''; + private _cursorIndex: number = 0; + private _aliases = new Aliases(); + private _environment: Environment; + private _history = new History(); + + private _fsModule: any; + private _fileSystem?: IFileSystem; + private _driveFS?: DriveFS; +} diff --git a/src/shell_worker.ts b/src/shell_worker.ts new file mode 100644 index 0000000..81b4bbb --- /dev/null +++ b/src/shell_worker.ts @@ -0,0 +1,59 @@ +import { expose } from 'comlink'; + +import { WorkerBufferedStdin } from './buffered_stdin'; +import { IShell, IShellWorker } from './defs'; +import { ShellImpl } from './shell_impl'; + +/** + * WebWorker running ShellImpl. + */ +export class ShellWorker implements IShell { + async initialize( + options: IShellWorker.IOptions, + outputCallback: IShellWorker.IProxyOutputCallback, + enableBufferedStdinCallback: IShellWorker.IProxyEnableBufferedStdinCallback + ) { + this._bufferedStdin = new WorkerBufferedStdin(options.sharedArrayBuffer); + this._outputCallback = outputCallback; + this._enableBufferedStdinCallback = enableBufferedStdinCallback; + + const { color, mountpoint, driveFsBaseUrl, initialDirectories, initialFiles } = options; + this._shellImpl = new ShellImpl({ + color, + mountpoint, + driveFsBaseUrl, + initialDirectories, + initialFiles, + outputCallback: this._outputCallback, + enableBufferedStdinCallback: this._enableBufferedStdinCallback!, + stdinCallback: this._bufferedStdin.get.bind(this._bufferedStdin) + }); + await this._shellImpl.initialize(); + } + + async input(char: string): Promise { + if (this._shellImpl) { + await this._shellImpl.input(char); + } + } + + async setSize(rows: number, columns: number): Promise { + if (this._shellImpl) { + await this._shellImpl.setSize(rows, columns); + } + } + + async start(): Promise { + if (this._shellImpl) { + await this._shellImpl.start(); + } + } + + private _shellImpl?: ShellImpl; + private _bufferedStdin?: WorkerBufferedStdin; + private _outputCallback?: IShellWorker.IProxyOutputCallback; + private _enableBufferedStdinCallback?: IShellWorker.IProxyEnableBufferedStdinCallback; +} + +const obj = new ShellWorker(); +expose(obj); diff --git a/test/package.json b/test/package.json index 1fae710..5ac9e37 100644 --- a/test/package.json +++ b/test/package.json @@ -15,6 +15,7 @@ "test:report": "playwright show-report" }, "devDependencies": { + "@jupyterlite/cockle": "file:../", "@playwright/test": "^1.45.3", "@rspack/cli": "^0.7.5", "@rspack/core": "^0.7.5", diff --git a/test/rspack.config.js b/test/rspack.config.js index 58b5219..0c58dd7 100644 --- a/test/rspack.config.js +++ b/test/rspack.config.js @@ -27,6 +27,10 @@ module.exports = { static: { directory: path.join(__dirname, 'assets') }, + headers: { + "Cross-Origin-Embedder-Policy": "require-corp", + "Cross-Origin-Opener-Policy": "same-origin", + }, port: 8000 } }; diff --git a/test/serve/index.ts b/test/serve/index.ts index d1ef8f3..efe6b5b 100644 --- a/test/serve/index.ts +++ b/test/serve/index.ts @@ -1,20 +1,13 @@ -import { Aliases, CommandRegistry, FileInput, TerminalInput, parse, tokenize } from '../../src'; -import { MockTerminalStdin } from './input_setup'; +import { Aliases, parse, tokenize } from '@jupyterlite/cockle'; import { shell_setup_empty, shell_setup_simple } from './shell_setup'; async function setup() { - // Attach required functions and classes to globalThis so that they can be accessed - // from within page.evaluate calls in browser context. const cockle = { Aliases, - CommandRegistry, - FileInput, - MockTerminalStdin, - TerminalInput, parse, - tokenize, shell_setup_empty, - shell_setup_simple + shell_setup_simple, + tokenize }; // @ts-expect-error Assigning to globalThis. diff --git a/test/serve/input_setup.ts b/test/serve/input_setup.ts deleted file mode 100644 index e09d8bc..0000000 --- a/test/serve/input_setup.ts +++ /dev/null @@ -1,20 +0,0 @@ -export class MockTerminalStdin { - enableBufferedStdinCallback(enable: boolean) { - if (enable) { - this.enableCallCount++; - } else { - this.disableCallCount++; - } - } - - stdinCallback(): number[] { - const ret = this.callCount < this._returns.length ? this._returns[this.callCount] : [4]; - this.callCount++; - return ret; - } - - public callCount = 0; - private _returns = [[90], [100], [122], [32], [100]]; - public enableCallCount = 0; - public disableCallCount = 0; -} diff --git a/test/serve/output_setup.ts b/test/serve/output_setup.ts index 2373a52..77cbd37 100644 --- a/test/serve/output_setup.ts +++ b/test/serve/output_setup.ts @@ -1,4 +1,4 @@ -import { IOutputCallback } from '../../src'; +import { IOutputCallback } from '@jupyterlite/cockle'; /** * Provides outputCallback to mock a terminal. diff --git a/test/serve/shell_setup.ts b/test/serve/shell_setup.ts index 29f4362..40c0583 100644 --- a/test/serve/shell_setup.ts +++ b/test/serve/shell_setup.ts @@ -1,17 +1,15 @@ +import { IShell, Shell } from '@jupyterlite/cockle'; import { MockTerminalOutput } from './output_setup'; -import { IEnableBufferedStdinCallback, IFileSystem, IStdinCallback, Shell } from '../../src'; export interface IShellSetup { shell: Shell; output: MockTerminalOutput; - fileSystem: IFileSystem; - FS: any; } export interface IOptions { - wantColor?: boolean; - enableBufferedStdinCallback?: IEnableBufferedStdinCallback; - stdinCallback?: IStdinCallback; + color?: boolean; + initialDirectories?: string[]; + initialFiles?: IShell.IFiles; } export async function shell_setup_empty(options: IOptions = {}): Promise { @@ -24,29 +22,33 @@ export async function shell_setup_simple(options: IOptions = {}): Promise { const output = new MockTerminalOutput(false); + + const initialDirectories = options.initialDirectories ?? []; + const initialFiles = options.initialFiles ?? {}; + if (level > 0) { + initialDirectories.push('dirA'); + initialFiles['file1'] = 'Contents of the file'; + initialFiles['file2'] = 'Some other file\nSecond line'; + } + const shell = new Shell({ + color: options.color ?? false, outputCallback: output.callback, - enableBufferedStdinCallback: options.enableBufferedStdinCallback, - stdinCallback: options.stdinCallback + initialDirectories, + initialFiles }); - const fileSystem = await shell.initFilesystem(); - const { FS } = fileSystem; - - const wantColor = options.wantColor ?? false; - if (!wantColor) { - // TODO: disable color in the prompt. - const { environment } = shell; - environment.delete('TERM'); - } + + // Monkey patch an inputLine function to enter a sequence of characters and append a '\r'. + // Cannot be used for multi-character ANSI escape codes. + // @ts-expect-error Function not in interface. + shell.inputLine = async (line: string) => { + for (const char of line) { + await shell.input(char); + } + await shell.input('\r'); + }; await shell.start(); output.start(); - - if (level > 0) { - FS.writeFile('file1', 'Contents of the file', { mode: 0o664 }); - FS.writeFile('file2', 'Some other file\nSecond line', { mode: 0o664 }); - FS.mkdir('dirA', 0o775); - } - - return { shell, output, fileSystem, FS }; + return { shell, output }; } diff --git a/test/tests/aliases.test.ts b/test/tests/aliases.test.ts index 52434c9..99af641 100644 --- a/test/tests/aliases.test.ts +++ b/test/tests/aliases.test.ts @@ -1,3 +1,4 @@ +/* import { expect, type Page } from '@playwright/test'; import { test } from './utils'; @@ -62,3 +63,4 @@ test.describe('Aliases', () => { }); }); }); +*/ diff --git a/test/tests/command/alias.test.ts b/test/tests/command/alias.test.ts index 7c9a832..a30f60d 100644 --- a/test/tests/command/alias.test.ts +++ b/test/tests/command/alias.test.ts @@ -1,10 +1,10 @@ import { expect } from '@playwright/test'; -import { shellRunSimple, shellRunSimpleN, test } from '../utils'; +import { shellLineSimple, shellLineSimpleN, test } from '../utils'; test.describe('alias command', () => { test('should write all to stdout', async ({ page }) => { - expect(await shellRunSimple(page, 'alias')).toEqual( - "dir='dir --color=auto'\r\n" + + expect(await shellLineSimple(page, 'alias')).toMatch( + "\r\ndir='dir --color=auto'\r\n" + "grep='grep --color=auto'\r\n" + "ls='ls --color=auto'\r\n" + "ll='ls -lF'\r\n" + @@ -13,13 +13,13 @@ test.describe('alias command', () => { }); test('should write individual aliases to stdout', async ({ page }) => { - expect(await shellRunSimple(page, 'alias vdir ls')).toEqual( - "vdir='vdir --color=auto'\r\nls='ls --color=auto'\r\n" + expect(await shellLineSimple(page, 'alias vdir ls')).toMatch( + "\r\nvdir='vdir --color=auto'\r\nls='ls --color=auto'\r\n" ); }); test('should set alias', async ({ page }) => { - const output = await shellRunSimpleN(page, ['alias abc="ls -alF"', 'alias | grep abc']); - expect(output).toEqual(['', "abc='ls -alF'\r\n"]); + const output = await shellLineSimpleN(page, ['alias abc="ls -alF"', 'alias | grep abc']); + expect(output[1]).toMatch("\r\nabc='ls -alF'\r\n"); }); }); diff --git a/test/tests/command/cat.test.ts b/test/tests/command/cat.test.ts index 4d96a55..167c9a4 100644 --- a/test/tests/command/cat.test.ts +++ b/test/tests/command/cat.test.ts @@ -1,8 +1,8 @@ import { expect } from '@playwright/test'; -import { shellRunSimple, test } from '../utils'; +import { shellLineSimple, test } from '../utils'; test.describe('cat command', () => { test('should write to stdout', async ({ page }) => { - expect(await shellRunSimple(page, 'cat file1')).toEqual('Contents of the file\r\n'); + expect(await shellLineSimple(page, 'cat file1')).toMatch('\r\nContents of the file\r\n'); }); }); diff --git a/test/tests/command/cd.test.ts b/test/tests/command/cd.test.ts index 28dd082..2b442c8 100644 --- a/test/tests/command/cd.test.ts +++ b/test/tests/command/cd.test.ts @@ -1,53 +1,41 @@ import { expect } from '@playwright/test'; -import { shellRunSimple, shellRunSimpleN, test } from '../utils'; +import { shellLineSimple, shellLineSimpleN, test } from '../utils'; test.describe('cd command', () => { test('should do nothing if no arguments', async ({ page }) => { - expect(await shellRunSimple(page, 'cd')).toEqual(''); + const output = await shellLineSimpleN(page, ['pwd', 'cd', 'pwd']); + expect(output[0]).toMatch('\r\n/drive\r\n'); + expect(output[2]).toMatch('\r\n/drive\r\n'); }); test('should error if more than one argument', async ({ page }) => { - expect(await shellRunSimple(page, 'cd a b')).toMatch(/cd: too many arguments/); + expect(await shellLineSimple(page, 'cd a b')).toMatch(/cd: too many arguments/); }); test('should change directory', async ({ page }) => { - const output = await shellRunSimpleN(page, ['pwd', 'cd dirA', 'pwd']); - expect(output).toEqual(['/drive\r\n', '', '/drive/dirA\r\n']); + const output = await shellLineSimpleN(page, ['pwd', 'cd dirA', 'pwd']); + expect(output[0]).toMatch('\r\n/drive\r\n'); + expect(output[2]).toMatch('\r\n/drive/dirA\r\n'); }); test('should update PWD', async ({ page }) => { - const output = await page.evaluate(async () => { - const { shell } = await globalThis.cockle.shell_setup_simple(); - const { environment } = shell; - const pwd0 = environment.get('PWD'); - await shell._runCommands('cd dirA'); - const pwd1 = environment.get('PWD'); - return [pwd0, pwd1]; - }); - expect(output).toEqual(['/drive', '/drive/dirA']); + const output = await shellLineSimpleN(page, ['env|grep PWD', 'cd dirA', 'env|grep PWD']); + expect(output[0]).toMatch('\r\nPWD=/drive\r\n'); + expect(output[2]).toMatch('\r\nPWD=/drive/dirA\r\n'); }); test('should support cd -', async ({ page }) => { - const output = await page.evaluate(async () => { - const { shell } = await globalThis.cockle.shell_setup_simple(); - const { environment } = shell; - const OLDPWD0 = environment.get('OLDPWD'); - await shell._runCommands('cd dirA'); - const OLDPWD1 = environment.get('OLDPWD'); - const PWD1 = environment.get('PWD'); - await shell._runCommands('cd -'); - const OLDPWD2 = environment.get('OLDPWD'); - const PWD2 = environment.get('PWD'); - return { OLDPWD0, OLDPWD1, PWD1, OLDPWD2, PWD2 }; - }); - expect(output['OLDPWD0']).toBeUndefined(); - expect(output['OLDPWD1']).toEqual('/drive'); - expect(output['PWD1']).toEqual('/drive/dirA'); - expect(output['OLDPWD2']).toEqual('/drive/dirA'); - expect(output['PWD2']).toEqual('/drive'); + const output = await shellLineSimpleN(page, [ + 'cd dirA', + 'env|grep PWD', + 'cd -', + 'env|grep PWD' + ]); + expect(output[1]).toMatch('\r\nPWD=/drive/dirA\r\nOLDPWD=/drive\r\n'); + expect(output[3]).toMatch('\r\nPWD=/drive\r\nOLDPWD=/drive/dirA\r\n'); }); test('should error if use cd - and OLDPWD not set', async ({ page }) => { - expect(await shellRunSimple(page, 'cd -')).toMatch(/cd: OLDPWD not set/); + expect(await shellLineSimple(page, 'cd -')).toMatch(/cd: OLDPWD not set/); }); }); diff --git a/test/tests/command/echo.test.ts b/test/tests/command/echo.test.ts index decb5eb..3e76e64 100644 --- a/test/tests/command/echo.test.ts +++ b/test/tests/command/echo.test.ts @@ -1,8 +1,8 @@ import { expect } from '@playwright/test'; -import { shellRunSimple, test } from '../utils'; +import { shellLineSimple, test } from '../utils'; test.describe('echo command', () => { test('should write to stdout', async ({ page }) => { - expect(await shellRunSimple(page, 'echo some text')).toEqual('some text\r\n'); + expect(await shellLineSimple(page, 'echo some text')).toMatch('\r\nsome text\r\n'); }); }); diff --git a/test/tests/command/env.test.ts b/test/tests/command/env.test.ts index b5ac6ed..5cd381c 100644 --- a/test/tests/command/env.test.ts +++ b/test/tests/command/env.test.ts @@ -1,30 +1,14 @@ import { expect } from '@playwright/test'; -import { test } from '../utils'; +import { shellLineSimple, test } from '../utils'; test.describe('env command', () => { test('should write to stdout', async ({ page }) => { - const output = await page.evaluate(async () => { - const { shell, output } = await globalThis.cockle.shell_setup_simple(); - const { environment } = shell; - const MYENV0 = environment.get('MYENV'); - await shell._runCommands('env MYENV=23'); - const MYENV1 = environment.get('MYENV'); - return { MYENV0, MYENV1, text: output.text }; - }); - expect(output.MYENV0).toBeUndefined(); - expect(output.MYENV1).toBeUndefined(); - expect(output.text.trim().split('\r\n').at(-1)).toEqual('MYENV=23'); + const output = await shellLineSimple(page, 'env MYENV=23|grep MYENV'); + expect(output).toMatch(/env MYENV=23|grep MYENV\r\nMYENV=23\r\n/); }); test('should support quotes', async ({ page }) => { - const output = await page.evaluate(async () => { - const { shell, output } = await globalThis.cockle.shell_setup_simple(); - const { environment } = shell; - await shell._runCommands('env MYENV="ls -alF"'); - const MYENV = environment.get('MYENV'); - return { MYENV, text: output.text }; - }); - expect(output.MYENV).toBeUndefined(); - expect(output.text.trim().split('\r\n').at(-1)).toEqual('MYENV=ls -alF'); + const output = await shellLineSimple(page, 'env MYENV="ls -alF"|grep MYENV'); + expect(output).toMatch(/env MYENV="ls -alF"|grep MYENV\r\nMYENV=ls -alF\r\n/); }); }); diff --git a/test/tests/command/export.test.ts b/test/tests/command/export.test.ts index 38c088e..cbda66b 100644 --- a/test/tests/command/export.test.ts +++ b/test/tests/command/export.test.ts @@ -1,10 +1,9 @@ import { expect } from '@playwright/test'; -import { shellRunSimpleN, test } from '../utils'; +import { shellLineSimpleN, test } from '../utils'; test.describe('export command', () => { test('should export to env', async ({ page }) => { - const output = await shellRunSimpleN(page, [ - 'env | grep SOME_NAME', + const output = await shellLineSimpleN(page, [ "export SOME_NAME='a b c'", 'env | grep SOME_NAME', 'export SOME_NAME=other23', @@ -12,14 +11,8 @@ test.describe('export command', () => { 'export SOME_NAME=', 'env | grep SOME_NAME' ]); - expect(output).toEqual([ - '', - '', - 'SOME_NAME=a b c\r\n', - '', - 'SOME_NAME=other23\r\n', - '', - 'SOME_NAME=\r\n' - ]); + expect(output[1]).toMatch('\r\nSOME_NAME=a b c\r\n'); + expect(output[3]).toMatch('\r\nSOME_NAME=other23\r\n'); + expect(output[5]).toMatch('\r\nSOME_NAME=\r\n'); }); }); diff --git a/test/tests/command/grep.test.ts b/test/tests/command/grep.test.ts index eda06c1..7734f90 100644 --- a/test/tests/command/grep.test.ts +++ b/test/tests/command/grep.test.ts @@ -1,30 +1,21 @@ import { expect } from '@playwright/test'; -import { shellRunSimple, test } from '../utils'; +import { shellLineSimple, shellLineSimpleN, test } from '../utils'; test.describe('grep command', () => { test('should write to stdout', async ({ page }) => { - expect(await shellRunSimple(page, 'grep cond file2')).toEqual('Second line\r\n'); + const output = await shellLineSimple(page, 'grep cond file2'); + expect(output).toMatch(/^grep cond file2\r\nSecond line\r\n/); }); test('should support ^ and $', async ({ page }) => { - const output = await page.evaluate(async () => { - const { shell, output, FS } = await globalThis.cockle.shell_setup_simple(); - FS.writeFile('file3', ' hello\nhello '); - await shell._runCommands('grep hello file3'); - const output0 = output.text; - output.clear(); - - await shell._runCommands('grep ^hello file3'); - const output1 = output.text; - output.clear(); - - await shell._runCommands('grep hello$ file3'); - return [output0, output1, output.text]; - }); - const line0 = ' hello'; - const line1 = 'hello '; - expect(output[0]).toEqual(line0 + '\r\n' + line1 + '\r\n'); - expect(output[1]).toEqual(line1 + '\r\n'); - expect(output[2]).toEqual(line0 + '\r\n'); + const options = { initialFiles: { file3: ' hello\nhello ' } }; + const output = await shellLineSimpleN( + page, + ['grep hello file3', 'grep ^hello file3', 'grep hello$ file3'], + options + ); + expect(output[0]).toMatch(/^grep hello file3\r\n hello\r\nhello \r\n/); + expect(output[1]).toMatch(/^grep \^hello file3\r\nhello \r\n/); + expect(output[2]).toMatch(/^grep hello\$ file3\r\n hello\r\n/); }); }); diff --git a/test/tests/command/ls.test.ts b/test/tests/command/ls.test.ts index bca47ea..53db581 100644 --- a/test/tests/command/ls.test.ts +++ b/test/tests/command/ls.test.ts @@ -1,56 +1,56 @@ import { expect } from '@playwright/test'; -import { shellRunEmpty, shellRunSimpleN, test } from '../utils'; +import { shellLineSimpleN, test } from '../utils'; test.describe('ls command', () => { test('should write to stdout', async ({ page }) => { - const output = await shellRunSimpleN(page, ['ls', 'ls -a']); - expect(output).toEqual(['dirA file1 file2\r\n', '. .. dirA file1 file2\r\n']); - }); - - test('should handle empty listing', async ({ page }) => { - expect(await shellRunEmpty(page, 'ls')).toEqual(''); + const output = await shellLineSimpleN(page, ['ls', 'ls -a']); + expect(output[0]).toMatch('\r\ndirA file1 file2\r\n'); + expect(output[1]).toMatch('\r\n. .. dirA file1 file2\r\n'); }); test('should vary with COLUMNS', async ({ page }) => { const output = await page.evaluate(async () => { - const { shell, output, FS } = await globalThis.cockle.shell_setup_simple(); - FS.writeFile('a', ''); - FS.writeFile('bb', ''); - FS.writeFile('ccc', ''); - FS.writeFile('dddd', ''); - FS.writeFile('eeeee', ''); - FS.writeFile('ffffff', ''); - FS.writeFile('ggggg', ''); - FS.writeFile('hhhh', ''); - FS.writeFile('iii', ''); - FS.writeFile('jj', ''); - FS.writeFile('k', ''); - + const options = { + initialFiles: { + a: '', + bb: '', + ccc: '', + dddd: '', + eeeee: '', + ffffff: '', + ggggg: '', + hhhh: '', + iii: '', + jj: '', + k: '' + } + }; + const { shell, output } = await globalThis.cockle.shell_setup_simple(options); await shell.setSize(10, 50); - await shell._runCommands('ls'); + await shell.inputLine('ls'); const ret = [output.text]; output.clear(); await shell.setSize(10, 40); - await shell._runCommands('ls'); + await shell.inputLine('ls'); ret.push(output.text); output.clear(); await shell.setSize(10, 20); - await shell._runCommands('ls'); + await shell.inputLine('ls'); ret.push(output.text); return ret; }); - expect(output[0]).toEqual( - 'a ccc dirA ffffff file2 hhhh jj\r\nb' + 'b dddd eeeee file1 ggggg iii k\r\n' + expect(output[0]).toMatch( + '\r\na ccc dirA ffffff file2 hhhh jj\r\nbb dddd eeeee file1 ggggg iii k\r\n' ); - expect(output[1]).toEqual( - 'a dddd ffffff ggggg jj\r\n' + + expect(output[1]).toMatch( + '\r\na dddd ffffff ggggg jj\r\n' + 'bb dirA file1 hhhh k\r\n' + 'ccc eeeee file2 iii\r\n' ); - expect(output[2]).toEqual( - 'a eeeee hhhh\r\n' + + expect(output[2]).toMatch( + '\r\na eeeee hhhh\r\n' + 'bb ffffff iii\r\n' + 'ccc file1 jj\r\n' + 'dddd file2 k\r\n' + diff --git a/test/tests/command/stty.test.ts b/test/tests/command/stty.test.ts index 78e6da4..3e5213f 100644 --- a/test/tests/command/stty.test.ts +++ b/test/tests/command/stty.test.ts @@ -5,15 +5,15 @@ test.describe('stty command', () => { test('should return default size', async ({ page }) => { const output = await page.evaluate(async () => { const { shell, output } = await globalThis.cockle.shell_setup_empty(); - await shell._runCommands('stty size'); + await shell.inputLine('stty size'); const output0 = output.text; output.clear(); await shell.setSize(10, 43); - await shell._runCommands('stty size'); + await shell.inputLine('stty size'); return [output0, output.text]; }); - expect(output[0]).toEqual('24 80\r\n'); - expect(output[1]).toEqual('10 43\r\n'); + expect(output[0]).toMatch('\r\n24 80\r\n'); + expect(output[1]).toMatch('\r\n10 43\r\n'); }); }); diff --git a/test/tests/command/tty.test.ts b/test/tests/command/tty.test.ts index 44c3301..fa50324 100644 --- a/test/tests/command/tty.test.ts +++ b/test/tests/command/tty.test.ts @@ -1,8 +1,8 @@ import { expect } from '@playwright/test'; -import { shellRunSimple, test } from '../utils'; +import { shellLineSimple, test } from '../utils'; test.describe('tty command', () => { test('should write to stdout', async ({ page }) => { - expect(await shellRunSimple(page, 'tty')).toEqual('/dev/tty\r\n'); + expect(await shellLineSimple(page, 'tty')).toMatch(/^tty\r\n\/dev\/tty\r\n/); }); }); diff --git a/test/tests/command/wc.test.ts b/test/tests/command/wc.test.ts index c0aae5b..debce55 100644 --- a/test/tests/command/wc.test.ts +++ b/test/tests/command/wc.test.ts @@ -1,18 +1,18 @@ import { expect } from '@playwright/test'; -import { shellRunSimple, test } from '../utils'; +import { shellLineSimple, test } from '../utils'; test.describe('wc command', () => { test('should read from single file as argument', async ({ page }) => { - expect(await shellRunSimple(page, 'wc file2')).toEqual(' 1 5 27 file2\r\n'); + expect(await shellLineSimple(page, 'wc file2')).toMatch('\r\n 1 5 27 file2\r\n'); }); test('should read from multiple files as arguments', async ({ page }) => { - expect(await shellRunSimple(page, 'wc file1 file2')).toEqual( - ' 0 4 20 file1\r\n 1 5 27 file2\r\n 1 9 47 total\r\n' + expect(await shellLineSimple(page, 'wc file1 file2')).toMatch( + '\r\n 0 4 20 file1\r\n 1 5 27 file2\r\n 1 9 47 total\r\n' ); }); test('should read single file from stdin', async ({ page }) => { - expect(await shellRunSimple(page, 'wc < file2')).toEqual(' 1 5 27\r\n'); + expect(await shellLineSimple(page, 'wc < file2')).toMatch('\r\n 1 5 27\r\n'); }); }); diff --git a/test/tests/command_registry.test.ts b/test/tests/command_registry.test.ts index 18f7196..ea4b9e9 100644 --- a/test/tests/command_registry.test.ts +++ b/test/tests/command_registry.test.ts @@ -1,3 +1,4 @@ +/* import { expect } from '@playwright/test'; import { test } from './utils'; @@ -30,3 +31,4 @@ test.describe('CommandRegistry', () => { expect(output[2]).toEqual(['echo', 'env', 'export', 'expr']); }); }); +*/ diff --git a/test/tests/history.test.ts b/test/tests/history.test.ts index 3fa1f1e..ee605dd 100644 --- a/test/tests/history.test.ts +++ b/test/tests/history.test.ts @@ -1,29 +1,37 @@ import { expect } from '@playwright/test'; -import { shellRunSimpleN, test } from './utils'; +import { shellLineSimpleN, test } from './utils'; test.describe('history', () => { test('should be stored', async ({ page }) => { - const output = await shellRunSimpleN(page, ['cat', 'echo', 'ls', 'history']); - expect(output.at(-1)).toEqual(' 0 cat\r\n 1 echo\r\n 2 ls\r\n 3 history\r\n'); + const output = await shellLineSimpleN(page, ['cat a', 'echo', 'ls', 'history']); + expect(output.at(-1)).toMatch( + '\r\n 0 cat a\r\n 1 echo\r\n 2 ls\r\n 3 history\r\n' + ); }); test('should ignore duplicates', async ({ page }) => { - const output = await shellRunSimpleN(page, ['cat', 'cat', 'history']); - expect(output.at(-1)).toEqual(' 0 cat\r\n 1 history\r\n'); + const output = await shellLineSimpleN(page, ['cat a', 'cat a', 'history']); + expect(output.at(-1)).toMatch('\r\n 0 cat a\r\n 1 history\r\n'); }); test('should ignore commands starting with whitespace', async ({ page }) => { - const output = await shellRunSimpleN(page, [' ls', 'history']); - expect(output.at(-1)).toEqual(' 0 history\r\n'); + const output = await shellLineSimpleN(page, [' ls', 'history']); + expect(output.at(-1)).toMatch('\r\n 0 history\r\n'); }); test('should clear using -c flag', async ({ page }) => { - const output = await shellRunSimpleN(page, ['cat', 'history', 'history -c', 'ls', 'history']); - expect(output[1]).toEqual(' 0 cat\r\n 1 history\r\n'); - expect(output[2]).toEqual(''); - expect(output[4]).toEqual(' 0 ls\r\n 1 history\r\n'); + const output = await shellLineSimpleN(page, [ + 'cat a', + 'history', + 'history -c', + 'ls', + 'history' + ]); + expect(output[1]).toMatch('\r\n 0 cat a\r\n 1 history\r\n'); + expect(output[4]).toMatch('\r\n 0 ls\r\n 1 history\r\n'); }); + /* test('should limit storage to max size', async ({ page }) => { const output = await page.evaluate(async () => { const { shell, output } = await globalThis.cockle.shell_setup_empty(); @@ -45,6 +53,7 @@ test.describe('history', () => { ); }); + /* test('should clip history when reduce max size', async ({ page }) => { const output = await page.evaluate(async () => { const { shell, output } = await globalThis.cockle.shell_setup_empty(); @@ -65,24 +74,25 @@ test.describe('history', () => { }); expect(output).toEqual(' 0 uname\r\n 1 uniq\r\n 2 history\r\n'); }); +*/ test('should rerun commands using !index syntax, negative and positive', async ({ page }) => { - const output = await shellRunSimpleN(page, ['cat', 'echo hello', 'ls', '!-2', '!1']); - expect(output.at(-2)).toEqual('hello\r\n'); - expect(output.at(-1)).toEqual('hello\r\n'); + const output = await shellLineSimpleN(page, ['cat a', 'echo hello', 'ls', '!-2', '!1']); + expect(output[3]).toMatch(/^!-2\r\nhello\r\n/); + expect(output[4]).toMatch(/^!1\r\nhello\r\n/); }); test('should handle !index out of bounds', async ({ page }) => { - const output = await shellRunSimpleN(page, ['ls', '!1']); - expect(output.at(-1)).toMatch(/!1: event not found/); + const output = await shellLineSimpleN(page, ['ls', '!1']); + expect(output[1]).toMatch('!1: event not found'); }); test('should scroll up and down', async ({ page }) => { const output = await page.evaluate(async () => { const { shell, output } = await globalThis.cockle.shell_setup_empty(); - await shell._runCommands('cat'); - await shell._runCommands('echo hello'); - await shell._runCommands('ls'); + await shell.inputLine('cat a'); + await shell.inputLine('echo hello'); + await shell.inputLine('ls'); output.clear(); const upArrow = '\x1B[A'; @@ -114,8 +124,8 @@ test.describe('history', () => { return ret; }); expect(output[0]).toMatch(/echo hello$/); - expect(output[1]).toMatch(/cat$/); - expect(output[2]).toMatch(/cat$/); + expect(output[1]).toMatch(/cat a$/); + expect(output[2]).toMatch(/cat a$/); expect(output[3]).toMatch(/echo hello$/); expect(output[4]).toMatch(/ls$/); expect(output[5]).toMatch(/ $/); diff --git a/test/tests/io/file_input.test.ts b/test/tests/io/file_input.test.ts index 5546558..7e47d3b 100644 --- a/test/tests/io/file_input.test.ts +++ b/test/tests/io/file_input.test.ts @@ -1,3 +1,4 @@ +/* import { expect } from '@playwright/test'; import { test } from '../utils'; @@ -32,3 +33,4 @@ test.describe('FileInput', () => { } }); }); +*/ diff --git a/test/tests/io/terminal_input.test.ts b/test/tests/io/terminal_input.test.ts index 0d4e3a7..6d2a401 100644 --- a/test/tests/io/terminal_input.test.ts +++ b/test/tests/io/terminal_input.test.ts @@ -1,3 +1,4 @@ +/* import { expect } from '@playwright/test'; import { test } from '../utils'; @@ -39,3 +40,4 @@ test.describe('TerminalInput', () => { } }); }); +*/ diff --git a/test/tests/shell.test.ts b/test/tests/shell.test.ts index 1872c67..aa44ebb 100644 --- a/test/tests/shell.test.ts +++ b/test/tests/shell.test.ts @@ -2,8 +2,8 @@ import { expect } from '@playwright/test'; import { shellInputsSimple, shellInputsSimpleN, - shellRunSimple, - shellRunSimpleN, + shellLineSimple, + shellLineSimpleN, test } from './utils'; @@ -16,41 +16,41 @@ const prev = '\x1B[1;2D'; const next = '\x1B[1;2C'; test.describe('Shell', () => { - test.describe('_runCommands', () => { + test.describe('run command', () => { test('should run ls command', async ({ page }) => { - expect(await shellRunSimple(page, 'ls')).toEqual('dirA file1 file2\r\n'); + expect(await shellLineSimple(page, 'ls')).toMatch(/^ls\r\ndirA {2}file1 {2}file2\r\n/); }); test('should run ls command with leading whitespace', async ({ page }) => { - expect(await shellRunSimple(page, ' ls')).toEqual('dirA file1 file2\r\n'); + expect(await shellLineSimple(page, ' ls')).toMatch(/^ {3}ls\r\ndirA {2}file1 {2}file2\r\n/); }); test('should output redirect to file', async ({ page }) => { - const output = await page.evaluate(async () => { - const { shell, FS } = await globalThis.cockle.shell_setup_simple(); - await shell._runCommands('echo Hello > out'); - const file1 = FS.readFile('out', { encoding: 'utf8' }); - - await shell._runCommands('echo Goodbye >> out'); - const file2 = FS.readFile('out', { encoding: 'utf8' }); - return { file1, file2 }; - }); - expect(output.file1).toEqual('Hello\n'); - expect(output.file2).toEqual('Hello\nGoodbye\n'); + const output = await shellLineSimpleN(page, [ + 'echo Hello > out', + 'cat out', + 'wc out', + 'echo Goodbye >> out', + 'cat out', + 'wc out' + ]); + expect(output[1]).toMatch('\r\nHello\r\n'); + expect(output[2]).toMatch('\r\n1 1 6 out\r\n'); + expect(output[4]).toMatch('\r\nHello\r\nGoodbye\r\n'); + expect(output[5]).toMatch('\r\n 2 2 14 out\r\n'); }); test('should input redirect from file', async ({ page }) => { - expect(await shellRunSimple(page, 'wc < file2')).toEqual(' 1 5 27\r\n'); + expect(await shellLineSimple(page, 'wc < file2')).toMatch(' 1 5 27\r\n'); }); test('should support pipe', async ({ page }) => { - const output = await shellRunSimpleN(page, ['ls -1|sort -r', 'ls -1|sort -r|uniq -c']); - expect(output).toEqual([ - 'file2\r\nfile1\r\ndirA\r\n', - ' 1 file2\r\n 1 file1\r\n 1 dirA\r\n' - ]); + const output = await shellLineSimpleN(page, ['ls -1|sort -r', 'ls -1|sort -r|uniq -c']); + expect(output[0]).toMatch('\r\nfile2\r\nfile1\r\ndirA\r\n'); + expect(output[1]).toMatch('\r\n 1 file2\r\n 1 file1\r\n 1 dirA\r\n'); }); + /* test('should support terminal stdin', async ({ page }) => { const [output, mockStdin] = await page.evaluate(async () => { const mockStdin = new globalThis.cockle.MockTerminalStdin(); @@ -65,64 +65,54 @@ test.describe('Shell', () => { expect(mockStdin.callCount).toEqual(6); expect(mockStdin.enableCallCount).toEqual(1); expect(mockStdin.disableCallCount).toEqual(1); - }); + });*/ test('should support quotes', async ({ page }) => { - expect(await shellRunSimple(page, 'echo "Hello x; yz"')).toEqual('Hello x; yz\r\n'); + const output = await shellLineSimple(page, 'echo "Hello x; yz"'); + expect(output).toMatch('\r\nHello x; yz\r\n'); }); test('should set $? (exit code)', async ({ page }) => { - const { exitCodes, outputs } = await page.evaluate(async () => { - const { shell, output } = await globalThis.cockle.shell_setup_simple(); - const { environment } = shell; - const exitCodes: (number | null)[] = []; - const outputs: string[] = []; - + const output = await shellLineSimpleN(page, [ // WASM command success. - await shell._runCommands('ls unknown'); - exitCodes.push(environment.getNumber('?')); - + 'ls unknown', + 'env|grep ?', // WASM command error. - await shell._runCommands('ls file2'); - exitCodes.push(environment.getNumber('?')); - + 'ls file2', + 'env|grep ?', // Built-in command success. - await shell._runCommands('cd unknown'); - exitCodes.push(environment.getNumber('?')); - + 'cd unknown', + 'env|grep ?', // Built-in command error. - await shell._runCommands('cd dirA'); - exitCodes.push(environment.getNumber('?')); - + 'cd dirA', + 'env|grep ?', // Parse error. - await shell._runCommands('ls "blah '); - exitCodes.push(environment.getNumber('?')); - + 'ls "blah ', + 'env|grep ?', // Command does not exist. - await shell._runCommands('abcde'); - exitCodes.push(environment.getNumber('?')); - + 'abcde', + 'env|grep ?', // Multiple commands success. - output.clear(); - await shell._runCommands('echo Hello; pwd'); - exitCodes.push(environment.getNumber('?')); - outputs.push(output.text); - + 'echo Hello; pwd', + 'env|grep ?', // Multiple commands failure. - output.clear(); - await shell._runCommands('cd a b; pwd'); - exitCodes.push(environment.getNumber('?')); - outputs.push(output.text); - - return { exitCodes, outputs }; - }); - expect(exitCodes).toEqual([2, 0, 1, 0, 1, 127, 0, 1]); - expect(outputs[0]).toEqual('Hello\r\n/drive/dirA\r\n'); - expect(outputs[1]).toMatch(/Error: cd: too many arguments/); + 'cd a b; pwd', + 'env|grep ?' + ]); + expect(output[1]).toMatch('\r\n?=2\r\n'); + expect(output[3]).toMatch('\r\n?=0\r\n'); + expect(output[5]).toMatch('\r\n?=1\r\n'); + expect(output[7]).toMatch('\r\n?=0\r\n'); + expect(output[9]).toMatch('\r\n?=1\r\n'); + expect(output[11]).toMatch('\r\n?=127\r\n'); + expect(output[12]).toMatch('\r\nHello\r\n/drive/dirA\r\n'); + expect(output[13]).toMatch('\r\n?=0\r\n'); + expect(output[14]).toMatch(/Error: cd: too many arguments/); + expect(output[15]).toMatch('\r\n?=1\r\n'); }); }); - test.describe('input', () => { + test.describe('echo input', () => { test('should echo input up to \\r', async ({ page }) => { expect(await shellInputsSimple(page, ['l', 's', ' ', '-', 'a', 'l'])).toEqual('ls -al'); }); @@ -161,12 +151,13 @@ test.describe('Shell', () => { const output = await page.evaluate(async () => { const { shell, output } = await globalThis.cockle.shell_setup_empty(); await shell.setSize(40, 10); - await shell.inputs(['t', '\t']); + await shell.input('t'); + await shell.input('\t'); const ret0 = output.text; output.clear(); await shell.setSize(40, 20); - await shell.inputs(['\t']); + await shell.input('\t'); const ret1 = output.text; return [ret0, ret1]; }); @@ -228,7 +219,7 @@ test.describe('Shell', () => { '/', '\t' ]); - expect(output).toMatch(/^ls \/drive\/\r\nfile1 {2}file2 {2}dirA/); + expect(output).toMatch(/^ls \/drive\/\r\ndirA\/ {2}file1 {2}file2\r\n/); expect(output).toMatch(/ls \/drive\/$/); }); @@ -245,13 +236,13 @@ test.describe('Shell', () => { 'e', '\t' ]); - expect(output).toMatch(/^ls \/drive\r\nfile1 {2}file2 {2}dirA/); + expect(output).toMatch(/^ls \/drive\r\ndirA\/ {2}file1 {2}file2\r\n/); expect(output).toMatch(/ls \/drive\/$/); }); test('should support . for current directory', async ({ page }) => { const output = await shellInputsSimple(page, ['l', 's', ' ', '.', '\t']); - expect(output).toMatch(/^ls .\r\n.\/ {2}..\//); + expect(output).toMatch(/^ls .\r\n.\/ {2}..\/\r\n/); expect(output).toMatch(/ls .$/); }); @@ -278,23 +269,19 @@ test.describe('Shell', () => { }); test('should show dot files/directories', async ({ page }) => { - const output = await page.evaluate(async () => { - const { shell, output, FS } = await globalThis.cockle.shell_setup_simple(); - FS.mkdir('.adir'); - FS.writeFile('.afile1', ''); - FS.writeFile('.afile2', ''); - await shell.inputs(['l', 's', ' ', '.', '\t']); - const ret = [output.text]; - output.clear(); - - await shell.inputs(['l', 's', ' ', '.', 'a', '\t']); - ret.push(output.text); - output.clear(); - - await shell.inputs(['l', 's', ' ', '.', 'a', 'f', '\t']); - ret.push(output.text); - return ret; - }); + const options = { + initialDirectories: ['.adir'], + initialFiles: { '.afile1': '', '.afile2': '' } + }; + const output = await shellInputsSimpleN( + page, + [ + ['l', 's', ' ', '.', '\t'], + ['l', 's', ' ', '.', 'a', '\t'], + ['l', 's', ' ', '.', 'a', 'f', '\t'] + ], + options + ); expect(output[0]).toMatch(/^ls .\r\n.\/ {2}..\/ {2}.adir\/ {2}.afile1 {2}.afile2\r\n/); expect(output[1]).toMatch(/^ls .a\r\n.adir\/ {2}.afile1 {2}.afile2\r\n/); expect(output[2]).toEqual('ls .afile'); @@ -304,30 +291,26 @@ test.describe('Shell', () => { test.describe('setSize', () => { test('should set envVars', async ({ page }) => { const output = await page.evaluate(async () => { - const { shell } = await globalThis.cockle.shell_setup_empty(); - const { environment } = shell; - const ret: object = {}; + const { output, shell } = await globalThis.cockle.shell_setup_empty(); + const ret: string[] = []; await shell.setSize(10, 44); - ret['LINES0'] = environment.getNumber('LINES'); - ret['COLUMNS0'] = environment.getNumber('COLUMNS'); + await shell.inputLine('env|grep LINES;env|grep COLUMNS'); + ret.push(output.text); + output.clear(); await shell.setSize(0, 45); - ret['LINES1'] = environment.getNumber('LINES'); - ret['COLUMNS1'] = environment.getNumber('COLUMNS'); + await shell.inputLine('env|grep LINES;env|grep COLUMNS'); + ret.push(output.text); + output.clear(); await shell.setSize(14, -1); - ret['LINES2'] = environment.getNumber('LINES'); - ret['COLUMNS2'] = environment.getNumber('COLUMNS'); + await shell.inputLine('env|grep LINES;env|grep COLUMNS'); + ret.push(output.text); return ret; }); - expect(output['LINES0']).toEqual(10); - expect(output['COLUMNS0']).toEqual(44); - - expect(output['LINES1']).toBeNull(); - expect(output['COLUMNS1']).toEqual(45); - - expect(output['LINES2']).toEqual(14); - expect(output['COLUMNS2']).toBeNull(); + expect(output[0]).toMatch('\r\nLINES=10\r\nCOLUMNS=44\r\n'); + expect(output[1]).toMatch('\r\nCOLUMNS=45\r\n'); + expect(output[2]).toMatch('\r\nLINES=14\r\n'); }); }); diff --git a/test/tests/utils.ts b/test/tests/utils.ts index e63ae0a..1185ee5 100644 --- a/test/tests/utils.ts +++ b/test/tests/utils.ts @@ -1,4 +1,5 @@ import { test as base, type Page } from '@playwright/test'; +import { IOptions } from '../serve/shell_setup'; // Override page fixture to navigate to specific page. export const test = base.extend({ @@ -10,46 +11,63 @@ export const test = base.extend({ // Wrappers to call shell functions in browser context. -// Shell.inputs -export async function shellInputsSimpleN(page: Page, charsArray: string[][]): Promise { - return await page.evaluate(async charsArray => { - const { shell, output } = await globalThis.cockle.shell_setup_simple(); - const ret: string[] = []; - for (const chars of charsArray) { - await shell.inputs(chars); - ret.push(output.text); - output.clear(); - } - return ret; - }, charsArray); +// Input multiple characters, one at a time. Support multi-character ANSI escape sequences. +export async function shellInputsSimpleN( + page: Page, + charsArray: string[][], + options: IOptions = {} +): Promise { + return await page.evaluate( + async ({ charsArray, options }) => { + const { shell, output } = await globalThis.cockle.shell_setup_simple(options); + const ret: string[] = []; + for (const chars of charsArray) { + for (const char of chars) { + await shell.input(char); + } + ret.push(output.text); + output.clear(); + } + return ret; + }, + { charsArray, options } + ); } -export async function shellInputsSimple(page: Page, chars: string[]): Promise { - return (await shellInputsSimpleN(page, [chars]))[0]; +export async function shellInputsSimple( + page: Page, + chars: string[], + options: IOptions = {} +): Promise { + return (await shellInputsSimpleN(page, [chars], options))[0]; } -// Shell._runCommands. -export async function shellRunEmpty(page: Page, text: string): Promise { - return await page.evaluate(async text => { - const { shell, output } = await globalThis.cockle.shell_setup_empty(); - await shell._runCommands(text); - return output.text; - }, text); +// Accepts multiple lines of input, cannot accept ANSI escape sequences which are multi-character. +// Append '\r' to each line of text to enter. +export async function shellLineSimpleN( + page: Page, + lines: string[], + options: IOptions = {} +): Promise { + return await page.evaluate( + async ({ lines, options }) => { + const { shell, output } = await globalThis.cockle.shell_setup_simple(options); + const ret: string[] = []; + for (const line of lines) { + await shell.inputLine(line); + ret.push(output.text); + output.clear(); + } + return ret; + }, + { lines, options } + ); } -export async function shellRunSimpleN(page: Page, texts: string[]): Promise { - return await page.evaluate(async texts => { - const { shell, output } = await globalThis.cockle.shell_setup_simple(); - const ret: string[] = []; - for (const text of texts) { - await shell._runCommands(text); - ret.push(output.text); - output.clear(); - } - return ret; - }, texts); -} - -export async function shellRunSimple(page: Page, text: string): Promise { - return (await shellRunSimpleN(page, [text]))[0]; +export async function shellLineSimple( + page: Page, + line: string, + options: IOptions = {} +): Promise { + return (await shellLineSimpleN(page, [line], options))[0]; } diff --git a/tsconfig.json b/tsconfig.json index 0c683e6..4a47ae1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "declaration": true, "esModuleInterop": true, "incremental": true, + "lib": ["es2022", "webworker"], "module": "esnext", "moduleResolution": "node", "noEmitOnError": true, @@ -15,9 +16,10 @@ "resolveJsonModule": true, "outDir": "lib", "rootDir": "src", + "skipLibCheck": true, "strict": true, "strictNullChecks": true, - "target": "es2020", + "target": "es2022", "sourceMap": true }, "include": ["src/**/*"]